457 lines
15 KiB
Bash
Executable File
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 "$@"
|