[关闭]
@FadeTrack 2015-11-20T11:31:22.000000Z 字数 8927 阅读 5884

Python 之 Post 登陆 Dz 论坛

Python


从头开始

也不是近来才有想法写这个,之前也是打算写一下这方面的东西,很多年没接触这些东西,没想到换了个名称,以前的封包分析现在都称为 协议分析,不知道是哪位大神想到的名字,膜拜~~

协议分析: 用他们的话说 就是一堆封包构成的一种行为模式!

网页分析 到 封包分析

先从DZ的登陆说起,我们大致可以知道的是,网页登陆一般采用的是 Post 提交的方式,那么首先要做的当然是分析一下页面的源代码。

先找到 DZ 的登陆页面。

http://www.mengwuji.net/member.php?mod=logging&action=login

这里以 梦织未来 为例, 可以看到 DZ 登陆页面是 /member.php?mod=logging&action=login,
这个是基本规则,记录下你。

贴出源代码:

  1. <form method="post" autocomplete="off" name="login" id="loginform_LUl75" class="cl" onsubmit="pwmd5('password3_LUl75');pwdclear = 1;ajaxpost('loginform_LUl75', 'returnmessage_LUl75', 'returnmessage_LUl75', 'onerror');return false;" action="member.php?mod=logging&amp;action=login&amp;loginsubmit=yes&amp;loginhash=LUl75">
  2. <div class="c cl">
  3. <input type="hidden" name="formhash" value="a67d07ed">
  4. <input type="hidden" name="referer" value="http://www.mengwuji.net/./">
  5. <div class="rfm">
  6. <table>
  7. <tbody><tr>
  8. <th>
  9. <span class="login_slct">
  10. <select name="loginfield" style="float: left; display: none;" width="45" id="loginfield_LUl75" selecti="0">
  11. <option value="username"></option></select><a href="javascript:;" id="loginfield_LUl75_ctrl" style="width:45px" tabindex="1">用户名</a>
  12. </span>
  13. </th>
  14. <td><input type="text" name="username" id="username_LUl75" autocomplete="off" size="30" class="px p_fre" tabindex="1" value=""></td>
  15. <td class="tipcol"><a href="member.php?mod=register">立即注册</a></td>
  16. </tr>
  17. </tbody></table>
  18. </div>
  19. <div class="rfm">
  20. <table>
  21. <tbody><tr>
  22. <th><label for="password3_LUl75">密码:</label></th>
  23. <td><input type="password" id="password3_LUl75" name="password" onfocus="clearpwd()" size="30" class="px p_fre" tabindex="1"></td>
  24. <td class="tipcol"><a href="javascript:;" onclick="display('layer_login_LUl75');display('layer_lostpw_LUl75');" title="找回密码">找回密码</a></td>
  25. </tr>
  26. </tbody></table>
  27. </div>
  28. <div class="rfm">
  29. <table>
  30. <tbody><tr>
  31. <th>安全提问:</th>
  32. <td><select id="loginquestionid_LUl75" width="213" name="questionid" onchange="if($('loginquestionid_LUl75').value > 0) {$('loginanswer_row_LUl75').style.display='';} else {$('loginanswer_row_LUl75').style.display='none';}">
  33. <option value="0">安全提问(未设置请忽略)</option>
  34. <option value="1">母亲的名字</option>
  35. <option value="2">爷爷的名字</option>
  36. <option value="3">父亲出生的城市</option>
  37. <option value="4">您其中一位老师的名字</option>
  38. <option value="5">您个人计算机的型号</option>
  39. <option value="6">您最喜欢的餐馆名称</option>
  40. <option value="7">驾驶执照最后四位数字</option>
  41. </select></td>
  42. </tr>
  43. </tbody></table>
  44. </div>
  45. <div class="rfm" id="loginanswer_row_LUl75" style="display:none">
  46. <table>
  47. <tbody><tr>
  48. <th>答案:</th>
  49. <td><input type="text" name="answer" id="loginanswer_LUl75" autocomplete="off" size="30" class="px p_fre" tabindex="1"></td>
  50. </tr>
  51. </tbody></table>
  52. </div>
  53. <div class="rfm ">
  54. <table>
  55. <tbody><tr>
  56. <th></th>
  57. <td><label for="cookietime_LUl75"><input type="checkbox" class="pc" name="cookietime" id="cookietime_LUl75" tabindex="1" value="2592000">自动登录</label></td>
  58. </tr>
  59. </tbody></table>
  60. </div>
  61. <div class="rfm mbw bw0">
  62. <table width="100%">
  63. <tbody><tr>
  64. <th>&nbsp;</th>
  65. <td>
  66. <button class="pn pnc" type="submit" name="loginsubmit" value="true" tabindex="1"><strong>登录</strong></button>
  67. </td>
  68. <td>
  69. <a href="javascript:;" onclick="ajaxget('member.php?mod=clearcookies&amp;formhash=a67d07ed', 'returnmessage_LUl75', 'returnmessage_LUl75');return false;" title="清除痕迹" class="y">清除痕迹</a></td>
  70. </tr>
  71. </tbody></table>
  72. </div>
  73. <div class="rfm bw0 mbw">
  74. <hr class="l">
  75. <table>
  76. <tbody><tr>
  77. <th>快捷登录:</th>
  78. <td>
  79. <a href="http://www.mengwuji.net/connect.php?mod=login&amp;op=init&amp;referer=http%3A%2F%2Fwww.mengwuji.net%2F.%2F&amp;statfrom=login" target="_top" rel="nofollow"><img src="static/image/common/qq_login.gif" class="vm"></a>
  80. </td>
  81. </tr>
  82. </tbody></table>
  83. </div>
  84. </div>
  85. </form>

核心代码大概就是这么多,把中间有用的提取一下。

  1. <form method="post" autocomplete="off" name="login" id="loginform_LUl75" class="cl" onsubmit="pwmd5('password3_LUl75');pwdclear = 1;ajaxpost('loginform_LUl75', 'returnmessage_LUl75', 'returnmessage_LUl75', 'onerror');return false;" action="member.php?mod=logging&amp;action=login&amp;loginsubmit=yes&amp;loginhash=LUl75">

提交方式是 Post ,提交地址是 member.php?mod=logging&amp;action=login&amp;loginsubmit=yes&amp;loginhash=LUl75

  1. <input type="hidden" name="formhash" value="a67d07ed">
  2. <input type="hidden" name="referer" value="http://www.mengwuji.net/./">

看这里 ,这是两个隐藏域,我们在提交的时候会一并提交上去,我们先记录下来待会抓包看。

  1. <input type="text" name="username" id="username_LUl75" autocomplete="off" size="30" class="px p_fre" tabindex="1" value="">
  2. <input type="password" id="password3_LUl75" name="password" onfocus="clearpwd()" size="30" class="px p_fre" tabindex="1">
  3. <input type="text" name="answer" id="loginanswer_LUl75" autocomplete="off" size="30" class="px p_fre" tabindex="1">
  4. <input type="checkbox" class="pc" name="cookietime" id="cookietime_LUl75" tabindex="1" value="2592000">

好了大概就这么些了。

接下来用 Wireshark 抓个包。

过滤器填写 http.request.method==POST , 因为我们已知是 Post 方式提交了。

这里需要我们关心的是 Post 的地址 及 Post 的内容。

Post封包截获

图中可以看到地址为:

http://www.mengwuji.net/member.php?mod=logging&action=login&loginsubmit=yes&loginhash=LUl75&inajax=1

  1. formhash=a67d07ed
  2. referer=http%3A%2F%2Fwww.mengwuji.net%2F.%2F
  3. loginfield=username
  4. username=FadeTrack
  5. password=140014464b4d6002f005067798cba159 (对中间的数字进行了替换,防止不法之徒)
  6. questionid=0
  7. answer=

对比我们静态分析时的记录可以发现 loginhashformhash 是不确定的。

由于这里是不确定的 所以我们要获取之。

经过测试这里只需要获取 formhash 即可,loginhash 的值并不影响登陆。

password 域同样经过测试,只需要原密码即可,不是非要 使用 md5 加密的才行。


至于你问我为什么是 md5 加密,可以回到静态代码看看。

  1. onsubmit="pwmd5('password3_LUl75')

这里贴出部分,意思是当登陆按钮按下时会触发这个事件,用的是 Js 实现的。

那么有人又问了,如果是非要提交MD5的密码上去怎么办呢?

网上有现成的 md5 模块供你调用。

又有人问了,如果那个网站是采用的自己的加密算法怎么办?或者是改过的怎么办?

<script src="static/js/md5.js?knh" type="text/javascript" reload="1"></script>

搜一下他的 js ,读一下就行了。

还有人问(丫的问题这么多),Html 和 Js 不懂怎么办?

不懂就去学呗,这个我没有偷懒的捷径,不过 js 和 'c' 倒是挺像的。


回归正题了。

代码编写

有了上面的东西,我们就可以开始写代码了,其实我也是现学现卖的那种了。 :D

先贴上代码,再逐一说明吧

  1. import urllib.request
  2. import urllib.parse
  3. import http.cookiejar,re
  4. opener = None
  5. # 带Cookie访问
  6. def openurl(parms):
  7. global opener
  8. if opener == None:
  9. #cookie设置
  10. cj = http.cookiejar.CookieJar()
  11. opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
  12. ret = opener.open(parms)
  13. return ret
  14. '''
  15. 通用的登陆DZ论坛
  16. 参数说明parms:
  17. username:用户名(必填),
  18. password :密码(必填),
  19. domain:网站域名,注意格式必须是:http://www.xxx.xx/(必填),
  20. answer:问题答案,
  21. questionid:问题ID,
  22. referer:跳转地址
  23. 这里使用了可变关键字参数(相关信息可参考手册)
  24. '''
  25. def login_dz(**parms):
  26. #初始化
  27. parms_key = ['domain','answer','password','questionid','referer','username']
  28. arg = {}
  29. for key in parms_key:
  30. if key in parms:
  31. arg[key] = parms[key]
  32. else:
  33. arg[key] = ''
  34. #获取formhash
  35. pre_login = arg['domain']+'member.php?mod=logging&action=login&infloat=yes&handlekey=login&inajax=1&ajaxtarget=fwin_content_login'
  36. html = openurl(pre_login).read().decode('gbk')
  37. patt = re.compile(r'.*?name="formhash".*?value="(.*?)".*?')
  38. formhash = patt.search(html)
  39. if not formhash:
  40. raise Exception('GET formhash Fail!')
  41. formhash = formhash.group(1)
  42. #登陆
  43. postdata = {
  44. 'answer':arg['answer'],
  45. 'formhash':formhash,
  46. 'password':arg['password'],
  47. 'questionid':0 if arg['questionid']=='' else arg['questionid'],
  48. 'referer':arg['domain'] if arg['referer']=='' else arg['referer'],
  49. 'username':arg['username'],
  50. }
  51. postdata = urllib.parse.urlencode(postdata)
  52. postdata = postdata.encode('utf-8')
  53. req = urllib.request.Request(
  54. url= arg['domain']+'member.php?mod=logging&action=login&loginsubmit=yes&handlekey=login&loginhash=LCaB3&inajax=1',
  55. data=postdata
  56. )
  57. html = openurl(req).read().decode('gbk')
  58. # 假设返回假
  59. flag = False
  60. if 'succeedhandle_login' in html:
  61. flag = True
  62. return flag
  63. # 代码开始
  64. # 用户名 及 密码
  65. while True:
  66. user = input('input your username:')
  67. pwd = input('input your password:')
  68. if len(user) != 0 and len(pwd) != 0:
  69. break
  70. print('输入错误')
  71. # 测试网站
  72. dom='http://www.mengwuji.net/'
  73. try:
  74. flag = login_dz(username=user,password=pwd,domain=dom)
  75. if not flag:
  76. print('登陆失败!')
  77. exit(0)
  78. else:
  79. print('登陆成功')
  80. html = openurl('http://www.mengwuji.net/forum.php').read(10000).decode('gbk')
  81. patt = re.compile(r'积分:(.*?)<')
  82. jf = patt.search(html)
  83. jf = jf.group(1)
  84. print('当前用户积分数量为:%s' % jf)
  85. except Exception as e:
  86. print('Error:',e)

首先说明一下的是,这份代码是我在一份 py2 的基础上修改之后的代码,当然也是 py3.4.3 的代码。

其实大家对着这份代码 看一下上面的分析,大抵上就明白了。

从重要的开始说,什么叫做带 Cookie访问?
大家可以做一个小实验,在 登陆DZ 论坛之后,清理掉浏览器 Cookie 再刷新页面,你会发现你的登陆状态没了,这就足以说明,证明了你登陆成功的一些信息就存储在 Cookie 中, 这里也可以把 Cookie 称为登陆凭证。

所以我们访问的时候也应该使用这样一种方式来访问,否则我们的登陆就没有意义了,当然这里我并没有把 Cookie 记录到文件中,而是全盘托付给了 http.cookiejar.CookieJar()

关于这个的用法呢,帮助文档中也是有的。

在帮助文档的索引中 输入 Cookie,然后拉到页面的最下面有几个例子,大家可以发现我写的这个函数和例子中是一样的只是进行了简单的包装。

关于 可变参数这个问题 我不多说了。

搜索一下 ** 就能发现 实际上一个参数被包装成了 map。

下面的获取formhash,没什么可以说的,一个基本的正则匹配而已。

重点看登陆。

  1. postdata = {
  2. 'answer':arg['answer'],
  3. 'formhash':formhash,
  4. 'password':arg['password'],
  5. 'questionid':0 if arg['questionid']=='' else arg['questionid'],
  6. 'referer':arg['domain'] if arg['referer']=='' else arg['referer'],
  7. 'username':arg['username'],
  8. }
  9. postdata = urllib.parse.urlencode(postdata)
  10. postdata = postdata.encode('utf-8')
  11. urllib.request.Request('.....')

为何要这样写?

首先我们如何提交数据,我们第一想法是 看一下这个 urllib.request 不是么?

我们看 urllib.request.urlopen 里面也可以 提交 data 诶。

但是 我们的 openurl 已经在别处实现了,于是 我们仔细读读 urlopen,发现第一个参数解释如下:

Open the URL url, which can be either a string or a Request object.

也可以是一个 Request 对象,我们再读一下 urllib.request.Request 类 (实际使用时点击一下Request就到了)

  1. class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)

我们发现 Request 类也是支持 提交 data 的,于是我们就能想到 将 data 包装到 Request 对象,之后再通过调用我们统一的访问函数即可(openurl)。

这个过程在 Request 中也说的比较清楚。它告诉我们需要使用 urllib.parse.urlencode(),来将 一个 映射 或者 序列包装成data
同时也说明了需要一次编码工作才能使用。

显然这里映射更符合我们的需求,于是我们给定成一个映射,之后再包装成 data之后进行编码,再之后变成 Request 对象。

说完了。

最后在登陆成功之后,获取了一下首页的金币信息作为对整个程序的一个验证,需要注意的是,之后的使用中,每一处打开网页均需要这个 openurl 函数来代替,urllib.request.urlopen

结尾

说明到这里结束,感觉讲的还算是比较的清晰,之后如果有时间的话就说一下怎么发帖或者做些其他的事情。

越发依赖 帮助文档 了,确实这个帮助文档对 Python 的作用不亚于 MSDN 对 Windows 程序员的作用

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注