某校园网认证api分析
发表于 [2022-07-06 13:22:00],更新于 [2022-11-14 21:08:35] 总字数 [2.7k], 阅读时长 [约13分钟], 阅读量 [
未知
] publish by lensfrex
封面:https://www.pixiv.net/artworks/61391031
最近突然来了兴趣,想搞一搞校园网认证页的认证api,方便以后自动登录,而不是每次联网都要手动登陆一遍
虽然我这里可以直接用拨号登录,而且我已经用上了路由器,直接全程连接,更加方便,但是还是想搞一搞这个登录认证
这里只是在研究认证页的时候的随手记,如果直接看的话可能会一脸懵逼,等有空了我再补充详细的过程吧(咕)
后续来啦:这里
学校校园网登录认证的过程大致分为两步
1. 获取challenge码 这部分简单,直接发学号用户名(学号)就行了
Base URL:http://59.68.177.183/cgi-bin/get_challenge
方法:GET
请求参数:
1 2 3 4 5 callback : jQuery112409362602503309623_1657072809873username : 【学号】ip : 10.15.2.109 _ : 1657072809875
callback: 回调函数,后半部分为时间戳,不设置callback的话只有ok
username: 顾名思义
ip: 顾名思义
_: 时间戳
注意:这里的时间戳是GMT时间,而不是咱们的东八区时间
服务器返回数据,有用的是challenge
1 2 3 4 5 6 7 8 9 10 challenge : "4d8e7d882e52c17537e1203191a8308fe294520bfb8e15c1f860bd1b03d983cc" client_ip : "10.15.2.109" ecode : 0 error : "ok" error_msg : "" expire : "60" online_ip : "10.15.2.109" res : "ok" srun_ver : "SRunCGIAuthIntfSvr V1.18 B20211105" st : 1657074027
2. 根据challenge码进行验证 这部分就有点复杂了,但还好,浏览器控制台跳几跳就看得出来是怎样的了
Base URL:http://59.68.177.183/cgi-bin/srun_portal
方法:GET
请求参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 callback : jQuery112409362602503309623_1657072809873action : loginusername : 【学号】password : {MD5}a25f5a06d8484669fb3ca3df0362efc8os : Windows 10 name : Windowsdouble_stack : 0 chksum : be73bd397721c3f39392e3fbfcc8124be9d0516ainfo : {SRBX1}F1DdbTEkXB8FyHKlvt+GnvkjogeyV6HZst2G+VWWo684jx/32 muHiDv3fKLh9AwIoaUCsu2rRaLlOB2Te5A4lYSFqCPLGJizZ5KsBUX5t6tNcN6coDACovbe4Pv0fF0pulYtqpy+i6D=ac_id : 7 ip : 10.15.2.109 n : 200 type : 1 _ : 1657072809876
password
字段的MD5由明文密码和上面得到的challenge
码拼接之后得到的MD5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ServerFlag : 0 ServicesIntfServerIP : "172.26.23.173" ServicesIntfServerPort : "8001" access_token : "4d8e7d882e52c17537e1203191a8308fe294520bfb8e15c1f860bd1b03d983cc" checkout_date : 0 client_ip : "10.15.2.109" ecode : 0 error : "ok" error_msg : "" online_ip : "10.15.2.109" ploy_msg : "E0000: Login is successful." real_name : "" remain_flux : 0 remain_times : 0 res : "ok" srun_ver : "SRunCGIAuthIntfSvr V1.18 B20211105" suc_msg : "login_ok" sysver : "1.01.20211105" username : "【学号】" wallet_balance : 0
在这里把登录认证发送部分的js代码挂上来(没有完全混淆代码而且还有注释,太棒啦):
这里的token
即是之前获取到的challenge
请求部分的代码在此:
查看完整代码
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 _loginAccount.set (_assertThisInitialized (_this), { writable : true , value : function value (obj ) { var type = 1 ; var n = 200 ; var enc = 'srun_bx1' ; var username = _this.userInfo .username + _this.userInfo .domain ; var password = _this.userInfo .password ; var ac_id = _this.portalInfo .acid ; var pendingReqNum = 0 ; var successMsg = '' ; var sendAuth = function sendAuth ( ) { var host = arguments .length > 0 && arguments [0 ] !== undefined ? arguments [0 ] : '' ; var ip = _this.portalInfo .doub && host ? '' : _this.userInfo .ip ; _classPrivateFieldGet (_assertThisInitialized (_this), _getToken) .call (_assertThisInitialized (_this), host, ip, function (token ) { var hmd5 = md5 (password, token); var i = _classPrivateFieldGet (_assertThisInitialized (_this), _encodeUserInfo).call (_assertThisInitialized (_this), { username : username, password : password, ip : ip, acid : ac_id, enc_ver : enc }, token); var str = token + username; str += token + hmd5; str += token + ac_id; str += token + ip; str += token + n; str += token + type; str += token + i; try { pendingReqNum += 1 ; _this.ajax .jsonp ({ host : host, url : _classPrivateFieldGet (_assertThisInitialized (_this), _api) .auth , params : { action : 'login' , username : username, password : _this.userInfo .otp ? '{OTP}' + password : '{MD5}' + hmd5, os : _this.portalInfo .userDevice .device , name : _this.portalInfo .userDevice .platform , double_stack : _this.portalInfo .doub && !host ? 1 : 0 , chksum : sha1 (str), info : i, ac_id : ac_id, ip : ip, n : n, type : type }, success : function success (res ) { pendingReqNum -= 1 ; _this.online = true ; _this.running .login = false ; if (res.suc_msg === 'ip_already_online_error' && obj.error ) return _this.confirm ({ message : _this.translate ('ip_already_online_error' ), confirm : function confirm ( ) { if (obj.error ) obj.error (); } }); successMsg = _this.translate (res); }, error : function error (res ) { pendingReqNum -= 1 ; _this.running .login = false ; if (res.ecode === 'E2620' && CREATER .useOnlineDeviceMgr ) return _this.confirm ({ message : _this.translate ('E2620Tips' ), confirm : function confirm ( ) { _this.dialog .open ('onlineDeviceMgr' , function ( ) { _this.getOnlineDevice (); }); } }); if (res.error_msg === 'ip_already_online_error' ) return _this.reAuth (obj); if (res.error_msg === 'not_online_error' ) return _this.showLog (); if (res.error_msg === 'no_response_data_error' ) return _this.showLog (); if (res.error_msg === 'RD000' ) return _this.showLog (); if (res.error_msg === 'user_must_modify_password' ) return _this.confirm ({ message : _this.translate (res), confirmText : _this.translate ('ToChangePassword' ), confirm : function confirm ( ) { return $('#forget' ) .click (); }, cancel : function cancel ( ) {} }); _this.confirm ({ message : _this.translate (res), confirm : function confirm ( ) { if (obj.error ) obj.error (res); } }); } }); } catch (err) { pendingReqNum -= 1 ; } }); };
上面的portalInfo
是从config
来的,而config
定义在登录认证首页下面:
1 2 3 4 5 6 7 8 9 10 11 12 var CONFIG = { page : 'account' , ip : "10.15.2.109" , nas : "" , mac : "" , url : "" , lang : "zh-CN" || 'zh-CN' , isIPV6 : false , portal : {"AuthIP" :"" ,"AuthIP6" :"" ,"ServiceIP" :"https://59.68.177.181:8800" ,"DoubleStackPC" :false ,"DoubleStackMobile" :false ,"AuthMode" :false ,"CloseLogout" :false ,"MacAuth" :false ,"RedirectUrl" :true ,"OtherPCStack" :"IPV4" ,"OtherMobileStack" :"IPV4" ,"MsgApi" :"new" ,"PublicSuccessPages" :true ,"TrafficCarry" :1000 ,"UserAgreeSwitch" :false ,"DialSwitch" :false }, notice : "list" , priceList : {"Prices" :"5,10,20,50,100" ,"Default" :"0.01" ,"BalanceWarning" :"5" } };
info字段的数据是在下面这里生成的,关键是encode函数,具体是什么算法看不出来
encode函数生成一段神奇的编码数据之后再用一种动过手脚的base64编码一下就得到了上面的i变量
把这段代码算法转成其他其他语言就好办很多了
展开查看代码(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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 _encodeUserInfo.set (_assertThisInitialized (_this), { writable : true , value : function value (info, token ) { var base64 = _this.clone ($.base64); base64.setAlpha ('LVoJPiCN2R8G90yg+hmFHuacZ1OWMnrsSTXkYpUq/3dlbfKwv6xztjI7DeBE45QA' ); info = JSON .stringify (info); function encode (str, key ) { if (str === '' ) return '' ; var v = s (str, true ); var k = s (key, false ); if (k.length < 4 ) k.length = 4 ; var n = v.length - 1 , z = v[n], y = v[0 ], c = 0x86014019 | 0x183639A0 , m, e, p, q = Math .floor (6 + 52 / (n + 1 )), d = 0 ; while (0 < q--) { d = d + c & (0x8CE0D9BF | 0x731F2640 ); e = d >>> 2 & 3 ; for (p = 0 ; p < n; p++) { y = v[p + 1 ]; m = z >>> 5 ^ y << 2 ; m += y >>> 3 ^ z << 4 ^ (d ^ y); m += k[p & 3 ^ e] ^ z; z = v[p] = v[p] + m & (0xEFB8D130 | 0x10472ECF ); } y = v[0 ]; m = z >>> 5 ^ y << 2 ; m += y >>> 3 ^ z << 4 ^ (d ^ y); m += k[p & 3 ^ e] ^ z; z = v[n] = v[n] + m & (0xBB390742 | 0x44C6F8BD ); } return l (v, false ); } function s (a, b ) { var c = a.length ; var v = []; for (var i = 0 ; i < c; i += 4 ) { v[i >> 2 ] = a.charCodeAt (i) | a.charCodeAt (i + 1 ) << 8 | a.charCodeAt (i + 2 ) << 16 | a.charCodeAt (i + 3 ) << 24 ; } if (b) v[v.length ] = c; return v; } function l (a, b ) { var d = a.length ; var c = d - 1 << 2 ; if (b) { var m = a[d - 1 ]; if (m < c - 3 || m > c) return null ; c = m; } for (var i = 0 ; i < d; i++) { a[i] = String .fromCharCode (a[i] & 0xff , a[i] >>> 8 & 0xff , a[i] >>> 16 & 0xff , a[i] >>> 24 & 0xff ); } return b ? a.join ('' ).substring (0 , c) : a.join ('' ); } return '{SRBX1}' + base64.encode (encode (info, token)); } });
用go简单实现了一下这段加密算法:
展开查看代码(go)
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 import ( "encoding/base64" "math" ) const magicBase64Alpha = "LVoJPiCN2R8G90yg+hmFHuacZ1OWMnrsSTXkYpUq/3dlbfKwv6xztjI7DeBE45QA" var magicBase64 = base64.NewEncoding(magicBase64Alpha)func Encode (str string , key string ) string { encodeResult := encode(str, key) base64Result := magicBase64.EncodeToString(int32ToAsciiBytes(encodeResult)) return "{SRBX1}" + base64Result } func encode (str string , key string ) []int32 { v := magicEncode(str, true ) k := magicEncode(key, false ) if len (k) < 4 { for i := 0 ; i < (4 - len (k)); i++ { k = append (k, 0 ) } } n := int32 (len (v) - 1 ) z := v[n] y := v[0 ] c := int32 (-1640531527 ) q := int (math.Floor(float64 (6 +52 /(n+1 )))) - 1 d := int32 (0 ) var e, p int32 var m int64 for ; q >= 0 ; q-- { d = d + c&(-1 ) e = uRightShift(d, 2 ) & 3 for p = 0 ; p < n; p++ { y = v[p+1 ] m = int64 (uRightShift(z, 5 ) ^ y<<2 ) m += int64 (uRightShift(y, 3 ) ^ z<<4 ^ (d ^ y)) m += int64 (k[p&3 ^e] ^ z) v[p] = v[p] + int32 (m&(-1 )) z = v[p] } y = v[0 ] m = int64 (uRightShift(z, 5 ) ^ y<<2 ) m += int64 (uRightShift(y, 3 ) ^ z<<4 ^ (d ^ y)) m += int64 (k[p&3 ^e] ^ z) v[n] = v[n] + int32 (m&(-1 )) z = v[n] } return magicDecode(v, false ) } func magicEncode (source string , sizeOnLast bool ) (result []int32 ) { data := []int32 (source) dataLen := len (data) resultLen := dataLen / 4 if sizeOnLast { result = make ([]int32 , resultLen+1 ) result[resultLen] = int32 (dataLen) } else { result = make ([]int32 , resultLen) } for i := 0 ; i < dataLen; i += 4 { result[i>>2 ] = get(data, i, dataLen) | get(data, i+1 , dataLen)<<8 | get(data, i+2 , dataLen)<<16 | get(data, i+3 , dataLen)<<24 } return result } func magicDecode (data []int32 , sizeOnLast bool ) (result []int32 ) { dataLength := len (data) c := dataLength - 1 <<2 if sizeOnLast { m := int (data[dataLength-1 ]) if m < c-3 || m > c { return nil } c = m } for i := 0 ; i < dataLength; i++ { result = append (result, data[i]&0xff , uRightShift(data[i], 8 )&0xff , uRightShift(data[i], 16 )&0xff , uRightShift(data[i], 24 )&0xff ) } if sizeOnLast { return append (result, int32 (c)) } else { return result } } func uRightShift (number int32 , shift int ) int32 { return int32 (uint32 (number) >> shift) } func get (data []int32 , index int , length int ) int32 { if index >= length { return 0 } else { return data[index] } } func int32ToAsciiBytes (data []int32 ) []byte { result := make ([]byte , len (data)) for i, number := range data { result[i] = byte (number) } return result }
更详细的加密算法分析过程请看这里