http://www.cnblogs.com/wuhuacong/archive/2010/11/08/1871866.html

为了有效阻止恶意用户的攻击,一般登录都会采用验证码方式方式处理登录,类似QQ的很多产品的验证码处理,但在一些OA系统中,系统通过非对称加密方式来处理登录的密码信息,登录页面每次提供对密码进行加密的公钥是不同的,因此如果要模拟登录,就需要先获取公钥,然后根据公钥把输入的密码加密,然后通过POST提交给服务器进行验证登录。由于公钥是页面刷新变化的,而加密是通过Javascript脚本进行加密,如下面的登录页面源码所示。

c# 执行js的方法-LMLPHP


<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> 
<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> 
         &nbsp;<input type="submit" name="submit" class="submit" value="登 录"> 
      </td> 
    </tr> 
  </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);
            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 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);

最后程序处理登录后,自动获取联系人的界面效果如下所示:

c# 执行js的方法-LMLPHP

除了上面的操作方式,还有一种途径是通过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);             }
        }

通过浏览器接口,我们可以实现页面内容在不可以见的浏览器控件中呈现,然后获取相应的页面对象或者页面源码进行分析,可以得到更加丰富的数据,模拟浏览器的实际操作和获得真实的显示结果。

05-04 11:06