网抓基础:搞定 oauth 登录态

Photo by Aaron Burden

VBA 玩抓没有 Python 方便,不过大多数办公环境都配备 Excel,无需安装环境即可使用,与报表结合更便于工作人员做信息处理,以个人经历来讲,办公环境下还是 VBA 用起来趁手。下面是几年前写的网抓登录模块,相关信息已经过脱敏处理。

Oauth 登录态

Oauth 登录经过多重握手请求,通过抓包记录关键接口,记录各个握手变量,最后合成 cookie。在 VBA 中插入新模块,定义登录过程:OauthLogin(UserID , UserPass)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
Public Sub OauthLogin(UserID As String, UserPASS As String)

Set WinHttp = CreateObject("WinHttp.WinHttpRequest.5.1")
With WinHttp

'获取PHPSESSID
.Option(6) = False
.Open "GET", "http://yourdomian.com/login/", False
.send
PHPSESSID = Split(.getResponseHeader("Set-Cookie"), ";")(0)

'获取握手变量和调用地址
Postdata = "callbackURL=http%3A%2F%2Fyourdomian.com%2Fudb_web%2Fudbport2.php%3Fdo%3Dcallback&denyCallbackURL=http%3A%2F%2Fyourdomian.com%2Fudb_web%2Fudbport2.php%3Fdo%3DdenyCallback"
.Open "POST", "http://yourdomian.com/udb_web/udbport2.php?do=authorizeURL", False
.setRequestHeader "Content-Length", Len(Postdata)
.setRequestHeader "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"
.setRequestHeader "Cookie", PHPSESSID
.setRequestHeader "Host", "yourdomian.com"
.setRequestHeader "Referer", "http://yourdomian.com/login/"
.setRequestHeader "User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko"
.send (Postdata)

oauth_token = Split(Split(.responsetext, "?")(1), "&")(0)
lgnURL = Replace(Split(.responsetext, """")(7), "\", "")

'获取LGNJSESSIONID_2
RndNum1 = Format(Rnd(), "0.00000000000000000") 'DIM
lgnURL = lgnURL & "&UIStyle=xqlogin&rdm=" & RndNum1
.Open "GET", lgnURL, False
.send
LGNJSESSIONID_2 = Split(.getResponseHeader("Set-Cookie"), ";")(0)

'登陆,获取oauth_verifier,OauthCallBackURL
Postdata = "appid=1111&cssid=2222&denyCallbackURL=http%3A%2F%2Fyourdomian.com%2Fudb_web%2Fudbport2.php%3Fdo%3DdenyCallback&isRemMe=0&UIStyle=xqlogin"
Postdata = Postdata & "&username=" & UserID & "&password=" & UserPASS & "&" & oauth_token
.Open "POST", "https://lgn.yourdomain.com/lgn/oauth/x/s/login_asyn.do", False
.setRequestHeader "Accept", "*/*"
.setRequestHeader "Accept-Encoding", "gzip, deflate"
.setRequestHeader "Accept-Language", "en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3"
.setRequestHeader "Cache-Control", "no-cache"
.setRequestHeader "Connection", "Keep-Alive"
.setRequestHeader "Content-Length", Len(Postdata)
.setRequestHeader "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"
.setRequestHeader "Cookie", LGNJSESSIONID_2
.setRequestHeader "Host", "lgn.yourdomain.com"
.setRequestHeader "Referer", lgnURL
.setRequestHeader "User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko"
.send (Postdata)

If Mid(.responsetext, 10, 1) = 0 Then
OauthCallBackURL = Split(.responsetext, """")(13)
oauth_verifier = Split(.responsetext, "&")(2)

'获取CookieCallBackURL,oauth_mckey4cookie,oauth_signature
.Open "GET", OauthCallBackURL, False
.send
CookieCallBackURL = Split(.responsetext, "'")(1)
oauth_mckey4cookie = Split(Split(.responsetext, "?")(1), "&")(0)
oauth_signature = Split(.responsetext, "&")(1)

'获取最终cookie,LGNJSESSIONID_3,DomainCallBackURL
RndNum2 = Format(Rnd(), "0.00000000000000000") 'DIM
CookieCallBackURL = CookieCallBackURL & "&rdm=" & RndNum2
.Open "GET", CookieCallBackURL, False
.send
CookieHeaders = Filter(Split(.getallResponseHeaders(), Chr(10)), "Set-Cookie") '获取全部Cookie,DIM

For I = 0 To UBound(CookieHeaders)
CookieHeaders(I) = Split(Split(CookieHeaders(I), ": ")(1), ";")(0)
Next

loginCookieBase = ""

For I = 1 To UBound(CookieHeaders)
If loginCookieBase = "" Then
loginCookieBase = CookieHeaders(I)
Else
loginCookieBase = loginCookieBase & "; " & CookieHeaders(I)
End If
Next

LGNJSESSIONID_3 = CookieHeaders(0)
domainuid = CookieHeaders(1)
username = CookieHeaders(2)
password = CookieHeaders(3)
osinfo = CookieHeaders(4)
udb_l = CookieHeaders(5)
udb_n = CookieHeaders(6)
oauthCookie = CookieHeaders(7)
DomainCallBackURL = Split(.responsetext, """")(19)

'获取LGNJSESSIONID_4
.Open "GET", DomainCallBackURL, False
.send
LGNJSESSIONID_4 = Split(.getResponseHeader("Set-Cookie"), ";")(0)
LoginState = 1
MsgBox "登陆成功!"

Else
LoginState = 0
MsgBox Split(Split(.responsetext, ":")(2), ",")(0)

End If
End With
End Sub

oauth 登录的难点在于多节点握手变量和地址的获取,以及站点对于 cookie 的组合方式,需要反复抓包验证

普通登录态

这类登录模拟比 oauth 登录简单,就只有一个 post 请求,这个是后来另一个业务的功能模块,由于功能上需要根据不同登录结果做判断,定义为登录函数:NormalLogin(UserID , UserPass)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
Public winHttp As Object              '登陆状态继承
Public mixCookie As String '登录Cookie

Function NormalLogin(userID As String, userPass As String) As Integer
'账号密码登录
'返回值:0=登录失败,1=登录成功

On Error Resume Next

Dim phpseSSID As String
Dim muCookie As String
Dim postData As String
Dim testStatus As Integer
Dim responseSTR As String
Dim loginStatus As String

Set winHttp = CreateObject("winHttp.winHttpRequest.5.1")
With winHttp
.Option(6) = False
.Open "GET", "http://yourdomain.com/admin/index.php", False
.send
testStatus = .Status

'判断是否未进入内网
If testStatus = 200 Or testStatus = 302 Then

phpseSSID = Split(.getresponseheader("Set-Cookie"), ";")(0)

'获取Cookie:_m_u
.Option(6) = False
.Open "GET", "http://yourdomain.com/admin/adminLoginCode.php?m=public&f=loginCode&w=90&h=45&n=5", False
.setRequestHeader "Cookie", phpseSSID
.send
muCookie = Split(.getresponseheader("Set-Cookie"), ";")(0)

'登录
postData = "name=" & userID & "&password=" & userPass & "&verify=132"
mixCookie = phpseSSID & ";" & muCookie
.Open "POST", "http://yourdomain.com/admin/login.php?m=public&f=login", False
.setRequestHeader "Content-Length", Len(postData)
.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
.setRequestHeader "Cookie", mixCookie
.setRequestHeader "Host", "yourdomain.com"
.setRequestHeader "Referer", "http://yourdomain.com/admin/login.php"
.setRequestHeader "User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0"
.send (postData)

'读取服务器返回的登录状态
responseSTR = Replace(.responsetext, vbCrLf, "")
loginStatus = Split(Split(responseSTR, "<b>")(1), "<")(0)
MsgBox loginStatus

'返回函数值
NormalLogin = 0 - (loginStatus = "登录成功")

Else
NormalLogin = 0
MsgBox "未连接到后台,请检查网络设置"
End If
End With

End Function