某上网行为认证设备登录密码认证接口分析
免责声明: 本文仅用于 JavaScript 逆向、RC4 加密算法与接口协议的学习研究,所有分析内容均来自前端公开代码。文中逻辑与代码仅作技术演示,请勿用于未经授权的自动登录、批量操作或其他违反网络管理规定及国家法律法规的行为,一切后果由使用者自行承担。
1. 登录接口
前端通过如下接口提交密码认证请求:
POST /ac_portal/login.php
请求参数中使用:
opr=pwdLogin
表示当前采用的是账号密码登录方式。
2. 关键前端代码
2.1 密码登录逻辑
function onPwdLogin() {
if (!pwdValidtor()) return;
if ($id("flux")) {
$id("flux").href = "https://" + document.domain + "/cgi-bin/showflux.cgi?user=" + encodeURIComponent($id("password_name").value);
}
var rckey = +(new Date()) + '';
var pwd = do_encrypt_rc4($id("password_pwd").value, rckey);
var params = {
opr: 'pwdLogin',
userName: $id("password_name").value,
pwd : pwd,
auth_tag: rckey,
rememberPwd: $id("rememberPwd").checked ? '1' : '0'
};
if (g_WXscanOpenID) {
params.openid = g_WXscanOpenID;
}
if (g_wxscan_mobile_openid) {
params.openid = g_wxscan_mobile_openid;
}
loginRequest(params, "mode_password", $id("password_submitBtn"));
}
3. 参数含义分析
根据 onPwdLogin() 可知,密码登录阶段关键参数如下:
-
opr: 'pwdLogin'- 指定登录操作类型为密码登录
-
userName- 用户名
-
pwd- 前端加密后的密码,不是明文
-
auth_tag- 当前毫秒时间戳字符串
- 同时作为密码加密所用的密钥
-
rememberPwd- 是否记住密码,
1为记住,0为不记住
- 是否记住密码,
4. 密码加密方式
核心代码如下:
var rckey = +(new Date()) + '';
var pwd = do_encrypt_rc4($id("password_pwd").value, rckey);
可得出以下结论:
rckey = 当前毫秒时间戳字符串
pwd = do_encrypt_rc4(明文密码, rckey)
auth_tag = rckey
即:
pwd = RC4(明文密码, auth_tag)
因此提交时的 auth_tag 不仅仅是一个普通时间戳参数,而是实际参与密码加密的密钥。
5. RC4 加密函数
前端定义如下:
function do_encrypt_rc4(src, passwd) {
src = $.trim(src+'');
passwd = passwd + '';
var i, j = 0, a = 0, b = 0, c = 0, temp;
var plen = passwd.length,
size = src.length;
var key = Array(256);
var sbox = Array(256);
var output = Array(size);
for (i = 0; i < 256; i++) {
key[i] = passwd.charCodeAt(i % plen);
sbox[i] = i;
}
for (i = 0; i < 256; i++) {
j = (j + sbox[i] + key[i]) % 256;
temp = sbox[i];
sbox[i] = sbox[j];
sbox[j] = temp;
}
for (i = 0; i < size; i++) {
a = (a + 1) % 256;
b = (b + sbox[a]) % 256;
temp = sbox[a];
sbox[a] = sbox[b];
sbox[b] = temp;
c = (sbox[a] + sbox[b]) % 256;
temp = src.charCodeAt(i) ^ sbox[c];
temp = temp.toString(16);
if (temp.length === 1) {
temp = '0' + temp;
} else if (temp.length === 0) {
temp = '00';
}
output[i] = temp;
}
return output.join('');
}
该函数的特点:
- 使用
passwd作为 RC4 密钥初始化key和sbox - 对输入明文
src做逐字节异或 - 输出结果转为十六进制字符串
- 每个字符对应 2 位十六进制
例如若密码长度为 6 位,则输出长度通常为 12 位十六进制字符串。
6. 为什么同一个密码每次抓包结果不同
因为:
var rckey = +(new Date()) + '';
每次登录都会生成新的毫秒时间戳,所以:
- 明文密码相同
auth_tag不同pwd也不同
这解释了抓包中同一密码出现不同加密结果的现象。
7. 后端校验方式推断
前端提交的数据为:
userName = 用户名
pwd = RC4加密后的密码
auth_tag = 时间戳字符串
因此后端大概率会:
- 读取
auth_tag - 以
auth_tag作为同样的 RC4 密钥 - 对
pwd做还原或等价校验 - 验证结果是否与真实密码匹配
所以 auth_tag 实际上承担了“会话级临时密钥”的作用。
8. 修改密码逻辑也采用同样机制
onChangePwd() 中同样使用了相同的方式:
function onChangePwd() {
if (!changePwdValidtor()) return;
var rckey = +(new Date()) + '';
var oldPwd = do_encrypt_rc4($id("changePwd_oldPwd").value, rckey);
var newPwd = do_encrypt_rc4($id("changePwd_newPwd").value, rckey);
var params = {
opr: 'changePwd',
userName: $id("changePwd_name").value,
auth_tag: rckey,
oldPwd:oldPwd,
newPwd:newPwd
};
changePwdRequest(params, "mode_changePwd", $id("changePwd_submitBtn"))
}
说明该 Portal 在涉及密码提交的场景中,统一采用:
时间戳作为 RC4 密钥
来保护明文密码。
9. 最终结论
该认证系统的密码字段:
- 不是明文
- 不是固定 MD5
- 不是固定密钥加密
- 而是:
pwd = RC4(明文密码, 当前毫秒时间戳字符串)
并且:
auth_tag = 当前毫秒时间戳字符串
最终随请求一起提交:
opr=pwdLogin
userName=用户名
pwd=RC4加密结果
auth_tag=时间戳
rememberPwd=0/1
10. 自动化实现结论
由于加密逻辑已经明确,因此可以用脚本稳定复现登录过程:
自动登录流程
- 获取当前毫秒时间戳
- 将时间戳转为字符串,作为
auth_tag - 使用相同 RC4 算法对明文密码加密
- 将
userName、pwd、auth_tag提交到/ac_portal/login.php - 解析返回结果,判断登录是否成功
这意味着该认证过程可以被实现为:
- 加密逻辑可通过编程复现,用于学习接口协议与加密流程。
11.自动化实现伪代码
#!/usr/bin/env python3
import time
import sys
import requests
# 注意:以下为演示地址,非真实接口
URL = "http://xxx.xxx.xxx.xxx/ac_portal/login.php"
# 注意:以下为示例账号密码,非真实信息
USERNAME = "your_username_example"
PASSWORD = "your_password_example"
HEADERS = {
"User-Agent": "Mozilla/5.0",
"Accept-Encoding": "gzip, deflate",
"X-Requested-With": "XMLHttpRequest",
"DNT": "1",
"Origin": "http://xxx.xxx.xxx.xxx",
"Referer": "http://xxx.xxx.xxx.xxx/ac_portal/",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
}
def do_encrypt_rc4(src, passwd):
"""
RC4加密算法实现(仅用于技术演示)
:param src: 待加密字符串
:param passwd: 加密密钥
:return: 十六进制加密结果
"""
src = str(src).strip()
passwd = str(passwd)
plen = len(passwd)
size = len(src)
key = [0] * 256
sbox = [0] * 256
output = []
# 初始化密钥流
for i in range(256):
key[i] = ord(passwd[i % plen])
sbox[i] = i
j = 0
for i in range(256):
j = (j + sbox[i] + key[i]) % 256
sbox[i], sbox[j] = sbox[j], sbox[i]
# 加密核心逻辑
a = 0
b = 0
for i in range(size):
a = (a + 1) % 256
b = (b + sbox[a]) % 256
sbox[a], sbox[b] = sbox[b], sbox[a]
c = (sbox[a] + sbox[b]) % 256
temp = ord(src[i]) ^ sbox[c]
output.append(f"{temp:02x}")
return "".join(output)
def login_demo():
"""
登录逻辑演示
"""
# 生成时间戳作为加密密钥
auth_tag = str(int(time.time() * 1000))
# RC4加密密码
pwd = do_encrypt_rc4(PASSWORD, auth_tag)
data = {
"opr": "pwdLogin",
"userName": USERNAME,
"pwd": pwd,
"auth_tag": auth_tag,
"rememberPwd": "0",
}
try:
# 仅为逻辑演示,实际运行需替换为合法的目标地址
r = requests.post(URL, headers=HEADERS, data=data, timeout=10)
print(f"HTTP {r.status_code}")
print(r.text)
if r.ok:
text = r.text.lower()
if "success" in text or '"success":true' in text or '"success":1' in text:
return 0
except Exception as e:
print(f"演示示例:请求异常 - {e}")
return 1
return 1
if __name__ == "__main__":
"""
免责声明:
1. 本代码仅用于学习RC4加密算法和JS逆向技术,请勿用于任何非法用途
2. 代码中所有接口、账号密码均为示例,无实际使用价值
3. 使用本代码需遵守所在单位/学校的网络安全规定及相关法律法规
"""
sys.exit(login_demo())