Bandit を使って Python コードのセキュリティをチェックしよう!

記事タイトルとURLをコピーする

Bandit とは

github.com

Python コードのセキュリティ問題をチェックしてくれるツールです。Amazon CodeGuru Reviewer の内部でも使用されています。

aws.amazon.com

試してみる

実際のプロジェクトへの導入方法を確認します。

インストール

pip でインストールが可能です。

$ pip install bandit

正しくインストールされていれば以下コマンドでバージョンを確認可能です。

$ bandit --version
bandit 1.7.4
  python version = 3.9.13 (main, Aug 21 2022, 11:28:09) [Clang 13.1.6 (clang-1316.0.21.2.5)]

実行方法

以下コマンドで、ファイルを指定して実行できます。

$ bandit bandit_sample.py
[main]  INFO    profile include tests: None
[main]  INFO    profile exclude tests: None
[main]  INFO    cli include tests: None
[main]  INFO    cli exclude tests: None
[main]  INFO    running on Python 3.9.13
[node_visitor]  WARNING Unable to find qualified name for module: bandit_sample.py
Run started:2022-09-26 10:15:47.166530

Test results:
>> Issue: [B307:blacklist] Use of possibly insecure function - consider using safer ast.literal_eval.
   Severity: Medium   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   Location: bandit_sample.py:3:0
   More Info: https://bandit.readthedocs.io/en/1.7.4/blacklists/blacklist_calls.html#b307-eval
2
3       eval(sys.args[1])
4

--------------------------------------------------

Code scanned:
        Total lines of code: 2
        Total lines skipped (#nosec): 0

Run metrics:
        Total issues (by severity):
                Undefined: 0
                Low: 0
                Medium: 1
                High: 0
        Total issues (by confidence):
                Undefined: 0
                Low: 0
                Medium: 0
                High: 1
Files skipped (0):

チェック対象にしたサンプルコードは以下の通りです。コマンドライン引数をそのまま eval するという暴挙に出ています。

# bandit_sample.py
import sys  
  
eval(sys.args[1])  

-r オプションでディレクトリ指定が可能です。

$ bandit -r path/to/dir

-h オプションでその他オプションの解説、およびチェック対象の一覧が表示されます。

$ bandit -h
usage: bandit [-h] [-r] [-a {file,vuln}] [-n CONTEXT_LINES] [-c CONFIG_FILE] [-p PROFILE] [-t TESTS] [-s SKIPS]
              [-l | --severity-level {all,low,medium,high}] [-i | --confidence-level {all,low,medium,high}]
              [-f {csv,custom,html,json,screen,txt,xml,yaml}] [--msg-template MSG_TEMPLATE] [-o [OUTPUT_FILE]] [-v] [-d] [-q]
              [--ignore-nosec] [-x EXCLUDED_PATHS] [-b BASELINE] [--ini INI_PATH] [--exit-zero] [--version]
              [targets ...]

Bandit - a Python source code security analyzer

positional arguments:
  targets               source file(s) or directory(s) to be tested

optional arguments:
  -h, --help            show this help message and exit
  -r, --recursive       find and process files in subdirectories
  -a {file,vuln}, --aggregate {file,vuln}
                        aggregate output by vulnerability (default) or by filename
  -n CONTEXT_LINES, --number CONTEXT_LINES
                        maximum number of code lines to output for each issue
  -c CONFIG_FILE, --configfile CONFIG_FILE
                        optional config file to use for selecting plugins and overriding defaults
  -p PROFILE, --profile PROFILE
                        profile to use (defaults to executing all tests)
  -t TESTS, --tests TESTS
                        comma-separated list of test IDs to run
  -s SKIPS, --skip SKIPS
                        comma-separated list of test IDs to skip
  -l, --level           report only issues of a given severity level or higher (-l for LOW, -ll for MEDIUM, -lll for HIGH)
  --severity-level {all,low,medium,high}
                        report only issues of a given severity level or higher. "all" and "low" are likely to produce the same
                        results, but it is possible for rules to be undefined which will not be listed in "low".
  -i, --confidence      report only issues of a given confidence level or higher (-i for LOW, -ii for MEDIUM, -iii for HIGH)
  --confidence-level {all,low,medium,high}
                        report only issues of a given confidence level or higher. "all" and "low" are likely to produce the same
                        results, but it is possible for rules to be undefined which will not be listed in "low".
  -f {csv,custom,html,json,screen,txt,xml,yaml}, --format {csv,custom,html,json,screen,txt,xml,yaml}
                        specify output format
  --msg-template MSG_TEMPLATE
                        specify output message template (only usable with --format custom), see CUSTOM FORMAT section for list of
                        available values
  -o [OUTPUT_FILE], --output [OUTPUT_FILE]
                        write report to filename
  -v, --verbose         output extra information like excluded and included files
  -d, --debug           turn on debug mode
  -q, --quiet, --silent
                        only show output in the case of an error
  --ignore-nosec        do not skip lines with # nosec comments
  -x EXCLUDED_PATHS, --exclude EXCLUDED_PATHS
                        comma-separated list of paths (glob patterns supported) to exclude from scan (note that these are in
                        addition to the excluded paths provided in the config file) (default:
                        .svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.eggs,*.egg)
  -b BASELINE, --baseline BASELINE
                        path of a baseline report to compare against (only JSON-formatted files are accepted)
  --ini INI_PATH        path to a .bandit file that supplies command line arguments
  --exit-zero           exit with 0, even with results found
  --version             show program's version number and exit

CUSTOM FORMATTING
-----------------

Available tags:

    {abspath}, {relpath}, {line}, {col}, {test_id},
    {severity}, {msg}, {confidence}, {range}

Example usage:

    Default template:
    bandit -r examples/ --format custom --msg-template \
    "{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}"

    Provides same output as:
    bandit -r examples/ --format custom

    Tags can also be formatted in python string.format() style:
    bandit -r examples/ --format custom --msg-template \
    "{relpath:20.20s}: {line:03}: {test_id:^8}: DEFECT: {msg:>20}"

    See python documentation for more information about formatting style:
    https://docs.python.org/3/library/string.html

The following tests were discovered and loaded:
-----------------------------------------------
        B101    assert_used
        B102    exec_used
        B103    set_bad_file_permissions
        B104    hardcoded_bind_all_interfaces
        B105    hardcoded_password_string
        B106    hardcoded_password_funcarg
        B107    hardcoded_password_default
        B108    hardcoded_tmp_directory
        B110    try_except_pass
        B112    try_except_continue
        B201    flask_debug_true
        B301    pickle
        B302    marshal
        B303    md5
        B304    ciphers
        B305    cipher_modes
        B306    mktemp_q
 
# 以下略

設定

プロジェクト導入時、毎回コマンドラインオプションを指定するのも面倒です。そんな時は設定ファイルを用意します。.bandit ファイルに INI ファイル形式で設定を記述できます。

# .bandit
[bandit]
exclude = tests
skips = B101,B601

上記例の場合、tests ディレクトリをチェック対象から除外、また B101B601 のテストは実施しなくなります。その他の設定は以下ページを参照ください。

bandit.readthedocs.io

PEP 518 に準拠して pyproject.toml にも記載可能です。以下のような記載になります。

# pyproject.toml
[tool.bandit]
exclude_dirs = ["tests"]
skips = ["B101", "B601"]

注意点は、pyproject.toml の場合 exclude -> exclude_dirs とすること、実行時に -c オプションで pyproject.toml を指定することです。

$ bandit -c pyproject.toml -r .

特定箇所のみチェックを無効にしたい

# nosec コメントで当該行のチェックを無効にできます。

# bandit_sample.py
import sys  
  
eval(sys.args[1])  # nosec B307

flake8 のプラグインとして

有志が開発した flake8-bandit を使えば flake8 のプラグインとして、つまり flake8 実行時に Bandit のチェックも実行してくれます。

github.com

こちらも pip でインストール可能です。事前に flake8 はインストールしておく必要がありますが、Bandit 自体は依存に含まれているので banditflake8-bandit を両方インストールする必要はありません。

$ pip install flake8-bandit

flake8 を実行すると Bandit のチェックが行われていることが確認できました。

$ flake8
./bandit_sample.py:3:1: S307 Use of possibly insecure function - consider using safer ast.literal_eval.

注意点としてはコード体系が BXXX ではなく SXXX となっているので読替えが必要な点です。これは flake8-bugbear という別プラグインが BXXX のコード体系を使用している経緯からです。

github.com

前述の特定箇所のみチェックを無効にする方法も flake8 式にする必要があり、# noqa で記載します。

# bandit_sample.py
import sys  
  
eval(sys.args[1])  # noqa S307

設定については .bandit ファイルを使用できます。BXXXSXXX 両方のコード体系に対応しているようです。.flake8 ファイルの場合は SXXX のコード体系のみ、pyproject.toml ファイルには対応していないようです。

まとめ

Bandit を使って Python コードのセキュリティをチェックする方法をご紹介しました。継続的にチェックしてセキュアなコーディングをしていきましょう!

あわせて読みたい

blog.serverworks.co.jp

blog.serverworks.co.jp