blog

reflected_xss 検出ツールの紹介

xssコードはURLに表示され、ブラウザはURLにアクセスし、サーバレスポンスはこのxssコードを含みます。...

Jan 1, 2014 · 7 min. read
シェア

1.リフレクティブXSS入門

xssコードがURLに表示され、ブラウザがそのURLにアクセスし、サーバーのレスポンス内容にこのxssコードが含まれる、このようなxssを反射型xssと呼びます。

2.ツールのプレゼンテーション

reflected_xssツールは現在、ウェブサイト上で反射されたxssが存在する可能性を検出するために使用でき、pythonで書かれています。

pythonのバージョン2.7.3を使っていて、python requestsモジュールが必要です。

このツールには4つのファイルがあります。

クロールの深さを指定し、urllist ファイルを生成します。

reflect_xss.pyは、反射型xssの脆弱性の可能性について、urllistファイルの各URLを検出します。

filter.py の関数は spider.py によって呼び出され、パラメータのない URL や重複した URL をフィルタリングします。

設定ファイルには現在、タイムアウトとスリープの2つのオプションしかありません。

タイムアウトは、リクエストが応答を待つことができる最大時間です。応答が送られたとき、リクエストは数秒後にタイムアウトしたとみなされます。

サイトによっては、アクセス速度を制限している場合があります。

3.ツールのワークフロー

spider.py は url パラメータを受け取り、その url に基づいてリクエストを送信し、レスポンスを受け取り、そのレスポンスの中からルールを満たす url を抽出して urllist ファイルに保存します。クロールの深さが指定されている場合、再帰的に url のルールをクロールします。 このルールは regular で記述され、現在のルールは url にクロールすることであり、url は url の下のサイトでなければならず、url はパラメータと一緒でなければなりません。

例えば、spider.py "http://..../.?=1″ は http://.....1 で始まり、url に ?記号があります。

reflect_xss.pyは、urllistファイル内の各URLにreflective xssがあるかどうかをチェックします。 検出プロセスは、各URLのパラメータを分析し、"parameter "を "parameter+xss payload "に1つずつ置き換えてから送信し、xssペイロード文字列があるかどうかレスポンスを分析します。"に置き換えて送信し、xssペイロード文字列があるかどうかを応答内容を分析します。xss ペイロードはカスタマイズ可能で、keywords ファイルの各行は xss ペイロードとして扱われます。 keywords ファイルに xss ペイロードを追加または削除できます。

4.ツール使用デモンストレーション

カレント・ディレクトリのファイルを見てみましょう。

ローカルテストに使った2つのファイルを見てみましょう。

http://.../.?=&;=2 rlはルールに沿っているので、クローラーが終わってしばらくすると、stにはこのrlしかないはずです。

spider.py は小さなクローラーです。

使い方を見るために spider.py を直接実行してください。

クローラーのクロールが終了し、urllistファイルを生成し、ファイルはURLのみ、反射xssの欠陥があるかどうか、このURLを検出するために瞬間

urlパラメータはsqlmapのように二重引用符で囲むのが望ましいです。

reflect_xss.pyは検出に使用されます。

python reflect_xss.pyを実行するだけで検出できます。

反射する可能性のあるxssが見つかった場合、欠陥のあるURLを持つFoundファイルが現在のディレクトリに生成されます。

検出中に例外が発生した場合、カレント・ディレクトリの error.log ファイルに記録されます。ここでは例外は発生しないので、ログには記録されません。ログファイルは非常に簡潔なので、ここでは触れません。

コア・コード

spider.py.

#!/usr/bin/env python  
#coding=utf-8  
import requests  
import re  
import string  
import sys  
import filter  
headers={'Connection':'keep-alive',"User-Agent":"Mozilla/5.0 (X11; Linux i686) AppleWebkit/537.36 (KHTML, like Gecko) Chrome/53.93 Safari/537.36","Origin":"http://..net",'Accept-Encoding':'gzip,deflate,sdch','Accept-Language':'ja-JP,zh;q=.8,en-US;q=.6,en;q=.4','X-Requested-With':'XMLHttpRequest','Accept':'application/json,text/javascript,*/*;q=.01'}  
def saveurl(urlset):  
try:  
f=open("urllist","a")  
for url in urlset:  
#unicode文字コードをファイルに書き込み、改行を追加する  
f.write(url.encode('UTF-8')+"n")  
#except:  
#    print "Failed to write urllist to file!"  
finally:  
f.close()  
def main(requestsurl,depth):  
try:  
#print "%d"%depth  
depth=depth+1 
urlset=parseContent(requests.get(requestsurl,timeout=2,headers=headers).text)  
saveurl(urlset)  
if depth==string.atoi(sys.argv[2]):  
pass 
else:  
for u in urlset:  
main(u,depth)  
except:  
pass 
def parseContent(content):  
strlist = re.split('"',content)  
urlset = set([])  
for strstr in strlist:  
#python正規マッチングのために\を表す  
#if re.match('http://.*com(/|w)+', str):  
#これは少しシンプルで、現在のサイトの  
#if re.match('http://'+domain, str):  
rules="http://"+domain+"[^,^ ^  ^']*" 
#strstrはunicodeオブジェクトである  
result=re.compile(rules).findall(strstr.encode("utf-8"))  
#result   
if len(result)==0:  
pass 
else:  
for i in result:  
urlset.add(i)  
return list(urlset)  
if __name__=="__main__":  
if len(sys.argv)!=3:  
print "usage:"+sys.argv[0]+" http://.com/"+" depth" 
print "example:"+sys.argv[0]+' "http://...1/.php?c=1"'+" 3" 
else:  
domain=sys.argv[1].split('/')[2]  
#最初のURLを保存する  
tmp=[]  
tmp.insert(0,sys.argv[1]);  
saveurl(tmp)  
#クロールを開始する  
main(sys.argv[1],0)  
filter.filter() 

reflect_xss.py

#!/usr/bin/env python  
#coding=utf-8  
import requests  
import sys  
import time  
import re  
#global payloads  
#payloads=['"><sCript>alert(1)</sCript>','<img src=@ onerror=x>']  
jindu=0 
def readconf(keyname):  
isFound=0 
try:  
f=open("config","r")  
lines=f.readlines()  
for line in lines:  
if line.startswith(keyname):  
isFound=1 
return line.split('=')[1]  
if isFound==0:  
errorlog("Warn:Can not to read key "+keyname+" from configure file")  
return False 
except:  
errorlog("Warn:can not to read configure file ")  
return False 
def errorlog(str):  
str=str+"n" 
t=time.strftime('%m-%d %H.%M.%S--->',time.localtime(time.time()))  
f=open("error.log","a")  
f.write(t+str)  
f.close()  
def findlog(url):  
try:  
f=open("Found","a")  
f.write(url+"n")  
except:  
errorlog("Fail:open 'Found' file")  
finally:  
f.close()  
def main(payload,canshu,checkurl):  
global jindu  
url=checkurl.replace(canshu,canshu+payload)  
#print "checking: "+url  
#TODO timeout,ipブロックを防ぐには  
if readconf("sleep"):  
time.sleep(float(readconf("sleep")))  
#タイムアウト例外の可能性  
try:  
a=requests.get(url,timeout=1)  
if a.text.find(payload)!=-1:  
#print "Find!!!!"  
#print url  
#print "-----------------------"  
findlog(url)  
else:  
if jindu%10==0:  
print time.strftime('%H:%M.%S->',time.localtime(time.time()))+"checking the "+str(jindu)+"th"+" url:"+url  
except:  
errorlog("Fail:request.get "+url)  
jindu=jindu+1 
def parse(url):  
#url=http://.com/.php?a=1&b=2&c=3  
#canshus=["a=1","c=3"]  
#urlがhttpである可能性がある://.comパラメータなしで  
#この場合、空のオブジェクトが返される  
try:  
canshus=url.split("?")[1].split("&")  
return canshus  
except:  
kong=[]  
return kong  
pass 
def readfile(filename):  
#このグローバルな位置は、効果が同じではない上に配置され、私はなぜ理解していない  
#global payloads  
try:  
aa=open(filename,"r")  
f=aa.readlines();  
for i in range(0,len(f)):  
#nをフィルタリングする  
f[i]=f[i].rstrip("n")  
return f  
except:  
print "Failed to access "+'"'+filename+'" '"file!" 
finally:  
aa.close()  
if __name__=="__main__":  
if len(sys.argv)!=1:  
print 'usage:'+sys.argv[0]+" url depth" 
print 'example:'+sys.argv[0]+'"http://url/.php?x=1&y=2" 3' 
else:  
#global payloads  
payloads=readfile("keywords.txt")  
urls=readfile("urllist")  
for checkurl in urls:  
for payload in payloads:  
for canshu in parse(checkurl):  
if len(canshu)!=0:  
main(payload,canshu,checkurl) 

Read next