為了有效阻止惡意使用者的攻擊,一般登入都會采用驗證碼方式方式處理登入,類似QQ的很多産品的驗證碼處理,但在一些OA系統中,系統通過非對稱加密方式來處理登入的密碼資訊,登入頁面每次提供對密碼進行加密的公鑰是不同的,是以如果要模拟登入,就需要先擷取公鑰,然後根據公鑰把輸入的密碼加密,然後通過POST送出給伺服器進行驗證登入。由于公鑰是頁面重新整理變化的,而加密是通過Javascript腳本進行加密,如下面的登入頁面源碼所示。
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<link rel="stylesheet" type="text/css" href="/templates/2008/index.css">
<link rel="shortcut icon" href="/images/tongda.ico">
<script src="/inc/js/rsa/jsbn.js"></script><script src="/inc/js/rsa/prng4.js"></script><script src="/inc/js/rsa/rng.js"></script><script src="/inc/js/rsa/rsa.js"></script>
<script type="text/javascript">
function CheckForm()
{
var rsa = new RSAKey();
rsa.setPublic("97e256ec6147b7aadc46a353b5c5d707a895b402d114290c0c24a28919507569", "10001");
try{
document.form1.PASSWORD.value = rsa.encrypt(document.form1.PASSWORD.value);
}
catch(ex){
return false;
return true;
}
</script>
</head>
<body onload="javascript:document.form1.PASSWORD.focus();">
<br>
<div align="center">
<form name="form1" method="post" action="logincheck.php" autocomplete="off" onsubmit="return CheckForm();">
<table cellspacing="0" cellpadding="0" align="center">
<tr class="img_field">
<td align="center"><img src="/attachment/2090997160/index_1.jpg" width="651" height="241"></td>
</tr>
<tr height="37" class="login_field">
<td align="center">
<b>使用者名</b> <input type="text" class="text" name="UNAME" size="15" onmouseover="this.focus()" onfocus="this.select()" value="">
<b>密碼</b> <input type="password" class="text" name="PASSWORD" onmouseover="this.focus()" onfocus="this.select()" size="15" value="">
<select name="UI">
<option value="0">标準界面</option></select>
&nbsp;<input type="submit" name="submit" class="submit" value="登 錄">
</td>
</table>
<br>
</form>
為了模拟登入,我們需要先擷取頁面的公鑰資訊,然後通過在C#中運作Javascript腳本,傳遞公鑰和明文密碼,然後擷取腳本加密的結果,再送出給伺服器處理。
如下面代碼所示,擷取公鑰就是分析HTML源碼,通過正規表達式比對即可擷取到,相對比較簡單,但是要擷取Javascript腳本的運作結果,就需要花點功夫了。
///inc/js/rsa/jsbn.js /inc/js/rsa/prng4.js /inc/js/rsa/rng.js /inc/js/rsa/rsa.js
string scriptUrl = "http://www.abc.cn:8080/inc/js/rsa/jsbn.js";
string scriptUrl2 = "http://www.abc.cn:8080/inc/js/rsa/prng4.js";
string scriptUrl3 = "http://www.abc.cn:8080/inc/js/rsa/rng.js";
string scriptUrl4 = "http://www.abc.cn:8080/inc/js/rsa/rsa.js";
string referen = "http://www.abc.cn:8080";
HttpHelper helper = new HttpHelper();
helper.Encoding = Encoding.Default;
string mainContent = helper.GetHtml(referen, cookie, referen);
string regex = "rsa.setPublic\\(\"(?<publicKey>.*?)\",\\s*\"(?<encrypt>.*?)\"\\);";
Regex re = new Regex(regex, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace);
Match mc = re.Match(mainContent);
if (mc.Success)
{
string publicKey = mc.Groups["publicKey"].Value;
string encrypt = mc.Groups["encrypt"].Value;
string pass = config.AppConfigGet("ContactPassword");
string source = "";//"var appName = \"Microsoft Internet Explorer\"; " + Environment.NewLine;
source += helper.GetHtml(scriptUrl);
source += helper.GetHtml(scriptUrl2);
source += helper.GetHtml(scriptUrl3);
source += helper.GetHtml(scriptUrl4);
//source = source.Replace("navigator.", "");
source = getJS(source);
encryptPass = scriptEngine.Eval("getRSAKey()",
source +
"\r\nfunction getRSAKey(){\r\nvar RSA = new RSAKey();\r\nRSA.setPublic(\"" +
publicKey + "\",\"" + encrypt + "\");\r\nvar Res = RSA.encrypt('" + pass + "');\r\nreturn Res;\r\n}").ToString();
}
#endregion
上面的運作Javascript腳本,需要先把用到的腳本全部下載下傳,把内容組合起來,然後添加一個虛拟的函數,運作得到傳回結果接口,虛拟的函數一定要寫正确,否則出來很多錯誤,得不到結果。
上面的代碼有source = getJS(source);這一句,是為了避免腳本調用navigator.appName來處理浏覽器類型和版本的判斷,有兩種方式可以跳過這個處理,一個增加一個appName的變量,如var appName =**這樣,然後統一替換navigator. 的字元,使得腳本判别浏覽器代碼失效;二是通過正規表達式替換掉響應的判斷代碼即可
private string getJS(string strJS)
{
if (!Regex.IsMatch(strJS, @"if\(j_lm \&\& \(navigator.appName == ""Microsoft Internet Explorer""\)\) {.+?dbits = 28;.+?}", RegexOptions.Singleline) ||
!Regex.IsMatch(strJS, @"if\(navigator.appName == ""Netscape"" && navigator.appVersion < ""5"" && window.crypto\) {.+?}", RegexOptions.Singleline))
{
return string.Empty;
}
strJS = Regex.Replace(strJS,
@"if\(j_lm \&\& \(navigator.appName == ""Microsoft Internet Explorer""\)\) {.+?dbits = 28;.+?}",
"BigInteger.prototype.am = am2;\r\ndbits = 30\r\n", RegexOptions.Singleline);
@"if\(navigator.appName == ""Netscape"" && navigator.appVersion < ""5"" && window.crypto\) {.+?}",
string.Empty, RegexOptions.Singleline);
return strJS;
}
得到處理過的密碼密文 ,一般通過POST方式送出登入頁面,即可完成系統的登入了,然後繼續可以通過HttpRequest方式擷取系統各種頁面的資訊了(如聯系人等),如下面所示。
string referen = "http://www.abc.cn:8080/";
string loginUrl = "http://www.abc.cn:8080/logincheck.php";
string login = "test";
string loginPostData = string.Format("UNAME={1}&PASSWORD={0}&UI=0&submit={2}", encryptPass, login, "%B5%C7+%C2%BC");
string conctactUrl = "http://www.abc.cn:8080/general/ipanel/user/search.php";
string itemRegex = "<tr\\s*class=\"TableLine\\d\">\\s*(.*?)\\s*</tr>";
string memberRegex = "<td.*?>\\s*(.*?)\\s*</td>";
List<ContactInfo> contactList = new List<ContactInfo>();
HttpHelper helper = new HttpHelper();
helper.Encoding = Encoding.Default;
string result = helper.GetHtml(loginUrl, cookie, loginPostData, true, "", loginUrl);
最後程式處理登入後,自動擷取聯系人的界面效果如下所示:
除了上面的操作方式,還有一種途徑是通過WebBrowser控件實作資料的自動送出,WebBrowser控件處理腳本的運作更加友善,但缺點是這個控件相對較慢,首先我介紹一下這種方式,在按鈕觸發中調用控件的Navigate函數,打開相應的登入連結位址。
webBrowser1.Navigate("http://www.abc.cn:8080");
接着在浏覽器控件的頁面完成函數進行中對資料進行處理,處理的思路就是調用腳本對輸入的内容進行加密,然後再觸發送出按鈕即可完成頁面的登入,記錄登入的資訊,然後再去擷取相關的頁面内容資訊,不過這種控件處理相對沒那麼大的彈性處理,不過可以作為一些功能的補充使用。
private int numtries = 0;
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
if (webBrowser1.Document.GetElementById("UNAME") != null)
webBrowser1.Document.GetElementById("UNAME").SetAttribute("value", "陳建才");
webBrowser1.Document.GetElementById("PASSWORD").SetAttribute("value", "voowoo770916");
if (numtries < 2)
{
IHTMLWindow2 login = (mshtml.IHTMLWindow2)webBrowser1.Document.Window.DomWindow;
//login.execScript("document.forms[0].submit();", "javascript");
login.execScript("CheckForm();", "javascript");
string value = webBrowser1.Document.GetElementById("PASSWORD").GetAttribute("value");
encryptPass = value;
GetContact();
numtries++;
}
if (webBrowser1.Document.GetElementById("userName") == null)
numtries = 0;
//string cookieString = webBrowser1.Document.Cookie;
//CookieCollection cc = new CookieCollection();
//CookieManger.SetCKAppendToCC(cc, cookieString, "http://www.abc.cn:8080");
//cookie.Add(cc);
通過浏覽器接口,我們可以實作頁面内容在不可以見的浏覽器控件中呈現,然後擷取相應的頁面對象或者頁面源碼進行分析,可以得到更加豐富的資料,模拟浏覽器的實際操作和獲得真實的顯示結果。