一、實驗簡介
1.1 基本介紹
本實驗中我們將通過分析登陸流程並使用 Python 實現模擬登陸到一個實驗提供的網站,在實驗過程中將學習並實踐 Python 的網絡編程,Python 實現模擬登陸的方法,使用 Firefox 抓包分析插件分析網絡數據包等知識。
模擬登錄可以幫助用戶自動化完成很多操作,在不同場合下有不同的用處,無論是自動化一些日常的繁瑣操作還是用於爬蟲都是一項很實用的技能。本課程通過 Firefox 和 Python 來實現,環境要求如下:
- Python 庫:urllib, urllib2, cookielib, Django
- Firefox 要求:裝有 live http header插件 (已提供)
1.2 知識點
本項目中我們將學習並實踐以下知識點:
- 網站登錄流程分析
- Python 網絡編程基礎
- Firefox 抓包分析插件 Live http header
- Python 模擬登陸實現流程
1.3 實驗材料
為了節省時間,實驗用到的材料已經提前制作完成,可以按照材料清單中給出的鏈接下載。
實驗網站源碼:
http://labfile.oss.aliyuncs.com/courses/640/mysite.zip FireFox抓包插件: http://labfile.oss.aliyuncs.com/courses/640/live_http_headers.xpi
1.4 實驗准備
1) 安裝抓包插件Live Http Headers
打開 Linux Xfce 終端並通過 wget 命令下載插件文件。下載成功后首選右鍵單擊xpi文件-->使用FireFox打開,之后按照界面提示安裝Live Http Header插件:


按照提示重啟之后,通過 打開菜單-->附加組件-->擴展 找到安裝好的插件,點擊 首選項,勾選 Config 選項卡中的Open LiveHTTPHeaders in a new tab 選項以方便使用。

2)啟動web應用
由於實驗樓會員環境中啟動的 WebIDE 會占用 8000 端口,所以如果是實驗樓會員,請先停止 codebox 進程后再部署下面的 Web 應用。
使用 ps -aux | grep codebox 查詢獲得 codebox 的進程號,然后使用 kill -9 進程號 停止 codebox 進程。執行過程見下圖:

首先安裝demo依賴的web框架django,並測試是否安裝成功:
$ sudo pip install django
$ python
>>> import django >>> django.VERSION (1, 10, 0, u'final', 1)
再通過unzip命令解壓網站文件,並啟動網站服務。
$ wget http://labfile.oss.aliyuncs.com/courses/640/mysite.zip $ unzip mysite.zip $ cd mysite $ ./manage.py runserver
啟動成功后在瀏覽器中輸入 http://localhost:8000/polls 看到登錄頁面表示啟動成功。

二、分析登錄過程
要通過編程實現登錄,首先需要理解一般Web應用的登陸過程。
不同的網站和應用登錄的安全性和復雜性都不同,因此它們的登錄實現過程自然會存在差異,盡管如此,最基礎,核心的過程依然是相同的。對於復雜的登錄過程(例如淘寶),對請求包和響應包的分析能力是很重要的。
瀏覽器有自帶的分析工具,但是界面比較窄,考慮到實驗環境界面較小,本課程選擇了 Live Http Header,同學們可以在本地選用任何自己喜歡的工具進行分析。我們先通過示例頁面分析一下簡單的登陸過程熟悉下分析過程。
2.1 抓取請求
- 輸入
http://localhost:8000/polls打開登錄頁。 - 打開live http header(
F10->工具->Live Http Header)。 - 輸入用戶名和密碼(都是
shiyanlou)並提交表單,登入系統。 - 切換到Live Http Header頁面查看http請求和響應信息
2.2原理分析
Live http Headers插件的Headers選項卡中會列出抓取到的所有的http請求和響應頭,一次請求的url、請求和響應之間通過空行隔開,不同的請求之間通過虛線隔開。在按照2.1中的步驟登入系統后,我們在列表中看到了2個請求。第一個請求核心內容如下(還記得HTTP協議的內容嗎?):
POST /polls/login HTTP/1.1 //請求行 ******************** //此處省略其他請求頭 Content-Type: application/x-www-form-urlencoded //請求實體類型 Content-Length: 41 //實體信息長度 name=shiyanlou&pwd=shiyanlou&commit=Login //實體內容 HTTP/1.0 302 Found //響應行,302重定向 Location: . //重定向的路徑 ******************** //此處省略其他響應頭 Set-Cookie: sessionid=aped4pzerxjgd3db0ixw0dowkgrdpsxb;expires=Thu, 15-Sep-2016 15:04:28 GMT; httponly; Max-Age=1209600; Path=/ //響應頭,服務器回寫cookie
為什么是2個請求而不是1個呢?通過分析登錄請求發現,登陸成功之后服務器發送了302重定向響應,服務器要求瀏覽器重新請求首頁,這就產生了第二個請求。再來分析第二個請求,可以看到它相比登錄請求多了一個請求頭:
Cookie: sessionid=aped4pzerxjgd3db0ixw0dowkgrdpsxb
這個 Cookie 中的 sessionid 是從上一個響應頭中的 Set-Cookie 行中獲取,所以值相同。session的意思是會話,服務器通過session存儲和維護與單個用戶之間的會話信息。不同的用戶在服務器端有不同的session,並且為了確保正常通信,每個用戶的session都是唯一的。登錄過程其實就是在驗證用戶登錄信息后在session中存儲用戶登錄標識的過程。在開發web應用時,“登陸成功”與“在session中存儲用戶登錄標識”是等價的。整個登錄流程如下圖所示:

那么問題來了,服務器到底是如何區別不同用戶的session的?為什么登陸成功會后要回寫cookie呢?答案就是sessionid!每個用戶的session都有獨立的、唯一的編號sessionid用來標識用戶身份。用戶登錄后,服務器通過從用戶的session中讀取登錄標識來進行身份認證,因此必須要知道sessionid來訪問用戶的session,而使用cookie正是滿足這個需求的方法。服務器將需要瀏覽器存儲的信息通過Set-Cookie響應頭發送給瀏覽器,瀏覽器會將cookie存儲在本地,並在每次訪問該網站時附帶發送指定的cookie以滿足服務器的需求,而通過cookie存儲sessionid就是其中的一種應用。
2.3小結
對於服務器來說,登錄=驗證+寫session。對於瀏覽器來說,登錄=發送登錄信息+獲取帶sessionid的cookie。可以說,只要獲得了sessionid,就算實現了模擬登錄。有了它我們便可以游離於系統之中。
三、使用Python實現登錄(簡單實例)
理解了登錄過程的原理和細節之后,開始用Python來編寫模擬登陸程序吧。在任意目錄下新建login_base.py文件,使用你喜歡的編輯器打開。實現流程如下:
3.1導入模塊
不要忘記編寫文件頭、導入必要的依賴模塊哦
#!/usr/bin/python #-*- coding:utf-8 -*- import urllib import urllib2 import cookielib
3.2構造登錄請求
一個http請求由3部分組成:請求url、請求頭以及請求實體(附帶數據)。在Python的urllib2庫中,由urllib2.Request對象描述一個request。其中url是一個字符串、請求頭是一個dict,key是請求頭名稱,value是請求頭的內容。至於請求數據就要分具體情況了,在表單提交這種場景下,請求數據類型為application/x-www-form-urlencoded,意思就是經過url編碼的表單數據,數據的組織形式是key=value,多組鍵值對之間用&分隔。登錄請求的實體部分如下:
Content-Type: application/x-www-form-urlencoded //請求實體類型 Content-Length: 41 //實體信息長度 name=shiyanlou&pwd=shiyanlou&commit=Login //實體內容
此時,我們只需要使用dict來存儲鍵值對,再用urllib.urlencode()方法進行編碼就可以了。最后創建urllib2.Request對象,全部代碼如下:
url = 'http://localhost:8000/polls/login' values = { 'name':'shiyanlou', 'pwd':'shiyanlou', 'commit':'Login' } headers = {'Referer':'http://localhost:8000/polls/show_login'} request = urllib2.Request(url,data=urllib.urlencode(values),headers=headers)
附加參數
需要注意的是,在瀏覽器頁面操作時,用戶只需要輸入用戶名和密碼,看起來好像只需要提交2個參數到服務器。但實際上前端頁面一般都會提交其他元參數到服務器,大多數都是與服務端的訪問api設計有關,還有一些控制參數。一個登錄請求包含5個以上的參數是再正常不過的事情,但並不是每一個參數都是登錄所必要的,有些參數即使沒有也不會影響正常登錄。在實驗demo中,commit這個參數就是必須提交的,否則會登錄失敗。
防盜鏈
Web 應用的資源都是有url的,只要獲得了url就能夠在任何地方引用。聽起來很方便,但這可能會導致你的資源被別人盜用。為了訪問量,把自己辛辛苦苦PS的一張美照放到個人站點上,卻被別人的站點給引用走了,豈不是很氣人?而HTTP的請求頭Referer就是為了解決這類問題而生,Referer描述了當前請求的發出者,也就是引用者,服務器可以通過Referer來判斷當前的引用者是否是合法的引用者從而決定是否返回請求的資源。考慮到一定的安全性,一般的網站登錄都會限制Referer域來防止非法登錄。因此,為了成功登錄,我們需要在request頭中填寫Referer頭,內容從抓取的請求頭中照搬來即可。
3.3發送請求並保存cookie
一般情況下,打開url默認使用urllib2的urlopen()函數,但是它不能處理cookie。這里我們需要自己創建能夠存儲cookie的opener。python內置有cookielib庫來處理cookie,其中的MozillaCookieJar可以將cookie存儲到文件中,並可以從文件中讀取cookie。先創建MozillaCookieJar對象,再使用urllib2.HTTPCookieProcessor創建cookie處理器,最后使用urllib2.build_opener創建opener。接下來就可以用opener發送請求並存儲cookie了。代碼如下:
# 創建opener cookies = cookielib.MozillaCookieJar('my_cookies.txt') # 指定cookie的存儲文件 cookie_handler = urllib2.HTTPCookieProcessor(cookies) opener = urllib2.build_opener(cookie_handler) # 發送請求 & 保存cookie response = opener.open(request) cookies.save() print response.code print response.read()
如果登陸成功,就可以在指定的文件my_cookies.txt中看到sessionid了。
localhost.local FALSE / FALSE 1474019663 sessionid vejomjbliggkwblzfagobv3u8foik6am
3.4 使用cookie訪問系統服務
登錄系統只不過是一個開始,使用sessionid訪問系統服務才是最終目的。舉一個簡單的例子,我們的網站demo的首頁中由一個查詢系統當前時間的接口(在現實生活中可能是更有用的,比如信息查詢),我們在登錄后點擊它,可以看到它的url是http://localhost:8000/polls/date,這是只為登錄用戶提供的服務。如果我們現在返回首頁,退出登錄之后直接在地址欄中輸入這個url,頁面將會302重定向至登錄頁,無法看到當前時間。


在我們模擬登錄成功后,就可以直接通過opener打開這個url來使用這項系統服務。代碼實現如下:
url2 = 'http://localhost:8000/polls/date' response2 = opener.open(url2) print response2.code print response2.read()
如果有是在另外一個py文件中使用這個cookie的話,再打開url之前需要先載入cookie:
# 載入cookie cookie = cookielib.MozillaCookieJar() cookie.load('my_cookies.txt') opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))
當然,以上只是一個簡單的示例,只是為了說明原理。實際應用中的請求中會有各種各樣的請求參數、元數據參數甚至是多樣的驗證cookie,只有理解了原理才能夠以不變應萬變。
四、高級登陸練習
是不是原理聽懂了,但是只實現demo感覺不夠過癮?我們來一起做一做下面這個稍微難一點的練習吧。
4.1題目
已知信息:
- 登錄頁面url : http://localhost:8000/admin/login/?next=/admin/
- 登錄提交url : http://localhost:8000/admin/login/?next=/admin/
- 用戶名 :admin
- 密碼 : djangoadmin
要求:
- 模擬登陸成功並存儲cookie
4.2 過程分析提示
管理員的登錄請求的請求如下:
POST /admin/login/?next=/admin/ HTTP/1.1 Referer: http://localhost:8000/admin/login/?next=/admin/ Cookie: csrftoken=adxAiQKnRYgQoS4RmoooCesA7mBgJtwVnsd98nttE1arcBAnGwRbILLvWeS5xLfM Content-Type: application/x-www-form-urlencoded Content-Length: 137 csrfmiddlewaretoken=tsxs3KcOUt4ZdMg1gMBCKkNV8JTxZigaGHd1ThVUHwYA1vMxAU4pQR6QXBamNAZ1&username=admin&password=djangoadmin&next=%2Fadmin%2F HTTP/1.0 302 Found Vary: Cookie Last-Modified: Fri, 02 Sep 2016 09:24:46 GMT Location: /admin/ Content-Type: text/html; charset=utf-8 Set-Cookie: csrftoken=seowCkz5vprAPZbkqF7M4QuzYZPKgoUrsO5nE3WWxtYnc5NRKB34rmmrTAIDdCaK; expires=Fri, 01-Sep-2017 09:24:46 GMT; Max-Age=31449600; Path=/ Set-Cookie: sessionid=cb6z7xlu31uxt8kdupfh0td43ewhgvpx; expires=Fri, 16-Sep-2016 09:24:46 GMT; httponly; Max-Age=1209600; Path=/
不難觀察到登錄請求中附帶了csrftoken這個cookie,表單數據中必須提交csrfmiddlewaretoken,username,password這三個參數,最后的next參數是url中附帶的,不需要額外添加。必須得到前兩個參數才能夠成功登陸,這兩個參數從名字上來看都用來作認證令牌,應該是由服務器生成的。其中的cookie肯定是來自上一個response的回寫,而從csrfmiddlewaretoken的位置來看,應該是在登陸表單當中。注銷登陸,重新進入登錄頁面,點擊右鍵查看源代碼發現了這個輸入域(每次請求的值都是不同的):
<input type='hidden' name='csrfmiddlewaretoken' value='PSM5KbmRGdHraaVXK9UEzsWmLYlHxYjUPstWMUJIIheexgxu45QWWYOeGzeAuczd' />
這樣一來我們的思路就很清晰了:

解析頁面可以使用python的正則表達式re模塊或者是比較火的beautifulsoup庫。希望同學們先自己動手挑戰一下,歡迎再課程回復中和我討論,屆時我將提供登錄代碼。
五、總結&擴展
模擬登陸的關鍵是弄清楚登錄請求需要提交的參數,重點是要獲取帶有sessionid的cookie,最終目的是不受限制的訪問系統提供的有價值的服務。
只有分析清楚登陸過程才能達到最終目的,這就需要具備強的HTTP包分析能力,理解HTTP協議,並配合包分析工具(FireBug,Live Http Header,Burp Suite等)解析出關鍵參數,有時需要編輯和重發包來刪減掉不必要的多余參數。下面給出一些提高的參考資料,學有余力的同學可以深入研究:
