Pythonセキュリティのベストプラクティス チートシート
Frank Fischer
2021年9月27日
0 分で読めます
2019年、Snykは初のPythonチートシートを公開しました。それ以来、Pythonセキュリティを取り巻く状況は大きく変化しています。開発者向けセキュリティ企業として得た知見と、Pythonに特化したベストプラクティスをまとめ、このチートシートを更新しました。Pythonコードを安全に保つために、ぜひご活用ください。先に進む前に、このチートシートの作成を手伝ってくれたChiboとDanielに心から感謝します。
2021年版 Pythonセキュリティのベストプラクティス チートシートをダウンロード
この記事では、次のPythonセキュリティのヒントを紹介します。
外部データは必ずサニタイズする
コードをスキャンする
パッケージのダウンロードには注意する
依存関係のライセンスを確認する
システム標準のPythonバージョンを使わない
Pythonの仮想環境機能を使う
本番環境では
DEBUG = Falseに設定する文字列のフォーマットに注意する
シリアライズとデシリアライズは慎重に行う
Pythonの型アノテーションを使う
始める前に、ひとつ補足しておきます。SnykのPythonエコシステムに関するデータや学術研究によると、Pythonは広く使われている他の言語と比べて、特に安全でも危険でもありません。このチートシートは、Pythonを使う開発者向けに特化したものです。他のエコシステムで安全を確保する方法については、その他のセキュリティチートシートもぜひご覧ください。
1. 外部データは必ずサニタイズする
あらゆるアプリケーションにとって、外部データは攻撃経路のひとつです。インジェクションやXSS、サービス拒否(DoS)攻撃に悪用される可能性があります。Pythonセキュリティを維持するための基本原則は、ユーザー入力フォーム、Webサイトのスクレイピング、データベースへのリクエストなど、データの出所にかかわらず、外部ソースから取得したデータを必ずサニタイズ(機密情報を取り除くこと)することです。安全でない方法でデータが処理されないよう、アプリケーションに取り込んだらすぐにサニタイズしましょう。こうすることで、サニタイズされていない機密データをアプリケーションが誤って処理するリスクを減らせます。
サニタイズでは、例外への対処を試みるよりも、入力がどのような形式であるべきかを検証するほうが適切です。また、メンテナンスが行き届いたライブラリを使うことをおすすめします。たとえば、次の2つがあります。
schemaは、「設定ファイル、フォーム、外部サービス、コマンドラインの解析などから取得したPythonのデータ構造を検証するライブラリです。JSON/YAML(またはその他の形式)からPythonのデータ型に変換されたデータを扱います。」
bleachは、「許可リスト方式のHTMLサニタイズライブラリで、マークアップや属性をエスケープまたは削除します。」
主要なフレームワークには、独自のサニタイズ関数が用意されています。たとえば、Flaskのflask.escape()やDjangoのdjango.utils.html.escape()です。これらの関数は、次のような悪意のある可能性があるHTML入力を安全に処理することを目的としています。
この方法の限界は、ライブラリがあらゆるケースに対応できるわけではないことです。それぞれのライブラリは特定の用途に特化しています。また、XMLなど、悪意のあるデータを含む可能性がある他のデータ形式の扱いについては、この記事の最後までお読みください。
HTMLのレンダリングをJinjaなどのテンプレートエンジンに任せる方法もよく使われます。テンプレートエンジンには多くの機能があり、そのひとつがMarkupSafeを使ったXSSを防ぐ自動エスケープです。
サニタイズでは、データがコマンドとして実行されるのを防ぐことも重要です。代表的な例がSQLインジェクションです。文字列と変数をつなぎ合わせてSQLクエリを生成するのではなく、名前付きパラメーターを使って、何をコマンドとして扱い、何をデータとして扱うかをデータベースに指定することをおすすめします。
さらにおすすめなのが、sqlalchemyなどのオブジェクト関係マッピング(ORM)を使うことです。先ほどのクエリは、次のように書けます。
コードが読みやすくなるだけでなく、キャッシュなどのORMによる最適化も利用でき、セキュリティとパフォーマンスも向上します。
詳しくは、SQLインジェクションのチートシートをご覧ください。
2. コードをスキャンする
Pythonセキュリティの維持に役立つ静的コード解析ツールは数多くあります。ここでは、異なる3つのレベルのツールを見ていきましょう。
まずはリンターです。PEP8は何十年にもわたり、Pythonのスタイルガイドとして使われてきました。pep8、pylint、flake8など、スタイルガイドへの準拠を確認するツールが各種提供されており、IDEに組み込まれているものもあります。
次に、banditのようなツールは、コードを抽象構文木(AST)に変換し、一般的なセキュリティ上の問題を見つけるために解析します。構文レベルで動作する一般的なリンターよりも一段深い解析を行います。ただし、banditは中間表現とパフォーマンスの制約を受けます。たとえば、データフローに関する問題(汚染解析、taint analysis)を検出できません。こうした問題は、SQLインジェクションやXSSなど、深刻な脆弱性につながる可能性があります。
最後に、静的アプリケーション・セキュリティ・テスト(SAST)ツールであるSnyk Codeは、セマンティック解析を行い、ファイルをまたぐ複雑な問題も考慮します。このレベルの他のツールと異なり、Snyk Codeは高速なスキャンとIDEとの連携(またはコマンドラインへの直接統合)により、開発者が使いやすいツールです。精度の高い検出結果をわかりやすく説明し、修正例など、Pythonのセキュリティ問題を解決するための支援を提供します。さらに、簡単に使い始められ、オープンソースでは無料で利用できます(オープンソース以外のテストも一部無料で利用可能です)。
3. パッケージのダウンロードには注意する
パッケージのインストールは簡単ですが、Pythonのセキュリティ脆弱性を持ち込む原因にもなります。通常、開発者はPythonの標準パッケージインストーラーであるpipを使い、Python Package Index(PyPI)からパッケージを取得します。そのため、PyPIにパッケージが追加される仕組みを理解しておくことが重要です。
PyPIにはセキュリティ上の懸念を報告する手順があります。悪意のあるパッケージやPyPI内の問題が報告されれば対処されますが、PyPIに追加されるパッケージは審査されません。PyPIを支えるボランティアに、すべてのパッケージの審査を求めるのは現実的ではないためです。
そのため、PyPIには悪意のあるパッケージが存在すると想定し、適切に対処するのが賢明です。インストールするパッケージについて事前に調べ、名前を正確に入力することなどが有効です(広く使われているパッケージの名前をわずかに変えたパッケージが、悪意のあるコードを実行する可能性があります)。パッケージをダウンロードする前に、Snyk Advisorで確認しましょう。

Snyk Advisorでパッケージを検索すると、そのパッケージに関する情報やコミュニティでのサポート状況、バグと修正の履歴など、さまざまな情報を確認できます。また、検索結果ページの上部にはインストールコマンドも表示されます。タイポスクワッティングを防ぐため、表示された名前をコピーして貼り付けるのがおすすめです。Snyk Advisorを使えば、パッケージを信頼できるかどうか判断できます。セキュリティ上の問題の履歴や、修正にかかった時間も確認できます。
もうひとつのベストプラクティスは、仮想環境を使ってプロジェクトを相互に分離することです。また、pip freezeなどのコマンドを使って環境の変更を記録し、requirementsリストに残しましょう。
最新の情報を維持するため、Snyk Open Sourceは、セキュリティ上の問題とその修正方法を記録した、業界をリードする脆弱性データベースを活用しています。Snyk Open Sourceはrequirementsを使ってスキャンを行い、直接依存関係と間接依存関係で見つかった脆弱性について実行可能な情報を提供し、すぐに修正できるよう支援します。
4. 依存関係のライセンスを確認する
オープンソースプロジェクトの利用を検討するときは、ライセンスの内容を理解することが重要です。オープンソースプロジェクトは無料で利用できますが、利用条件が定められている場合があります。条件には通常、ソフトウェアの使用方法や、加えた変更を公開する必要があるかどうかなどが含まれます。法的なリスクを避けるため、利用するプロジェクトに適用されるオープンソースライセンスについて理解しておきましょう。
想定より制約の厳しいライセンス(GPL、SSPLなど)が採用されている場合、身動きが取れなくなる可能性があります。ライセンス条件に従うか、プロジェクトの利用をやめるかを迫られることもあります。また、ライセンスのないプロジェクトに変更を加えると、著作権法に抵触するおそれがあります。
プロジェクトを持続可能な状態に保ち、不要なPythonセキュリティ上および法的なリスクにさらされないよう、プロジェクトの依存関係に含まれるライセンスと脆弱性の問題をスキャンして修正しましょう。
Snyk Open Sourceは、オープンソースライセンスのコンプライアンス管理を支援します。開発者が使いやすい方法でエンドツーエンドの可視性を実現し、柔軟なガバナンスを提供します。
5. システム標準のPythonバージョンを使わない
多くのPOSIXシステムには、Pythonがプリインストールされています。標準で組み込まれているPythonディストリビューションの多くは、最新ではないという問題があります。
そのため、システムで利用できる最新バージョンのPythonと、Pythonの実行用に設計された公式コンテナを使い、常に最新の状態に保ちましょう。Snykがその作業を支援します。Snyk Containerでコンテナをスキャンして必要な更新を確認し、Snyk Open Sourceで依存関係をチェックできます。
6. Pythonの仮想環境を使う
Pythonには、アプリケーション開発を仮想環境に分離する機能が備わっています。仮想環境は、その環境にインストールされたPythonインタープリター、ライブラリ、スクリプトを分離します。つまり、すべてのプロジェクトでグローバルのPythonバージョンと依存関係を使う代わりに、プロジェクトごとに独自のPythonと依存関係のバージョンを使える仮想環境を作成できます。
Anaconda Navigatorなどの多くのIDE、CLI、ダッシュボードには、仮想環境を切り替える機能が組み込まれています。
プロのヒント:Python 3.5以降ではvenvの使用が推奨されています。Python 3.6ではpyvenvは非推奨になりました。
仮想環境を使うと、安全なPythonアプリケーションの開発、パッケージ化、配布が簡単になります。ぜひ活用してください。詳しくはPython venvのドキュメントをご覧ください。
7. 本番環境ではDEBUG = Falseに設定する
開発環境では、詳細なエラーメッセージを表示するのが合理的です。しかし本番環境では、攻撃者が環境やライブラリ、コードについて知る手がかりとなる情報の漏えいを防ぐ必要があります。
ほとんどのフレームワークでは、デバッグ機能がデフォルトで有効になっています。たとえばDjangoでは、settings.pyで有効になっています。攻撃者へのアプリケーションの機密情報漏えいを防ぐため、本番環境ではデバッグ機能をFalseに設定してください。
プロのヒント:本番環境にデプロイした後、継続的デプロイシステムでこの設定が無効になっていることを確認するとよいでしょう。
8. 文字列のフォーマットに注意する
Pythonには「物事を行う方法はひとつだけ」という考え方がありますが、実際には文字列をフォーマットする方法が4種類あります(Python 3.6より前のバージョンでは3種類)。
文字列のフォーマット方法は、柔軟性と機能性が徐々に高まってきました(特にf文字列は興味深い機能です)。しかし、柔軟性が高まるほど、悪用される可能性も高まります。そのため、Pythonユーザーは、ユーザー入力を含む文字列をどのようにフォーマットするか慎重に検討する必要があります。
Pythonにはstringという組み込みモジュールがあります。このモジュールには、テンプレート文字列を作成するためのTemplateクラスが含まれています。
次の例を見てみましょう。
上記のコードでは、greeting変数は「Hello World, my name is Hayley.」と評価されます。
この文字列フォーマットは、import文が必要で、型に対する柔軟性も低いため、少し扱いにくい方法です。また、f文字列のようにPython文を評価することもありません。こうした制約があるため、ユーザー入力を扱う場合にはテンプレート文字列が適しています。
文字列のフォーマットについて、もう一点。前述のとおり、生のSQLを扱う際は特に注意してください。
9. シリアライズ/デシリアライズは慎重に
Pythonには、pickleモジュールを使ってPythonオブジェクトをシリアライズ/デシリアライズする「ピクリング」という組み込みの仕組みがあります。これは安全ではないことが知られており、信頼できるデータソースに対してのみ、細心の注意を払って使用することをおすすめします。
シリアライズ/デシリアライズの新たなデファクトスタンダードはYAMLです。PyYAMLパッケージを使うと、カスタムデータ型をYAMLにシリアライズし、再び元の形式に戻すことができます。しかし、PyYAMLにはさまざまな攻撃ベクトルの可能性が潜んでいます。PyYAMLを安全に使うためのシンプルかつ効果的な方法は、ローダーとしてyaml.Loader()ではなくyaml.SafeLoader()を使うことです。
これによりカスタムクラスの読み込みは防止されますが、ハッシュや配列などの標準的な型は引き続き利用できます。
もうひとつよくある用途がXMLです。標準ライブラリがよく使われますが、DOS攻撃や外部エンティティの展開(外部ソースが参照される)といった典型的な攻撃に対して脆弱です。最初の防御策として有効なのが、defusedxmlというパッケージです。こうしたXMLに関する一般的なセキュリティ問題への対策が組み込まれています。
おまけ:セキュリティ以外のヒント — Pythonの型アノテーションを使う
バージョン3.5で型ヒントが導入されました。Pythonのランタイムは型アノテーションを強制しませんが、型チェッカー、IDE、リンター、SASTなどのツールは、開発者がより明示的に記述することでその恩恵を受けられます。考え方を示す例を紹介します。
Literal[...]はバージョン3.8で導入されました。ランタイムによる強制はありません(この例では任意の文字列を渡せます)が、型チェッカーを使うと、パラメーターが許可された範囲外であることを検出して警告できます。これはPythonのセキュリティに限らず、非常に便利な機能です。
注:ランタイムによる強制がないため、型ヒントをセキュリティ目的で利用するには限界があります。
CTFを始めよう
オンデマンドのバーチャル入門ワークショップで、CTFチャレンジの解き方を学びましょう。



