Tokenやパスワードを見つけるツール調査と検証
Jul 7, 2024 13:30 · 2304 words · 5 minute read
「AWSの認証情報を誤って公開してしまった。認証情報を悪用されて被害が発生した。」という事件が後を絶ちません。
認証情報を検出するScannerを調べて検証したので、ブログに残しておきます。
目次
Scanner一覧
Secret KeyやTokenをScanするツールの中から有名なものをいくつか選定して調査しました。 ソースコードが公開されているツールを対象にしています。
trufflehog
- GitHub - trufflesecurity/trufflehog: Find and verify secrets
- push済みのremote repositoryやGitHub Organization全体をscanする
- Pre-commit Hookでcommitする前のScanも可能
- S3やGCS Bucket、Docker Image, Postman, Jenkins server, Elastic searchのscanもできる
- Go製
Gitleaks
- GitHub - gitleaks/gitleaks: Protect and discover secrets using Gitleaks 🔑
- CLIツールとしてローカルで実行、GitHub Actionで実行、pre-commitを使ってcommit前の実行ができる
- git repositoryをscanする
- gitleaks detectでcommit後のデータをscanする
- git leaks protectでcommit前のデータをScanする
- Go製
git-secrets
- awslabs/git-secrets: Prevents you from committing secrets and credentials into git repositories (github.com)
- AWSが開発しているツールということもあり、ScanできるのはAWSの認証情報のみ
- 実体はBash Script
secretlint
- GitHub - secretlint/secretlint: Pluggable linting tool to prevent committing credential.
- Node.jsで実行するPre-commit実行、CIで実行、CIで実行、Scan結果をPull requestコメントに追加、Browser Extentionなど、多機能。
repo-security-scanner
- GitHub - techjacker/repo-security-scanner: CLI tool that finds secrets accidentally committed to a git repo, eg passwords, private keys
- シンプルなCLI Scanner
- Go製
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化した状態で出力します。
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を検出するツールを調べました。 複数のツールがあるので、自分の需要に合わせて適切なツールを選ぶと良さそうですね。