示例地址:https://zentao.demo.qucheng.cc/index.php?m=user&f=login&referer=Lw==

点击登录后,通过fiddler抓包即可发现两次登录的值不一样,说明被加密了,每次加密的数值会发生变化,这就造成了当前抓取的密码不能用到下一次请求中

1.通过网页登录的 JavaScript 破解禅道登录

既然要获取密码的加密值,我们就需要搞定前端页面的密码加密逻辑,这需要有一点 js 的前端基础,否则比较难理解

  • 首先,通过点击登录,找到对应 js 文

  • 接着找到js 对密码的加密逻辑

md5(md5(password) + rand):这是一个双重MD5加密加盐的过程:

首先对原始密码进行一次MD5哈希(md5(password))。

然后将第一次哈希的结果与一个随机值 rand 拼接(+ rand)。

最后对拼接后的字符串再进行一次MD5哈希(md5(...))

  • 然后获取password真实值

  • 通过抓包,找到rand值

#获取rand
rand_url = 'https://zentao.demo.qucheng.cc/index.php?m=user&f=refreshRandom'
header_rand = {
    "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
    "referer": "https://zentao.demo.qucheng.cc/index.php?m=user&f=login&referer=L2luZGV4LnBocD9tPW15JmY9aW5kZXg=",
    "x-requested-with": "XMLHttpRequest"
}
res_rand = requests.get(rand_url, headers=header_rand,cookies=d)
print(res_rand.text)
  • 保存网页对应的md5加密文件,引用到本地

  • 调用js文件的py:

若要获取随机加密的密码,我们需要用python实现一遍js的加密逻辑,我们上面已经理清楚了密码是如何加密的,我们可以按照js的逻辑来用python实现。

先封装一个python执行js方法的类,用于调用js文件中的md5方法:需要先安装模块:pip3 insta11 PyExecJs

import execjs
import os


rootPath = os.path.dirname(__file__)
jsPath = os.path.join(rootPath, "md5.js")


class ExecJs(object):

    _instance = False

    def _get_js(self, name):
        js_str = ''
        with open(name, 'r', encoding="utf-8") as f:
            line = f.readline()
            while line:
                js_str = js_str + line
                line = f.readline()
        return js_str

    def get_encrypt_pwd(self, function, *args):
        ctx = execjs.compile(self._get_js(jsPath))
        return ctx.call(function, *args)
  • 对密码进行两次加密+rand

#调用js
password =  'quickon4You' 
from encrypt import ExecJs
e = ExecJs()
s1 = e.get_encrypt_pwd('md5',password)
s2 = e.get_encrypt_pwd('md5',s1 + res_rand.text)

2.问题排查

以上问题解决完后,发现还是访问不了接口,通过排查,发现登录的时候会传一个cookies进去:

然后发现另外一个接口会 响应一个 cookie:

这个时候,我们就要获取到 cookie 值,然后传给 post

#获取cookie
url_cookie = 'https://zentao.demo.qucheng.cc/index.php?m=user&f=login&referer=Lw=='
header_cookie = {
    "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
}
res_cookie = requests.get(url_cookie, headers=header_cookie)
d = requests.utils.dict_from_cookiejar(res_cookie.cookies) #将 requests 请求返回的 CookieJar 对象转换为 Python 字典格式,便于后续操作或存储。

完整代码:

#禅道登录

#1.MD5加密逻辑:md5(md5(password)+rand())
#2.password : quickon4You
import requests


#获取cookie
url_cookie = 'https://zentao.demo.qucheng.cc/index.php?m=user&f=login&referer=Lw=='
header_cookie = {
    "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
}
res_cookie = requests.get(url_cookie, headers=header_cookie)
d = requests.utils.dict_from_cookiejar(res_cookie.cookies)
print(d)

#获取rand
rand_url = 'https://zentao.demo.qucheng.cc/index.php?m=user&f=refreshRandom'
header_rand = {
    "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
    "referer": "https://zentao.demo.qucheng.cc/index.php?m=user&f=login&referer=L2luZGV4LnBocD9tPW15JmY9aW5kZXg=",
    "x-requested-with": "XMLHttpRequest"
}
res_rand = requests.get(rand_url, headers=header_rand,cookies=d)
print(res_rand.text)

#调用js
password =  'quickon4You'
from encrypt import ExecJs
e = ExecJs()
s1 = e.get_encrypt_pwd('md5',password)
s2 = e.get_encrypt_pwd('md5',s1 + res_rand.text)

url = 'https://zentao.demo.qucheng.cc/index.php?m=user&f=login'

data = {
    "account":"demo",
    "password":s2,
    "passwordStrength":"0",
    "referer":"/",
    "verifyRand":res_rand.text,
    "keepLogin":"1",
    "captcha":"",

}

header = {
    "host": "zentao.demo.qucheng.cc",
    "content-length": "765",
    "sec-ch-ua-platform": "macOS",
    "x-requested-with": "XMLHttpRequest",
    "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
    "sec-ch-ua": 'Not)A;Brand;v="8", Chromium;v="138"',
    "sec-ch-ua-mobile": "?0",
    "accept": "*/*",
    "origin": "https://zentao.demo.qucheng.cc",
    "sec-fetch-site": "same-origin",
    "sec-fetch-mode": "cors",
    "sec-fetch-dest": "empty",
    "referer": "https://zentao.demo.qucheng.cc/index.php?m=user&f=login&referer=Lw==",
    "accept-encoding": "gzip, deflate, br, zstd",
    "accept-language": "zh-CN,zh;q=0.9",
    "priority": "u=1, i",
}




res4 = requests.post(url,data=data,headers=header,cookies=d)
print(res4.text)

3.实践总结

  • 禅道对请求头要求比较高,尽量传满,否则还是会报错

  • 该禅道示例地址的 content-type 会多了一个boundary的随机值,这个字段传少了依旧访问不了

解决方法:在传请求头的时候,直接去掉content-type的传入,大多数 HTTP 客户端库(如 Python 的 requests、Java 的 REST Assured)或测试工具(如 Postman、JMeter)在发送 multipart/form-data 请求时,会自动生成 boundary 并添加到请求头中,无需手动指定