From 1e9ae403975cf8b2c004945e870b13e2b46c9759 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 7 Oct 2010 12:12:26 +0000 Subject: [PATCH] major refactoring --- extra/xmlobject/__init__.py | 19 ++ extra/xmlobject/xmlobject.py | 534 +++++++++++++++++++++++++++++++++++ lib/controller/checks.py | 32 +-- lib/core/agent.py | 40 ++- lib/core/common.py | 8 + lib/core/option.py | 2 + xml/injections.xml | 72 +++-- 7 files changed, 646 insertions(+), 61 deletions(-) create mode 100755 extra/xmlobject/__init__.py create mode 100644 extra/xmlobject/xmlobject.py diff --git a/extra/xmlobject/__init__.py b/extra/xmlobject/__init__.py new file mode 100755 index 000000000..d79a05bda --- /dev/null +++ b/extra/xmlobject/__init__.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# +# Copyright 2007-2008 David McNab +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . +# + +pass diff --git a/extra/xmlobject/xmlobject.py b/extra/xmlobject/xmlobject.py new file mode 100644 index 000000000..ff6059942 --- /dev/null +++ b/extra/xmlobject/xmlobject.py @@ -0,0 +1,534 @@ +""" +Allows XML files to be operated on like Python objects. + +Features: + - load XML source from file pathnames, readable file objects or raw strings + - add, get and set tag attributes like with python attributes + - iterate over nodes + - save the modified XMLFile or XMLObject to file + +Example XML file:: + + + + + + + + + + + + + + + +Example usage:: + + >>> from xmlobject import XMLFile + + >>> x = XMLFile(path="sample.xml) + + >>> print x + + + >>> print x.root + + + >>> print x.root._children + [, , , + , ] + + >>> print x.root.person + [, ] + + >>> print x.root.person[0].name + John Smith + + >>> john = x.root.person[0] + + >>> john.height = 184 + + >>> c = john._addNode("crime") + + >>> c.name = "Grand Theft Auto" + + >>> c.date = "4 May, 2005" + + >>> print x.toxml() + + + + + + + + + + + + + + + >>> + +""" + +import sys, os +import xml.dom +import xml.dom.minidom +from xml.dom.minidom import parse, parseString, getDOMImplementation + +impl = getDOMImplementation() + +class MissingRootTag(Exception): + """root tag name was not given""" + +class InvalidXML(Exception): + """failed to parse XML input""" + +class CannotSave(Exception): + """unable to save""" + +class InvalidNode(Exception): + """not a valid minidom node""" + +class XMLFile: + """ + Allows an xml file to be viewed and operated on + as a python object. + + (If you're viewing the epydoc-generated HTML documentation, click the 'show private' + link at the top right of this page to see all the methods) + + Holds the root node in the .root attribute, also in an attribute + with the same name as this root node. + """ + def __init__(self, **kw): + """ + Create an XMLFile + + Keywords: + - path - a pathname from which the file can be read + - file - an open file object from which the raw xml + can be read + - raw - the raw xml itself + - root - name of root tag, if not reading content + + Usage scenarios: + 1. Working with existing content - you must supply input in + one of the following ways: + - 'path' must be an existing file, or + - 'file' must be a readable file object, or + - 'raw' must contain raw xml as a string + 2. Creating whole new content - you must give the name + of the root tag in the 'root' keyword + + Notes: + - Keyword precedence governing existing content is: + 1. path (if existing file) + 2. file + 3. raw + - If working with existing content: + - if the 'root' is given, then the content's toplevel tag + MUST match the value given for 'root' + - trying to _save will raise an exception unless 'path' + has been given + - if not working with existing content: + - 'root' must be given + - _save() will raise an exception unless 'path' has been given + """ + path = kw.get("path", None) + fobj = kw.get("file", None) + raw = kw.get("raw", None) + root = kw.get("root", None) + + if path: + self.path = path + try: + fobj = file(path) + except IOError: + pass + else: + self.path = None + + if fobj: + raw = fobj.read() + + if raw: + self.dom = xml.dom.minidom.parseString(raw) + else: + # could not source content, so create a blank slate + if not root: + # in which case, must give a root node name + raise MissingRootTag( + "No existing content, so must specify root") + + # ok, create a blank dom + self.dom = impl.createDocument(None, root, None) + + # get the root node, save it as attributes 'root' and name of node + rootnode = self.dom.documentElement + + # now validate root tag + if root: + if rootnode.nodeName != root: + raise IncorrectRootTag("Gave root='%s', input has root='%s'" % ( + root, rootnode.nodeName)) + + # need this for recursion in XMLNode + self._childrenByName = {} + self._children = [] + + # add all the child nodes + for child in self.dom.childNodes: + childnode = XMLNode(self, child) + #print "compare %s to %s" % (rootnode, child) + if child == rootnode: + #print "found root" + self.root = childnode + setattr(self, rootnode.nodeName, self.root) + + def save(self, where=None, obj=None): + """ + Saves the document. + + If argument 'where' is given, saves to it, otherwise + tries to save to the original given 'path' (or barfs) + + Value can be a string (taken to be a file path), or an open + file object. + """ + obj = obj or self.dom + + if not where: + if self._root.path: + where = self._root.path + + if isinstance(where, str): + where = file(where, "w") + + if not where: + raise CannotSave("No save destination, and no original path") + + where.write(obj.toxml()) + where.flush() + + def saveAs(self, path): + """ + save this time, and all subsequent times, to filename 'path' + """ + self.path = path + self.save() + + def toxml(self): + return self.dom.toxml() + + def __len__(self): + """ + returns number of child nodes + """ + return len(self._children) + + def __getitem__(self, idx): + if isinstance(idx, int): + return self._children[idx] + else: + return self._childrenByName[idx] + + +class XMLNode: + """ + This is the workhorse for the xml object interface + + (If you're viewing the epydoc-generated HTML documentation, click the 'show private' + link at the top right of this page to see all the methods) + + """ + def __init__(self, parent, node): + """ + You shouldn't need to instantiate this directly + """ + self._parent = parent + if isinstance(parent, XMLFile): + self._root = parent + else: + self._root = parent._root + self._node = node + self._childrenByName = {} + self._children = [] + + # add ourself to parent's children registry + parent._children.append(self) + + # the deal with named subtags is that we store the first instance + # as itself, and with second and subsequent instances, we make a list + parentDict = self._parent._childrenByName + nodeName = node.nodeName + if not parentDict.has_key(nodeName): + parentDict[nodeName] = parent.__dict__[nodeName] = self + else: + if isinstance(parentDict[nodeName], XMLNode): + # this is the second child node of a given tag name, so convert + # the instance to a list + parentDict[nodeName] = parent.__dict__[nodeName] = [parentDict[nodeName]] + parentDict[nodeName].append(self) + + # figure out our type + self._value = None + if isinstance(node, xml.dom.minidom.Text): + self._type = "text" + self._value = node.nodeValue + elif isinstance(node, xml.dom.minidom.Element): + self._type = "node" + elif isinstance(node, xml.dom.minidom.Comment): + self._type = "comment" + self._value = node.nodeValue + else: + raise InvalidNode("node class %s" % node.__class__) + + # and wrap all the child nodes + for child in node.childNodes: + XMLNode(self, child) + + def _render(self): + """ + Produces well-formed XML of this node's contents, + indented as required + """ + return self._node.toxml() + + def __repr__(self): + if self._type == "node": + return "" % self._node.nodeName + else: + return "" % self._type + + def __getattr__(self, attr): + """ + Fetches an attribute or child node of this tag + + If it's an attribute, then returns the attribute value as a string. + + If a child node, then: + - if there is only one child node of that name, return it + - if there is more than one child node of that name, return a list + of child nodes of that tag name + + Supports some magic attributes: + - _text - the value of the first child node of type text + """ + #print "%s: __getattr__: attr=%s" % (self, attr) + + if attr == '_text': + # magic attribute to return text + tnode = self['#text'] + if isinstance(tnode, list): + tnode = tnode[0] + return tnode._value + + if self._type in ['text', 'comment']: + if attr == '_value': + return self._node.nodeValue + else: + raise AttributeError(attr) + + if self._node.hasAttribute(attr): + return self._node.getAttribute(attr) + elif self._childrenByName.has_key(attr): + return self._childrenByName[attr] + + #elif attr == 'value': + # magic attribute + + else: + raise AttributeError(attr) + + + def __setattr__(self, attr, val): + """ + Change the value of an attribute of this tag + + The magic attribute '_text' can be used to set the first child + text node's value + + For example:: + + Consider: + + + foo + + + >>> somenode + + >>> somenode.child + + >>> somenode.child._text + 'foo' + >>> somenode._toxml() + u'foo' + >>> somenode.child._text = 'bar' + >>> somenode.child._text + 'bar' + >>> somenode.child._toxml() + u'bar/child>' + + """ + if attr.startswith("_"): + + # magic attribute for setting _text + if attr == '_text': + tnode = self['#text'] + if isinstance(tnode, list): + tnode = tnode[0] + tnode._node.nodeValue = val + tnode._value = val + return + + self.__dict__[attr] = val + elif self._type in ['text', 'comment']: + self._node.nodeValue = val + else: + # discern between attribute and child node + if self._childrenByName.has_key(attr): + raise Exception("Attribute Exists") + self._node.setAttribute(attr, str(val)) + + def _keys(self): + """ + Return a list of attribute names + """ + return self._node.attributes.keys() + + def _values(self): + """ + Returns a list of (attrname, attrval) tuples for this tag + """ + return [self._node.getAttribute(k) for k in self._node.attributes.keys()] + + def _items(self): + """ + returns a list of attribute values for this tag + """ + return [(k, self._node.getAttribute(k)) for k in self._node.attributes.keys()] + + def _has_key(self, k): + """ + returns True if this tag has an attribute of the given name + """ + return self._node.hasAttribute(k) or self._childrenByName.has_key(k) + + def _get(self, k, default=None): + """ + returns the value of attribute k, or default if no such attribute + """ + if self._has_key(k): + return getattr(self, k) + else: + return default + def __len__(self): + """ + returns number of child nodes + """ + return len(self._children) + + def __getitem__(self, idx): + """ + if given key is numeric, return the nth child, otherwise + try to return the child tag (or list of child tags) having + the key as the tag name + """ + #print "__getitem__: idx=%s" % str(idx) + + if isinstance(idx, slice) or isinstance(idx, int): + return self._children[idx] + elif isinstance(idx, str): + return self._childrenByName[idx] + else: + raise IndexError(idx) + + def _addNode(self, child): + """ + Tries to append a child node to the tree, and returns it + + Value of 'child' must be one of: + - a string (in which case it is taken to be the name + of the new node's tag) + - a dom object, in which case it will be wrapped and added + - an XMLNode object, in which case it will be added without + wrapping + """ + + if isinstance(child, XMLNode): + + # add it to our children registry + self._children.append(child) + + parentDict = self._childrenByName + nodeName = child._node.nodeName + + if not parentDict.has_key(nodeName): + parentDict[nodeName] = parent.__dict__[nodeName] = child + else: + if isinstance(parentDict[nodeName], XMLNode): + # this is the second child node of a given tag name, so convert + # the instance to a list + parentDict[nodeName] = self.__dict__[nodeName] = [parentDict[nodeName]] + parentDict[nodeName].append(child) + + # and stick it in the dom + self._node.appendChild(child._node) + + return child + + elif isinstance(child, str): + childNode = self._root.dom.createElement(child) + self._node.appendChild(childNode) + + elif isinstance(child, xml.dom.minidom.Element): + childNode = child + child = childNode.nodeName + self._node.appendChild(childNode) + + + return XMLNode(self, childNode) + + def _addText(self, value): + """ + Tries to append a child text node, with the given text, to the tree, + and returns the created node object + """ + childNode = self._root.dom.createTextNode(value) + self._node.appendChild(childNode) + return XMLNode(self, childNode) + + def _addComment(self, comment): + """ + Tries to append a child comment node (with the given text value) + to the tree, and returns the create node object + """ + childNode = self._root.dom.createCommentNode(comment) + self._node.appendChild(childNode) + return XMLNode(self, childNode) + + def _save(self, where=None): + """ + Generates well-formed XML from just this node, and saves it + to a file. + + Argument 'where' is either an open file object, or a pathname + + If 'where' is not given, then saves the entire document tree. + """ + if not where: + self._root.save() + else: + self._root.save(where, self._node) + + def _toxml(self): + """ + renders just this node out to raw xml code + """ + return self._node.toxml() + diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 048736928..965923728 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -31,7 +31,6 @@ from lib.core.common import getUnicode from lib.core.common import preparePageForLineComparison from lib.core.common import randomInt from lib.core.common import randomStr -from lib.core.common import readXmlFile from lib.core.common import DynamicContentItem from lib.core.convert import md5hash from lib.core.data import conf @@ -67,41 +66,30 @@ def checkSqlInjection(place, parameter, value, parenthesis): if conf.postfix: postfix = conf.postfix - injections = readXmlFile(paths.INJECTIONS_XML) + for case in kb.injections.root.case: + positive = case.test.positive + negative = case.test.negative - for case in injections.getElementsByTagName("case"): - tag = case.getAttribute("tag") - desc = case.getAttribute("desc") - - positive = case.getElementsByTagName("positive")[0] - negative = case.getElementsByTagName("negative")[0] - - params = positive.getAttribute("params") - format = positive.getAttribute("format") - - if not prefix and not postfix and tag == "custom": + if not prefix and not postfix and case.name == "custom": continue - infoMsg = "testing %s injection " % desc + infoMsg = "testing %s injection " % case.desc infoMsg += "on %s parameter '%s'" % (place, parameter) logger.info(infoMsg) - - payload = agent.payload(place, parameter, value, format % eval(params)) + payload = agent.payload(place, parameter, value, positive.format % eval(positive.params)) trueResult = Request.queryPage(payload, place) if trueResult: - params = negative.getAttribute("params") - format = negative.getAttribute("format") - payload = agent.payload(place, parameter, value, format % eval(params)) + payload = agent.payload(place, parameter, value, negative.format % eval(negative.params)) falseResult = Request.queryPage(payload, place) if not falseResult: - infoMsg = "%s parameter '%s' is %s injectable " % (place, parameter, desc) + infoMsg = "%s parameter '%s' is %s injectable " % (place, parameter, case.desc) infoMsg += "with %d parenthesis" % parenthesis logger.info(infoMsg) - return tag + return case.name return None @@ -187,7 +175,7 @@ def checkDynamicContent(*pages): break found = False - + if not found: kb.dynamicContent.append(item) diff --git a/lib/core/agent.py b/lib/core/agent.py index 31be663a3..6d87fc857 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -26,6 +26,7 @@ import re from xml.etree import ElementTree as ET +from lib.core.common import getInjectionCase from lib.core.common import randomInt from lib.core.common import randomStr from lib.core.common import replaceSpaces @@ -142,19 +143,19 @@ class Agent: if conf.direct: return self.payloadDirect(string) - query = "" + query = str() + case = getInjectionCase(kb.injType) + + if case is None: + raise sqlmapNoneDataException, "unsupported injection type" if conf.prefix: query = conf.prefix else: - if kb.injType == "numeric" or conf.postfix: - pass - elif kb.injType in ( "stringsingle", "likesingle" ): - query = "'" - elif kb.injType in ( "stringdouble", "likedouble" ): - query = "\"" - else: - raise sqlmapNoneDataException, "unsupported injection type" + if case.usage.prefix._has_key('value'): + query = case.usage.prefix.value + elif case.usage.prefix._has_key('format'): + query = case.usage.prefix.format % eval(case.usage.prefix.params) if kb.parenthesis not in ( None, 0 ): query += "%s " % (")" * kb.parenthesis) @@ -172,6 +173,11 @@ class Agent: if conf.direct: return self.payloadDirect(string) + case = getInjectionCase(kb.injType) + + if case is None: + raise sqlmapNoneDataException, "unsupported injection type" + randInt = randomInt() randStr = randomStr() @@ -186,18 +192,10 @@ class Agent: else: raise sqlmapNoneDataException, "unable to get the number of parenthesis" - if kb.injType == "numeric": - string += "%d=%d" % (randInt, randInt) - elif kb.injType == "stringsingle": - string += "'%s'='%s" % (randStr, randStr) - elif kb.injType == "likesingle": - string += "'%s' LIKE '%s" % (randStr, randStr) - elif kb.injType == "stringdouble": - string += "\"%s\"=\"%s" % (randStr, randStr) - elif kb.injType == "likedouble": - string += "\"%s\" LIKE \"%s" % (randStr, randStr) - else: - raise sqlmapNoneDataException, "unsupported injection type" + if case.usage.postfix._has_key('value'): + string += case.usage.postfix.value + elif case.usage.postfix._has_key('format'): + string += case.usage.postfix.format % eval(case.usage.postfix.params) return replaceSpaces(string) diff --git a/lib/core/common.py b/lib/core/common.py index 1b0e654a3..ea52e9a39 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1241,6 +1241,14 @@ def calculateDeltaSeconds(start, epsilon=0.05): """ return int(time.time() - start + epsilon) +def getInjectionCase(name): + retVal = None + for case in kb.injections.root.case: + if case.name == name: + retVal = case + break + return retVal + def initCommonOutputs(): kb.commonOutputs = {} key = None diff --git a/lib/core/option.py b/lib/core/option.py index 4ba0fe0fd..e3e257162 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -34,6 +34,7 @@ import urllib2 import urlparse from extra.keepalive import keepalive +from extra.xmlobject import xmlobject from lib.core.common import getConsoleWidth from lib.core.common import getFileType from lib.core.common import normalizePath @@ -1010,6 +1011,7 @@ def __setKnowledgeBaseAttributes(): kb.injParameter = None kb.injPlace = None kb.injType = None + kb.injections = xmlobject.XMLFile(path=paths.INJECTIONS_XML) kb.hintValue = None kb.nullConnection = None diff --git a/xml/injections.xml b/xml/injections.xml index 1d13d5aab..742db8d0c 100644 --- a/xml/injections.xml +++ b/xml/injections.xml @@ -1,28 +1,64 @@ - - - + + + + + + + + + - - - + + + + + + + + + - - - + + + + + + + + + - - - + + + + + + + + + - - - + + + + + + + + + - - - + + + + + + + + +