diff --git a/README.md b/README.md index 61136a6..ec5f200 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,5 @@ From within the main folder run: If you wish to login then crawl: ```scrapy crawl xss_spider -a url='http://something.com/login_page' -a login='username' -a pw='secret_password'``` + +Vulnerable URLs will be put in the file called vulnerable-urls.txt diff --git a/xsscrapy/items.py b/xsscrapy/items.py index 06c7c64..e878873 100644 --- a/xsscrapy/items.py +++ b/xsscrapy/items.py @@ -5,5 +5,9 @@ from scrapy.item import Item, Field -class Link(Item): +class URL(Item): vuln_url = Field() + high = Field() + medium = Field() + low = Field() + error = Field() diff --git a/xsscrapy/spiders/xss_spider.py b/xsscrapy/spiders/xss_spider.py index b888fc4..a8aaa7d 100644 --- a/xsscrapy/spiders/xss_spider.py +++ b/xsscrapy/spiders/xss_spider.py @@ -3,13 +3,14 @@ from scrapy.contrib.spiders import CrawlSpider, Rule from scrapy.selector import Selector from scrapy.http import Request, FormRequest -from xsscrapy.items import Link +from xsscrapy.items import URL from loginform import fill_login_form -from lxml.html import fromstring from urlparse import urlparse, parse_qsl +import lxml.html import urllib import re +import sys ''' xss headers: @@ -66,7 +67,7 @@ class XSSspider(CrawlSpider): payloaded_urls = self.makeURLs(resp_url, self.test_str) if payloaded_urls: - payloaded_reqs = [Request(url, callback=self.injection_finder, meta={'payload':self.test_str}) for url in payloaded_urls] # Meta is the payload + payloaded_reqs = [Request(url, callback=self.injection_finder, meta={'payload':self.test_str, 'orig_url':url}) for url in payloaded_urls] # Meta is the payload return payloaded_reqs return @@ -151,39 +152,153 @@ class XSSspider(CrawlSpider): def injection_finder(self, response): ''' Check for injection locations in app ''' body = response.body - root = fromstring(body) + root = lxml.html.fromstring(body) +# html = lxml.html.tostring(root, pretty_print=True) url = response.url payload = response.meta['payload'] + injections = [] + num_injections = 0 + quote_enclosure = self.single_or_double_quote(body) attr_xss = root.xpath("//@*[contains(., '%s')]" % payload) elem_attr = self.get_elem_attr(attr_xss) text_xss = root.xpath("//*[contains(text(), '%s')]" % payload) tags = self.get_text_xss_tags(text_xss) - ####################### Redundancy - - if len(attr_xss) + len(text_xss) > 0: - self.log('*************** lxml found %d injections points in %s' % (len(attr_xss)+len(text_xss), url)) - if tags: - for t in tags: - self.log('***** Found injection point between <%s> tags' % t) if elem_attr: - for ea in elem_attr: - self.log('***** Found attribute injection point in <%s> tag\'s attribute "%s"' % (ea[0], ea[1])) + num_injections = len(elem_attr) + if tags: + num_injections = num_injections + len(tags) + + if num_injections > 0: + self.log('%d injection points in %s' % (num_injections, url)) + + if tags: + for t in tags: + line = t[0] + tag = t[1] + self.log('Found injection point between <%s> tags' % tag) + injections.append((line, 'tag', tag)) + if elem_attr: + for ea in elem_attr: + line = ea[0] + tag = ea[1] + attr = ea[2] + attr_val = ea[3] + if attr == 'href' and payload == attr_val: + redir = True # sets the stage for a JaVASCrIPT:prompt(55) payload since the href value is the same as the payload + else: + redir = None + self.log('Found attribute injection point in <%s> tag\'s attribute "%s"' % (tag, attr)) + injections.append((line, 'attr', tag, attr, redir)) + + injections = sorted(injections) + orig_url = response.meta['orig_url'] + xss_reqs = self.xss_req_maker(injections, orig_url, quote_enclosure) + return xss_reqs + ################################## - if len(attr_xss) + len(text_xss) > 0: - # If it's an XSS in an attribute then we add the ' and " chars to the payload since those are necessary to break out of the attr - if elem_attr: - xss_chars = '(\'"):=<>' + def xss_req_maker(self, injections, orig_url, quote_enclosure): + # If injection between