翻譯文章,原文:Writing a scanner to find reflected XSS vulnerabilities — Part 1[1]
程式碼地址:https://github。com/akhil-reni/xsstutorial , 可以直接看程式碼,感覺文章本身有點亂,看完才覺得好多地方說的很亂
2016年,我從事一個與burp suite非常相似的Web應用程式掃描程式專案,該專案代理來自瀏覽器或Selenium自動化工具的HTTP請求,並將它們傳送到不同的模組/外掛以進行漏洞掃描。我們構建的架構是相當模組化的,大多數應用程式都是用Python編寫的,而前端則是使用Django和Celery編寫的,用於非同步任務。
我寫這篇部落格的目的是幫助安全工程師為自己或社群編寫漏洞掃描程式。
在構建任何東西時,我們首先需要堅持基礎知識並弄清楚以下幾點:
•
它如何運作?
建立一個簡單的流程圖來說明掃描器的工作流程,它可能是包括:需要什麼輸入,怎麼分析這些資料,最終輸出什麼資料。
•
使用什麼技術實現?
選擇合適的技術非常重要,在選擇一種技術時,應當瞭解需要的功能庫以及如何根據需要擴充套件他們。但最重要的是你熟悉它。如果我用20小時編寫的GoLang程式碼和我用5小時編寫的Python程式碼,在功能輸出上僅僅是好一些,那麼我會義無反顧的使用PYthon編寫程式碼。
讓我們開始吧,因為我之前的專案是使用Python,所以我會堅持下去。首先,我們先建立一個功能圖。需要了解了解和如何標識他,請看文章:reflected cross-site scripting vulnerability[2]。
它是什麼工作的?
整個掃描器可以分成以下幾個模組:
•原始HTTP請求解析器
•初始探測器
•上下文分析器
•Payload生成器
•Payload驗證
首先建立每個模組,然後最後將它們整合在一起。
建立一個python virtualenv
pip3 install virtualenvpython3 -m virtualenv xss_env
啟用virtualenv
cd xss_env/Scripts && activate
在virtualenv資料夾之外建立一個新資料夾
mkdir rxss
1 原始HTTP請求解析器
現在我們已經設定好環境,我們開始編寫一些程式碼。第一個模組將是原始HTTP請求解析器,該解析器從檔案中獲取輸入並轉換為請求物件。為此,我們將使用python3中的現有http庫
from __future__ import absolute_import, unicode_literalsfrom http。server import BaseHTTPRequestHandlerfrom io import BytesIOclass HTTPRequest(BaseHTTPRequestHandler): def __init__(self, request_text): self。rfile = BytesIO(request_text) self。raw_requestline = self。rfile。readline() self。error_code = self。error_message = None self。parse_request() def send_error(self, code, message): self。error_code = code self。error_message = message
上面的類接受原始的HTTP字串並將其轉換為請求物件。
POST /search。php?test=query HTTP/1。1Host: testphp。vulnweb。comUser-Agent: Mozilla/5。0 (Windows NT 10。0; Win64; x64; rv:75。0) Gecko/20100101 Firefox/75。0Accept: text/html,application/xhtml+xml,application/xml;q=0。9,image/webp,*/*;q=0。8Accept-Language: en-US,en;q=0。5Accept-Encoding: gzip, deflateContent-Type: application/x-www-form-urlencodedContent-Length: 27Origin: http://testphp。vulnweb。comConnection: closeReferer: http://testphp。vulnweb。com/search。php?test=queryUpgrade-Insecure-Requests: 1searchFor=asdas&goButton=go
將以上請求儲存在request。txt中
from __future__ import absolute_import, unicode_literalsfrom http。server import BaseHTTPRequestHandlerfrom io import BytesIOclass HTTPRequest(BaseHTTPRequestHandler): def __init__(self, request_text): self。rfile = BytesIO(request_text) self。raw_requestline = self。rfile。readline() self。error_code = self。error_message = None self。parse_request() def send_error(self, code, message): self。error_code = code self。error_message = messagewith open(“requests。txt”, “rb”) as f: request = HTTPRequest(f。read()) if not request。error_code: print(request。command) # prints method print(request。path) # prints request。path print(request。headers。keys()) # prints requests headers print(request。headers[‘host’]) # prints requests host content_len = int(request。headers。get(‘Content-Length’)) print(request。rfile。read(content_len)) # prints request body
將以上程式碼另存為request_parser。py並使用以下命令執行
python3 request_parser。py
POST /search。php?test=query[‘Host’, ‘User-Agent’, ‘Accept’, ‘Accept-Language’, ‘Accept-Encoding’, ‘Content-Type’, ‘Content-Length’, ‘Origin’, ‘Connection’, ‘Referer’, ‘Upgrade-Insecure-Requests’]testphp。vulnweb。comb‘searchFor=asdas&goButton=go’
我們已經成功解析了一個HTTP請求。現在剩下的最後一件事是將主體和請求引數轉換為DICT,以便我們可以輕鬆地解析和新增自己的有效負載。
完整的請求解析程式碼可以在這裡找到:https://gist。github。com/akhil-reni/5c20f40729179858570ad1ffdf4502f3
from __future__ import absolute_import, unicode_literalsfrom http。server import BaseHTTPRequestHandlerfrom io import BytesIOfrom urllib import parseclass Request: def __init__(self): self。headers = None self。params = None self。data = None self。path = None def replace(self, string, payload): for k, v in self。headers。items(): k。replace(string, payload) v。replace(string, payload) for k, v in self。params。items(): self。params[k] = self。params[k]。replace(string, payload) for k, v in self。data。items(): self。data[k] = self。data[k]。replace(string, payload) print(self。data)class RequestParser(object): def __init__(self, request_text): self。request = Request() try: self。raw_request = HTTPRequest(request_text) if self。raw_request。error_code: raise Exception(“failed parsing request”) self。request。method = self。raw_request。command self。request。path = self。construct_path() self。request。headers = self。raw_request。headers self。request。data = self。convert(self。construct_data()) self。request。params = self。convert(self。construct_params()) except Exception as e: raise e def convert(self, data): if isinstance(data, bytes): return data。decode() if isinstance(data, (str, int)): return str(data) if isinstance(data, dict): return dict(map(self。convert, data。items())) if isinstance(data, tuple): return tuple(map(self。convert, data)) if isinstance(data, list): return list(map(self。convert, data)) if isinstance(data, set): return set(map(self。convert, data)) def construct_path(self): return parse。urlsplit(self。raw_request。path)。path def construct_data(self): return dict(parse。parse_qsl(self。raw_request。rfile。read(int(self。raw_request。headers。get(‘content-length’))))) def construct_params(self): return dict(parse。parse_qsl(parse。urlsplit(self。raw_request。path)。query))with open(“requests。txt”, “rb”) as f: parser = RequestParser(f。read()) print(parser。request。method) # prints method print(parser。request。path) # prints request。path print(parser。request。headers) # prints requests headers print(parser。request。data) # prints requests body print(parser。request。params) # prints requests params
2 初始探測器
解析完成後,我們需要找到一種在請求引數和帖子正文中插入探針的方法,並檢查它是否在響應中反映出來。為此,我們將在python中使用請求包。
pip3 install requests
建立一個新的檔案create_insertions。py,程式碼如下:https://gist。github。com/akhil-reni/ed890e7fb7d90a7581c3ce380744b609
import copyclass GetInsertionPoints: def __init__(self, request): self。request = request self。requests = [] self。params(append=True) self。body(append=True) def params(self, append: bool = False) -> None: if self。request。params: for q in self。request。params: request = copy。deepcopy(self。request) if append: request。params[q] = str(request。params[q])+“ teyascan” else: request。params[q] = “teyascan” request。insertion = q request。iplace = ‘params’ self。requests。append(request) def body(self, append: bool = False) -> None: if self。request。data: for q in self。request。data: request = copy。deepcopy(self。request) if append: request。data[q] = str(request。data[q])+“ teyascan” else: request。data[q] = “teyascan” request。insertion = q request。iplace = ‘body’ self。requests。append(request)with open(“requests。txt”, “rb”) as f: parser = RequestParser(f。read()) print(parser。request。method) # prints method print(parser。request。path) # prints request。path print(parser。request。headers) # prints requests headers print(parser。request。data) # prints requests body print(parser。request。params) # prints requests params i_p = GetInsertionPoints(parser。request) print(i_p。requests)
上面的程式碼解析引數和正文,以使用探針作為有效負載建立請求物件的列表。
python3 create_insertions。py[<__main__。HTTPRequest object at 0x0000021E8AD34A30>, <__main__。HTTPRequest object at 0x0000021E8AD34B80>, <__main__。HTTPRequest object at 0x0000021E8AD34BB0>]
現在,我們傳送每個請求,並檢查響應中反映了哪個引數值。
import requestsdef send_request(request, scheme): url = “{}://{}{}”。format(scheme, request。headers。get(“host”), request。path) req = requests。Request(request。method, url, params=request。params, data=request。data, headers=request。headers) r = req。prepare() s = requests。Session() response = s。send(r, allow_redirects=False, verify=False) return responsewith open(“requests。txt”, “rb”) as f: parser = RequestParser(f。read()) i_p = GetInsertionPoints(parser。request) for request in i_p。requests: response = send_request(request, “http”) if “teyascan” in response。text: print(“probe reflection found in ”+request。insertion)
輸出將是這樣的:
python 。\test。pyprobe reflection found in searchFor
XSS反射的型別及其轉義
HTML標籤:
必須使用<>字元來構造有效負載。
HTML屬性名字:
空格和=是必需的,“和‘是可選的
HTML屬性值:
直接Payload或是用”,’來構造有效載荷
HTML文字節點:
必須使用<>字元才能轉義文字區域並構造有效內容
HTML註釋:
<>!必須使用字元來轉義註釋並構造有效內容
樣式:
必須使用<>字元才能轉義樣式並構造有效內容
樣式屬性:
<,>,“字元需要轉義文字區域並構造有效內容
Href屬性:
需要直接載荷或”來逃逸文字區域並構造載荷
JS節點:
<,>是轉義指令碼所必需的。或其他特殊字元可轉義JS變數或函式。
注意:我們並未構建涵蓋所有上下文的高階掃描器。
為了編寫上下文分析器,我們將使用一個名為LXML的程式包,該程式包將HTML解析為XML樹。
pip3 install lxml
我們將編寫一個接收原始HTML響應的類,將其轉換為XML樹,搜尋字串,然後返回上下文列表。
例如,執行以下程式碼:
from lxml import htmlstring = “
teyascan
”search_string = “teyascan”page_html_tree = html。fromstring(string)xpath = ‘//*[contains(text(),\’‘ + search_string + ’\‘)]’n = page_html_tree。xpath(xpath)if len(n): print(“INPUT IS REFLECTED BACK INSIDE HTML TAG CONTEXT”)您將看到類似以下的輸出:
(xss_env) C:\Users\hungrysoul\Downloads\teya\teya>python test。pyprobe reflection found in searchFor{‘payload’: ‘teyascan’, ‘contexts’: [{‘type’: ‘text’, ‘count’: 1}]}(xss_env) C:\Users\hungrysoul\Downloads\teya\teya>
在上面的程式碼中,您可以看到原始HTML字串已被解析並轉換為XML樹。稍後,我們使用正則表示式搜尋XML樹,以找到反映了字串的上下文。
分析程式碼地址:https://gist。github。com/akhil-reni/aa001c76748b1dddb3d50d141098905e#file-context_analyzer-py
執行程式碼:
(xss_env) C:\Users\hungrysoul\Downloads\teya\teya>python test。pyprobe reflection found in searchFor{‘payload’: ‘teyascan’, ‘contexts’: [{‘type’: ‘text’, ‘count’: 1}]}(xss_env) C:\Users\hungrysoul\Downloads\teya\teya>
現在,我們已經成功建立了一個上下文分析器。剩下的就是根據上下文建立有效負載並確認這些有效負載。
對於這一部分,我們將不使用有效負載列表或其他任何東西,而只是使掃描器足夠智慧。
首先建立一個名為payload_generator。py的新檔案,然後建立函式payload_generator。該函式獲取上下文並返回帶有正則表示式的列表,以在XML樹和有效負載中查詢。
程式碼如下:https://gist。github。com/akhil-reni/d60a88c64f3bd02690e0f19cb3752458#file-payload_generator-py
執行以下程式碼:
with open(“requests。txt”, “rb”) as f: parser = RequestParser(f。read()) i_p = GetInsertionPoints(parser。request) for request in i_p。requests: response = send_request(request, “http”) if “teyascan” in response。text: print(“probe reflection found in ”+request。insertion) contexts = ContextAnalyzer。get_contexts(response。text, “teyascan”) final_payloads = [] for context in contexts[“contexts”]: print(context) payloads = payload_generator(context[‘type’]) final_payloads。extend(payloads) print(final_payloads)
輸出:
(xss_env) C:\Users\hungrysoul\Downloads\teya\teya>python test。pyprobe reflection found in searchFor{‘type’: ‘htmltag’, ‘count’: 1}[{‘payload’: ‘
我們已經根據上下文成功建立了有效負載。接下來的事情是傳送帶有有效負載的請求,並確認有效負載是否成功。
為此,我們需要傳送一個HTTP請求,但是這次不是探測字串,而是與有效載荷一起傳送。我們首先使用Deepcopy複製請求。
dup = copy。deepcopy(request)
然後使用以下命令替換請求引數,標頭和正文中的“teyascan”
def replace(request, string, payload): for k, v in request。headers。items(): k。replace(string, payload) v。replace(string, payload) for k, v in request。params。items(): request。params[k] = request。params[k]。replace(string, payload) for k, v in self。data。items(): request。data[k] = request。data[k]。replace(string, payload)
完成後,我們可以使用在第一篇文章中建立的send_request函式傳送請求物件。
with open(“requests。txt”, “rb”) as f: parser = RequestParser(f。read()) i_p = GetInsertionPoints(parser。request) for request in i_p。requests: response = send_request(request, “http”) if “teyascan” in response。text: print(“probe reflection found in ”+request。insertion) contexts = ContextAnalyzer。get_contexts(response。text, “teyascan”) for context in contexts[“contexts”]: print(context) payloads = payloadGenerator(context[‘type’]) for payload in payloads: dup = copy。deepcopy(request) dup。replace(“teyascan”, payload[‘payload’]) response = send_request(dup, “http”) page_html_tree = html。fromstring(response。text) count = page_html_tree。xpath(payload[‘find’]) if len(count): print(“request vulnerable”) print(dup。headers) http = MakeRawHTTP(dup) print(http。rawRequest)
輸出:
(xss_env) C:\Users\hungrysoul\Downloads\teya\teya>python test。pyprobe reflection found in searchForVULNERABLE TO XSSPOST /search。php HTTP/1。1Host: testphp。vulnweb。comUser-Agent: Mozilla/5。0 (Windows NT 10。0; Win64; x64; rv:75。0) Gecko/20100101 Firefox/75。0Accept: text/html,application/xhtml+xml,application/xml;q=0。9,image/webp,*/*;q=0。8Accept-Language: en-US,en;q=0。5Accept-Encoding: gzip, deflateContent-Type: application/x-www-form-urlencodedContent-Length: 27Origin: http://testphp。vulnweb。comConnection: closeReferer: http://testphp。vulnweb。com/search。php?test=queryUpgrade-Insecure-Requests: 1searchFor=asdas
在瀏覽器中重複請求以確認漏洞
我們已經成功地自動找到了XSS
教程程式碼可以在https://github。com/akhil-reni/xsstutorial中找到
總結
這個是我後加的總結,感覺寫的有點亂,文章沒必必須要全讀,可以直接看看程式碼,是不是有參考價值
References
[1] Writing a scanner to find reflected XSS vulnerabilities — Part 1: https://medium。com/@hungry。soul/writing-a-scanner-to-find-reflected-xss-vulnerabilities-part-1-5dd6de7d1a35[2] reflected cross-site scripting vulnerability: https://portswigger。net/web-security/cross-site-scripting/reflected