Tokenやパスワードを見つけるツール調査と検証

Jul 7, 2024 13:30 · 2304 words · 5 minute read security python

「AWSの認証情報を誤って公開してしまった。認証情報を悪用されて被害が発生した。」という事件が後を絶ちません。

認証情報を検出するScannerを調べて検証したので、ブログに残しておきます。

目次

Scanner一覧

Secret KeyやTokenをScanするツールの中から有名なものをいくつか選定して調査しました。 ソースコードが公開されているツールを対象にしています。

trufflehog

Gitleaks

git-secrets

secretlint

repo-security-scanner

detect-secrets

  • GitHub - Yelp/detect-secrets: An enterprise friendly way of detecting and preventing secrets in code.
  • CLIからの実行だけでなく、Pythonコードからの実行もサポートしている
  • Baselineを作成し、Baselineとの差分で新規Secretを検知&対応する
  • wordlistを使って検出対象から除外するキーワードを指定する、Entropyの制限を調整するといった制御ができる
  • Pluginが多数あり、AWS Tokenなど以外にもTelegram TokenやJWTなどを検出できる
  • Python製

detect-secretsを試してみる

CLIで実行するツールが大半でしたが、detect-secretsはPythonコードからのScanもサポートしていました。 検知できるパターンも多そうなので、今回はdetect-secretsを試してみました。

インストール

pip install detect-secrets

CLIから実行

検出したsecretsは、hash化した状態で出力します。

[Question] What does “hashed_secrets” stands for in the output? · Issue #175 · Yelp/detect-secrets (github.com)

Saltは使っていません。そのため、脆弱なパスワードを検出した場合はRainbow Tableなどを使って元のパスワードを取得できる可能性があります。 Rainbow Tableを使ったパスワードの取得は本来想定している使い方ではないと思いますし、Rainbow Tableを使うくらいならソースコードを少しいじってHash化の処理をBypassする方が効率がいいと思いますが。

出力結果のサンプルはこちら

detect-secrets scan test_data/dummy_config.ini

{
  "version": "1.5.0",
  "plugins_used": [
    {
      "name": "ArtifactoryDetector"
    },
    {
      "name": "AWSKeyDetector"
    },
    {
      "name": "AzureStorageKeyDetector"
    },
    {
      "name": "Base64HighEntropyString",
      "limit": 4.5
    },
    {
      "name": "BasicAuthDetector"
    },
    {
      "name": "CloudantDetector"
    },
    {
      "name": "DiscordBotTokenDetector"
    },
    {
      "name": "GitHubTokenDetector"
    },
    {
      "name": "GitLabTokenDetector"
    },
    {
      "name": "HexHighEntropyString",
      "limit": 3.0
    },
    {
      "name": "IbmCloudIamDetector"
    },
    {
      "name": "IbmCosHmacDetector"
    },
    {
      "name": "IPPublicDetector"
    },
    {
      "name": "JwtTokenDetector"
    },
    {
      "name": "KeywordDetector",
      "keyword_exclude": ""
    },
    {
      "name": "MailchimpDetector"
    },
    {
      "name": "NpmDetector"
    },
    {
      "name": "OpenAIDetector"
    },
    {
      "name": "PrivateKeyDetector"
    },
    {
      "name": "PypiTokenDetector"
    },
    {
      "name": "SendGridDetector"
    },
    {
      "name": "SlackDetector"
    },
    {
      "name": "SoftlayerDetector"
    },
    {
      "name": "SquareOAuthDetector"
    },
    {
      "name": "StripeDetector"
    },
    {
      "name": "TelegramBotTokenDetector"
    },
    {
      "name": "TwilioKeyDetector"
    }
  ],
  "filters_used": [
    {
      "path": "detect_secrets.filters.allowlist.is_line_allowlisted"
    },
    {
      "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
      "min_level": 2
    },
    {
      "path": "detect_secrets.filters.heuristic.is_indirect_reference"
    },
    {
      "path": "detect_secrets.filters.heuristic.is_likely_id_string"
    },
    {
      "path": "detect_secrets.filters.heuristic.is_lock_file"
    },
    {
      "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
    },
    {
      "path": "detect_secrets.filters.heuristic.is_potential_uuid"
    },
    {
      "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
    },
    {
      "path": "detect_secrets.filters.heuristic.is_sequential_string"
    },
    {
      "path": "detect_secrets.filters.heuristic.is_swagger_file"
    },
    {
      "path": "detect_secrets.filters.heuristic.is_templated_secret"
    }
  ],
  "results": {
    "test_data/dummy_config.ini": [
      {
        "type": "AWS Access Key",
        "filename": "test_data/dummy_config.ini",
        "hashed_secret": "d70eab08607a4d05faa2d0d6647206599e9abc65",
        "is_verified": false,
        "line_number": 3
      },
      {
        "type": "Base64 High Entropy String",
        "filename": "test_data/dummy_config.ini",
        "hashed_secret": "d70eab08607a4d05faa2d0d6647206599e9abc65",
        "is_verified": false,
        "line_number": 3
      },
      {
        "type": "Secret Keyword",
        "filename": "test_data/dummy_config.ini",
        "hashed_secret": "d70eab08607a4d05faa2d0d6647206599e9abc65",
        "is_verified": false,
        "line_number": 3
      },
      {
        "type": "Secret Keyword",
        "filename": "test_data/dummy_config.ini",
        "hashed_secret": "70160aa810531303052a96d802080b5a31c8f05b",
        "is_verified": false,
        "line_number": 7
      },
      {
        "type": "Base64 High Entropy String",
        "filename": "test_data/dummy_config.ini",
        "hashed_secret": "3689379b5e6e9f87afe3faaff327d0f2f63b4e08",
        "is_verified": false,
        "line_number": 10
      },
      {
        "type": "GitHub Token",
        "filename": "test_data/dummy_config.ini",
        "hashed_secret": "e175c6f5f2a92e8623bd9a4820edb4e8c1b0fd10",
        "is_verified": false,
        "line_number": 10
      },
      {
        "type": "Secret Keyword",
        "filename": "test_data/dummy_config.ini",
        "hashed_secret": "439bf0fd6cce926a2cbc126d741d3a760cbf6216",
        "is_verified": false,
        "line_number": 13
      },
      {
        "type": "Secret Keyword",
        "filename": "test_data/dummy_config.ini",
        "hashed_secret": "c25fc3fbde02ea5169dda6e5c2980c1f36c1c342",
        "is_verified": false,
        "line_number": 14
      },
      {
        "type": "Secret Keyword",
        "filename": "test_data/dummy_config.ini",
        "hashed_secret": "45fadbb33d880d1c2669c287187f57f7b6eca8aa",
        "is_verified": false,
        "line_number": 17
      }
    ]
  },
  "generated_at": "2024-07-06T02:01:04Z

ファイル単位ではなく、git repository単位でのscanも可能です。

detect-secrets scan nodejs-scraping-sample/

Pythonコードから実行

サンプルコードです。 出力結果はCLIから実行した場合と同じです。

from detect_secrets import SecretsCollection
from detect_secrets.settings import default_settings

secrets = SecretsCollection()
with default_settings():
    secrets.scan_file('test_data/dummy_config.ini')


import json
print(json.dumps(secrets.json(), indent=2))

READMEでは、各プラグインの設定値を変更したり、Custom Pluginの作成、Filterを追加するサンプルコードも紹介されています。

ファイル以外の形式でデータを渡せるか調査

READMEとソースコードを読んだところ、公式でサポートされているインプットはファイル形式のみでした。

Pythonにはtempfileという一時的にファイルを作る便利なライブラリがあります。tempfileを使うことで、任意のテキストのScanも可能です。

import tempfile

from detect_secrets import SecretsCollection
from detect_secrets.settings import default_settings


f = tempfile.NamedTemporaryFile()
f.write(b'{password: "password12345"}')
f.seek(0)

secrets = SecretsCollection()
with default_settings():
    secrets.scan_file(f.name)


import json
print(json.dumps(secrets.json(), indent=2))

detect-secretsではScan処理の中でos.pathを操作しています。そのため、一時的に実体ファイルを作成するNamedTemporaryFile()の場合はエラーは発生しませんが、実体ファイルをを作らないTemporaryFile()の場合はos.pathの操作でエラーが発生します。

NamedTemporaryFile()を使った場合、tempfileが付けた名前が結果に出力されます。

{
  "/tmp/tmpmukttf9y": [
    {
      "type": "Secret Keyword",
      "filename": "/tmp/tmpmukttf9y",
      "hashed_secret": "ae9030c665364eb2651d450e8321ae62dd51a726",
      "is_verified": false,
      "line_number": 1
    }
  ]
}

最後に

Secretを検出するツールを調べました。 複数のツールがあるので、自分の需要に合わせて適切なツールを選ぶと良さそうですね。

tweet Share