背景
去年在公司寫過一個爬蟲工具,用于抓取自動化報(bào)告通過率、自動發(fā)送報(bào)告。由于當(dāng)時(shí)是第一次接觸爬蟲,難免會遇到各種問題,解決方案全都是按照網(wǎng)上的一些爬蟲文章示例,照貓畫虎寫的。雖然能正常使用,但其實(shí)很多地方都沒弄明白。最近學(xué)習(xí)了一些前端和后臺的原理,了解了cookie與session的機(jī)制,總算弄明白了爬蟲登錄過程中的一個疑問。
用戶登錄請求中的authenticity_token
編寫爬蟲第一步,在登錄公司的自動化平臺時(shí)就遇到了一個難題,登錄請求中必須包含一個authenticity_token字段。令人頭大的是,完全不知道這個字段從何而來,而且該字段還每次都不一樣,參考的爬蟲登錄示例也沒教??!真是急壞苯寶寶了??

后來翻了好多CSDN的爬蟲貼,了解到知乎的登錄請求中也包含這樣一個字段,而作者的處理方式就是先訪問一次登錄頁,然后從登錄頁中查找一個隱藏的authenticity_token字段。
借助F12發(fā)現(xiàn),公司的自動化平臺登錄頁中也包含了這樣一個隱藏字段,試之,果然成功了......

#登錄源碼:
def login(login_url = 'http://****.com/users/sign_in', username, password):
#請求頭
my_headers = {
'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36',
'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Encoding' : 'gzip',
'Accept-Language' : 'zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4'
}
#獲取token
sss = requests.Session()
r = sss.get(login_url, headers = my_headers)
reg = r'<input name="authenticity_token" type="hidden" value="(.*)" />'
pattern = re.compile(reg)
result = pattern.findall(r.content)
token = result[0]
#postdata
my_data = {
'commit' : '登錄',
'utf8' : '%E2%9C%93',
'authenticity_token' : token,
'user[email]': username,
'user[password]':password
}
#登錄后
r = sss.post(login_url, headers = my_headers, data = my_data)
return sss
"多年后的一個平靜的下午,當(dāng)我無意間瀏覽了一片CSRF攻擊的帖子,突然眼前一亮......老衲終于明白了這個authenticity_token的含義了!??!終于徹底理解了當(dāng)年困擾我兩小時(shí)的難題了?。?!"
其實(shí),該token的作用就是防御CSRF攻擊,關(guān)于什么是CSRF,還得先了解下Session id。
關(guān)于Session id的機(jī)制
HTTP請求的一大特點(diǎn)就是無狀態(tài),這也就導(dǎo)致服務(wù)端無法區(qū)分請求來自哪個客戶端。為了記錄每個用戶的狀態(tài),跟蹤用戶的整個會話,web程序普遍采用了cookie與session技術(shù)。(由于cookie與session的內(nèi)容過多,在此不表,詳細(xì)原理可以參考一片文章:Cookie與Session機(jī)制)
關(guān)于cookie與session,最需要了解的幾點(diǎn)是:
- session機(jī)制運(yùn)行依賴于session id,用于服務(wù)端跟蹤每個會話,而session id存在于本地的cookie當(dāng)中;
- session id會隨瀏覽器進(jìn)程關(guān)閉的關(guān)閉而清除,也就表示一次完整的會話結(jié)束了。當(dāng)下次再次訪問該網(wǎng)站還需要登錄,重新建立一個會話;
- 現(xiàn)在絕大多數(shù)瀏覽器都支持子窗體,子窗體能共享父窗體的session id,而另起的瀏覽器進(jìn)程無法訪問該session。這也是為什么當(dāng)我們在某網(wǎng)站登錄后,在新的頁簽下打開該網(wǎng)站依然是登錄狀態(tài),而另起一個瀏覽器進(jìn)程訪問卻是非登錄狀態(tài)。
根據(jù)session機(jī)制以上特點(diǎn),就引申出了一個問題:CSRF攻擊。
什么是跨站請求偽造(CSRF)攻擊?
用戶每次點(diǎn)擊一個鏈接、提交一個表單,其本質(zhì)就是對服務(wù)端發(fā)起一次請求。而CSRF攻擊的原理就是:攻擊者誘導(dǎo)用戶點(diǎn)擊一個鏈接,用戶在不知情的情況下提交了一次表單請求。而表單的內(nèi)容則是攻擊者事先準(zhǔn)備好的。
簡單舉個栗子??:
- 用戶小明登錄了論壇A,同時(shí)也打開了一個危險(xiǎn)網(wǎng)站B(同一個瀏覽器中);
- 網(wǎng)站B上有一個鏈接,該鏈接的實(shí)質(zhì)內(nèi)容是針對論壇A的一個發(fā)帖請求(比如廣告貼)。
- 小明處于好奇點(diǎn)擊了該鏈接,造成的結(jié)果就是:小明在完全不知情的情況下在論壇A成功發(fā)表了一篇帖子。
備注: 以上攻擊成功實(shí)施的關(guān)鍵在于,小明已經(jīng)登錄論壇A,并且點(diǎn)擊跳轉(zhuǎn)后的瀏覽器子窗體是可以訪問父窗體的session id的。
假如小明復(fù)制該鏈接,然后手動打開一個新的瀏覽器粘貼訪問該鏈接,則會提示用戶處于非登錄狀態(tài),該發(fā)帖請求會被拒絕。原因是新打開的瀏覽器無法獲取前一個瀏覽器中的session id,服務(wù)端會將該請求當(dāng)成一個新的會話,需要重新登錄后才能成功執(zhí)行發(fā)帖請求。
CRSF攻擊防御
既然大家都了解CRSF攻擊,自然有相應(yīng)的防御措施,其中比較常用的就是采用token驗(yàn)證。
工作機(jī)制就是:用戶在發(fā)送表單時(shí)還需要攜帶一個token值。該token一般是填寫表單頁中的一個隱藏字段,每次訪問都不同。通過該token的驗(yàn)證,服務(wù)端就能知道用戶的表單請求是否從表單填寫頁面跳轉(zhuǎn)而來了。
簡單舉例:
- 當(dāng)小明主動發(fā)帖時(shí),必定要先點(diǎn)擊發(fā)帖編輯頁面A,當(dāng)填寫完帖子內(nèi)容后再點(diǎn)擊【發(fā)帖】按鈕。此時(shí)會將小明填寫的表單內(nèi)容連帶頁面A中隱藏的一個token發(fā)送給服務(wù)端。服務(wù)端驗(yàn)證token通過后才表示發(fā)帖成功。
- 當(dāng)危險(xiǎn)網(wǎng)站誘導(dǎo)小明點(diǎn)擊危險(xiǎn)鏈接時(shí),由于該鏈接實(shí)質(zhì)就是一個發(fā)帖的post請求,跳過了訪問發(fā)帖編輯頁面A的過程,自然也就無法獲取有效token,最終服務(wù)端會認(rèn)為該發(fā)帖請求不合法。
簡單來說,服務(wù)端每次通過請求數(shù)據(jù)中的token來驗(yàn)證表單請求是否由用戶主動發(fā)送的,從而有效防御了CRSF攻擊。
至此,也就明白了為什么登錄頁面時(shí)需要攜帶一個authenticity_token參數(shù)了,同時(shí)也理解了為什么需要訪問登錄頁面獲取該token。??