Python開発者のためのセキュアコーディングのコツ10個

本記事は2021年9月27日に公開した[Python security best practices cheat sheet](https://snyk.io/blog/python-security-best-practices-cheat-sheet/)を日本語化した内容です。

[![logo-solid-background.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2216557/56ac62e4-846c-6656-fdeb-e7853f597d80.png)](https://snyk.io/jp)

2019年、Snykは[最初のPythonチートシート](https://res.cloudinary.com/snyk/image/upload/v1551385381/Python_Security_Best_Practices_Cheat_Sheet__.pdf)をリリースしました。それ以来、Pythonのセキュリティの多くの側面が変化しています。開発者向けセキュリティ企業として学んだこと、そしてPython特有のベストプラクティスに基づいて、Pythonのコードを安全に保つために、この最新のチートシートをまとめました。

[【チートシート】2021年版Pythonセキュリティベストプラクティス](https://snyk.io/wp-content/uploads/cheat-sheet-python-security-2021.pdf)

本記事では、下記に関するPythonのセキュリティに関するヒントを紹介します。

1. 外部データを常にサニタイズする
2. コードをスキャンする
3. パッケージのダウンロードに注意
4. 依存先パッケージのライセンスを確認する
5. システム標準版のPythonを使用しない
6. Pythonの仮想環境向け機能を利用する
7. 本番では`DEBUG = False`に設定する
8. 文字列の書式設定に注意
9. (デ)シリアライズを慎重に行う
10. Pythonの型アノテーションを使用する

その前に、ひとつだけ注意点があります。SnykのPythonエコシステムに関するデータ、および学術研究によると、Pythonは他の広く使われている言語と比較して安全性が高い（または低い）わけではありません。心にとめておきましょう。このチートシートは、私たちPythonista（Python開発者）のために特別に用意したものです。他のエコシステムで安全である方法を学ぶために、[弊社が作成している他のすべてのセキュリティチートシート](https://snyk.io/security-resources/cheat-sheet/)をチェックすることをお勧めします。

# 1. 外部データを常にサニタイズする
あらゆるアプリケーションの攻撃ベクトルの1つは外部データであり、インジェクション、[XSS](https://snyk.io/learn/cross-site-scripting/)、サービス拒否（DOS）攻撃などに利用される可能性があります。Pythonのセキュリティを維持するための一般的なルールは、データがユーザー入力フォーム、ウェブサイトのスクレイピング、またはデータベースリクエストに由来するかにかかわらず、外部ソースからのデータを常にサニタイズ（要注意なデータを削除）することです。また、安全でない処理を防ぐために、データがアプリケーションに入るとすぐにサニタイズします。これにより、サニタイズされていない危険なデータが誤ってアプリケーションで扱われるリスクを減らすことができます。

[入力値にある問題をチェックするのではなく](https://www.advancedcyber.co.uk/it-security-blog/phishing-technique-certainly-vulnerable-to)、サニタイズなど、入力値が正規の形であるのを確かめることが、理にかなっています。また、サニタイズにはきちんと整備されているライブラリを使用することをお勧めします。以下はその2つです。

* [schema](https://pypi.org/project/schema/)は、「設定ファイル、フォーム、外部サービス、コマンドラインパースなどから取得され、JSON/YAML（または他の何か）からPythonデータ型に変換されたPythonデータ構造の検証を行うライブラリ」です。
* [bleach](https://pypi.org/project/bleach/)は、「マークアップと属性をエスケープまたは除去する、許可リストベースのHTMLサニタイズライブラリ」です。


主要なフレームワークには、[Flask](https://flask.palletsprojects.com/en/2.0.x/api/?highlight=escape#flask.escape)の`flask.escape()`や[Django](https://docs.djangoproject.com/en/2.0/_modules/django/utils/html/)の`django.utils.html.escape()`のような、独自のサニタイズ関数が付属しています。これらの関数の目的は、下記のような、潜在的に悪意のあるHTML入力から無害化することです。

```
>>> import bleach
>>> bleach.clean('an XSS <script>navigate(...)</script> example')
'an XSS &lt;script&gt;navigate(...)&lt;/script&gt; example'
```

この方法の限界は、ライブラリは万能ではないことです。ライブラリは、ある領域に特化しているのです。補足ですが、悪意のあるデータを含む可能性のあるXML他のデータ形式にも触れていますので、この記事の最後を必ず読んでください。

もう1つのよく使われる方法は、HTMLのレンダリングを[Jinja](https://jinja.palletsprojects.com/en/3.0.x/)のようなテンプレートエンジンに任せることです。これは多くの機能を提供しますが、その中でも[MarkupSafe](https://markupsafe.palletsprojects.com/en/2.0.x/)を使用した[XSSを防ぐための自動エスケープ機能](https://jinja.palletsprojects.com/en/3.0.x/intro/)があります。

サニタイズのもう一つの側面は、データがコマンドとして使用されるのを防ぐことです。典型的な例としては、[SQLインジェクション](https://snyk.io/learn/sql-injection/)があります。文字列や変数をつなぎ合わせてSQLクエリを生成するのではなく、名前付きパラメータを使って、何をコマンドとして扱い、何をデータとして扱うかをデータベースに伝えることが望ましいです。


```
# Instead of this …
cursor.execute(f"SELECT admin FROM users WHERE username = '{username}'");
# ...do this...
cursor.execute("SELECT admin FROM users WHERE username = %(username)s", {'username': username}); 
```

あるいは、[sqlalchemy](https://www.sqlalchemy.org/)などのORM(Object-Relational Mapping)を使えば、下記のようなクエリになります：

```
query = session.query(User).filter(User.name.like('%{username}'))
```

ここでは、より読みやすいコードと、キャッシュなどのORMの最適化、さらにセキュリティとパフォーマンスが向上しています!

もっと詳しく知りたい方は、[SQLインジェクションのチートシート](https://snyk.io/blog/sql-injection-cheat-sheet/)をご覧ください。

# 2. コードをスキャンする
開発者は、Pythonのセキュリティを維持するために、さまざまな静的コード解析ツールを自由に使うことができます。ここでは、3つの異なるレベルのツールについて見てみましょう。

まず、[**リンター**](https://sourcelevel.io/blog/what-is-a-linter-and-why-your-team-should-use-it)のレベルです。[PEP8](https://www.python.org/dev/peps/pep-0008/)はPythonのスタイルガイドとして、もう何十年も役立っています。[pep8](https://pypi.org/project/pep8/)、[pylint](https://www.pylint.org/)、[flake8](https://flake8.pycqa.org/en/latest/#)など、このスタイルガイドと照らし合わせるためのさまざまなツールが利用可能です（IDEに組み込まれていることもあります）。

次に、[bandit](https://pypi.org/project/bandit/)のようなツールは、コードを抽象構文木（AST）に変換し、それに対してクエリーを実行して、典型的な[セキュリティ問題](https://snyk.io/learn/security-vulnerability-exploits-threats/)を見つけます。これは、構文レベルで動作する一般的なリンターが行うものより一段高いレベルです。それでも、banditは中間表現と性能に限界があります。例えば、バンディットはデータフローに関連する問題（テイント分析として知られている）を検出できず、これらは破壊的な欠陥（例としてSQLインジェクションやXSSなどのインジェクション）につながります。

最後に、[Snyk Code](https://snyk.io/product/snyk-code/)のような[静的アプリケーションセキュリティテスト（SAST）ツール](https://snyk.io/learn/application-security/static-application-security-testing/)は、複雑なファイル間の問題まで考慮に入れて、意味論的分析を実行します。このレベルの他のツールとは異なり、Snyk Codeは高速にスキャンし、[IDE（または直接コマンドライン）に統合できるので](https://snyk.io/ide-plugins)、開発者フレンドリーなツールとなっています。Snyk Codeは、非常に正確な調査結果を説明し、Pythonのセキュリティ問題を修正する方法の例を含むヘルプを提供します。その上、簡単に始めることが可能で、無料でオープンソースに対してスキャンを行えます（さらに、オープンソースソフトウェア以外を対象にしたテストも制限の範囲内で利用できます）。

# 3. パッケージのダウンロードに注意

パッケージをインストールするのは簡単ですが、Pythonのセキュリティ脆弱性を導入する簡単な方法でもあります。通常、開発者はPython Package Index (PyPI) を利用する Pythonの標準パッケージインストーラ (pip) を使用します。このため、パッケージがどのようにPyPIに追加されるかを理解することが重要です。

[PyPIには、セキュリティ上の懸念を報告するための手順](https://pypi.org/security/)があります。誰かが悪意のあるパッケージやPyPI内の問題を報告した場合、それに対処しますが、PyPIに追加されたパッケージはレビューを受けません - PyPIを保守するのがボランティアであることを考慮すると、レビューを期待するのは非現実的です。

したがって、PyPI内に悪意のあるパッケージが存在すると仮定し、それに従って行動することが賢明です。合理的な手順としては、インストールしたいパッケージについて少し調べ、パッケージ名を慎重に綴るようにします（[一般的なパッケージの綴りを間違えたパッケージは、悪質なコードを実行する可能性があります](https://www.theregister.com/2021/03/02/python_pypi_purges/)）。パッケージをダウンロードする前に、必ず[Snyk Advisor](https://snyk.io/advisor/)でチェックしてください。

![blog-python-cheat-sheet-advisor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2216557/cb36cc4f-ff85-f542-b0a4-2cf563bdb7c0.png)

Snyk Advisorでパッケージを検索すると、パッケージに関する多くの情報、コミュニティでのサポート、バグや修正の歴史、その他多くの情報を得ることができます。また、Snyk Advisorは結果ページの上部にインストールコマンドを表示します。[URLハイジャッキング(typosquatting)](https://snyk.io/blog/typosquatting-attacks/)を防ぐために、そのスペルをコピーして貼り付けるのがベストプラクティスです。Snyk Advisor は、パッケージを信頼すべきかどうかを教えてくれます。セキュリティ問題の履歴と、その修正に要した時間を見ることができます。

もう一つのベストプラクティスは、仮想環境を使ってプロジェクトを互いに分離することです。また、`pip freeze`または同等のコマンドを使用して、環境の変更を[requirements.txtファイル](https://pip.pypa.io/en/stable/user_guide/#requirements-files)に記録します。

常に最新のリファレンスを維持する[Snyk Open Source](https://snyk.io/product/open-source-security-management/)は、セキュリティ問題と可能な修正を記録した業界最先端の[脆弱性データベース](https://snyk.io/product/vulnerability-database/)をベースにしています。Snyk Open Sourceでは、requirements.txtファイルを使用してスキャンを実行し、発見された直接および推移的な依存関係の脆弱性に関する実用的な情報を提供し、それらをすぐに修正することができます。

# 4. 依存先パッケージのライセンスを確認する
オープンソースプロジェクトの利用を検討する場合、これらのプロジェクトがどのようにライセンスされているかを理解することが重要です。オープンソース・プロジェクトは無料で使用できますが、条件が適用される場合があります。これらの条件は通常、ソフトウェアの使用方法、ソフトウェアに加えた変更を一般に公開する必要があるかどうか、およびその他の類似の要件に関係します。あなたが利用するプロジェクトに必要な[オープンソースライセンス](https://snyk.io/learn/open-source-licenses/)に精通し、あなたが法的に妥協していないことを確認する必要があります。

プロジェクトが[想定よりも厳格なライセンス](https://snyk.io/learn/what-is-copyleft-license/)([GPL](https://snyk.io/learn/what-is-gpl-license-gplv3-explained/)、SSPL など) を採用している場合、ライセンスの条件を守るか、そのプロジェクトの使用を中止するかのどちらかを迫られ、自分自身を追い詰める結果になる可能性があります。さらに、ライセンスのないプロジェクトに変更を加える必要がある場合、著作権法に抵触する可能性があります。

あなたのプロジェクトが持続可能で、Pythonのセキュリティリスクや法的リスクに不必要にさらされないようにするために、あなたのプロジェクトが依存しているパッケージのライセンスと脆弱性の問題をスキャンして修正しましょう。

[Snyk Open Source](https://snyk.io/product/open-source-security-management/)は、[オープンソースライセンスのコンプライアンス管理](https://snyk.io/product/open-source-license-compliance/)で、サポートすることができます。柔軟なガバナンスを提供しながら、エンドツーエンドの可視性を得るための開発者向けのツールを提供しています。

# 5. システム標準版のPythonを使用しない

ほとんどのPOSIXシステムには、Pythonのバージョンがプリロードされています。ほとんどのビルトインPythonディストリビューションの問題は、それらが最新ではないことです。

ですから、あなたのシステムで利用可能な最新バージョンのPythonを使用しましょう。[Pythonの公式コンテナ](https://hub.docker.com/_/python)を使い、最新のバージョンに更新しておきましょう。Snykは常にあなたをサポートします。[Snyk Container](https://snyk.io/product/container-vulnerability-management/)を使用してコンテナをスキャンして必要な更新を行い、[Snyk Open Source](https://snyk.io/product/open-source-security-management/)を使用して依存先のパッケージをチェックしてください。

# 6. Pythonの仮想環境向け機能を利用する
Pythonは、アプリケーションの開発を仮想環境に分離する機能を備えています。仮想環境は、そこにインストールされたPythonインタープリタ、ライブラリ、スクリプトを分離します。つまり、すべてのプロジェクトでグローバルなPythonのバージョンとPythonのパッケージを使う代わりに、プロジェクト固有の仮想環境を用意して、独自のPythonとパッケージのバージョンを使うことができるのです！

ほとんどの IDE、CLI、そして[Anaconda Navigator](https://www.anaconda.com/products/individual)のようなダッシュボードには、仮想環境間を切り替えるためのビルトイン関数があります。

**ワンポイントアドバイス**：Pythonバージョン3.5 では、`venv`の使用が推奨され、バージョン3.6では`pyvenv`は非推奨となりました。

仮想環境は、安全なPythonアプリケーションの開発、パッケージ化、リリースをより簡単にします。仮想環境の利用は強く推奨されます。詳しくは[Python venv doc](https://docs.python.org/3/library/venv.html)を参照してください。

# 7. 本番では`DEBUG = False`に設定する

開発環境では、詳細なエラーメッセージを表示することは理にかなっています。しかし、実稼働環境では、攻撃者があなたの環境、ライブラリ、コードについてより詳しく知るための情報漏えいを防ぎたいものです。

デフォルトでは、ほとんどのフレームワークでデバッグのスイッチがオンになっています。例えば、Djangoはsettings.pyで有効になっています。攻撃者にアプリケーションの機密情報を漏らさないようにするために、実運用ではデバッグを`False`に切り替えることを忘れないようにしましょう。

**ワンポイントアドバイス**：本番環境にデプロイするとき、CD（継続的デプロイメント）システムに、デプロイ後はこの設定が無効になっていることを確認させると便利です。

# 8. 文字列の書式設定に注意

Pythonには「いいやり方はただひとつ」という考え方がありますが、実際には文字列をフォーマットする方法が4種類あります（Python 3.6より前のバージョンでは3種類）。

文字列の書式は徐々に柔軟で強力になってきていますが（f-stringは特に興味深い）、柔軟性が増すにつれて、悪用される可能性も高くなります。このため、Pythonのユーザーは、ユーザーが提供する入力で文字列をどのようにフォーマットするかを慎重に検討する必要があります。

Pythonには`string`という名前の組み込みモジュールがあります。このモジュールには`Template`クラスが含まれており、テンプレート文字列を作成するために使用されます。

次の例を見てください。

```
from string import Template
greeting_template = Template(“Hello World, my name is $name.”)
greeting = greeting_template.substitute(name=”Hayley”)
```

上記のコードでは、変数greetingは次のように評価されます。"Hello World, my name is Hayley." と評価されます。

この文字列フォーマットはimport文が必要で、型に対する柔軟性に欠けるため、少し面倒です。また、f-stringのようにPythonのステートメントを評価することもできません。これらの制約により、テンプレート文字列はユーザー入力を扱う際に優れた選択肢となります。

文字列フォーマットに関して注意点をもうひとつ。上で述べたように、生のSQLの扱いには特に注意してください。

# 9. (デ)シリアライズを慎重に行う

Pythonには、[pickle](https://docs.python.org/3/library/pickle.html)モジュールを使ってPythonオブジェクトをシリアライズ/デシリアライズする "pickling "という仕組みが組み込まれています。これは安全でないことが知られており、**非常に慎重に、信頼できるデータソースにのみ使用することが推奨されます。**

シリアライズ/デシリアライズのための新しいデファクトスタンダードはYAMLです。[PyYAML](https://pypi.org/project/pyaml/)パッケージは、カスタムデータ型をYAMLにシリアライズし、再び戻すためのメカニズムを提供します。しかし、PyYAMLはさまざまな[攻撃ベクトルをはらんでいます](https://snyk.io/vuln/pip:PyYAML)。PyYAMLの使用を安全にするシンプルで効果的な方法は、ローダーとして`yaml.Loader()`の代わりに `yaml.SafeLoader()`を使用することです。

```
Data = yaml.load(input_file, Loader=yaml.SafeLoader)
```

これにより、カスタムクラスの読み込みはできませんが、ハッシュや配列などの標準的な型はサポートされます。

もう一つの典型的なユースケースはXMLです。標準的なライブラリがよく使われますが、[典型的な攻撃](https://docs.python.org/3/library/xml.html#xml-vulnerabilities)、すなわちDOS攻撃や外部エンティティ展開（外部ソースが参照）に対して脆弱性があります。防御の第一線として、[defusedxml](https://snyk.io/advisor/python/defusedxml)と呼ばれるパッケージがよいでしょう。これは、これらの典型的なXMLのセキュリティ問題に対する安全策を備えています。

# 10. Pythonの型アノテーションを使用する

バージョン3.5から、[型ヒント](https://docs.python.org/3/library/typing.html)が導入されました。Pythonランタイムは型アノテーション（型注釈）を強制しませんが、型チェッカー、IDE、リンター、SASTなどのツールは、開発者がより明示的であることで利益を得ることができます。以下はそのアイデアを強調するための例です。

```
MODE = Literal['r', 'rb', 'w', 'wb']
def open_helper(file: str, mode: MODE) -> str:
    ...
open_helper('/some/path', 'r')  # Passes type check
open_helper('/other/path', 'typo')  # Error in type checker
```

[Literal[...]](https://www.python.org/dev/peps/pep-0586/)はバージョン3.8で導入され、ランタイムによって強制されることはありません（この例では好きな文字列を渡すことができます）が、型チェッカーはパラメータが許可された範囲に含まれないことを発見して警告してくれるようになりました。これは素晴らしい機能で、Pythonセキュリティ以外の面でも有用です。

**注意**：ランタイムによって強制されないので、型ヒントのセキュリティ上の使用は制限されています。

# 最後に
Snykの公式ウェブサイトを公開しました。またSNSにて最新の脆弱性情報などを発信しているので、ぜひフォローをお願いします。

* [Snyk公式ウェブサイト](https://snyk.io/jp)
* [Snyk Japan Twitter](https://twitter.com/snykjp)
* [Snyk Japan Facebook](https://www.facebook.com/snykJP/)

またSnykは無料でお試しいただけます。ぜひお試しください！

https://qiita.com/SnykSec/items/aa80a976eb17299fff62


Snykを活用したブログは、ぜひQiitaアドカレ2021もご参照ください。

https://qiita.com/advent-calendar/2021/snyk


(2022年8月31日追記）
この度、来たる２０２２年10月14日（金）にエンジニア向けのCTF競技大会「Snyk Capture the Flag 101（Snyk キャプチャー・ザ・フラッグ 101）」を開催することになりました。こちらはPythonを用います。

キャプチャー・ザ・フラッグ (CTF) は、様々なチャレンジを解いて「フラッグ（Flag/旗）」を獲得する競技大会です。チャレンジごとに難易度が設定されており、解決した課題の難易度に応じてスコアをゲットできます。CTFを通してゲーム感覚で楽しみながらセキュリティスキルを身につけることができますので、よろしければご参加ください。

【イベント概要】
日時：2022年10月14日（金）15:00-17:00
場所：アマゾン ウェブ サービス ジャパン合同会社 オフィス（目黒セントラルスクエア ：東京都品川区上大崎3丁目1-1）
形式：ハイブリッド（会場での参加とオンライン参加）
お申し込み：事前登録制（無料）
URL：https://go.snyk.io/jp-ctf-aws-20221014.html