The mystery of SQLMap’s --eval
2022-3-23 16:55:40 Author: infosecwriteups.com(查看原文) 阅读量:37 收藏

Master the power of exploiting most complex SQL injections

Sometimes you need the power of the four elements to automate the exploitation of a SQL injection.
That’s what --eval gives you, and here’s how to take over this power.

depending on the case, you may need to modify different parts of the request.
SQLMap uses python’s exec() method to execute your script. the tool passes different parts of the request to this method as local variables. this way, you can access and modify them.

the post body of the request can be of different data types, like JSON, XML, etc. to provide access to the elements of each type, SQLMap recognizes the type by these regexes. then asks if you want the tool to process the data:
JSON/XML/... data found in POST/PUT/... body. Do you want to process it?
obviously, you need to answer YES if you want everything to work as intended.

URI

the URI of the request, without the query string, is accessible via uri parameter.
if the user specifies a custom injection point in the path of the URL, the uri parameter will contain the URL-encoded injected payload too.

example:
command:
sqlmap -u “http://example.com/path?id=123"
local variable:
uri --> http://example.com/path

command:
sqlmap -u “http://example.com/path*?id=123"
local variable:
uri --> http://example.com/path<URL-ENCODED PAYLOAD>

GET query

the value of each GET query parameter, plus the injected payload (if any), is accessible via local variables with the name of the original GET parameters.
example:
command:
sqlmap -u "http://example.com/path?param1=123&param2=456&class.id=789"
local variables:
param1 --> 123
param2 --> 456
EVAL_636c6173732e6964 --> 789 (read the note below)

Note: if the parameter name contains special characters or is a reserved keyword in python, before passing the variable to the exec() method, SQLMap changes its name based on this pattern:
EVAL_<HEX OF ORIGINAL NAME>
it applies to all the variables which are passed to the exec() method.
including cookies and POST data.

cookies

the content of the Cookie header, plus the injected payload (if any), is accessible via cookie variable.
command:
sqlmap -u "http://example.com/path?id=123" --cookie=”C1=123; C2=456"
local variable:
cookie --> C1=123; C2=456

headers

there are two dictionaries available in exec() method related to the request headers:
_locals['headers']
_locals['auxHeaders']
you can access the request headers via the headers dictionary, but modifying it will not affect the request.
to make changes to the headers of the final request, you need to update() the auxHeaders dictionary.
the value of auxHeaders is None by default. it means you need to add all the headers — modified or not, to the auxHeaders dictionary.
example:
let’s say you want to modify the value of the User-Agent header and add a custom authentication header X-Auth.
this can be the code:

# _locals['auxHeaders'] is None here
_locals['headers']['User-Agent'] = "pentesting, no worries!!"
_locals['headers']['X-Auth'] = "My_Secret_Token"
_locals['auxHeaders'].update(_locals['headers'])

POST body

SQLMap recognizes six types of POST data using regular expressions:

JSON
JSON-like (regex)
XML
Multipart
Array-like (regex)
Form-based

to see what JSON-like and array-like data types look like, refer to the regular expressions that recognize them.

at the time of writing this article SQLMap only parses form-based and json data types.
for other data types, you don’t have access to the value of elements.

but you can still modify the value of the elements by declaring a variable with the same name as the element’s name. SQLMap replaces the original value with the new one using a regex.
note this implementation is buggy with array-like data types, but you can find a solution here.

payload

the SQL payload that SQLMap has injected to the request is accessible via the _locals['payload'] variable.

long scripts

When the code you want to execute is a few lines, you can add the script as the value of --eval in the terminal:
sqlmap ... --eval="#first_line; #second_line; #third_line"

but if your script is longer, that’s not a good idea to write the entire script in the terminal. instead, you can write your code in a .py file and import it as a module:
sqlmap ... --eval="import myModule; #use_functions_of_myModule"

debugging

if you want to debug your code inside SQLMap or want to get familiar with the environment, you can use the ipdp module.
it gives you a debugging shell and you can move around and see what’s going on. the locals() method can be a good starting point.
sqlmap ... --eval=”import ipdb; ipdb.set_trace()”

A new implementation

as you can see there is room for improvement with the --eval flag.
so I rewrote the code of --eval flag. in this new implementation, all data types are supported in a unified way.
also, I made the raw GET/POST data accessible as local variables. so, if the user needs to modify the request, and the already local existing variables are not useful, he can use the raw data and parse it in the way he needs.

documentation:
user has access to these dictionaries as local variables:

headers: all the request headers. modifying this dictionary will effect the request.
get_data: processed GET query data
post_data: processed POST body. no matter what data type is, the user has access to ALL the elements and attributes of the data type.
get_query: raw GET query data
post_body: raw POST body data

Note: modifying get_query or post_body will overwrite the changes made to get_data and post_data accordingly.

the code:

if conf.evalCode:
delimiter = conf.paramDel or DEFAULT_GET_POST_DELIMITER
variables = {"uri": uri, "get_query": get, "headers": headers, "post_body": post, "get_data": {}, "post_data": {}, "lastPage": threadData.lastPage, "_locals": locals()}
original_get = get
original_post = post
if get:
for part in get.split(delimiter):
if '=' in part:
name, value = part.split('=', 1)
name = name.strip()
value = urldecode(value, convall=True)
variables['get_data'][name] = value
if kb.postHint:
if kb.postHint in (POST_HINT.XML, POST_HINT.SOAP):
variables['post_data'] = xmltodict.parse(post)
if kb.postHint == POST_HINT.JSON:
variables['post_data'] = json.loads(post)
if kb.postHint == POST_HINT.JSON_LIKE:
if json_like_type == 3:
post = re.sub(r'(,|\{)\s*([^\'\s{,]+)\s*:', '\g<1>"\g<2>":', post)
if json_like_type == 4:
post = re.sub(r'(,|\{)\s*([^\'\s{,]+)\s*:', "\g<1>'\g<2>':", post)
if json_like_type in (2, 4):
post = post.replace("\\'", REPLACEMENT_MARKER).replace('\"','\\"').replace("'",'"').replace(REPLACEMENT_MARKER, "'")
variables['post_data'] = json.loads(post)
if kb.postHint == POST_HINT.MULTIPART:
multipart = MultipartDecoder(bytes(post, 'utf-8'), contentType)
boundary = '--' + multipart.boundary.decode('utf-8')
for part in multipart.parts:
name = re.search(r'"([^\"]*)"', part.headers._store[b'content-disposition'][1].decode('utf-8')).group(1)
value = part.text
variables['post_data'][name] = value
if kb.postHint == POST_HINT.ARRAY_LIKE:
post = re.sub(r"\A%s" % delimiter, "", post)
array_name = re.findall(r"%s(.*?)\[\]=" % delimiter, post)[0].strip()
variables['post_data'] = []
for value in post.split("%s[]=" % array_name)[1:]:
variables['post_data'].append(value.replace(delimiter, ""))
elif post:
for part in post.split(delimiter):
if '=' in part:
name, value = part.split('=', 1)
name = name.strip()
value = urldecode(value, convall=True, spaceplus=kb.postSpaceToPlus)
variables['post_data'][name] = value
evaluateCode(conf.evalCode, variables)if kb.postHint:
if kb.postHint in (POST_HINT.XML, POST_HINT.SOAP):
post = xmltodict.unparse(variables['post_data'])
if kb.postHint == POST_HINT.JSON:
post = json.dumps(variables['post_data'])
if kb.postHint == POST_HINT.JSON_LIKE:
post = json.dumps(variables['post_data'])
if json_like_type in (3, 4):
post = re.sub(r'"([^"]+)":', '\g<1>:', post)
if json_like_type in (2, 4):
post = post.replace('\\"', REPLACEMENT_MARKER).replace("'", "\\'").replace('"', "'").replace(REPLACEMENT_MARKER, '"')
if kb.postHint == POST_HINT.MULTIPART:
for name, value in variables['post_data'].items():
post = re.sub(r"(?s)(name=\"%s\"(?:; ?filename=.+?)?\r\n\r\n).*?(%s)" % (name, boundary), r"\g<1>%s\r\n\g<2>" % value.replace('\\', r'\\'), post)
if kb.postHint == POST_HINT.ARRAY_LIKE:
post = array_name + "[]=" + (delimiter + array_name + "[]=").join(variables['post_data'])
else:
post = delimiter.join(f'{key}={value}' for key, value in variables['post_data'].items())
uri = variables['uri']
get = delimiter.join(f'{key}={value}' for key, value in variables['get_data'].items())
auxHeaders.update(variables['headers'])
cookie = variables['headers']['Cookie'] if 'Cookie' in variables['headers'] else None
get = variables['get_query'] if variables['get_query'] != original_get else get
post = variables['post_body'] if variables['post_body'] != original_post else post

you need to add these libraries to the thirdparty folder:
xmltodict
requests_toolblet

for more information about this implementation, refer to the pull request on Github.

was it helpful?
I don’t ask you to buy me a cup of coffee,
teach me something…
Discord: REND#9702


文章来源: https://infosecwriteups.com/the-mystery-of-sqlmaps-eval-f6c7bf43e1f?source=rss----7b722bfd1b8d--bug_bounty
如有侵权请联系:admin#unsafe.sh