一覧SD-09-2 · 外部通信とSSRF

外部への通信と応答サイズの制限

要求が送られる前の拒否と、応答を受けた後の打切りが守る範囲を説明します。

説明

外部取得の宛先を制限しても、大きな応答や長時間の接続によって資源が使われる場合があります。接続経路、読み取る量、接続の終了までを取得処理の範囲として設計します。

固定のHTTP配信試料へ、許可外のscheme・port、出口拒否、応答量の上限を適用します。実際にサーバーへ到達した要求と、クライアントが採用した本文を照合します。

要求が送られる前の拒否と、応答を受けた後の打切りが守る範囲を説明します。

1正常な取得と到達記録を確認する

取得上限は12バイト、通常本文public feedは11バイトです。

from sd09 import * lab = LabHTTP() print(lab.fetch固定本文を取得する("https://feed.example.test/final")) print(lab.log相手が受けた要求を読む)

200の本文を受け取り、サーバーにも1要求が残ります。以後は同じ機能の条件を一つずつ変えます。

2schemeとportを入口で制限する

HTTPSの443だけを受け付け、別サービス向けURLを拒否します。

lab.log.clear() for url inファイル・平文HTTP・別ポートを比較する ("file:///tmp/report","http://feed.example.test/final","https://feed.example.test:8443/final"): print(lab.fetch(url)[0]) print(lab.log)

すべてscheme_or_portで、到達記録は空です。ライブラリが扱える方式をすべて業務機能へ開放する必要はありません。

3出口の許可がない場合は接続前に止める

URLは許可条件を満たしたまま、出口の判定を拒否へ変えます。

print(lab.fetch("https://feed.example.test/final",egress=False接続直前の出口判定を拒否へ変える)) print(lab.log)

egress_deniedで到達記録は空です。この試料は接続直前の判定を表します。ネットワーク側の出口制御を設ける場合も、許可経路以外へ迂回できるかを別途確認します。

4本文上限より一つ多く読み超過を検出する

/largeの本文は16バイト。受け入れる上限を12バイトにします。

print(lab.fetch("https://feed.example.test/large",limit=12読取り後の採用上限を12にする)) print(lab.log)

body_limitですが要求は相手に届いています。最大13バイトだけを読み、超過を判定して接続を閉じます。拒否した本文を後続処理へ渡しません。

5同じ応答の上限境界を比較する

同じ16バイトの応答へ15と16の上限を適用します。

print(lab.fetch("https://feed.example.test/large",limit=15本文より1バイト小さい上限を試す)[0]) print(lab.fetch("https://feed.example.test/large",limit=16本文と同じ上限で試す)[0])

15ではbody_limit、16では全本文です。Content-Lengthの申告だけで判断せず、実際に読んだバイト数も制限しています。

6転送先にも本文と宛先の制約を適用する

転送自体が成功しても、最終本文が上限を超えれば採用しません。

lab.log.clear() print(lab.fetch("https://feed.example.test/redirect",limit=10転送後の本文にも上限を適用する)) print(lab.log)

302の後に200へ到達しますが、11バイト本文に対して上限10なのでbody_limitです。最初のURLだけでなく、最終応答まで同じ制約を引き継ぎます。

7打切り後も新しい要求を処理できることを確認する

超過した接続を閉じた後、通常の本文を再取得します。

print(lab.fetch("https://feed.example.test/final",limit=12通常上限で改めて取得する)) print(lab.log[-1]最後の到達先を確認する)

通常本文が返りました。このクライアントは接続ごとに2秒のsocket timeoutも設定します。全工程の総時間、並列数、圧縮を展開した後の量は、さらに別の制限対象です。

まとめ

scheme・port・出口の条件は接続前に検査します。

実際に読む本文量を制限し、超過時は接続を閉じます。

宛先検査と資源制限を転送後も継続します。

操作履歴と解説を振り返れます。

Python 3.15 / 固定DNS資料・ループバックHTTP試料

1
from sd09 import * lab = LabHTTP() print(lab.fetch("https://feed.example.test/final")) print(lab.log)
('public feed', [('feed.example.test', '93.184.215.14', 200)])
[('GET', '/final', 'feed.example.test')]
2
lab.log.clear() for url in ("file:///tmp/report","http://feed.example.test/final","https://feed.example.test:8443/final"): print(lab.fetch(url)[0]) print(lab.log)
scheme_or_port
scheme_or_port
scheme_or_port
[]
3
print(lab.fetch("https://feed.example.test/final",egress=False)) print(lab.log)
('egress_denied', [])
[]
4
print(lab.fetch("https://feed.example.test/large",limit=12)) print(lab.log)
('body_limit', [('feed.example.test', '93.184.215.14', 200)])
[('GET', '/large', 'feed.example.test')]
5
print(lab.fetch("https://feed.example.test/large",limit=15)[0]) print(lab.fetch("https://feed.example.test/large",limit=16)[0])
body_limit
0123456789ABCDEF
6
lab.log.clear() print(lab.fetch("https://feed.example.test/redirect",limit=10)) print(lab.log)
('body_limit', [('feed.example.test', '93.184.215.14', 302), ('feed.example.test', '93.184.215.14', 200)])
[('GET', '/redirect', 'feed.example.test'), ('GET', '/final', 'feed.example.test')]
7
print(lab.fetch("https://feed.example.test/final",limit=12)) print(lab.log[-1])
('public feed', [('feed.example.test', '93.184.215.14', 200)])
('GET', '/final', 'feed.example.test')

演習完了