Python爬蟲模擬登錄遇到的問題——CSRF防御

背景

去年在公司寫過一個爬蟲工具,用于抓取自動化報(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),公司的自動化平臺登錄頁中也包含了這樣一個隱藏字段,試之,果然成功了......

登錄頁面中隱藏的token
#登錄源碼:
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)是:

  1. session機(jī)制運(yùn)行依賴于session id,用于服務(wù)端跟蹤每個會話,而session id存在于本地的cookie當(dāng)中;
  2. session id會隨瀏覽器進(jìn)程關(guān)閉的關(guān)閉而清除,也就表示一次完整的會話結(jié)束了。當(dāng)下次再次訪問該網(wǎng)站還需要登錄,重新建立一個會話;
  3. 現(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)備好的。
簡單舉個栗子??:

  1. 用戶小明登錄了論壇A,同時(shí)也打開了一個危險(xiǎn)網(wǎng)站B(同一個瀏覽器中);
  2. 網(wǎng)站B上有一個鏈接,該鏈接的實(shí)質(zhì)內(nèi)容是針對論壇A的一個發(fā)帖請求(比如廣告貼)。
  3. 小明處于好奇點(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)而來了。
簡單舉例:

  1. 當(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ā)帖成功。
  2. 當(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。??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容