以Discuz的官方站为例。直接点击网页右上角的登录按钮,会弹出一个带验证码的登录窗口。输入验证码之后,会检查验证码是否正确。然后登录。首先,通过抓包分析,这些过程浏览器和服务器交换了哪些数据。
整个过程产生了5条数据:
第一个是GET请求,返回了一段html代码
<div id="main_messaqge_LZH8S">
<div id="layer_login_LZH8S">
<h3 class="flb">
<em id="returnmessage_LZH8S">
用户登录</em>
<span><a href="javascript:;" class="flbc" onclick="hideWindow('login', 0, 1);" title="关闭">关闭</a></span>
</h3>
<form method="post" autocomplete="off" name="login" id="loginform_LZH8S" class="cl" onsubmit="pwdclear = 1;ajaxpost('loginform_LZH8S', 'returnmessage_LZH8S', 'returnmessage_LZH8S', 'onerror');return false;" action="member.php?mod=logging&action=login&loginsubmit=yes&handlekey=login&loginhash=LZH8S">
<div class="c cl">
<input type="hidden" name="formhash" value="41969484" />
<input type="hidden" name="referer" value="http://www.discuz.net/forum.php" />
<div class="rfm">
<table>
<tr>
<th>
<span class="login_slct">
<select name="loginfield" style="float: left;" width="45" id="loginfield_LZH8S">
<option value="username">用户名</option>
<option value="email">Email</option>
</select>
</span>
</th>
<td><input type="text" name="username" id="username_LZH8S" autocomplete="off" size="30" class="px p_fre" tabindex="1" value="" /></td>
<td class="tipcol"><a href="member.php?mod=register">立即注册</a></td>
</tr>
</table>
</div>
<div class="rfm">
<table>
<tr>
<th><label for="password3_LZH8S">密码:</label></th>
<td><input type="password" id="password3_LZH8S" name="password" onfocus="clearpwd()" size="30" class="px p_fre" tabindex="1" /></td>
<td class="tipcol"><a href="javascript:;" onclick="display('layer_login_LZH8S');display('layer_lostpw_LZH8S');" title="找回密码">找回密码</a></td>
</tr>
</table>
</div>
<div class="rfm">
<table>
<tr>
<th>安全提问:</th>
<td><select id="loginquestionid_LZH8S" width="213" name="questionid" onchange="if($('loginquestionid_LZH8S').value > 0) {$('loginanswer_row_LZH8S').style.display='';} else {$('loginanswer_row_LZH8S').style.display='none';}">
<option value="0">安全提问(未设置请忽略)</option>
<option value="1">母亲的名字</option>
<option value="2">爷爷的名字</option>
<option value="3">父亲出生的城市</option>
<option value="4">您其中一位老师的名字</option>
<option value="5">您个人计算机的型号</option>
<option value="6">您最喜欢的餐馆名称</option>
<option value="7">驾驶执照最后四位数字</option>
</select></td>
</tr>
</table>
</div>
<div class="rfm" id="loginanswer_row_LZH8S" style="display:none">
<table>
<tr>
<th>答案:</th>
<td><input type="text" name="answer" id="loginanswer_LZH8S" autocomplete="off" size="30" class="px p_fre" tabindex="1" /></td>
</tr>
</table>
</div>
<span id="seccode_cSA"></span>
<script type="text/javascript" reload="1">updateseccode('cSA', '<div class="rfm"><table><tr><th><sec>: </th><td><sec><br /><sec></td></tr></table></div>', 'member::logging');</script>
<div class="rfm bw0">
<table>
<tr>
<th></th>
<td><label for="cookietime_LZH8S"><input type="checkbox" class="pc" name="cookietime" id="cookietime_LZH8S" tabindex="1" value="2592000" />自动登录</label></td>
</tr>
</table>
</div>
<div class="rfm mbw bw0">
<table width="100%">
<tr>
<th> </th>
<td>
<button class="pn pnc" type="submit" name="loginsubmit" value="true" tabindex="1"><strong>登录</strong></button>
</td>
<td>
</td>
</tr>
</table>
</div>
<div class="rfm bw0 ">
<hr class="l" />
<table>
<tr>
<th>快捷登录:</th>
<td>
<a href="http://www.discuz.net/connect.php?mod=login&op=init&referer=http%3A%2F%2Fwww.discuz.net%2Fforum.php&statfrom=login" target="_top" rel="nofollow"><img src="static/image/common/qq_login.gif" class="vm" /></a>
<a href="plugin.php?id=wechat:login"><img src="source/plugin/wechat/image/wechat_login.png" class="vm" /></a>
</td>
</tr>
</table>
</div>
</div>
</form>
</div>
<div id="layer_lostpw_LZH8S" style="display: none;">
<h3 class="flb">
<em id="returnmessage3_LZH8S">找回密码</em>
<span><a href="javascript:;" class="flbc" onclick="hideWindow('login')" title="关闭">关闭</a></span>
</h3>
<form method="post" autocomplete="off" id="lostpwform_LZH8S" class="cl" onsubmit="ajaxpost('lostpwform_LZH8S', 'returnmessage3_LZH8S', 'returnmessage3_LZH8S', 'onerror');return false;" action="member.php?mod=lostpasswd&lostpwsubmit=yes&infloat=yes">
<div class="c cl">
<input type="hidden" name="formhash" value="41969484" />
<input type="hidden" name="handlekey" value="lostpwform" />
<div class="rfm">
<table>
<tr>
<th><span class="rq">*</span><label for="lostpw_email">Email:</label></th>
<td><input type="text" name="email" id="lostpw_email" size="30" value="" tabindex="1" class="px p_fre" /></td>
</tr>
</table>
</div>
<div class="rfm">
<table>
<tr>
<th><label for="lostpw_username">用户名:</label></th>
<td><input type="text" name="username" id="lostpw_username" size="30" value="" tabindex="1" class="px p_fre" /></td>
</tr>
</table>
</div>
<div class="rfm mbw bw0">
<table>
<tr>
<th></th>
<td><button class="pn pnc" type="submit" name="lostpwsubmit" value="true" tabindex="100"><span>提交</span></button></td>
</tr>
</table>
</div>
</div>
</form>
</div>
</div>
<div id="layer_message_LZH8S" style="display: none;">
<h3 class="flb" id="layer_header_LZH8S">
<em>用户登录</em>
<span><a href="javascript:;" class="flbc" onclick="hideWindow('login')" title="关闭">关闭</a></span>
</h3>
<div class="c"><div class="alert_right">
<div id="messageleft_LZH8S"></div>
<p class="alert_btnleft" id="messageright_LZH8S"></p>
</div>
</div>
<script type="text/javascript" reload="1">
var pwdclear = 0;
function initinput_login() {
document.body.focus();
if($('loginform_LZH8S')) {
$('loginform_LZH8S').username.focus();
}
simulateSelect('loginfield_LZH8S');
}
initinput_login();
function clearpwd() {
if(pwdclear) {
$('password3_LZH8S').value = '';
}
pwdclear = 0;
}
</script>
特别注意到,这段代码中formhash出现两次,刷新之后,loginhash和formhash发生了变化。
loginhash=LZH8S
<input type="hidden" name="formhash" value="41969484" />
第二个GET请求返回了一段JavaScript代码:
1 if($('seccode_cSA')) {
2 if(!$('vseccode_cSA')) {
3 var sectpl = seccheck_tpl['cSA'] != '' ? seccheck_tpl['cSA'].replace(/<hash>/g, 'codecSA') : '';
4 var sectplcode = sectpl != '' ? sectpl.split('<sec>') : Array('<br />',': ','<br />','');
5 var string = '<input name="seccodehash" type="hidden" value="cSA" /><input name="seccodemodid" type="hidden" value="member::logging" />' + sectplcode[0] + '验证码' + sectplcode[1] + '<input name="seccodeverify" id="seccodeverify_cSA" type="text" autocomplete="off" style="ime-mode:disabled;width:100px" class="txt px vm" οnblur="checksec(\'code\', \'cSA\', 0, null, \'member::logging\')" />' +
6 ' <a href="javascript:;" οnclick="updateseccode(\'cSA\');doane(event);" class="xi2">换一个</a>' +
7 '<span id="checkseccodeverify_cSA"><img src="' + STATICURL + 'image/common/none.gif" width="16" height="16" class="vm" /></span>' +
8 sectplcode[2] + '<span id="vseccode_cSA">请输入下面动画图片中的字符<br /><img οnclick="updateseccode(\'cSA\')" width="100" height="35" src="misc.php?mod=seccode&update=18644&idhash=cSA" class="vm" alt="" /></span>' + sectplcode[3];
9 evalscript(string);
10 $('seccode_cSA').innerHTML = string;
11 } else {
12 var string = '请输入下面动画图片中的字符<br /><img οnclick="updateseccode(\'cSA\')" width="100" height="35" src="misc.php?mod=seccode&update=18644&idhash=cSA" class="vm" alt="" />';
13 evalscript(string);
14 $('vseccode_cSA').innerHTML = string;
15 }
16
17 }
其中的update值在刷新和点击换一个验证码之后产生变化:
src="misc.php?mod=seccode&update=18644&idhash=cSA"
第三个GET请求的头文件包含: 根据Accept属性可知返回的是一张图片,请求链接中包含了上一条请求的update值。
GET http://www.discuz.net/misc.php?mod=seccode&update=18644&idhash=cSA HTTP/1.1
Accept: image/webp,image/*,*/*;q=0.8
第四个GET请求的链接为:
GET http://www.discuz.net/misc.php?mod=seccode&action=check&inajax=1&modid=member::logging&idhash=cSA&secverify=e38p
最后的secverify=e38p为验证码,返回的文本为
<?xml version="1.0" encoding="gbk"?>
<root><![CDATA[succeed]]></root>
根据链接中的action=check和返回的文本推断,这是在检查输入的验证码是否正确。
第五个为POST请求,headers为:
1 POST http://www.discuz.net/member.php?mod=logging&action=login&loginsubmit=yes&handlekey=login&loginhash=LZH8S&inajax=1 HTTP/1.1
2 Host: www.discuz.net
3 Connection: keep-alive
4 Content-Length: 200
5 Cache-Control: max-age=0
6 Origin: http://www.discuz.net
7 Upgrade-Insecure-Requests: 1
8 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
9 Content-Type: application/x-www-form-urlencoded
10 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
11 Referer: http://www.discuz.net/forum.php
12 Accept-Encoding: gzip, deflate
13 Accept-Language: zh-CN,zh;q=0.8
14 Cookie: t7asq_4ad6_saltkey=X3kj05VV; t7asq_4ad6_lastvisit=1496539605; t7asq_4ad6_nofavfid=1; t7asq_4ad6_ulastactivity=1496722517%7C0; t7asq_4ad6_lastcheckfeed=3051978%7C1496722517; t7asq_4ad6_security_cookiereport=798bNiNKvpmS%2BJb6aMC1CK0u2rBbgm%2Bl4RHOagb%2FADNc1uz1WEc6; t7asq_4ad6_connect_is_bind=0; t7asq_4ad6_forum_lastvisit=D_10_1496722811; pgv_pvi=7116158144; pgv_info=ssi=s5527714008; t7asq_4ad6_seccode=14415.a12e22e835ed3df84a; t7asq_4ad6_lastact=1496730222%09misc.php%09seccode
15
16 formhash=41969484&referer=http%3A%2F%2Fwww.discuz.net%2Fforum.php&loginfield=username&username=123&password=123&questionid=0&answer=&seccodehash=cSA&seccodemodid=member%3A%3Alogging&seccodeverify=e38p
请求的链接中含有loginhash=LZH8S,提交的数据中含有formhash=41969484和验证码seccodeverify=e38p和没有被加密的用户名和密码。
因为用户名和密码是随便输入的,所以返回的文本为
<?xml version="1.0" encoding="gbk"?>
<root><![CDATA[登录失败,您还可以尝试 4 次<script type="text/javascript" reload="1">if(typeof errorhandle_login=='function') {errorhandle_login('登录失败,您还可以尝试 4 次', {'loginperm':'4'});}</script>]]></root>
下面进行模拟登录,注意要获取loginhash、formhash和update的值。
一、模拟登录:登录过程中的5个请求的headers都不同,在模拟请求时,完全复制抓包到的headers。
import requests
session=requests.session()
#获取登录窗口中的loginhash和formhash
def get_login_window():
url='http://www.discuz.net/member.php?mod=logging&action=login&infloat=yes&handlekey=login&inajax=1&ajaxtarget=fwin_content_login'
headers={'Host':'www.discuz.net','Connection':'keep-alive','User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36','X-Requested-With':'XMLHttpRequest','Accept':'*/*','Referer':'http://www.discuz.net/forum.php','Accept-Encoding':'gzip, deflate, sdch','Accept-Language':'zh-CN,zh;q=0.8'}
#清空原来的headers
session.headers.clear()
#更新headers
session.headers.update(headers)
r=session.get(url)
#获取loginhash
p=r.text.find('loginhash')+len('loginhash')+1
loginhash=r.text[p:p+5]
#获取formhash
p=r.text.find('formhash')+len('formhash" value="')
formhash=r.text[p:p+8]
return (loginhash,formhash)
#获取update
def get_code_info():
url='http://www.discuz.net/misc.php?mod=seccode&action=update&idhash=cSA&0.3916181418197131&modid=member::logging'
r=session.get(url)
p=r.text.find('update=')
update=r.text[p+7:p+12]
return update
#获取验证码
def get_code(update):
url='http://www.discuz.net/misc.php?mod=seccode&update='+update+'&idhash=cSA'
headers={'Host':'www.discuz.net','Connection':'keep-alive','User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36','Accept':'image/webp,image/*,*/*;q=0.8','Referer':'http://www.discuz.net/forum.php','Accept-Encoding':'gzip, deflate, sdch','Accept-Language':'zh-CN,zh;q=0.8'}
session.headers.clear()
session.headers.update(headers)
r=session.get(url)
if(r.content[:3]==b'GIF'):
#保存验证码图片
file=open('code.gif','wb')
file.write(r.content)
file.close()
else:
#打印错误信息
print(r.text)
#检查验证码是否正确
#通过人工识别验证码code,:)
def check_code(code):
url='http://www.discuz.net/misc.php?mod=seccode&action=check&inajax=1&modid=member::logging&idhash=cSA&secverify='+code
headers={'Host':'www.discuz.net','Connection':'keep-alive','User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36','Accept':'image/webp,image/*,*/*;q=0.8','Referer':'http://www.discuz.net/forum.php','Accept-Encoding':'gzip, deflate, sdch','Accept-Language':'zh-CN,zh;q=0.8'}
session.headers.clear()
session.headers.update(headers)
r=session.get(url)
return r.text
#模拟登录
def login(loginhash,formhash,code,username,password):
url='http://www.discuz.net/member.php?mod=logging&action=login&loginsubmit=yes&handlekey=login&loginhash='+loginhash+'&inajax=1'
data={'formhash':formhash,
'referer':'http://www.discuz.net/forum.php',
'loginfield':'username',
'username':username,
'password':password,
',
'answer':'',
'seccodehash':'cSA',
'seccodemodid':'member::logging',
'seccodeverify':code}
headers={','User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36','Content-Type':'application/x-www-form-urlencoded','Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8','Referer':'http://www.discuz.net/forum.php','Accept-Encoding':'gzip, deflate','Accept-Language':'zh-CN,zh;q=0.8'}
session.headers.clear()
session.headers.update(headers)
r=session.post(url,data)
print(r.text)
模拟登录:
(loginhash,formhash)=get_login_window()
get_code(get_code_info())
code=input()#人工识别 :)
check_code(code)
#[CDATA[succeed]]
login(loginhash,formhash,code,username,password)
#欢迎您回来,现在将转入登录前页面
测试:此页面只有在登录后才能显示
url='http://www.discuz.net/home.php?mod=space&do=pm'
标题为 ”用户名 – Discuz! 官方站 – Powered by Discuz!“
如果未登录,则标题为 ”提示信息 – Discuz! 官方站 – Powered by Discuz!“
session.headers.clear()
r=session.get(url)
p=r.text.find('<title>')+len('<title>')
print(r.text[p:r.text.find('<',p)])
如果打印出了 ”用户名 – Discuz! 官方站 – Powered by Discuz!“,则证明登录成功。