这篇文章距离最后更新已过239 天,如果文章内容或图片资源失效,请留言反馈,我会及时处理,谢谢!
南京工业大学统一身份认证平台(SSO)逆向分析
📋 目录
1. 概述
1.1 目标系统
系统名称 : 南京工业大学统一身份认证平台
登录地址 : https://sfgl.njtech.edu.cn/cas/login
认证协议 : CAS (Central Authentication Service)
前端框架 : Angular
加密库 : CryptoJS
1.2 核心文件
文件名
作用
关键内容
https://sfgl.njtech.edu.cn/cas/login
登录页面主体
密钥2、execution参数
https://sfgl.njtech.edu.cn/gate/public/deploy/deploy.js
脚本加载器
密钥1混淆、配置请求
https://sfgl.njtech.edu.cn/gate/public/utils/loginNew.js
设备检测
PC/Mobile判断
crypto-js.min.js
加密库
DES/MD5/SHA256算法
1.3 加密技术栈
1 2 3 4 加密算法: DES 加密模式: ECB 填充方式: PKCS7 编码格式: Base64
2. 系统架构分析
2.1 页面加载流程
从 login 可以看到页面加载顺序:
1 2 3 4 5 6 7 8 9 10 <!-- 1. 首先加载设备检测脚本 --> <script src="/public/utils/loginNew.js? 1768455907648"> <!-- 内容实际是 loginNew.js --> <!-- 2. 然后加载主加载器 --> <script src="/public/deploy/deploy.js?1768455907751"> <!-- 内容实际是 deploy.js --> <!-- 3. 最后加载加密库 --> <script src="/public/crypto/crypto-js.min.js">
2.2 脚本依赖关系
从 deploy.js 第52-58行可以看到完整的依赖加载:
1 2 3 4 5 6 7 8 9 var cptfile = [ baseUrl + '/public/convertBaseUrl.js', baseUrl + '/public/crypto/md5.min.js', baseUrl + '/public/crypto/core.min.js', baseUrl + '/public/crypto/crypto-js.min.js', baseUrl + '/public/crypto/enc-base64.min.js', baseUrl + '/public/crypto/mode-ecb.min.js', baseUrl + '/public/crypto/pad-pkcs7.min. js' ];
这些文件通过 loadScript() 函数(第38-50行)依次加载。
2.3 页面初始化流程
从 deploy.js 第175行的 _casPageInit() 函数可以看到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 function _casPageInit() { var loadedJs = []; var hasRun; function nextLoad(jsPath) { loadedJs.push(jsPath); var hasRuntime; var hasPolyfills; // 等待runtime和polyfills都加载完成 for (var i = 0; i < loadedJs.length; i++) { var path = loadedJs[i]; if (path.indexOf('runtime') !== -1) { hasRuntime = true; } else if (path.indexOf('polyfills') !== -1) { hasPolyfills = true; } } // 加载main脚本 if (hasRuntime && hasPolyfills && !hasRun) { hasRun = true; for (var i = 0; i < jsCssConfig.js.length; i++) { var jsPath = jsCssConfig.js[i]; if (jsPath.indexOf('main') !== -1) { loadOneJS(jsPath); } } } } }
3. 双密钥体系原理
3.1 密钥1:API通信密钥
3.1.1 存储位置与混淆
在 deploy.js 第124行:
1 var url_mseAge = '_%C3%D8%AF%7C%A1%BD%BE%C2%C4%C4%92';
3.1.2 解混淆函数
在 deploy.js 第16-26行:
1 2 3 4 5 6 7 8 var uncompile = function (code) { code = unescape(code); let c = String.fromCharCode(code.charCodeAt(0) - code.length); for (let i = 1; i < code.length; i++) { c += String.fromCharCode(code. charCodeAt(i) - c.charCodeAt(i - 1)); } return c; };
算法分析 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 输入: '_%C3%D8%AF%7C%A1%BD%BE%C2%C4%C4%92' 步骤1: unescape() URL解码 '_%C3%D8%AF%7C%A1%BD%BE%C2%C4%C4%92' → '_ÃØ¯|¡½¾ÂÄÄ'' 步骤2: 差分解码 第一个字符: charCode(0) - length '_'. charCodeAt(0) = 95 length = 12 第一个输出: 95 - 12 = 83 → 'S' 第二个字符: charCode(1) - charCode(result[0]) 'Ã'. charCodeAt(0) = 195 result[0]. charCodeAt(0) = 83 第二个输出: 195 - 83 = 112 → 'p' 依此类推... 最终输出: 'SphG5lQmUoU='
这是一种差分编码(Delta Encoding) ,每个字符的ASCII值是前一个字符加上一个差值。
3.1.3 使用场景
从 deploy.js 第130-165行的 getSelfConfig 函数可以看到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function getSelfConfig(key, cb) { let rfurl = baseUrl + '/linkid/protected/api/dictconfig/get'; // 判断是否需要加密 let hasCrypto = false; if (configUrl) { const allconfigkey = Object.keys(configUrl); for (let i = 0; i < allconfigkey.length; i++) { for (let o = 0; o < configUrl[allconfigkey[i]].length; o++) { const CurrentconfigUrl = configUrl[allconfigkey[i]][o]; if (isIncludeUrl(rfurl, CurrentconfigUrl)) { hasCrypto = true; break; } } } } // 发送加密请求 if (hasCrypto) { xhr.setRequestHeader('hasCrypto', 'true'); xhr.send(desEncrypt(uncompile(url_mseAge), JSON.stringify([key]))); } else { xhr.send(JSON.stringify([key])); } // 处理加密响应 if (! isJSONTmp) { var tmpgetunpackbody = getUnPackBody(uncompile(url_mseAge), jsonObj); } }
从测试结果可以看到,密钥1保护以下API:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { "5G": [ "/data-sync/public/network/openUser/xxx", "/data-sync/public/network/cancelUser/xxx", "/data-sync/api/account/protected/tel/add", "/data-sync/api/account/tel/add", "/data-sync/api/thirdparty/tel/verify" ], "SID": [ "/linkid/api/aggregate/app/protected/obtain/", "/linkid/api/aggregate/app/protected/face/user/active/", "/linkid/protected/api/user/userId/check/", "/api/protected/realname/preQuery", "/api/protected/realname/auth", // ... 共23个接口 ] }
3.2 密钥2:密码加密密钥
3.2.1 存储位置
在 login 第77行:
1 2 3 <div style="display: none"> <p id="login-croypto">B0MCcDgT4Eo=</p> </div>
实际访问时获得的值为 zgEBWBZtq5s=(动态生成)。
3.2.2 参数提取
从页面隐藏元素中可以看到完整的登录参数:
1 2 3 4 5 6 7 8 <div style="display: none"> <p id="current-login-type">UsernamePassword</p> <p id="login-croypto">zgEBWBZtq5s=</p> <p id="login-page-flowkey">edd59515-9448-42e3-8f17-cb1e8abb8d9e_H4sIAA... </p> <p id="captcha-url">api/captcha/generate/DEFAULT</p> <p id="redirect-uri">service=http%3A%2F%2Fjwgl.njtech.edu.cn%2Fsso%2Fktiotlogin%3Fstatus%3Dsuccess</p> <p id="execution">3dc54c41-da03-4f06-8052-823888cc4e9d_H4sIAA...</p> </div>
3.3 密钥独立性验证
从实际测试的登录请求可以看到:
1 2 3 4 5 6 7 8 POST /cas/login username=202412345 password=TeDO/u3gzkxtgmHPJV47qA== # 使用密钥2加密 croypto=zgEBWBZtq5s= # 密钥2原样传回 type=UsernamePassword _eventId=submit execution=3dc54c41-da03-4f06-8052-823888cc4e9d_H4sIAA...
登录流程中没有使用密钥1,证明两个密钥完全独立。
4. 加密算法分析
4.1 加密函数定位
在 deploy.js 第7-15行:
1 2 3 4 5 6 7 8 var desEncrypt = function (key, content) { const keyHex = CryptoJS.enc.Base64.parse(key); const encString = CryptoJS.DES.encrypt(content, keyHex, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); return encString.toString(); };
4.2 解密函数
在 deploy.js 第1-6行:
1 2 3 4 5 6 7 8 var desDecrypt = function (key, content) { const keyHex = CryptoJS.enc. Base64.parse(key); const decString = CryptoJS.DES.decrypt(content, keyHex, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad. Pkcs7 }); return decString.toString(CryptoJS.enc.Utf8); };
4.3 DES算法特征
从 crypto-js.min.js 中可以看到DES实现的关键部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var d = u.DES = s.extend({ _doReset: function() { // 密钥调度 for (var t = this._key.words, r = t.words, e = t.sigBytes / 4, i = this._nRounds = e + 6, n = 4 * (i + 1), s = this._keySchedule = [], a = 0; a < n; a++) // ... }, encryptBlock: function(t, r) { this._doCryptBlock(t, r, this._keySchedule, a, c, h, l, o) }, decryptBlock: function(t, r) { var e = t[r + 1]; t[r + 1] = t[r + 3], t[r + 3] = e, this._doCryptBlock(t, r, this._invKeySchedule, f, u, d, v, s); // ... } });
4.4 ECB模式特征
从加密配置可以看到使用了ECB模式:
1 2 3 4 { mode: CryptoJS.mode. ECB, padding: CryptoJS.pad.Pkcs7 }
ECB模式的工作原理:
1 2 3 4 5 明文: [Block1] [Block2] [Block3] ↓ ↓ ↓ DES-E DES-E DES-E ↓ ↓ ↓ 密文: [Cipher1][Cipher2][Cipher3]
每个块独立加密,不依赖前后块。
4.5 PKCS7填充
从 crypto-js.min.js 可以看到填充实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 var _ = p. Pkcs7 = { pad: function(t, r) { for (var e = 4 * r, i = e - t.sigBytes % e, n = i << 24 | i << 16 | i << 8 | i, s = [], a = 0; a < i; a += 4) s.push(n); var c = o.create(s, i); t.concat(c) }, unpad: function(t) { var r = 255 & t.words[t.sigBytes - 1 >>> 2]; t.sigBytes -= r } };
填充规则:
1 2 3 4 5 6 原始数据长度 % 8 == 剩余字节数: 0字节 → 填充 08 08 08 08 08 08 08 08 1字节 → 填充 07 07 07 07 07 07 07 2字节 → 填充 06 06 06 06 06 06 ... 7字节 → 填充 01
4.6 加密过程验证
测试输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 // 输入 密钥: zgEBWBZtq5s= 明文: test123 // 加密过程 1. Base64解码密钥: ce0101581656db9b (8字节) 2. UTF-8编码明文: 74657374313233 (7字节) 3. PKCS7填充: 7465737431323301 (8字节, 填充了0x01) 4. DES-ECB加密: 0acbdcc58742a8d4 (8字节) 5. Base64编码: CsvcxYdCqNQ= // 输出 加密结果: CsvcxYdCqNQ=
5. 前端混淆破解
5.1 差分编码原理
5.1.1 编码过程推导
从 uncompile 函数的逆向分析可以推导出编码过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // 解码: result[i] = encoded[i] - result[i-1] // 反推编码: encoded[i] = original[i] + original[i-1] 原始字符串: S p h G 5 l Q m U o U = ASCII码: 83 112 104 71 53 108 81 109 85 111 85 61 长度: 12 编码过程: 位置0: 83 + 12 = 95 → '_' 位置1: 112 + 83 = 195 → 'Ã' (escape后 %C3) 位置2: 104 + 112 = 216 → 'Ø' (escape后 %D8) 位置3: 71 + 104 = 175 → '¯' (escape后 %AF) 位置4: 53 + 71 = 124 → '|' (escape后 %7C) 位置5: 108 + 53 = 161 → '¡' (escape后 %A1) 位置6: 81 + 108 = 189 → '½' (escape后 %BD) 位置7: 109 + 81 = 190 → '¾' (escape后 %BE) 位置8: 85 + 109 = 194 → 'Â' (escape后 %C2) 位置9: 111 + 85 = 196 → 'Ä' (escape后 %C4) 位置10: 85 + 111 = 196 → 'Ä' (escape后 %C4) 位置11: 61 + 85 = 146 → ''' (escape后 %92) 最终: '_%C3%D8%AF%7C%A1%BD%BE%C2%C4%C4%92'
5.1.2 算法特征
差分编码的特点:
可逆性 :解码函数可以完全恢复原始数据
依赖性 :每个字符依赖前一个字符
错误传播 :一个字符错误会影响后续所有字符
长度保持 :编码后长度不变
5.2 URL编码层
escape() 函数的作用:
1 2 3 4 5 6 7 8 // ASCII可见字符(不编码) '_' → '_' '|' → '|' // 非ASCII字符(编码) 'Ã' (195) → '%C3' 'Ø' (216) → '%D8' '¯' (175) → '%AF'
5.3 多层解密
从 deploy.js 第126-145行可以看到多层解密逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function getUnPackBody(key, body) { let dec = ''; let isJSON = false; const convertData = function (body) { // 开始解密 dec = desDecrypt(key, body); isJSON = false; if (dec != '') { try { JSON.parse(dec); isJSON = true; return; } catch (str_error) { isJSON = false; } // 如果解密结果不是JSON,继续解密 if (!isJSON) { convertData(dec); } } }; convertData(body); return JSON.parse(dec || null); }
这说明服务器可能返回嵌套加密 的数据,需要递归解密直到得到有效的JSON。
6. 动态参数分析
6.1 execution参数
从 login 可以看到execution的格式:
1 2 <input type="hidden" name="execution" value="3dc54c41-da03-4f06-8052-823888cc4e9d_H4sIAAAAAAAAAO1ce2wcxRkfn9+OEzvBhKSUNE3j8Msdn... "/>
格式分析:
1 2 3 4 5 6 7 8 9 10 11 12 3dc54c41-da03-4f06-8052-823888cc4e9d_H4sIAAAAAAAAAO1ce2xcxRkfn9+... 组成部分: ├─ UUID部分: 3dc54c41-da03-4f06-8052-823888cc4e9d │ • 长度: 36字符 │ • 格式: 8-4-4-4-12 │ • 作用: 唯一标识本次登录流程 │ └─ 数据部分: H4sIAAAAAAAAAO1ce2wcxRkfn9+... • 格式: Base64编码 • 识别: H4sI 是Gzip压缩的Magic Number • 内容: Gzip(JSON数据)
6.2 login-page-flowkey参数
从 login 第80行:
1 <p id="login-page-flowkey">edd59515-9448-42e3-8f17-cb1e8abb8d9e_H4sIAAAAAAAAALU6a2xb53lH1FuWLdmWnbhZXE... </p>
与execution格式相同:UUID_Base64GzipData
6.3 验证码URL
从 login 第79行:
1 <p id="captcha-url"></p>
该元素为空,但从 deploy.js 第285行可以看到检测逻辑:
1 2 3 4 var captchaUrlElem = doc.getElementById('captcha-url'); if (captchaUrlElem && captchaUrlElem.innerText. trim()) { this.captchaUrl = `${this.gateUrl}/${captchaUrlElem.innerText.trim()}`; }
从 loginNew.js 可以看到验证码相关代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var ruleList_content = document.body; var d_style = null; if (window.getComputedStyle) { d_style = window.getComputedStyle(ruleList_content, null); } else { d_style = ruleList_content.currentStyle; } if (d_style. content. indexOf('x') !== -1) { if(document.getElementById('recaptcha-invisible')){ document.getElementById('recaptcha-invisible').innerText = true; document.getElementById('captcha-url').innerText = "api/captcha/generate/DEFAULT"; } }
通过CSS的 content 属性判断是否需要验证码。
6.4 认证类型列表
从 login 第31-42行可以看到支持的认证方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <div style="display: none" class="ruleList"> <div class="code">UsernamePassword</div> <div class="showName">用户名密码认证</div> <div class="image">./images/login-page/UsernamePassword.png</div> <div class="isRedirect">false</div> </div> <div style="display: none" class="ruleList"> <div class="code">smsLogin</div> <div class="showName">手机验证码认证</div> <div class="image">./images/login-page/smsLogin.png</div> <div class="isRedirect">false</div> </div> <div style="display: none" class="ruleList"> <div class="code">corpwechatQr</div> <div class="showName">企业微信扫码认证</div> <div class="image"></div> <div class="isRedirect">false</div> </div>
6.5 第三方认证
从 login 第85-132行可以看到完整的第三方认证列表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <div id="list-providers" class="card d-sm-none d-md-block bg-light"> <div class="card-body"> <h3 class="card-title">Or login with:</h3> <div class="card-text"> <ul> <li><a href="clientredirect? client_name=adapter&service=... ">adapter</a></li> <li><a href="clientredirect?client_name=corpWechat&service=...">corpWechat</a></li> <li><a href="clientredirect?client_name=Wechat&service=... ">Wechat</a></li> <li><a href="clientredirect?client_name=tyEduCloud&service=...">tyEduCloud</a></li> <li><a href="clientredirect?client_name=dingDing&service=...">dingDing</a></li> <li><a href="clientredirect?client_name=dingDingWlan&service=...">dingDingWlan</a></li> <li><a href="clientredirect?client_name=singleWechatOauth&service=...">singleWechatOauth</a></li> <li><a href="clientredirect? client_name=feishuOauth&service=...">feishuOauth</a></li> <li><a href="clientredirect?client_name=zhilinOauth&service=... ">zhilinOauth</a></li> <li><a href="clientredirect?client_name=isni&service=...">isni</a></li> <li><a href="clientredirect?client_name=ssoOauth&service=...">ssoOauth</a></li> <li><a href="clientredirect?client_name=corpWechatOauth2&service=...">corpWechatOauth2</a></li> <li><a href="clientredirect?client_name=welinkOauth&service=...">welinkOauth</a></li> <li><a href="clientredirect?client_name=ccwork&service=...">ccwork</a></li> </ul> </div> </div> </div>
7. 登录流程解析
7.1 请求参数完整映射
从实际抓包数据:
1 2 3 4 5 6 7 8 9 10 POST /cas/login HTTP/1.1 username=202412345 type=UsernamePassword _eventId=submit geolocation= execution=3dc54c41-da03-4f06-8052-823888cc4e9d_H4sIAA... captcha_code=a3d2 croypto=zgEBWBZtq5s= password=TeDO/u3gzkxtgmHPJV47qA==
对应关系:
表单字段
来源
说明
username
用户输入
学号/用户名
password
加密计算
DES(密钥2, 明文密码)
croypto
#login-croypto
密钥2原值
type
#current-login-type
固定值 UsernamePassword
_eventId
固定值
submit
execution
<input name="execution">
UUID_GzipBase64
captcha_code
用户输入
验证码(可选)
geolocation
固定值
空字符串
7.2 加密时序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1. 页面加载 ↓ 2. 提取 login-croypto → 密钥2 (zgEBWBZtq5s=) ↓ 3. 用户输入密码 → password (test123) ↓ 4. Base64解码密钥2 zgEBWBZtq5s= → [0xce, 0x01, 0x01, 0x58, 0x16, 0x56, 0xdb, 0x9b] ↓ 5. DES-ECB加密 plaintext: test123 (7字节) padding: test123\x01 (8字节, PKCS7) encrypted: [0x0a, 0xcb, 0xdc, 0xc5, 0x87, 0x42, 0xa8, 0xd4] ↓ 6. Base64编码 CsvcxYdCqNQ= ↓ 7. 提交表单
7.3 设备检测流程
从 loginNew.js 可以看到完整的设备检测逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 // 1. 创建检测元素 var newElement = document.createElement('div'); newElement.classList.add('pl_box'); newElement.id ='pl_dom'; document.body.appendChild(newElement); // 2. CSS媒体查询 // @media screen and (orientation: portrait) { // .pl_box { width: 20px; height: 30px; } // } // @media screen and (orientation: landscape) { // .pl_box { width: 30px; height: 20px; } // } // 3. 判断方向 const width = document.getElementById('pl_dom').clientWidth; const height = document. getElementById('pl_dom').clientHeight; if (parseInt(width) < parseInt(height)) { neworientation. current = 'portrait'; // 竖屏 } else { neworientation. current = 'landscape'; // 横屏 } // 4. 根据方向加载不同页面 if (landscape && res == 'new') { device = 'PC'; script. onload = function () { window.casPageInit("loginnew"); }; } else if (landscape) { device = 'PC'; script.onload = function () { window.casPageInit("login"); }; } else if (portrait) { device = 'PHONE'; script.onload = function () { window.casPageInit("login"); }; }
7.4 自动重定向逻辑
从 login 第203-229行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 var list = document.getElementsByClassName('ruleList'); var isAdapterRedirect = false; var isAutoRedirect = false; var isAutoRedirectClientName = null; // 检查是否有自动重定向的认证方式 for (var i = 0; i < list.length; i++) { var ele = list[i]. getElementsByClassName('code')[0]; var code = ele.innerText; var autoRedirect = list[i].getElementsByClassName('isRedirect')[0]; var autoRedirectCode = autoRedirect.innerText; if (autoRedirectCode == 'true') { isAutoRedirect = true; isAutoRedirectClientName = code; break; } if (code == 'adapter') { isAdapterRedirect = true; } } // 执行重定向 if (isAutoRedirect) { url = url.replace('#client_name', isAutoRedirectClientName); window.location. href = url; } if (isAdapterRedirect) { url = url.replace('#client_name', 'adapter'); window.location.href = url; }
7.5 配置请求流程
从 deploy.js 第267-283行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 getSelfConfig('URL_CRYPTO_DATA', function () { console.log(configUrl, config); getSelfConfig('pc. self.page.config', function () { console.log(configUrl, config); if (pageName === 'serviceerror') { // 处理服务错误页面 } else if (pageName === 'logout' || pageName === 'propagatelogout' || pageName === 'error') { // 处理登出页面 if (document.getElementById('logout-header-logo')) { document.getElementById('logout-header-logo').src = window.convertHref(config.logo); } if (document.getElementById('logout-body-bg')) { document.getElementById('logout-body-bg').style.backgroundImage = "url('" + window.convertHref(config.background) + "')"; } } else { // 处理登录页面 _casPageInit(); } }); });
配置加载顺序:
URL_CRYPTO_DATA - 获取需要加密的API列表
pc.self.page.config - 获取页面自定义配置
根据页面类型执行相应初始化
7.6 错误处理
从 login 第234-252行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var clientType = document.getElementById("current-login-type").innerText; var errorCode = document.getElementById("login-error-code").innerText; if (errorCode === "1410041" && (clientType == "dingDingWlan" || clientType == "adapter" || ... )) { if (clientType == "") { clientType = "appToken" } url = "./public/client/bind/fail?clientname=#clientname"; url = url.replace('#clientname', clientType); window.location.href = url; } if (errorCode === "1030028" && (clientType == "dingDingWlan" || clientType == "adapter" || ...)) { if (clientType == "") { clientType = "appToken" } url = "./public/client/bind/error?clientname=#clientname"; url = url.replace('#clientname', clientType); window.location.href = url; }
错误码含义:
1410041 - 绑定失败,跳转到 bind/fail 页面
1030028 - 绑定错误,跳转到 bind/error 页面
本报告的所有内容仅供学习和研究使用。作者通过分析公开可访问的前端代码和网络请求,学习和理解现代Web应用的认证机制,加密实现和前端架构设计。
作者从未、也绝不会利用本内容进行任何非法活动。
作者不对他人使用本报告内容产生的任何后果承担责任。
任何人使用本报告内容从事违法活动,均与作者无关,由使用者自行承担全部法律责任。