關於12306第三方軟體檢測研究,12306第三方檢測
轉載請註明出處:http://blog.csdn.net/tang9140/article/details/42869269
首先申明下,本文章純作為個人喜好的技術性研究,請不要用於非法操作盈取不正當利益,你懂的。
問題引出
大概在2014年12月的17、18號,大量針對12306的刷票軟體出現了非法請求或‘使用第三方購票軟體’提示,並且驗證碼識別出錯。後面得知12306為了對惡意搶票軟體、外掛程式進行遏制與防控,通過手段能識別出搶票軟體和使用者購票的行為差別。
那麼問題來了。。。,我就很好奇,他們通過什麼手段做到的呢。
補充下,我之前是有做過JAVA版的搶票軟體,僅做學慣用途,沒其它目的。在12306加了第三方軟體檢測後,我的刷票軟體同樣出現了‘非法請求’提示,連登入都進不去。於是引發了我的思考。
搶票軟體本質是什嗎?
我個人認為搶票軟體無非是用機器人(實際上所謂的機器人就是一個軟體,這裡只是形象表述)代替實體人去購買車票。大家都知道機器反應快,因此在同時開搶的前提下,肯定是機器比實體人快,這也是為什麼黃牛黨能買到車票,而你買不到車票的原因。繼續往下思考,為什麼機器人能夠代替實體人去進行買票的一系列操作?這就引出了下一個問題。
使用者在瀏覽器上購買車票的一系列操作,從技術角度看,本質上發生了什嗎?
大家都知道,12306網站提供的是基於B/S架構的WEB服務,是建立在http協議之上的服務。http協議是典型的請求-接聽模式的協議,是無狀態的協議。實際上使用者在瀏覽器上的所有操作,最終都是依託瀏覽器發送請求到服務端,服務端接收到請求後進行相應的業務處理並將響應結果返回到瀏覽器端,瀏覽器再顯示給使用者。更具體點說,瀏覽器接收使用者的各種事件(例如按一下滑鼠事件,鍵盤輸入事件)後,然後在後台發送http請求到服務端,同時瀏覽器後台會接收到服務端的響應內容並展示為HTML頁面。
更一般化,對於伺服器來說,只要收到的請求是符合HTTP協議的就會進行處理,它不關心請求是通過瀏覽器發送過來的還是刷票軟體發送過來的(瀏覽器本身也是一個軟體,一般作業系統都內建瀏覽器)。從上面分析可以看出,只要第三方軟體去完全類比瀏覽器發送符合規範的HTTP請求,WEB伺服器就會當作合法的請求並進行處理。那麼問題又來了,為什麼刷票軟體會出現“非法請求”提示呢,這不跟你剛才的分析有矛盾嗎?這個嘛,其實不矛盾。請注意我剛才說的是“完全類比瀏覽器”,之前作為學習版的搶票軟體並沒有嚴格按照瀏覽器方式去發送HTTP請求,這也就導致12306能通過一些技術手段檢測出非法的請求。那麼,引出了我們的終極問題
12306怎麼進行技術檢測的,怎麼區別正常請求跟非法請求?
在回答這個問題前,大家先要瞭解下HTTP協議。HTTP請求訊息分為四部分:請求行、要求標頭、空行、可選的請求訊息體;共有八種要求方法,最常用的就兩種:GET請求和POST請求。GET請求將參數帶在URL後面(沒有訊息體),而POST請求將參數帶在訊息體中。要求標頭中可能包含Cookie資訊等。回到正題,12306對於非法請求檢測無非對三方面進行檢查,即要求標頭、Cookie、請求參數。
12306檢測三方面:
1、要求標頭
在類比HTTP請求時,需要注意要求標頭的順序。經過本人測試,如果登入請求的‘Cookie要求標頭’放置在‘Connection要求標頭’後面時,會提示‘非法請求’
2、Cookie
Cookie要求標頭同樣要注意順序,先是JSESSIONID(中間其它cookie)最後是BIGipServerotn,current_captcha_type
3、請求參數
還是順序的問題,請嚴格參照瀏覽器發送請求參數的順序進行發送。除此之外,12306在登入和提交訂單時還增加了動態key驗證。你需要先擷取到動態js檔案的url地址,然後訪問該js檔案內容,提取出其中的key值,並用該js檔案中的密碼編譯演算法對key加密後形相應的value值。
通過上述三方面的檢測,就能夠發現一些非法請求。當然有第三方軟體檢測,也就有反檢測。在12306推出這一系列第三方軟體檢測、監控及更換驗證碼(我相信有三套不同的驗證碼)後的數小時,一些刷票軟體就進行了破解。其實本人覺得所謂的第三方軟體檢測完全是治標不治本,就好像遊戲中的反外掛檢測一樣,在強大的外掛研發人員看來,完全不堪一擊。不管你加了多少次驗證,不管你怎麼更換驗證規則,都會很快的被破解。我倒覺得,與其把時間花在怎麼反外掛上,還不如內部提供外掛,借鑒遊戲的做法,12306倒不如內部提供一鍵搶票功能,不需要經過選車次,選乘客,提交下單驗證碼,再次確認等複雜過程。直接按照使用者事先設定好的規則一鍵搶票(需要保證用內部一鍵搶票功能比外部刷票軟體搶到票的機率要高),這樣還來得實在些,你怎麼看呢。支援的贊個
碼字不易,源頭來自http://blog.csdn.net/tang9140。
另附上關於KEY密碼編譯演算法的JAVA版實現代碼,感興趣的同學可以看下
public class DynamicJsUtil { private static String keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; static class Base32 { private static int delta = 0x9E3779B8; public static String longArrayToString(int[] data, boolean includeLength) { int length = data.length; int n = (length - 1) << 2; if (includeLength) { int m = data[length - 1]; if ((m < n - 3) || (m > n)) return null; n = m; } StringBuilder sb = new StringBuilder(""); for (int i = 0; i < length; i++) { int i0 = data[i] & 0xff; int i8 = data[i] >>> 8 & 0xff; int i16 = data[i] >>> 16 & 0xff; int i24 = data[i] >>> 24 & 0xff; if (i0 != 0) sb.append((char)i0); if (i8 != 0) sb.append((char)i8); if (i16 != 0) sb.append((char)i16); if (i24 != 0) sb.append((char)i24); } String result; if (includeLength) { result = sb.substring(0, n); } else result = sb.toString(); return result; } public static int[] stringToLongArray(String str, boolean includeLength) { int length = str.length(); int arrsize = length % 4 == 0 ? length / 4 : length / 4 + 1; int[] result = new int[arrsize]; for (int i = 0; i < length; i += 4) { if (i + 4 > length) { int char8 = i + 1 >= length ? 0 : str.charAt(i + 1) << 8; int char16 = i + 2 >= length ? 0 : str.charAt(i + 1) << 16; int char24 = i + 3 >= length ? 0 : str.charAt(i + 1) << 24; result[i >> 2] = str.charAt(i) | char8 | char16 | char24; } else result[i >> 2] = str.charAt(i) | str.charAt(i + 1) << 8 | str.charAt(i + 2) << 16 | str.charAt(i + 3) << 24; } if (includeLength) { int[] newArr = new int[arrsize + 1]; System.arraycopy(result, 0, newArr, 0, arrsize); newArr[arrsize] = length; result = newArr; } return result; } public static String encrypt(String str, String key) { if (str == "") { return ""; } int[] v = stringToLongArray(str, true); int[] k = stringToLongArray(key, false); if (k.length < 4) { int[] newArr = new int[4]; System.arraycopy(k, 0, newArr, 0, k.length); k = newArr; } int n = v.length - 1; int z = v[n], y = v[0]; int mx, e, p, sum = 0; int q = 6 + 52 / (n + 1); 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); }; } public static String encode32(String input) { input = escape(input); StringBuilder output = new StringBuilder(); int length = input.length(); int chr1, chr2, chr3; int enc1, enc2, enc3, enc4; int i = 0; do { chr1 = input.charAt(i++); enc1 = chr1 >> 2; output.append(keyStr.charAt(enc1)); if (i < length) { chr2 = input.charAt(i++); enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); output.append(keyStr.charAt(enc2)); if (i < length) { chr3 = input.charAt(i++); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; output.append(keyStr.charAt(enc3)).append(keyStr.charAt(enc4)); } else { enc3 = ((chr2 & 15) << 2) | (0 >> 6); output.append(keyStr.charAt(enc3)).append(keyStr.charAt(64)); } } else { enc2 = ((chr1 & 3) << 4) | (0 >> 4); output.append(keyStr.charAt(enc2)).append(keyStr.charAt(64)).append(keyStr.charAt(64)); } } while (i < length); return output.toString(); } static String bin216(String s) { s += ""; String output = ""; int l = s.length(); for (int i = 0; i < l; i++) { char c = s.charAt(i); String temp = Integer.toString(c, 16); output += temp.length() < 2 ? "0" + temp : temp; } return output; } public static String escape(String src) { char j; StringBuffer tmp = new StringBuffer(src.length() * 2); for (int i = 0; i < src.length(); i++) { j = src.charAt(i); if (Character.isDigit(j) || Character.isLowerCase(j) || Character.isUpperCase(j)) tmp.append(j); else if (j < 256) { if (j == '*' || j == '@' || j == '-' || j == '_' || j == '+' || j == '.' || j == '/') { tmp.append(j); } else { tmp.append("%"); if (j < 16) tmp.append("0"); tmp.append(Integer.toString(j, 16).toUpperCase()); } } else { tmp.append("%u"); tmp.append(Integer.toString(j, 16)); } } return tmp.toString(); } /** * 擷取動態加密value值 * @param key * @return */ public static String getRandomParamValue(String key) { return encode32(DynamicJsUtil.bin216(Base32.encrypt("1111", key))); }}