Files
hack/tools/scripts/llm-key-hunter/llm-key-hunter.sh
T

457 lines
15 KiB
Bash
Executable File

#!/bin/bash
# ═══════════════════════════════════════════════════════════════
# LLM Key Hunter — 全网泄露 LLM API Key 搜索 & 验证工具
# ═══════════════════════════════════════════════════════════════
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PATTERNS_FILE="${SCRIPT_DIR}/patterns.conf"
RESULTS_DIR="${SCRIPT_DIR}/results"
GH_TOKEN="${GH_TOKEN:-}"
LIMIT=1000
CONCURRENCY=10
VERIFIED_ONLY=false
RAW_MODE=false
DEEP_SCAN=false
TARGET_ORG=""
TARGET_USER=""
REPORT_DIR="${RESULTS_DIR}"
# ── 颜色 ──────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
log_info() { echo -e "${CYAN}[*]${NC} $1"; }
log_found() { echo -e "${GREEN}[+]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[!]${NC} $1"; }
log_err() { echo -e "${RED}[-]${NC} $1"; }
# ── 用法 ──────────────────────────────────────────────────
usage() {
cat <<EOF
${CYAN}LLM Key Hunter${NC} — 全网泄露 LLM API Key 搜索 & 验证
${YELLOW}用法:${NC}
$0 <mode> [options]
${YELLOW}模式:${NC}
broad GitHub 全局广搜 (gh search code)
scan 深度扫描指定 org/user 的所有 repo (trufflehog)
verify 验证候选 key 是否 live
gist 扫描公开 GitHub Gist
full 完整 pipeline: broad → verify → report
${YELLOW}选项:${NC}
-t, --token <token> GitHub PAT (或设 GH_TOKEN 环境变量)
-o, --org <org> 目标 org (scan 模式)
-u, --user <user> 目标 user (scan 模式)
-l, --limit <n> 每次搜索结果上限 (默认 1000)
-c, --concurrency <n> 并发数 (默认 10)
-v, --verified 只输出验证通过的 key
-r, --report <dir> 报告输出路径 (默认 ./results/)
-p, --patterns <file> 自定义 patterns.conf 路径
--raw 跳过验证,输出所有正则匹配
--deep 启用 trufflehog 深度 history 扫描
${YELLOW}示例:${NC}
$0 broad -t ghp_xxx -v
$0 scan -o target-org -t ghp_xxx --deep
$0 full -t ghp_xxx -v -r ./reports/
$0 gist -t ghp_xxx
EOF
exit 1
}
# ── 参数解析 ──────────────────────────────────────────────
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
-t|--token) GH_TOKEN="$2"; shift 2 ;;
-o|--org) TARGET_ORG="$2"; shift 2 ;;
-u|--user) TARGET_USER="$2"; shift 2 ;;
-l|--limit) LIMIT="$2"; shift 2 ;;
-c|--concurrency) CONCURRENCY="$2"; shift 2 ;;
-v|--verified) VERIFIED_ONLY=true; shift ;;
-r|--report) REPORT_DIR="$2"; shift 2 ;;
-p|--patterns) PATTERNS_FILE="$2"; shift 2 ;;
--raw) RAW_MODE=true; shift ;;
--deep) DEEP_SCAN=true; shift ;;
*) shift ;;
esac
done
}
# ── 依赖检查 ──────────────────────────────────────────────
check_deps() {
local missing=()
command -v gh &>/dev/null || missing+=("gh")
command -v curl &>/dev/null || missing+=("curl")
command -v jq &>/dev/null || missing+=("jq")
command -v grep &>/dev/null || missing+=("grep")
if [[ ${#missing[@]} -gt 0 ]]; then
log_err "缺少依赖: ${missing[*]}"
exit 1
fi
if [[ -n "$GH_TOKEN" ]]; then
export GH_TOKEN
gh auth status &>/dev/null || {
echo "$GH_TOKEN" | gh auth login --with-token &>/dev/null || true
}
fi
mkdir -p "$REPORT_DIR"
}
# ── 加载正则模式 ──────────────────────────────────────────
load_patterns() {
if [[ ! -f "$PATTERNS_FILE" ]]; then
log_err "patterns 文件不存在: $PATTERNS_FILE"
exit 1
fi
grep -v '^#' "$PATTERNS_FILE" | grep -v '^$'
}
# ── 构建搜索查询 ──────────────────────────────────────────
get_search_queries() {
cat <<'QUERIES'
sk-proj- extension:env
sk-proj- extension:py
sk-proj- extension:js
sk-proj- extension:ts
sk-ant- extension:env
sk-ant- extension:py
sk-ant- extension:js
OPENAI_API_KEY sk- extension:env
OPENAI_API_KEY sk- extension:sh
ANTHROPIC_API_KEY sk-ant extension:py
GOOGLE_API_KEY AIza extension:env
GEMINI_API_KEY AIza extension:py
HUGGINGFACE_TOKEN hf_ extension:py
HUGGINGFACE_API_TOKEN hf_ extension:env
GROQ_API_KEY gsk_ extension:env
GROQ_API_KEY gsk_ extension:py
REPLICATE_API_TOKEN r8_ extension:env
openai.api_key sk- extension:ipynb
anthropic_api_key sk-ant extension:ipynb
api_key=sk- extension:cfg
api_key=sk-ant extension:ini
sk-ant- extension:jupyter
OPENAI_API_KEY extension:ipynb
ANTHROPIC_API_KEY extension:ipynb
GOOGLE_API_KEY extension:ipynb
sk-proj- path:.env
sk-ant- path:.env
ARK_API_KEY extension:env
ARK_API_KEY extension:py
VOLC_API_KEY extension:env
volces.com ARK_API_KEY extension:py
ZHIPUAI_API_KEY extension:env
ZHIPUAI_API_KEY extension:py
GLM_API_KEY extension:env
bigmodel.cn extension:py
DASHSCOPE_API_KEY extension:env
DASHSCOPE_API_KEY extension:py
dashscope.aliyuncs.com extension:py
MOONSHOT_API_KEY extension:env
MOONSHOT_API_KEY extension:py
api.moonshot.cn extension:py
MINIMAX_API_KEY extension:env
MINIMAX_API_KEY extension:py
api.minimaxi.com extension:py
HUNYUAN_API_KEY extension:env
hunyuan.tencentcloudapi.com extension:py
QIANFAN_API_KEY extension:env
QIANFAN_API_KEY extension:py
qianfan.baidubce.com extension:py
SPARK_API_KEY extension:env
SPARK_API_KEY extension:py
spark-api.xf-yun.com extension:py
maas-coding-api.xf-yun.com extension:py
maas-coding-api.cn-huabei-1.xf-yun.com extension:py
ASTRON_API_KEY extension:env
CODING_PLAN_API_KEY extension:env
astron-code-latest extension:py
maas.xfyun.cn extension:py
LINGYIWANWU_API_KEY extension:env
api.lingyiwanwu.com extension:py
STEPFUN_API_KEY extension:env
api.stepfun.com extension:py
SILICONFLOW_API_KEY extension:env
api.siliconflow.cn extension:py
ARK_API_KEY extension:ipynb
ZHIPUAI_API_KEY extension:ipynb
DASHSCOPE_API_KEY extension:ipynb
MOONSHOT_API_KEY extension:ipynb
MINIMAX_API_KEY extension:ipynb
CSDN_API_KEY extension:env
CSDN_API_KEY extension:py
CSDN_CODING_PLAN_KEY extension:env
CSDN_CODING_PLAN_KEY extension:py
CSDN_TOKEN extension:env
STARMAP_API_KEY extension:env
ai.csdn.net extension:py
ai.csdn.net extension:js
ai.csdn.net extension:ts
ai.csdn.net extension:json
ai.csdn.net extension:yaml
ai.csdn.net extension:toml
glm_for_coding extension:py
glm_for_coding extension:js
glm_for_coding extension:ts
csdn.net/api/model extension:py
csdn codingplan extension:env
csdn codingplan extension:py
HUAWEI_API_KEY extension:env
HUAWEICLOUD_API_KEY extension:env
CODEARTS_API_KEY extension:env
huaweicloud codearts extension:py
MIMO_API_KEY extension:env
XIAOMI_API_KEY extension:env
MIMO_KEY extension:env
api.xiaomimimo.com extension:py
INFINI_API_KEY extension:env
INFINIAI_API_KEY extension:env
cloud.infini-ai.com extension:py
JD_API_KEY extension:env
JDCLOUD_API_KEY extension:env
jdcloud.com coding extension:py
MTHREADS_API_KEY extension:env
code.mthreads.com extension:py
KWAIKAT_API_KEY extension:env
KUAISHOU_API_KEY extension:env
STREAMLAKE_API_KEY extension:env
streamlake.com extension:py
UCLOUD_API_KEY extension:env
COMPSHARE_API_KEY extension:env
compshare.cn extension:py
ANOMALY_API_KEY extension:env
OPENCODE_GO_KEY extension:env
opencode.ai extension:py
UNICOM_API_KEY extension:env
CUCLOUD_API_KEY extension:env
YUANJING_API_KEY extension:env
cucloud.cn extension:py
SCNET_API_KEY extension:env
SCNET_API_KEY extension:py
scnet.cn extension:api_key extension:env
scnet.cn extension:py
sk-sp- extension:env
sk-sp- extension:py
sk-tp- extension:env
sk-tp- extension:py
coding.dashscope.aliyuncs.com extension:py
coding.dashscope.aliyuncs.com extension:env
ALIBABA_CODING_PLAN_KEY extension:env
ALIBABA_CODING_PLAN_KEY extension:py
ZYLOO_API_KEY extension:env
ZYLOO_API_KEY extension:py
ZYLOO_KEY extension:env
ZYLOO_KEY extension:py
zyloo.io extension:py
zyloo.io extension:env
sk-zy- extension:env
sk-zy- extension:py
fe_oa_ extension:env
fe_oa_ extension:py
fe_oa_ extension:js
fe_oa_ extension:ts
fe_oa_ extension:json
fe_oa_ extension:yaml
fe_oa_ extension:cfg
fe_oa_ extension:sh
fe_oa_ extension:ipynb
freemodel.dev extension:env
freemodel.dev extension:py
freemodel.dev extension:js
freemodel.dev extension:ts
freemodel.dev extension:json
freemodel.dev extension:yaml
FREEMODEL_API_KEY extension:env
FREEMODEL_API_KEY extension:py
FREEMODEL_KEY extension:env
FREEMODEL_KEY extension:py
cc.freemodel.dev extension:py
api.freemodel.dev extension:py
vip-sg.freemodel.dev extension:py
work.freemodel.dev extension:py
QUERIES
}
# ── GitHub 全局广搜 (Python 引擎) ─────────────────────────
mode_broad() {
log_info "启动 Python 搜索引擎 (github_search.py)..."
local candidates_file="${REPORT_DIR}/candidates.json"
local search_args=()
search_args+=("--output" "$candidates_file")
# Pass tokens
if [[ -n "$GH_TOKEN" ]]; then
search_args+=("--token" "$GH_TOKEN")
fi
# Support multi-token via GH_TOKENS env var (colon-separated)
if [[ -n "${GH_TOKENS:-}" ]]; then
IFS=':' read -ra TOKEN_ARRAY <<< "$GH_TOKENS"
for t in "${TOKEN_ARRAY[@]}"; do
search_args+=("--token" "$t")
done
fi
# Gitee token
if [[ -n "${GITEE_TOKEN:-}" ]]; then
search_args+=("--gitee-token" "$GITEE_TOKEN")
fi
# Flags
[[ "$RAW_MODE" == "true" ]] && search_args+=("--no-commits" "--no-topics")
python3 "${SCRIPT_DIR}/github_search.py" "${search_args[@]}" || {
log_err "Python 搜索引擎失败,回退到 bash 广搜..."
_mode_broad_bash
return
}
local final_count
final_count=$(jq 'length' "$candidates_file" 2>/dev/null || echo 0)
log_found "广搜完成: $final_count 个唯一候选文件"
echo "$candidates_file"
}
# ── Bash fallback 广搜 (原版) ─────────────────────────────
_mode_broad_bash() {
log_info "Bash 广搜 (fallback)..."
local candidates_file="${REPORT_DIR}/candidates.json"
echo '[]' > "$candidates_file"
local queries
queries=$(get_search_queries)
local total=0
while IFS= read -r query; do
[[ -z "$query" || "$query" =~ ^# ]] && continue
log_info "搜索: $query"
local encoded_query
encoded_query=$(echo "$query" | sed 's/ /+/g')
local results
results=$(gh api "search/code?q=${encoded_query}&per_page=${LIMIT}" \
--jq '[.items[] | {repository: {full_name: .repository.full_name}, path: .path, url: .html_url}]' 2>/dev/null || echo '[]')
local count
count=$(echo "$results" | jq 'length' 2>/dev/null | tr -dc '0-9')
count=${count:-0}
if [[ "$count" -gt 0 ]]; then
log_found "找到 $count 个候选文件"
jq -s '.[0] + .[1]' "$candidates_file" <(echo "$results") > "${candidates_file}.tmp" 2>/dev/null || true
mv "${candidates_file}.tmp" "$candidates_file" 2>/dev/null || true
total=$((total + count))
fi
sleep 7
done <<< "$queries"
jq 'unique_by(.url)' "$candidates_file" > "${candidates_file}.dedup" 2>/dev/null || true
mv "${candidates_file}.dedup" "$candidates_file" 2>/dev/null || true
local final_count
final_count=$(jq 'length' "$candidates_file" 2>/dev/null || echo 0)
log_found "广搜完成: $final_count 个唯一候选文件"
echo "$candidates_file"
}
# ── 从候选文件提取 key ────────────────────────────────────
extract_keys() {
local candidates_file="$1"
local keys_file="${REPORT_DIR}/extracted_keys.txt"
> "$keys_file"
local patterns
patterns=$(load_patterns)
log_info "从候选文件提取 key..."
jq -r '.[].url' "$candidates_file" 2>/dev/null | while read -r url; do
[[ -z "$url" ]] && continue
local raw_url
raw_url=$(echo "$url" | sed 's|github.com|raw.githubusercontent.com|;s|/blob/|/|')
local content
content=$(curl -sL --max-time 10 "$raw_url" 2>/dev/null) || continue
[[ -z "$content" ]] && continue
while IFS='|' read -r provider regex desc; do
[[ -z "$regex" ]] && continue
echo "$content" | grep -oE "$regex" 2>/dev/null | while read -r key; do
echo "${provider}|${key}|${url}|${desc}"
done
done <<< "$patterns"
done | sort -u > "$keys_file"
local key_count
key_count=$(wc -l < "$keys_file" | tr -d ' ')
log_found "提取完成: $key_count 个候选 key"
echo "$keys_file"
}
# ── 主入口 ────────────────────────────────────────────────
main() {
[[ $# -lt 1 ]] && usage
local mode="$1"
shift
parse_args "$@"
check_deps
case "$mode" in
broad)
mode_broad
;;
full)
log_info "=== 完整 Pipeline 启动 ==="
local candidates
candidates=$(mode_broad)
local keys
keys=$(extract_keys "$candidates")
if [[ "$RAW_MODE" == "false" ]]; then
bash "${SCRIPT_DIR}/verify.sh" "$keys" "$REPORT_DIR" "$VERIFIED_ONLY"
else
log_info "Raw 模式,跳过验证"
cat "$keys"
fi
;;
scan)
bash "${SCRIPT_DIR}/scan.sh" \
--org "$TARGET_ORG" \
--user "$TARGET_USER" \
--token "$GH_TOKEN" \
--concurrency "$CONCURRENCY" \
--deep "$DEEP_SCAN" \
--report "$REPORT_DIR"
;;
verify)
local keys="${REPORT_DIR}/extracted_keys.txt"
[[ ! -f "$keys" ]] && { log_err "无候选 key 文件,先运行 broad 或 full"; exit 1; }
bash "${SCRIPT_DIR}/verify.sh" "$keys" "$REPORT_DIR" "$VERIFIED_ONLY"
;;
gist)
bash "${SCRIPT_DIR}/gist.sh" --token "$GH_TOKEN" --report "$REPORT_DIR"
;;
*)
usage
;;
esac
}
main "$@"