#!/usr/bin/env python3 # get_flag_full.py # 自动:登录 -> start_challenge -> 解析 -> 提交多种带 cookie 混淆的 token -> 打印 flag(若有)
import requests import time import re import sys import urllib3 import base64 import hashlib
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
BASE = "https://eci-2ze45f6hdflj9xk7stzq.cloudeci1.ichunqiu.com:5000" LOGIN_URL = f"{BASE}/login" START_URL = f"{BASE}/start_challenge" VERIFY_URL = f"{BASE}/verify_token"
USERNAME = "Doro" PASSWORD = "Doro_nJlPVs_@123"
HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:143.0) Gecko/20100101 Firefox/143.0", "Accept": "*/*", "Content-Type": "application/json", "Origin": BASE, "Referer": f"{BASE}/home", }
def login(sess: requests.Session) -> bool: """访问登录页再提交表单以建立 session cookies""" try: sess.get(LOGIN_URL, headers={"User-Agent": HEADERS["User-Agent"]}, verify=False, timeout=10) except Exception as e: print("[!] 无法访问登录页:", e) return False
data = {"username": USERNAME, "password": PASSWORD} headers = { "User-Agent": HEADERS["User-Agent"], "Content-Type": "application/x-www-form-urlencoded", "Referer": LOGIN_URL }
try: r = sess.post(LOGIN_URL, data=data, headers=headers, verify=False, timeout=10, allow_redirects=True) except Exception as e: print("[!] 登录请求失败:", e) return False
print("[*] 登录返回状态:", r.status_code) # 简单判断是否登录成功(若登录页面仍然被返回,通常 HTML 里会包含登录页面的标题) if r.status_code == 200 and "Doro的保险冰箱" not in r.text: print("[+] 登录成功(表面判断)") return True # 也当作成功(有些应用会重定向到首页,返回首页 HTML) if r.status_code in (200, 302): print("[+] 假定登录成功(HTTP %s)" return True
print("[!] 登录可能失败,HTTP", r.status_code) return False
def parse_challenge(data: dict): """从 start_challenge 的 JSON 中解析 timestamp(如果有)、multiplier、xor_value、expression 字符串""" multiplier = None xor_value = None timestamp_in_expr = None expr = data.get("expression", "")
if "multiplier" in data: try: multiplier = int(data["multiplier"]) except Exception: multiplier = None if "xor_value" in data: xv = data["xor_value"] try: xor_value = int(str(xv), 16) except Exception: try: xor_value = int(xv) except Exception: xor_value = None
# 提取形如 (1759814299 * 99784) m = re.search(r'\(\s*(\d+)\s*\*\s*(\d+)\s*\)', expr) if m: timestamp_in_expr = int(m.group(1)) mul_from_expr = int(m.group(2)) if multiplier is None: multiplier = mul_from_expr
return timestamp_in_expr, multiplier, xor_value, expr
def extract_addon_candidates(session_cookie_value: str): """ 尝试从 Flask session cookie 中生成若干可能的 "addon" 值。 返回一个字典:name -> integer 我们会尝试多种解读方式,以增加命中率。 """ cands = {} cookie = session_cookie_value or "" parts = cookie.split(".") cands["raw_len"] = len(cookie) # 1) bytes sum of middle+last part (utf-8) if len(parts) >= 2: p12 = "".join(parts[1:]) try: sbytes = p12.encode("utf-8") cands["sum_bytes_mid_last"] = sum(b for b in sbytes) cands["sha1_6b"] = int.from_bytes(hashlib.sha1(sbytes).digest()[:6], "big") cands["md5_6b"] = int.from_bytes(hashlib.md5(sbytes).digest()[:6], "big") except Exception: pass
# 2) sum of characters (ord) try: cands["sum_ord_all"] = sum(ord(ch) for ch in cookie) except Exception: pass
# 3) try base64-url decode the first part (Flask session data often in first part) if len(parts) >= 1 and parts[0]: p0 = parts[0] # try fix padding and decode for pad in ("", "=", "=="): try: dec = base64.urlsafe_b64decode(p0 + pad) # if zlib compressed, attempt decompress, else use raw bytes try: import zlib decomp = zlib.decompress(dec) cands["p0_zlib_sum"] = sum(b for b in decomp) cands["p0_zlib_sha1_6b"] = int.from_bytes(hashlib.sha1(decomp).digest()[:6], "big") except Exception: # fallback to raw decoded bytes sum cands["p0_b64_sum"] = sum(b for b in dec) cands["p0_b64_sha1_6b"] = int.from_bytes(hashlib.sha1(dec).digest()[:6], "big") break except Exception: continue
# 4) interpret last part as ascii bytes and take some ints if len(parts) >= 3 and parts[-1]: tail = parts[-1] try: tbytes = tail.encode("utf-8") cands["tail_first6"] = int.from_bytes(tbytes[:6].ljust(6, b'\x00'), "big") except Exception: pass
# 5) fallback: hash of whole cookie try: cands["sha256_8b"] = int.from_bytes(hashlib.sha256(cookie.encode("utf-8")).digest()[:8], "big") except Exception: pass
# Remove any zero or None entries and ensure int res = {} for k, v in cands.items(): try: vi = int(v) if vi != 0: res[k] = vi except Exception: continue return res
def try_variants_and_submit(sess: requests.Session, base_token: int, addons: dict): """ 对 base_token 使用多种运算与 addon 值尝试提交,直到成功或全部尝试完毕。 ops: xor, add, sub """ tried = [] # always include base_token itself first candidates = [("base", base_token)]
# add variants using each addon for name, a in addons.items(): candidates.append((f"xor_{name}", base_token ^ a)) candidates.append((f"add_{name}", base_token + a)) candidates.append((f"sub_{name}", base_token - a if base_token - a >= 0 else base_token)) # avoid negative # try shifted xor candidates.append((f"xor_shift8_{name}", base_token ^ (a << 8))) # try mixing with lower 32 bits candidates.append((f"xor_low32_{name}", base_token ^ (a & 0xffffffff)))
# deduplicate preserving order seen = set() uniq_candidates = [] for tag, val in candidates: if val in seen: continue seen.add(val) uniq_candidates.append((tag, val))
print(f"[*] 将尝试 {len(uniq_candidates)} 种 token 组合(包含 base)") for tag, t in uniq_candidates: print(f"[*] 尝试 {tag}: token={t}") try: r = sess.post(VERIFY_URL, headers=HEADERS, json={"token": int(t)}, verify=False, timeout=10) except Exception as e: print("[!] 提交失败:", e) continue try: j = r.json() except Exception: j = r.text print("[*] HTTP", r.status_code, j) # 判断成功 if isinstance(j, dict) and j.get("success") is True: print("\n=== 成功! ===") print("模式:", tag) print("响应:", j) return True, (tag, t, j) # 小优化:如果返回错误信息里提示“需要session里的某值”之类也打印并继续 time.sleep(0.05) # tiny sleep to avoid overwhelming return False, None
def main(): sess = requests.Session() sess.headers.update(HEADERS)
print("[*] 登录中...") if not login(sess): print("[!] 登录失败,请检查网络或凭证") sys.exit(1)
# start challenge print("[*] 请求 /start_challenge ...") try: r = sess.post(START_URL, headers=HEADERS, json={}, verify=False, timeout=10) except Exception as e: print("[!] /start_challenge 请求失败:", e) sys.exit(1)
try: data = r.json() except Exception as e: print("[!] /start_challenge 未返回 JSON:", e) print("HTTP", r.status_code, r.text[:300]) sys.exit(1)
print("[*] start_challenge 返回:", data) if "error" in data: print("[!] 服务端返回错误:", data["error"]) sys.exit(1)
# 打印并读取 cookies cookie_dict = sess.cookies.get_dict() print("[*] 当前 session cookies:", cookie_dict) session_cookie_value = cookie_dict.get("session", "")
ts_in_expr, multiplier, xor_value, expr = parse_challenge(data) if multiplier is None or xor_value is None: print("[!] 无法解析 multiplier 或 xor_value,请贴上完整 JSON") sys.exit(1)
# 优先使用表达式内时间戳(通常题目把时间戳写在表达式里) if ts_in_expr: used_ts = ts_in_expr print(f"[*] 使用表达式内时间戳:{used_ts}") else: used_ts = int(time.time()) print(f"[*] 使用本地时间戳:{used_ts}")
base_token = (int(used_ts) * int(multiplier)) ^ int(xor_value) print(f"[*] 计算 base_token = ({used_ts} * {multiplier}) ^ {hex(xor_value)} = {base_token}")
# 从 cookie 里提取若干可能的 addon addons = extract_addon_candidates(session_cookie_value) print("[*] 从 session cookie 提取到的 addon 候选:") for k, v in addons.items(): print(f" {k} = {v}")
success, info = try_variants_and_submit(sess, base_token, addons) if success: tag, token_used, resp = info print("[*] 已成功,模式:", tag) print("[*] 使用 token:", token_used) print("[*] 服务端响应:", resp) else: print("\n[!] 所有尝试均失败。下面给出调试建议:") print(" - 请确认浏览器能正常完成挑战;若浏览器能成功,抓包查看浏览器请求具体发送的 token 值与 cookie 内容") print(" - 把 /start_challenge 返回的完整 JSON 和上面打印的 cookie 发给我,我来基于真实值再做深入分析") print(" - 我可以把脚本再改为对时间做微调(尝试 ±1、±2 秒的 timestamp)以防时钟差问题")
if __name__ == "__main__": main()
|