【申明:本文所涉及的技術和分析的目的都是爲了學習和交流,任何人使用文中所提的技術或成果作出的違法事情與我無關,你們購買火車票仍是去12306官網上去購買。】javascript
從今天起,我開始分析12306網站的Http請求,以及編寫一個客戶端工具,廢話很少說,這篇文章的重點是分析登陸。css
常規的登陸思路:帳號+密碼,稍微有點防範意識的:帳號+密碼+驗證碼,高級點的:帳號+密碼+驗證碼,其中密碼加密。咱們看看12306他是採用了哪些策略。html
首先進入12306的官網,嘗試下登陸,而後抓包,這裏有個技巧,最好登陸的時候用錯誤的密碼或者驗證碼進行登陸。java
如上圖,發起了兩個Post請求(通常提交的動做,跟蹤post類的請求)web
咱們先無論第一個請求是幹嗎的(確定是用的~),看看第二個請求,第二個請求顯然提交登陸的動做,繼續抓包看看它到底提交了哪些參數。cookie
咱們分析下參數的意義和來源:ide
longinRand:969這個目前不得而知,refundLogin:N 這個寫死就好了,refundFlag:Y 這個不得而知,loginUser.user_name:用戶名,user.password:密碼,randCode:驗證碼,函數
NzYxNDgz:N2RlNzU1YWQ0ZTE5M2NiYg== 這個不得而知,動態參數。工具
這個時候咱們回頭看看第一個請求,看他返回的是什麼:post
{"loginRand":"969","randError":"Y"}
至此,咱們知道了longinRand,refundFlag是經過第一個請求獲得的,soga...
那麼動態參數咱們怎麼取得呢?
繼續觀察,https://dynamic.12306.cn/otsweb/loginAction.do?method=init 的源碼(這個是初始化登錄頁面,不要問我怎麼知道的,觀察,你懂的)
發現裏面有這麼一段:
<script src="/otsweb/dynamicJsAction.do?jsversion=5022&method=loginJs" type="text/javascript"></script>
這段腳本是幹嗎的呢。。。果斷下載下來,研究下,發現了奧妙:
var inputObj=$('<input type="hidden" name="'+keyVlues[0]+'" value="'+encode32(bin216(Base32.encrypt(keyVlues[1],keyVlues[0])))+'" />');
這個type爲hidden的文本框,它的name爲value度是動態取值的,注意到這句腳本里面gc()的方法 裏面有一句var key='MzUyNzc0NQ==';
這個顯然就是咱們的key了,那麼value值了,經過閱讀源碼知道 ,原來這個方法是檢測是否存在刷票插件,若是存在value=value+'0',不然value=value+'1';
那麼它是如何檢測存在刷票插件的呢,接着看腳本
var cssArr=['selectSeatType',' ev_light',' ev_light ','fishTimeRangePicker','updatesFound','tipScript','refreshButton','fish_clock','refreshStudentButton'
,'btnMoreOptions','btnAutoLogin','fish_button','defaultSafeModeTime','ticket-navigation-item'];
var idArr=['btnMoreOptions','refreshStudentButton','fishTimeRangePicker','helpertooltable','outerbox','updateInfo'
,'fish_clock','refreshStudentButton','btnAutoRefresh','btnAutoSubmit','btnRefreshPassenger'
,'autoLogin','bnAutoRefreshStu','orderCountCell','refreshStudentButton','enableAdvPanel','autoDelayInvoke','refreshButton','refreshTimesBar','chkAllSeat'];
var keywordArr=[{
key:".enter_right",values:["親","搶票","助手"]
}
,{
key:".cx_form",values:["點發車","刷票"]
}
,{
key:"#gridbox",values:["只選","僅選","checkBox","checkbox"]
}
,{
key:".enter_w",values:["助手"]
}
] ;
原來,這個腳本做用有2個,一個是檢測頁面是否掛載了刷票的腳本工具,一個是動態生成一個密鑰。看來12306的確被刷票工具搞得夠慘,不得不該對了,由於這個腳本是動態生成的,因此12306能夠按期生成這些關鍵詞庫,來防止,這個對於通常的刷票工具能夠起到必定得做用,可是昨天我看了下獵豹的刷票的源碼(僅舉一例):
<button class="normalButton" id="FriSep272013100801GMT08001" type="button" style="margin:0;width:97px;font-size:14px;vertical-align:middle;">♣ 刷 票</button>
獵豹也夠牛的,估計是上次12306更新了防刷腳本後,獵豹直接把本身的腳本上的控件ID都動態化了。。。。
言歸正傳,剛纔咱們看到了動態key的產生,實際就是去請求https://dynamic.12306.cn/otsweb/loginAction.do?method=init,而後獲得<script src="/otsweb/dynamicJsAction.do?jsversion=5022&method=loginJs" type="text/javascript"></script>,進一步獲得那個js的地址,而後從腳本中提取到var key=的值,這個就是動態key,那麼動態value呢,剛纔說過value值是檢測刷票插件用的,咱直接無視,value爲1111(由於它檢測了4次)。
繼續看這句, value="'+encode32(bin216(Base32.encrypt(keyVlues[1],keyVlues[0])))+'",顯然動態密碼是通過加密的,傳進的值就是動態key,動態value。怎麼調用這個加密方法呢,個人作法是直接提取它的腳本加密函數爲放到一個文件,而後用C#調用(詳見:http://www.cnblogs.com/djhama/archive/2012/03/26/2418205.html)
提取到的密碼腳本以下:
function bin216(s){ var i,l,o = "",n; s += ""; b = ""; for(i = 0,l = s.length;i < l;i ++ ){ b = s.charCodeAt(i); n = b.toString(16); o += n.length < 2 ? "0" + n : n; } return o; }; var Base32 = new function(){ var delta = 0x9E3779B8; function longArrayToString(data,includeLength){ var length = data.length; var n = (length - 1) << 2; if (includeLength){ var m = data[length - 1]; if((m < n - 3) || (m > n))return null; n = m; } for(var i = 0;i < length;i ++ ){ data[i] = String.fromCharCode(data[i] & 0xff,data[i] >>> 8 & 0xff,data[i] >>> 16 & 0xff,data[i] >>> 24 & 0xff); } if (includeLength){ return data.join('').substring(0, n); } else{ return data.join(''); } }; function stringToLongArray(string, includeLength){ var length = string.length; var result = []; for (var i = 0;i < length;i += 4){ result[i >> 2] = string.charCodeAt(i) | string.charCodeAt(i + 1) << 8 | string.charCodeAt(i + 2) << 16 | string.charCodeAt(i + 3) << 24; } if (includeLength){ result[result.length] = length; } return result; }; this.encrypt = function(string, key){ if (string == ""){ return ""; } var v = stringToLongArray(string, true); var k = stringToLongArray(key, false); if (k.length < 4){ k.length = 4; } var n = v.length - 1; var z = v[n], y = v[0]; var mx, e, p, q = Math.floor(6 + 52 / (n + 1)), sum = 0; while (0 < q -- ){ sum = sum + delta & 0xffffffff; e = sum >>> 2 & 3; for (p = 0;p < n;p ++ ){ y = v[p + 1]; mx = (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z); z = v[p] = v[p] + mx & 0xffffffff; } y = v[0]; mx = (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z); z = v[n] = v[n] + mx & 0xffffffff; } return longArrayToString(v, false); }; }; var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; function encode32(input){ input = escape(input); var output = ""; var chr1, chr2, chr3 = ""; var enc1, enc2, enc3, enc4 = ""; var i = 0; do{ chr1 = input.charCodeAt(i ++ ); chr2 = input.charCodeAt(i ++ ); chr3 = input.charCodeAt(i ++ ); enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; if (isNaN(chr2)){ enc3 = enc4 = 64; } else if (isNaN(chr3)){ enc4 = 64; } output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4); chr1 = chr2 = chr3 = ""; enc1 = enc2 = enc3 = enc4 = ""; } while (i < input.length); return output; };
OK,至此,咱們須要的參數都搞定了,咱們就能夠模擬登錄了,最後捋一下登錄用到Http請求(按照順序):
1.加載驗證碼,用於獲取圖片和cookie
https://dynamic.12306.cn/otsweb/passCodeNewAction.do?module=login&rand=sjrand(get)
2.加載https://dynamic.12306.cn/otsweb/loginAction.do?method=init (get),用於獲取動態生成key的請求地址,獲得的地址是https://dynamic.12306.cn/otsweb/dynamicJsAction.do?jsversion=123123&method=loginJs
3.請求https://dynamic.12306.cn/otsweb/dynamicJsAction.do?jsversion=123123&method=loginJs (get),獲得動態key,而後調用加密js獲得動態value
4.請求https://dynamic.12306.cn/otsweb/loginAction.do?method=loginAysnSuggest(post),獲得loginRand,refundfLag,
5.構造參數,請求https://dynamic.12306.cn/otsweb/loginAction.do?method=login(post),而後解析返回的html。
最後上一張我寫的登陸的圖:
敬請期待下一篇,玩轉12306之購票查詢。