Winform下动态执行JavaScript脚本获取运行结果,谈谈网站的自动登录及资料获取操作
为了有效阻止恶意用户的攻击,一般登录都会采用验证码方式方式处理登录,类似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
>
<
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脚本的运行结果,就需要花点功夫了。
///
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);
最后程序处理登录后,自动获取联系人的界面效果如下所示:
除了上面的操作方式,还有一种途径是通过WebBrowser控件实现数据的自动提交,WebBrowser控件处理脚本的运行更加方便,但缺点是这个控件相对较慢,首先我介绍一下这种方式,在按钮触发中调用控件的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);
}
}
通过浏览器接口,我们可以实现页面内容在不可以见的浏览器控件中呈现,然后获取相应的页面对象或者页面源码进行分析,可以得到更加丰富的数据,模拟浏览器的实际操作和获得真实的显示结果。