「コードが動けばOK」という考えに騙されないで
ウェブ開発を始めたばかりの頃、私はコードが正しい結果を出しさえすれば、それで仕事は終わりだと思っていました。ロジックは安定し、UIはスムーズ、テストケースはすべてパス。デプロイするのに十分な自信がありました。しかし、現実はそう甘くありません。文字列処理のために何気なく使った1つのeval()関数や、更新を忘れた「古代」のライブラリがあるだけで、ハッカーがあなたのサーバーを自分たちの遊び場に変えてしまうには十分なのです。
私のプロジェクトは、最初の200行からわずか数ヶ月で2,000行を超えました。この時点で、セキュリティエラーを見つけるために一行ずつ手作業で「監視」することは不可能です。だからこそ、最初からDevSecOpsの考え方をワークフローに取り入れ、日常業務を自動化する過程で脆弱性をスキャンすることにより、より安心して眠れるようになります。
なぜPythonのソースコードは標的になりやすいのか?
Pythonは学習が容易なことで有名ですが、その柔軟性ゆえにプログラマは油断しがちです。問題は通常、以下の3つのブラインドスポットにあります。
- ロジックのブラックホール: APIキーをコードにハードコードしたり、
exec()関数を使ってスクリプトを柔軟に実行したり、入力データをフィルタリングせずにsubprocess経由でシステムコマンドを呼び出したりすることです。 - サードパーティライブラリ(Dependencies): 私たちはネットで見つけたものを何でも
pip installしてしまいがちです。そのライブラリに、大昔に公開されたCVE脆弱性が含まれていないと確信を持てますか? - 設定ミス: 本番環境で
DEBUG = Trueをオフにし忘れるのは典型的なミスであり、エラーが発生した際に環境情報がすべて「丸見え」になってしまいます。
Bandit:ソースコードのバグを照らす「顕微鏡」
BanditはPython専用の静的解析ツール(SAST)です。コードを実行するのではなく、既存のルールセットに基づいて、安全でないプログラミングパターンを探すためにソースファイルをスキャンします。
クイックインストール
pip install bandit
実践への適用
あなたのapp.pyファイルに、次のような「時限爆弾」がいくつか含まれているとしましょう。
import subprocess
import yaml
# インジェクションのリスク:ハッカーが '&& rm -rf /' などのコマンドを挿入する可能性があります
def run_ping(ip):
subprocess.run(f"ping -c 4 {ip}", shell=True)
# safe_load() の代わりに load() を使用すると、リモートコード実行の脆弱性を引き起こしやすくなります
def load_config(data):
return yaml.load(data)
# ここにパスワードを絶対に置かないでください!
DB_PASSWORD = "admin123"
bandit -r app.pyコマンドを実行すると、ツールはすぐに警告フラグを立てます。Banditはエラーを低、中、高(Low, Medium, High)のレベルで分類します。例えば、shell=Trueの使用は極めて危険であると指摘します。ハッカーは入力フィールドに127.0.0.1; cat /etc/passwdと入力するだけで、システムのデータを盗み見ることができてしまいます。
Safety:悪意のあるライブラリを阻止する盾
Banditがあなたの書いたコードをチェックするなら、Safetyはコミュニティから「借りてきた」ものをチェックします。現在のほとんどのPythonプロジェクトは、PyPIからの数十のライブラリに依存しています。Safetyは、requirements.txtファイルをセキュリティ脆弱性データベース(PyUp.ioなど)と照合し、早期に警告を発します。
インストール
pip install safety
依存関係のクイックスキャン
実行するコマンドは1つだけです:
safety check -r requirements.txt
結果は非常に具体的です。例えば、Django==2.2.1を使用している場合、SafetyはこのバージョンにCVE-2019-14234の脆弱性があることを即座に通知し、安全のために2.2.2以上にアップグレードするよう要求します。これは、Python 仮想環境で構築したアプリケーションをDockerにパッケージ化したり、サーバーにプッシュしたりする前の極めて重要なステップです。
セキュリティを自動化ラインに組み込む
暇ができるまでセキュリティスキャンの実行を待たないでください。Git HooksやCI/CDパイプラインに直接統合しましょう。コミットするたびに、システムが自動的にスキャンします。「High」レベルのエラーが検出された場合、修正が終わるまでそのコミットはブロックされます。
私がよく実行する実際のワークフローは、シンプルなスクリプトファイルを介しています:
# run_security_scan.sh
echo "--- ソースコードの脆弱性をスキャン中 (Bandit) ---"
bandit -r . -x ./venv
echo "\n--- ライブラリをチェック中 (Safety) ---"
safety check
脆弱性を「芽のうちに」摘み取る
ツールはあくまで補助であり、あなたのセキュリティ意識こそが最も重要です。Banditに何度も「叱られた」後、私は3つの不変の原則を導き出しました:
- すべての入力を常に疑う: ユーザーから送信されるすべてのデータは悪意があるものと見なします。個々の文字をブロックしようとするのではなく、ホワイトリストを使用してフィルタリングしてください。
- デフォルトで安全な方法を選択する:
yaml.load()ではなくyaml.safe_load()を優先します。subprocessを使用する場合は、shell=Trueを使用せず、引数をリスト形式で渡してください。 - シークレットを分離する: GitHubにパスワードをコミットしないでください。
.envファイルとpython-dotenvを組み合わせて環境変数を管理します。
おわりに
セキュリティはセキュリティ専門家だけの特権ではありません。BanditとSafetyを使えば、誰でも基本的かつ非常に強力な防御層を構築できます。今日、自分のプロジェクトをスキャンしてみてください。ずっと存在していた「初歩的な」脆弱性をいくつか発見するかもしれません。高速かつ難攻不落なシステムを構築できることを願っています。

