为了有效阻止恶意用户的攻击,一般登录都会采用验证码方式方式处理登录,类似QQ的很多产品的验证码处理,但在一些OA系统中,系统通过非对称加密方式来处理登录的密码信息,登录页面每次提供对密码进行加密的公钥是不同的,因此如果要模拟登录,就需要先获取公钥,然后根据公钥把输入的密码加密,然后通过POST提交给服务器进行验证登录。由于公钥是页面刷新变化的,而加密是通过Javascript脚本进行加密,如下面的登录页面源码所示。
< 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 >
< br >
< br >
< 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 >
< input type ="submit" name ="submit" class ="submit" value ="登 录" >
</ td >
</ tr >
</ table >
< br >
</ form >
为了模拟登录,我们需要先获取页面的公钥信息,然后通过在C#中运行Javascript脚本,传递公钥和明文密码,然后获取脚本加密的结果,再提交给服务器处理。
如下面代码所示,获取公钥就是分析HTML源码,通过正则表达式匹配即可获取到,相对比较简单,但是要获取Javascript脚本的运行结果,就需要花点功夫了。
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);
strJS = Regex.Replace(strJS,
@" 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 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函数,打开相应的登录链接地址。
接着在浏览器控件的页面完成函数处理中对数据进行处理,处理的思路就是调用脚本对输入的内容进行加密,然后再触发提交按钮即可完成页面的登录,记录登录的信息,然后再去获取相关的页面内容信息,不过这种控件处理相对没那么大的弹性处理,不过可以作为一些功能的补充使用。
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);
}
}
通过浏览器接口,我们可以实现页面内容在不可以见的浏览器控件中呈现,然后获取相应的页面对象或者页面源码进行分析,可以得到更加丰富的数据,模拟浏览器的实际操作和获得真实的显示结果。
本文转自博客园伍华聪的博客,原文链接:Winform下动态执行JavaScript脚本获取运行结果,谈谈网站的自动登录及资料获取操作,如需转载请自行联系原博主。