整合營銷服務商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          HTML注入:利用HTML標簽繞過CSP

          讓我們看看如下這個web應用示例:

          <html>
           <meta http?equiv="Content?Security?Policy"
           content="script?src 'nonce?...' 'unsafe?eval'">
           <div id="template_target"></div>
           <script type="application/template" id="template">
           Hello World! 1 + 1 = {{ 1 + 1 }}
           </script>
           Your search is <?php echo $_GET['q']; ?>
           <script nonce="...">
           let template = document.getElementById('template');
           template_target.innerHTML = template.innerText.replace(/{{(.?)}}/g,eval)
           </script>
          </html>
          

          以上這段簡單的HTML代碼可能反映了現(xiàn)在滲透測試人員經(jīng)常碰到的模板化Web應用。某些模板內容存儲在Web頁面中,然后再轉換為HTML代碼的一部分。上段代碼中含有id為template的HTML元素內容先被讀取,然后再執(zhí)行{{}}括號內的內容,最后在某個單獨HTML元素中呈現(xiàn)出來。

          Hello World! 1 + 1 = 2
          Your search is ........... 
          

          其次,這段代碼應用程序會在頁面上打印URL中的參數(shù)值。這顯而易見是一個XSS漏洞,但由于CSP(內容安全策略)的存在,攻擊者并不能直接執(zhí)行javascript。雖然直接運行javascript的路被堵死,但是我們可以找到其他繞過方法。

          乍一看,貌似eval函數(shù)是一個可以利用的點,我們或許可以直接插入某些特制代碼,讓eval函數(shù)去執(zhí)行。

          為了實現(xiàn)這點,我們必須插入HTML元素中id為template的代碼。但在我們插入語句的前面已有id為template的HTML元素,而document.getElementById('template')只會去獲取第一個HTML元素,并不是我們所輸入的語句。

          此刻,我們需要換個角度,看看瀏覽器是否能出現(xiàn)“意外”,以前就出現(xiàn)過很多瀏覽器的異常解析所導致的XSS攻擊。我把所有能使用的tag都收集起來進行測試,看看是否有驚喜。測試代碼如下:

          <div id="template">First Tag</div>
           {% for tag in tag_list %}
           <{{tag}} id="template">{{tag}}</{{tag}}>
           {% endfor %}
          <script>console.log(document.getElementById('template'));</script>
          

          當程序運行完畢時,我得到一個奇怪的結果:當輪到<html>時,頁面結構似乎發(fā)生了大變,此時已不再是<div>排在前面。讓我們看下當插入<html id="template">時的變化:

          此時<html>已排在文檔頂部(在我所測試的所有瀏覽器中都是如此?。F(xiàn)在getElementById('template')將獲取<html>中的惡意數(shù)據(jù),而不是<div>的內容。

          只需簡單的?q=<html id="template">{{ alert("xss") }}</html>就可進行攻擊

          最終,由于瀏覽器這個“莫名其妙”的特性,我們繞過了CSP成功進行了XSS攻擊!

          本文由白帽匯整理并翻譯,不代表白帽匯任何觀點和立場

          來源:https://nosec.org/home/detail/2860.html

          原文:https://pagedout.institute/download/PagedOut_001_beta1.pdf(該PDF文檔的第62頁

          白帽匯從事信息安全,專注于安全大數(shù)據(jù)、企業(yè)威脅情報。

          公司產(chǎn)品:FOFA-網(wǎng)絡空間安全搜索引擎、FOEYE-網(wǎng)絡空間檢索系統(tǒng)、NOSEC-安全訊息平臺。

          為您提供:網(wǎng)絡空間測繪、企業(yè)資產(chǎn)收集、企業(yè)威脅情報、應急響應服務。

          了應對 Spectre 漏洞,Google 推出了名為“Strict Site Isolation”(嚴苛網(wǎng)站隔離)的安全功能,主要是防止未經(jīng)授權的數(shù)據(jù)被盜。不過近日一支由多所國際大學組成的團隊發(fā)現(xiàn)了 Spook.js,這種惡意的 JavaScript 代碼可以繞過 Google 的這項安全功能從其他標簽中獲取密碼等敏感數(shù)據(jù)。

          目前,安全專家已經(jīng)證實 Intel 處理器和蘋果M1 芯片受到影響,但AMD芯片也被認為存在風險,但是目前并沒有得到充分證明。

          該團隊由喬治亞理工學院、阿德萊德大學、密歇根大學和特拉維夫大學的研究人員組成。他們說,“盡管 Google 試圖通過部署嚴格的網(wǎng)站隔離來緩解 Spectre,但在某些情況下,通過惡意的 JavaScript 代碼提取信息仍然是可能的”。

          研究人員繼續(xù)說:“更具體地說,我們表明,攻擊者控制的網(wǎng)頁可以知道用戶當前正在瀏覽同一網(wǎng)站的哪些其他頁面,從這些頁面中獲取敏感信息,甚至在自動填充時恢復登錄憑證(例如,用戶名和密碼)。我們進一步證明,如果用戶安裝了一個惡意擴展,攻擊者可以從 Chrome 擴展(如憑證管理器)中檢索數(shù)據(jù)”。

          安全研究人員分享了幾段視頻,展示了 Spook.js 的行動。在第一段視頻中,該攻擊被用來從 Chrome 瀏覽器的內置憑證管理器中獲取 Tumblr 博客的密碼。

          在第二個視頻中,一個惡意的瀏覽器擴展被用來從 LastPass 盜取主密碼。

          要學習Python。關注小編頭條號,私信【學習資料】,即可免費領取一整套系統(tǒng)的板Python學習教程!

          建立工程項目

          我們會在 virtualenv 中建立我們的項目,這可以讓我們封裝一下依賴關系。首先我們在~/scrapers/zipru中創(chuàng)建一個virtualenv ,并且安裝scrapy包。

          mkdir ~/scrapers/zipru
          cd ~/scrapers/zipru
          virtualenv env
          . env/bin/activate
          pip install scrapy
          

          你運行的終端將被配置為使用本地的virtualenv。如果你打開另一個終端,那么你就需要再次運行. ~/scrapers/zipru/env/bin/active 命令 (否則你有可能得到命令或者模塊無法找到的錯誤消息)。

          現(xiàn)在你可以通過運行下面的命令來創(chuàng)建一個新的項目框架:

          scrapy startproject zipru_scraper
          

          這樣就會創(chuàng)建下面的目錄結構。

          └── zipru_scraper
           ├── zipru_scraper
           │ ├── __init__.py
           │ ├── items.py
           │ ├── middlewares.py
           │ ├── pipelines.py
           │ ├── settings.py
           │ └── spiders
           │ └── __init__.py
           └── scrapy.cfg
          

          大多數(shù)默認情況下產(chǎn)生的這些文件實際上不會被用到,它們只是建議以一種合理的方式來構建我們的代碼。從現(xiàn)在開始,你應該把 ~/scrapers/zipru/zipru_scraper 當做這個項目的根目錄。這里是任何scrapy命令運行的目錄,同時也是所有相對路徑的根。

          添加一個基本的爬蟲功能

          現(xiàn)在我們需要添加一個Spieder類來讓我們的scrapy真正地做一些事情。Spider類是scrapy爬蟲用來解析文本,爬取新的url鏈接或是提取數(shù)據(jù)的一個類。我們非常依賴于默認Spider類的實現(xiàn),以最大限度地減少我們必須要編寫的代碼量。這里要做的事情看起來有點自動化,但假如你看過文檔,事情會變得更加簡單。

          首先,在zipru_scraper/spiders/目錄下創(chuàng)建一個文件,命名為 zipru_spider.py ,輸入下面內容。

          import scrapy
          class ZipruSpider(scrapy.Spider):
           name = 'zipru'
           start_urls = ['http://zipru.to/torrents.php?category=TV']
          

          你可以在上面的網(wǎng)頁中看到許多指向其他頁面的連接。我們想讓我們的爬蟲跟蹤這些鏈接,并且解析他們的內容。為了完成這個任務,我們首先需要識別出這些鏈接并且弄清楚他們指向的位置。

          在這個階段,DOM檢查器將起到很大的助力。如果你右擊其中的一個頁面鏈接,在DOM檢查器里面查看它,然后你就會看到指向其他頁面的鏈接看起來像是這樣的:

          <a href="/torrents.php?...page=2" title="page 2">2</a>
          <a href="/torrents.php?...page=3" title="page 3">3</a>
          <a href="/torrents.php?...page=4" title="page 4">4</a>
          

          接下來我們需要為這些鏈接構造一個選擇器表達式。有幾種類型似乎用css或者xpath選擇器進行搜索更適合,所以我通常傾向于靈活地混合使用這幾種選擇器。我強烈推薦學習xpath,但是不幸的是,它有點超出了本教程的范圍。我個人認為xpath對于網(wǎng)絡爬蟲,web UI 測試,甚至一般的web開發(fā)來說都是不可或缺的。我接下來仍然會使用css選擇器,因為它對于大多數(shù)人來說可能比較熟悉。

          要選擇這些頁面鏈接,我們可以把 a[title ~= page] 作為一個 css 選擇器,來查找標題中有 “page” 字符的 <a>標簽。如果你在 DOM 檢查器中按 ctrl-f,那么你就會發(fā)現(xiàn)你也可以使用這個css表達式作為一條查找語句(也可以使用xpath)。這樣我們就可以循環(huán)查看所有的匹配項了。這是一個很棒的方法,可以用來檢查一個表達式是否有效,并且表達式足夠明確不會在不小心中匹配到其他的標簽。我們的頁面鏈接選擇器同時滿足了這兩個條件。

          為了講解我們的爬蟲是怎樣發(fā)現(xiàn)其他頁面的,我們在 ZipruSpider 類中添加一個 parse(response) 方法,就像下面這樣:

          def parse(self, response):
           # proceed to other pages of the listings
           for page_url in response.css('a[title ~= page]::attr(href)').extract():
           page_url = response.urljoin(page_url)
           yield scrapy.Request(url=page_url, callback=self.parse)
          

          當我們開始爬取的時候,我們添加到 start_urls 中的鏈接將會被自動獲取到,響應內容會被傳遞到 parse(response) 方法中。之后我們的代碼就會找到所有指向其他頁面的鏈接,并且產(chǎn)生新的請求對象,這些請求對象將使用同一個 parse(response) 作為回調函數(shù)。這些請求將被轉化成響應對象,只要 url 仍然產(chǎn)生,響應就會持續(xù)地返回到 parse(response) 函數(shù)(感謝去重器)。

          我們的爬蟲已經(jīng)可以找到了頁面中列出的所有不同的頁面,并且對它們發(fā)出了請求,但我們仍然需要提取一些對爬蟲來說有用的數(shù)據(jù)。torrent 列表位于 <table> 標簽之內,并且有屬性 class="list2at" ,每個單獨的 torrent 都位于帶有屬性 class="lista2" 的 <tr> 標簽,其中的每一行都包含 8 個 <td>標簽,分別與 “類別”,“文件”,“添加時間”,“文件大小”,“保種的人”,“下載文件的人”,“文件描述”,和“上傳者”相對應。在代碼中查看其它的細節(jié)可能是最簡單的方法,下面是我們修改后的 parse(response) 方法:

          def parse(self, response):
           # proceed to other pages of the listings
           for page_url in response.xpath('//a[contains(@title, "page ")]/@href').extract():
           page_url = response.urljoin(page_url)
           yield scrapy.Request(url=page_url, callback=self.parse)
           
           # extract the torrent items
           for tr in response.css('table.lista2t tr.lista2'):
           tds = tr.css('td')
           link = tds[1].css('a')[0]
           yield {
           'title' : link.css('::attr(title)').extract_first(),
           'url' : response.urljoin(link.css('::attr(href)').extract_first()),
           'date' : tds[2].css('::text').extract_first(),
           'size' : tds[3].css('::text').extract_first(),
           'seeders': int(tds[4].css('::text').extract_first()),
           'leechers': int(tds[5].css('::text').extract_first()),
           'uploader': tds[7].css('::text').extract_first(),
           }
          

          我們的 parse(response) 方法現(xiàn)在能夠返回字典類型的數(shù)據(jù),并且根據(jù)它們的類型自動區(qū)分請求。每個字典都會被解釋為一項,并且作為爬蟲數(shù)據(jù)輸出的一部分。

          如果我們只是爬取大多數(shù)常見的網(wǎng)站,那我們已經(jīng)完成了。我們只需要使用下面的命令來運行:

          scrapy crawl zipru -o torrents.jl
          

          幾分鐘之后我們本應該得到一個 [JSON Lines] 格式 torrents.jl 文件,里面有我們所有的torrent 數(shù)據(jù)。取而代之的是我們得到下面的錯誤信息(和一大堆其他的東西):

          [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
          [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
          [scrapy.core.engine] DEBUG: Crawled (403) <GET http://zipru.to/robots.txt> (referer: None) ['partial']
          [scrapy.core.engine] DEBUG: Crawled (403) <GET http://zipru.to/torrents.php?category=TV> (referer: None) ['partial']
          [scrapy.spidermiddlewares.httperror] INFO: Ignoring response <403 http://zipru.to/torrents.php?category=TV>: HTTP status code is not handled or not allowed
          [scrapy.core.engine] INFO: Closing spider (finished)
          

          我好氣??!我們現(xiàn)在必須變得更聰明來獲得我們完全可以從公共API得到的數(shù)據(jù),因為上面的代碼永遠都無法爬取到那些數(shù)據(jù)。

          簡單的問題

          我們的第一個請求返回了一個 403 響應,所以這個url被爬蟲忽略掉了,然后一切都關閉了,因為我們只給爬蟲提供了一個 url 鏈接。同樣的請求在網(wǎng)頁瀏覽器里運行正常,即使是在沒有會話(session)歷史的隱匿模式也可以,所以這一定是由于兩者請求頭信息的差異造成的。我們可以使用 tcpdump 來比較這兩個請求的頭信息,但其實有個常見錯誤,所以我們應該首先檢查: user agent 。

          Scrapy 默認把 user-agent 設置為 “Scrapy/1.3.3 (+http://scrapy.org)“,一些服務器可能會屏蔽這樣的請求,甚至使用白名單只允許少量的user agent 通過。你可以在線查看 最常見的 user agent ,使用其中任何一個通常就足以繞過基本反爬蟲策略。選擇一個你最喜歡的 User-agent ,然后打開 zipru_scraper/settings.py ,替換 User agent

          # Crawl responsibly by identifying yourself (and your website) on the user-agent
          #USER_AGENT = 'zipru_scraper (+http://www.yourdomain.com)'
          

          使用下面內容替換 USER_AGENT :

          USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36'
          

          你可能注意到了,默認的scrapy設置中有一些令爬蟲蒙羞的事。關于這個問題的觀點眾說紛紜,但是我個人認為假如你想讓爬蟲表現(xiàn)的像是一個人在使用普通的網(wǎng)頁瀏覽器,那么你就應該把你的爬蟲設置地像普通的網(wǎng)絡瀏覽器那樣。所以讓我們一起添加下面的設置來降低一下爬蟲響應速度:

          CONCURRENT_REQUESTS = 1
          DOWNLOAD_DELAY = 5
          

          通過 AutoThrottle 擴展 ,上面的設置會創(chuàng)建一個稍微真實一點的瀏覽模式。我們的爬蟲在默認情況下會遵守 robots.txt,所以現(xiàn)在我們的行為非常檢點。

          現(xiàn)在使用 scrapy crawl zipru -o torrents.jl 命令再次運行爬蟲,應該會產(chǎn)生下面的輸出:

          [scrapy.core.engine] DEBUG: Crawled (200) <GET http://zipru.to/robots.txt> (referer: None)
          [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (302) to <GET http://zipru.to/threat_defense.php?defense=1&r=78213556> from <GET http://zipru.to/torrents.php?category=TV>
          [scrapy.core.engine] DEBUG: Crawled (200) <GET http://zipru.to/threat_defense.php?defense=1&r=78213556> (referer: None) ['partial']
          [scrapy.core.engine] INFO: Closing spider (finished)
          

          這是一個巨大的進步!我們獲得了兩個 200 狀態(tài)碼和一個 302狀態(tài)碼,下載中間件知道如何處理 302 狀態(tài)碼。不幸的是,這個 302 將我們的請求重定向到了一個看起來不太吉利的頁面threat_defense.php。不出所料,爬蟲沒有發(fā)現(xiàn)任何有用的東西,然后爬蟲就停止運行了。

          注: 假如網(wǎng)站檢測到你的爬蟲,那么網(wǎng)站就會把你的請求重定向到 threat_defense.php 頁面,使你的爬蟲失效,用來防止頻繁的爬蟲請求影響了網(wǎng)站正常用戶的使用。

          下載中間件

          在我們深入研究我們目前所面臨的更復雜的問題之前,先了解一下請求和響應在爬蟲中是怎樣被處理的,將會很有幫助。當我們創(chuàng)建了我們基本的爬蟲,我們生成了一個 scrapy.Request 對象,然后這些請求會以某種方法轉化為與服務器的響應相對應的 scrapy.Response 對象。這里的 “某種方法” 很大一部分是來自于下載中間件。

          下載中間件繼承自 scrapy.downloadermiddlewares.DownloaderMiddleware 類并且實現(xiàn)了process_request(request, spider) 和 process_response(request, response, spider) 方法。你大概可以從他們的名字中猜到他們是做什么的。實際上這里有一大堆的默認開啟的中間件。下面是標準的中間件配置(你當然可以禁用、添加或是重新設置這些選項):

          DOWNLOADER_MIDDLEWARES_BASE = {
           'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100,
           'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300,
           'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350,
           'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400,
           'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500,
           'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550,
           'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560,
           'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580,
           'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590,
           'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600,
           'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700,
           'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750,
           'scrapy.downloadermiddlewares.stats.DownloaderStats': 850,
           'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900,
          }
          

          當一個請求到達服務器時,他們會通過每個這些中間件的 process_request(request, spider) 方法。 這是按照數(shù)字順序發(fā)生的,RobotsTxtMiddleware 中間件首先產(chǎn)生請求,并且HttpCacheMiddleware 中間件最后產(chǎn)生請求。一旦接收到一個響應,它就會通過任何已啟用的中間件的 process_response(request,response,spider) 方法來返回響應。這次是以相反的順序發(fā)生的,所以數(shù)字越高越先發(fā)送到服務器,數(shù)字越低越先被爬蟲獲取到。

          一個特別簡單的中間件是 CookiesMiddleware。它簡單地檢查響應中請求頭的 Set-Cookie,并且保存 cookie 。然后當響應返回的時候,他們會適當?shù)卦O置 Cookie 請求頭標記,這樣這些標記就會被包含在發(fā)出的請求中了。這個由于時間太久的原因要比我們說的要稍微復雜些,但你會明白的。

          另一個相對基本的就是 RedirectMiddleware 中間件,它是用來處理 3XX 重定向的。它讓一切不是 3XX 狀態(tài)碼的響應都能夠成功的通過,但假如響應中還有重定向發(fā)生會怎樣? 唯一能夠弄清楚服務器如何響應重定向URL的方法就是創(chuàng)建一個新的請求,而且這個中間件就是這么做的。當 process_response(request, response, spider) 方法返回一個請求對象而不是響應對象的時候,那么當前響應就會被丟棄,一切都會從新的請求開始。這就是 RedirectMiddleware 中間件怎樣處理重定向的,這個功能我們稍后會用到。

          如果你對于有那么多的中間件默認是開啟的感到驚訝的話,那么你可能有興趣看看 體系架構概覽。實際上同時還有很多其他的事情在進行,但是,再說一次,scrapy的最大優(yōu)點之一就是你不需要知道它的大部分原理。你甚至不需要知道下載中間件的存在,卻能寫一個實用的爬蟲,你不必知道其他部分就可以寫一個實用的下載中間件。

          困難的問題

          回到我們的爬蟲上來,我們發(fā)現(xiàn)我們被重定向到某個 threat_defense.php?defense=1&...URL上,而不是我們要找的頁面。當我們在瀏覽器里面訪問這個頁面的時候,我們看到下面的東西停留了幾秒:

          在被重定向到 threat_defense.php?defense=2&... 頁面之前,會出現(xiàn)像下面的提示:

          看看第一個頁面的源代碼就會發(fā)現(xiàn),有一些 javascript 代碼負責構造一個特殊的重定向URL,并且構造瀏覽器的cookies。如果我們想要完成這個任務,那我們就必須同時解決上面這兩個問題。

          接下來,當然我們也需要解決驗證碼并提交答案。如果我們碰巧弄錯了,那么我們有時會被重定向到另一個驗證碼頁面,或者我們會在類似于下面的頁面上結束訪問:

          在上面的頁面中,我們需要點擊 “Click here” 鏈接來開始整個重定向的循環(huán),小菜一碟,對吧?

          我們所有的問題都源于最開始的 302 重定向,因此處理它們的方法自然而然應該是做一個自定義的 重定向中間件。我們想讓我們的中間件在所有情況下都像是正常重定向中間件一樣,除非有一個 302 狀態(tài)碼并且請求被重定向到 threat_defense.php 頁面。當它遇到特殊的 302 狀態(tài)碼時,我們希望它能夠繞過所有的防御機制,把訪問cookie添加到 session 會話中,最后重新請求原來的頁面。如果我們能夠做到這一點,那么我們的Spider類就不必知道這些事情,因為請求會全部成功。

          打開 zipru_scraper/middlewares.py 文件,并且把內容替換成下面的代碼:

          import os, tempfile, time, sys, logging
          logger = logging.getLogger(__name__)
           
          import dryscrape
          import pytesseract
          from PIL import Image
           
          from scrapy.downloadermiddlewares.redirect import RedirectMiddleware
           
          class ThreatDefenceRedirectMiddleware(RedirectMiddleware):
           def _redirect(self, redirected, request, spider, reason):
           # act normally if this isn't a threat defense redirect
           if not self.is_threat_defense_url(redirected.url):
           return super()._redirect(redirected, request, spider, reason)
           
           logger.debug(f'Zipru threat defense triggered for {request.url}')
           request.cookies = self.bypass_threat_defense(redirected.url)
           request.dont_filter = True # prevents the original link being marked a dupe
           return request
           
           def is_threat_defense_url(self, url):
           return '://zipru.to/threat_defense.php' in url
          

          你可能注意到我們繼承了 RedirectMiddleware 類,而不是直接繼承 DownloaderMiddleware 類。這樣就允許我們重用大部分的重定向處理函數(shù),并且把我們的代碼插入到 _redirect(redirected, request, spider, reason) 函數(shù)中,一旦有重定向的請求被創(chuàng)建,process_response(request, response, spider) 函數(shù)就會調用這個函數(shù)。我們只是把對于普通的重定向的處理推遲到父類進行處理,但是對于特殊的威脅防御重定向的處理是不一樣的。我們到目前為止還沒有實現(xiàn) bypass_threat_defense(url) 方法,但是我們可以知道它應該返回訪問cookies,并把它附加到原來的請求中,然后原來的請求將被重新處理。

          為了開啟我們新的中間件,我們需要把下面的內容添加到 zipru_scraper/settings.py中:

          DOWNLOADER_MIDDLEWARES = {
           'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': None,
           'zipru_scraper.middlewares.ThreatDefenceRedirectMiddleware': 600,
          }
          

          這會禁用默認的重定向中間件,并且把我們的中間件添加在中間件堆棧中和默認重定向中間件相同的位置。我們必須安裝一些額外的包,雖然我們現(xiàn)在沒有用到,但是稍后我們會導入它們:

          pip install dryscrape # headless webkit
          pip install Pillow # image processing
          pip install pytesseract # OCR
          

          請注意,這三個包都有 pip 無法處理的外部依賴,如果你運行出錯,那么你可能需要訪問 dryscrape, Pillow, 和 pytesseract 的安裝教程,遵循平臺的具體說明來解決。

          我們的中間件現(xiàn)在應該能夠替代原來的標準重定向中間件,現(xiàn)在我們只需要實現(xiàn) bypass_thread_defense(url) 方法。我們可以解析 javascript 代碼來得到我們需要的變量,然后用 python 重建邏輯,但這看起來很不牢靠,而且需要大量的工作。讓我們采用更簡單的方法,盡管可能還是比較笨重,使用無頭的 webkit 實例。有幾個不同選擇,但我個人比較喜歡 dryscrape (我們已經(jīng)在上面安裝了)

          首先,讓我們在中間件構造函數(shù)中初始化一個 dryscrape 會話。

          def __init__(self, settings):
           super().__init__(settings)
           
           # start xvfb to support headless scraping
           if 'linux' in sys.platform:
           dryscrape.start_xvfb()
           
           self.dryscrape_session = dryscrape.Session(base_url='http://zipru.to')
          

          你可以把這個會話對象當做是一個單獨的瀏覽器標簽頁,它可以完成一切瀏覽器通??梢宰龅氖虑?例如:獲取外部資源,執(zhí)行腳本)。我們可以在新的標簽頁中打開新的 URL 鏈接,點擊一些東西,或者在輸入框中輸入內容,或是做其他的各種事情。Scrapy 支持并發(fā)請求和多項處理,但是響應的處理是單線程的。這意味著我們可以使用這個單獨的 dryscrapy 會話,而不必擔心線程安全。

          現(xiàn)在讓我們實現(xiàn)繞過威脅防御機制的基本邏輯。

          def bypass_threat_defense(self, url=None):
           # only navigate if any explicit url is provided
           if url:
           self.dryscrape_session.visit(url)
           
           # solve the captcha if there is one
           captcha_images = self.dryscrape_session.css('img[src *= captcha]')
           if len(captcha_images) > 0:
           return self.solve_captcha(captcha_images[0])
           
           # click on any explicit retry links
           retry_links = self.dryscrape_session.css('a[href *= threat_defense]')
           if len(retry_links) > 0:
           return self.bypass_threat_defense(retry_links[0].get_attr('href'))
           
           # otherwise, we're on a redirect page so wait for the redirect and try again
           self.wait_for_redirect()
           return self.bypass_threat_defense()
           
           def wait_for_redirect(self, url = None, wait = 0.1, timeout=10):
           url = url or self.dryscrape_session.url()
           for i in range(int(timeout//wait)):
           time.sleep(wait)
           if self.dryscrape_session.url() != url:
           return self.dryscrape_session.url()
           logger.error(f'Maybe {self.dryscrape_session.url()} isn\'t a redirect URL?')
           raise Exception('Timed out on the zipru redirect page.')
          

          這樣就處理了我們在瀏覽器中遇到的所有不同的情況,并且完全符合人類在每種情況中的行為。在任何給定情況下采取的措施都取決于當前頁面的情況,所以這種方法可以稍微優(yōu)雅一點地處理各種不同的情況。

          最后一個難題是如果如何解決驗證碼。網(wǎng)上提供了 驗證碼識別 服務,你可以在必要時使用它的API,但是這次的這些驗證碼非常簡單,我們只用 OCR 就可以解決它。使用 pytessertact 的 OCR 功能,最后我們可以添加 solve_captcha(img) 函數(shù),這樣就完善了 bypass_threat_defense() 函數(shù)。

          def solve_captcha(self, img, width=1280, height=800):
           # take a screenshot of the page
           self.dryscrape_session.set_viewport_size(width, height)
           filename = tempfile.mktemp('.png')
           self.dryscrape_session.render(filename, width, height)
           
           # inject javascript to find the bounds of the captcha
           js = 'document.querySelector("img[src *= captcha]").getBoundingClientRect()'
           rect = self.dryscrape_session.eval_script(js)
           box = (int(rect['left']), int(rect['top']), int(rect['right']), int(rect['bottom']))
           
           # solve the captcha in the screenshot
           image = Image.open(filename)
           os.unlink(filename)
           captcha_image = image.crop(box)
           captcha = pytesseract.image_to_string(captcha_image)
           logger.debug(f'Solved the Zipru captcha: "{captcha}"')
           
           # submit the captcha
           input = self.dryscrape_session.xpath('//input[@id = "solve_string"]')[0]
           input.set(captcha)
           button = self.dryscrape_session.xpath('//button[@id = "button_submit"]')[0]
           url = self.dryscrape_session.url()
           button.click()
           
           # try again if it we redirect to a threat defense URL
           if self.is_threat_defense_url(self.wait_for_redirect(url)):
           return self.bypass_threat_defense()
           
           # otherwise return the cookies as a dict
           cookies = {}
           for cookie_string in self.dryscrape_session.cookies():
           if 'domain=zipru.to' in cookie_string:
           key, value = cookie_string.split(';')[0].split('=')
           cookies[key] = value
           return cookies
          

          你可能注意到如果驗證碼因為某些原因識別失敗的話,它就會委托給 back to the bypass_threat_defense() 函數(shù)。這樣就給了我們多次識別驗證碼的機會,但重點是,我們會在得到正確結果之前一直在驗證碼識別過程中循環(huán)。

          這應該足夠讓我們的爬蟲工作,但是它有可能陷入死循環(huán)中。

          [scrapy.core.engine] DEBUG: Crawled (200) <GET http://zipru.to/robots.txt> (referer: None)
          [zipru_scraper.middlewares] DEBUG: Zipru threat defense triggered for http://zipru.to/torrents.php?category=TV
          [zipru_scraper.middlewares] DEBUG: Solved the Zipru captcha: "UJM39"
          [zipru_scraper.middlewares] DEBUG: Zipru threat defense triggered for http://zipru.to/torrents.php?category=TV
          [zipru_scraper.middlewares] DEBUG: Solved the Zipru captcha: "TQ9OG"
          [zipru_scraper.middlewares] DEBUG: Zipru threat defense triggered for http://zipru.to/torrents.php?category=TV
          [zipru_scraper.middlewares] DEBUG: Solved the Zipru captcha: "KH9A8"
          ...
          

          至少看起來我們的中間件已經(jīng)成功地解決了驗證碼,然后補發(fā)了請求。問題在于,新的請求再次觸發(fā)了威脅防御機制。我第一個想法是我可能在怎樣解析或是添加cookie上面有錯誤,但是我檢查了三次,代碼是正確的。這是另外一種情況 “唯一可能不同的事情就是請求頭” 。

          很明顯,scrapy 和 dryscrape 的請求頭都繞過了最初的觸發(fā) 403 響應的過濾器,因為我們現(xiàn)在不會得到任何 403 的響應。這肯定是因為它們的請求頭信息不一致導致的。我的猜測是其中一個加密的訪問cookies包含了整個請求頭信息的散列值,如果這個散列不匹配,就會觸發(fā)威脅防御機制。這樣的目的可能是防止有人把瀏覽器的cookie復制到爬蟲中去,但是它只是增加了你需要解決的問題而已。

          所以讓我們在 zipru_scraper/settings.py 中把請求頭信息修改成下面這個樣子。

          DEFAULT_REQUEST_HEADERS = {
           'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
           'User-Agent': USER_AGENT,
           'Connection': 'Keep-Alive',
           'Accept-Encoding': 'gzip, deflate',
           'Accept-Language': 'en-US,*',
          }
          

          注意我們已經(jīng)把 User-Agent 頭信息修改成了我們之前定義的 USER_AGENT 中去.這個工作是由 user agent 中間件自動添加進去的,但是把所有的這些配置放到一個地方可以使得 dryscrape 更容易復制請求頭信息。我們可以通過修改 ThreatDefenceRedirectMiddleware 初始化函數(shù)像下面這樣:

          def __init__(self, settings):
           super().__init__(settings)
           
           # start xvfb to support headless scraping
           if 'linux' in sys.platform:
           dryscrape.start_xvfb()
           
           self.dryscrape_session = dryscrape.Session(base_url='http://zipru.to')
           for key, value in settings['DEFAULT_REQUEST_HEADERS'].items():
           # seems to be a bug with how webkit-server handles accept-encoding
           if key.lower() != 'accept-encoding':
           self.dryscrape_session.set_header(key, value)
          

          現(xiàn)在,當我們可以通過命令 scrapy crawl zipru -o torrents.jl 再次運行爬蟲。我們可以看到源源不斷的爬取的內容,并且我們的 torrents.jl 文件記錄把爬取的內容全部記錄了下來。我們已經(jīng)成功地繞過了所有的威脅防御機制。

          總結

          我們已經(jīng)成功地寫了一個能夠解決四種截然不同的威脅防御機制的爬蟲,這四種防御機制分別是:

          1. User agent 過濾
          2. 模糊的 Javascript 重定向
          3. 驗證碼
          4. 請求頭一致性檢查

          我們的目標網(wǎng)站 Zipru 可能是虛構的,但是這些機制都是你會在真實網(wǎng)站上遇到的真實的反爬蟲技術。希望我們使用的方法對你自己爬蟲中遇到的挑戰(zhàn)有幫助。

          想要學習Python。關注小編頭條號,私信【學習資料】,即可免費領取一整套系統(tǒng)的板Python學習教程!


          上一篇:HTML和CSS編碼規(guī)范
          下一篇:HTML 圖像
          主站蜘蛛池模板: 国产精品小黄鸭一区二区三区 | 中文激情在线一区二区| 另类一区二区三区| 亚洲一区二区三区久久| 精品视频午夜一区二区| 亚洲视频一区在线播放| 色一情一乱一区二区三区啪啪高| 国产福利电影一区二区三区,亚洲国模精品一区 | 性色AV一区二区三区天美传媒| 国内自拍视频一区二区三区| 一区二区三区电影网| 曰韩人妻无码一区二区三区综合部 | 99久久国产精品免费一区二区| 无码人妻精品一区二区蜜桃百度| 69久久精品无码一区二区| 亚洲国产福利精品一区二区| 精品一区二区91| 精品国产一区AV天美传媒| 国产一区在线视频| 亚洲AV综合色区无码一区| 国产一区二区三区免费在线观看 | 狠狠做深爱婷婷久久综合一区 | 三级韩国一区久久二区综合 | 蜜臀AV一区二区| 精品乱子伦一区二区三区| 无码精品黑人一区二区三区| 精品一区二区三区在线观看视频| 糖心vlog精品一区二区三区| 国产综合视频在线观看一区| 日韩免费视频一区二区| 亚洲无码一区二区三区| 三上悠亚国产精品一区| 无码人妻一区二区三区av| 一区二区三区午夜视频| 国产精品女同一区二区久久| 亚洲色精品三区二区一区 | 国产精品无码一区二区三区免费| 亚洲国产专区一区| 国产色情一区二区三区在线播放 | 日韩精品无码一区二区三区不卡| 国产精品一区二区四区|