Server Crash (サーバーのクラッシュ)¶
概要¶
API サーバーが HTTP レスポンスを返さずに接続をクローズしました。通常、これはリクエストを処理中にサーバーが完全にクラッシュしたことを意味します。
API サーバーは、サーバー管理者またはインフラストラクチャによって意図的に終了されるまで、リクエストを処理します。未処理のエラーが起きたリクエストには "500 Internal Server Error" が返され、後続のリクエストに影響を与えるべきではありません。
一部のケースでは、未処理のエラーが API 処理全体をクラッシュさせ、処理を完了することな突然の終了につながる場合があります。クラッシュした API サーバーによってリクエストが処理されていたすべてのクライアントでは、サーバーからの HTTP リクエストを受信することなく、接続が途中で切断されます。
この問題は、サービス拒否攻撃につながる可能性があります。攻撃者は問題があるリクエストを繰り返し送信し、API が再起動を試みても、連続して API をダウンさせることができます。
推奨事項¶
未処理のエラーがサーバー全体をダウンさせることがなく、後続のリクエストに影響を与えないようにします。
サンプル¶
多くの API フレームワークは、キャッチされない例外を幅広く "500 Internal Server Error" HTTP レスポンスに変換することで、サーバーのクラッシュを起きにくくしています。
結果として、この種の問題は、フレームワークの外にあるコードまたは何らかの方法でフレームワークを迂回するコードで発生する傾向があります。次の FastAPI サンプルは、os._exit によってフレームワークを迂回しています。
from fastapi import FastAPI
import os
app = FastAPI()
@app.get("/test")
async def test():
os._exit(1)
単一のエンドポイント /test は一方的に os._exit を呼び出し、プログラムをただちに終了しています。このエンドポイントを何度か呼び出してみます。
$ curl localhost:8000/test
curl: (52) Empty reply from server
$ curl localhost:8000/test
curl: (7) Failed to connect to localhost port 8000: Connection refused
最初の実行で接続が突然にクローズされ、curl はこれを "Empty reply" としてレポートします。これは、このページで説明している動作です。
2 回目の実行は、まったく接続できません。後続のリクエストの動作はまちまちですが、これはよくある例です。サーバーは完全に終了しているため、後続の接続が失敗します。
後続のリクエストがまったく影響を受けない場合でも、このようなレスポンスは、サーバーにとっては過大な負荷 (プロセスおよびスレッドの再開など) であり、潜在的なサービス拒否攻撃ベクトルとなります。
めったにないことですが、API 開発者が意図的にこのような突然の接続のクローズを使用する場合もあります。そのような処理を行う場合、"mapi run" コマンドに --ignore-rule ServerCrash
を渡すことで、これらの問題をレポートしないようにできます。
参考資料¶
- Common Weakness Enumeration: CWE-730.