隨著網絡投票的興起,刷票工具也應運而生。相關的技術分析也看了不少,正好碰上個機會,用python做了一個基于urllib的簡單人肉刷票機。重點在思路分析和練手,所以下面把目標站點的信息都擦了,主要看個思路,不建議大家做壞事喲~
投票場景基本分析
首先瀏覽一下投票頁面,試著投了一票。發現再打開投票鏈接的時候瀏覽器就提示“你已參加過投票活動”。不可能靠IP識別用戶,基本就是靠cookie了,果然清空一下就能反復投票,也沒有發現對其它用戶特征的識別限制。不過投票之前要先點擊“獲取驗證碼”,獲取一個驗證碼圖片進行輸入驗證,獲取過程應該就是JS觸發一個GET請求,驗證碼也都是規整的字母數字,估計隨便找個在線OCR能搞定(好吧這里是我天真了),反正先按照套路接下來就是分析投票的HTTP請求,試著用程序模擬了。
HTTP請求分析
各類文章對HTTP請求的分析也很多,就不詳細說了,掛上Burpsuit直接看結果:
先看看訪問投票頁面的響應,有用的信息有幾個:
- 頭部Set-Cookie的內容
- 返回頁面上驗證碼區域有一個"InstanceId"參數
投票首頁的返回頭部
投票首頁返回部分內容
再看攔截到的請求驗證碼的過程
- GET請求里有兩個變化的參數 t和d,t就是投票頁面返回的那個InstanceId,(和上面那張圖里的參數不一樣..因為不是一次過程的,我懶得找了=、=),d目測是個時間參數,事實證明刪了也無妨..于是就被我去掉了。(后面提交投票的時候也有這個參數,那個時候就不能忽略了)
- 注意這個請求是帶cookie的,經測試沒有cookie的話,請求出來的永遠都是同一張圖片。后來分析應該是它在后臺根據cookie里jac這個字段和t一起去隨機匹配了一個圖片,并在后臺和jac關聯了,提交之后根據這個進行驗證。
請求驗證碼圖片的請求報文頭
插曲
到這里,已經能通過模擬獲得驗證碼了,本來打算用個在線OCR識別一下做成全自動,然而在做參數試驗的時候,網站對提交應該是有防護預警的,識別到可疑行為之后先是暫停了一下,再開放之后驗證碼難度飆升,各種扭曲旋轉中文字,反正它對單IP的訪問頻率也有限制,俺們也不是真的要做壞事,這里就搞成人肉模式了,獲取驗證碼之后會彈出圖片和程序輸入提示,人肉識別輸入完成提交。
提交的請求
先吐槽一下,這里的數據提交都還是用GET請求..基本沒什么問題
- rn參數里前半段是固定的,后半段就是cookie里jac的數值
- t參數,就是當前時間,time.time()*1000
- validate_text和btuserinput是驗證字符串,urlencoded
提交投票請求
一路提交完,刷一下頁面就可以看到投票成功咯~
幾個思考
- 后面代碼里可以看到,這種模擬還是比較低級的,包括手動處理cookie信息。后來查到有一些庫在模擬瀏覽器會話上做了高層封裝,應該會更方便,以后可以進一步研究。
- 圖片驗證碼? 隨著識別技術不斷發展,純粹的圖片驗證碼要么就是分分鐘被程序做掉,要么就是分分鐘把用戶做掉(對啊對啊,我說的就是你,12xxx)。不知道未來的方向會是怎么樣,現在開始出現越來越多基于行為的驗證碼輸入(比如要你拖動滑塊完成圖片拼圖),搜到很多似乎都是來自極驗驗證的,也許是今后的一個趨勢。
- 網站防護與數據分析。當傳統的驗證手段越來越難以阻止用戶進行非常規操作(我覺得今后業余用戶能寫幾手代碼抓幾行包的能力會越來越強,何況還有這么多工具提供者),我們如果作為網站的運營維護人員,要怎么應對?答案也許在于對訪問數據的充分挖掘和分析上。我自己的觀念也在轉變,做好網站安全,并不是上一套一套安全設備,一個個檢查特征庫更新全,補丁打完就夠了。漏洞防不勝防,總有各種0day,依賴對已知攻擊的特征檢查和防護永遠慢人一步。而如果能從更多的方面來分析訪問請求、從數據統計上著手,也許我們距離最前沿的攻擊就只差半步了。當然現實往往是殘酷的,很多網站也許根本就倒在了第一步“數據收集”,要么信息不全要么記錄漫無目的。其實這也許才是最重要的一步,該記錄哪些數據?記在哪里?怎么記?能否有效快速訪問?這一步做好了,后面的分析處理就可以天馬行空了。像我這次這樣簡單的刷票應該分分鐘就被干掉或者統計時就被排除掉了^^
最后附上代碼,比較簡單就沒有寫注釋(好吧我承認還是因為我懶),和上面的過程是一樣的大家不要做壞事喲
#coding=utf-8
from BeautifulSoup import *
import cStringIO
import urllib
import re
from PIL import Image
import time
import urllib2
url1 = 'http://www.example.com/'
url2 = 'http://www.example.com/***?activity=***&get=image&c=DesignerInitializedCaptcha&t='
while True:
cookie = ''
iid = ''
page = urllib.urlopen(url1)
for header in page.info().headers:
if 'Set-Cookie' in header:
cookie += re.findall(r'Set-Cookie:( \S*;)',header)[0]
rnd = re.findall(r'jac*****=(.*);',cookie)[0]
soup = BeautifulSoup(page)
tags = soup('img')
for tag in tags:
if tag.get('instanceid', None):
iid = tag.get('instanceid')
req = urllib2.Request((url2 + iid))
req.add_header('Host',' www.example.com')
req.add_header('Proxy-Connection','keep-alive')
req.add_header('Accept','image/webp,image/*,*/*;q=0.8')
req.add_header('Referer','http://www.example.com/')
req.add_header('User-Agent','Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.97 Safari/537.36')
req.add_header('Accept-Encoding','gzip, deflate, sdch')
req.add_header('Accept-Language','zh-CN,zh;q=0.8,en;q=0.6,ja;q=0.4')
req.add_header('Cookie', cookie)
cfile = cStringIO.StringIO(urllib2.urlopen(req).read())
img = Image.open(cfile)
img.show()
cpt = raw_input('驗證碼是多少:')
data = {'validate_text':cpt, 'source':'directphone', 'submittype':'1', 'rn':'3525361439.'+rnd, 'btuserinput':cpt, 'btcaptchaId':'DesignerInitializedCaptcha', 'btinstanceId':iid, 't':str(int(time.time()*1000)), 'submitdata':'1$29|31|32|33', 'useget':1}
url3 = 'http://www.example.com/***?curid=7125344&starttime=2016%2F2%2F3%2019%3A32%3A15&' + urllib.urlencode(data)
req = urllib2.Request(url3)
req.add_header('Host',' www.example.com')
req.add_header('Proxy-Connection','keep-alive')
req.add_header('Accept','*/*')
req.add_header('X-Requested-With','XMLHttpRequest')
req.add_header('Referer','http://www.example.com/')
req.add_header('User-Agent','Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.97 Safari/537.36')
req.add_header('Accept-Encoding','gzip, deflate, sdch')
req.add_header('Accept-Language','zh-CN,zh;q=0.8,en;q=0.6,ja;q=0.4')
req.add_header('Cookie', cookie)
result = urllib2.urlopen(req)
print result.read()
time.sleep(10)