0%

Sqlmap源码-请求参数的检验和解析(二)

Pre:

上篇,讲的解析请求参数函数_setRequestParams(),里面有多处会调用到paramToDict()函数.

如图所示,会调用paramToDict去不同位置的参数:

  • Get参数

  • Post参数

  • Cookie参数


函数说明:

1
2
3
4
5
def paramToDict(place, parameters=None):
"""
Split the parameters into names and values, check if these parameters
are within the testable parameters and return in a dictionary.
"""

作用:

  1. 将参数名和参数值分开

  2. 判断给的参数在不在这里面

paramToDict的主要流程

去除html编码

1
parameters = re.sub(r"&(\w{1,4});", r"%s\g<1>%s" % (PARAMETER_AMP_MARKER, PARAMETER_SEMICOLON_MARKER), parameters)

参数里可能会包含html编码字符,例如__AMP__, __SEMICOLON__
如果有的话,将参数提取出来.

算是一种优化的手段吧.

参数分隔

1
2
3
4
if place == PLACE.COOKIE:
splitParams = parameters.split(conf.cookieDel or DEFAULT_COOKIE_DELIMITER)
else:
splitParams = parameters.split(conf.paramDel or DEFAULT_GET_POST_DELIMITER)

如果是Cookie里的参数,一般用;作为分隔符

例子:

1
cookie: gdpr-cookie-consent=accepted; _ga=GA1.2.56372231.1535340163; token=5b836e9cc3dcfa1d7812d915%2F0utWy6TvJ8pITwm1G76m7XXgrQBDY5bZ4Jfkqj9ousVXssC2hOyrDeMFs8PFtkWL;

如果是Get、Post里的参数,一般用&作为分隔符

例子:

1
https://www.baidu.com/s?wd=parameter%20&rsv_spt=1&rsv_iqid=0x895d84b600069e8d&issp=1&f=3&rsv_bp=1&rsv_idx=2&ie=utf-8

处理后的结果为列表:

1
[u'wd=parameter ', u'rsv_spt=1', u'rsv_iqid=0x895d84b600069e8d', u'issp=1', u'f=3', u'rsv_bp=1', u'rsv_idx=2', u'ie=utf-8']

对每个参数进行检验、解析

参数名和值分隔

1
2
3
4
5
6
7
8
9
10
11
12
for element in splitParams:
element = re.sub(r"%s(.+?)%s" % (PARAMETER_AMP_MARKER, PARAMETER_SEMICOLON_MARKER), r"&\g<1>;", element) # 去除html标签
parts = element.split("=")

if len(parts) >= 2:
parameter = urldecode(parts[0].replace(" ", "")) # 解码

if not parameter:
continue

if conf.paramDel and conf.paramDel == '\n':
parts[-1] = parts[-1].rstrip()
  • 参数如果含HTML标签,去掉HTML标签

  • 参数名和值用=分隔

  • urldecode参数名

判断是否满足检验的条件

1
2
3
condition = not conf.testParameter
condition |= conf.testParameter is not None and parameter in conf.testParameter
condition |= place == PLACE.COOKIE and len(intersect((PLACE.COOKIE,), conf.testParameter, True)) > 0

这个条件condition是并列的or判断,满足任意情况即可

  • 没有指定参数

  • 指定了参数 同时 该参数在指定的参数范围里

  • 该参数的位置在cookie 同时 该参数在cookie里


开始检验参数值-参数值是否异常(被污染)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if condition:
testableParameters[parameter] = "=".join(parts[1:])
if not conf.multipleTargets and not (conf.csrfToken and re.search(conf.csrfToken, parameter, re.I)):
_ = urldecode(testableParameters[parameter], convall=True) # url解码
if (_.endswith("'") and _.count("'") == 1 or re.search(r'\A9{3,}', _) or re.search(r'\A-\d+\Z', _) or re.search(DUMMY_USER_INJECTION, _)) and not parameter.upper().startswith(GOOGLE_ANALYTICS_COOKIE_PREFIX): # 正则匹配是否含异常字段
warnMsg = "it appears that you have provided tainted parameter values " # 提示信息
warnMsg += "('%s') with most likely leftover " % element
warnMsg += "chars/statements from manual SQL injection test(s). "
warnMsg += "Please, always use only valid parameter values "
warnMsg += "so sqlmap could be able to run properly"
logger.warn(warnMsg)

message = "are you really sure that you want to continue (sqlmap could have problems)? [y/N] "

if not readInput(message, default='N', boolean=True):
raise SqlmapSilentQuitException
elif not _:
warnMsg = "provided value for parameter '%s' is empty. " % parameter
warnMsg += "Please, always use only valid parameter values "
warnMsg += "so sqlmap could be able to run properly"
logger.warn(warnMsg)

如果同时满足以下情况:

  • 配置里不是多个检测目标

  • 没有cstf-token

会尝试检测参数值是否异常(被污染)

url解码参数值之后,检测出以下情况则认为参数值异常(被污染)

  • 含有单引号'

  • 开头有3个9或者更多 (不知道为什么)

  • -数字 (类似id=2-1 这种payload吧)

  • 含以下关键词ANDORUNIONSELECTFROMCONCATinformation_schemaSLEEPDELAYFLOOR(RAND) (常见payload关键字)

  • 含google分析cookie前缀__UTM

之所以要这样做异常检测,是跟后面sql注入在参数值前后插入payload的动作有关系的

如果参照值提前含有这些payload字眼,很可能会造成后面的异常.

所以它会提示你

1
are you really sure that you want to continue (sqlmap could have problems)? [y/N] 

检验Get、Post参数值

1
2
3
4
5
if place in (PLACE.POST, PLACE.GET):
for regex in (r"\A((?:<[^>]+>)+\w+)((?:<[^>]+>)+)\Z", r"\A([^\w]+.*\w+)([^\w]+)\Z"):
match = re.search(regex, testableParameters[parameter]) # 正则匹配
if match:
# ....

通过正则去检验参数值是否特殊,如含特殊的符号.

这两个正则的意思是:

  • 参数含有标签 (可能是xml数据)

  • 参数除了有数字和字母,有其他符号 (可能是json数据)

这两个正则有点难理解.


解析Get、Post参数值

参数值如果比较特殊,就会尝试去解析.

  • json反序列化

  • xml解析

尝试json反序列化

1
2
deserialized = json.loads(testableParameters[parameter])
walk(deserialized)

主要是这个walk()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def walk(head, current=None):
if current is None:
current = head
if isListLike(current):
for _ in current:
walk(head, _)
elif isinstance(current, dict):
for key in current.keys():
value = current[key]
if isinstance(value, (list, tuple, set, dict)):
if value:
walk(head, value)
elif isinstance(value, (bool, int, float, basestring)):
original = current[key]
if isinstance(value, bool):
current[key] = "%s%s" % (getUnicode(value).lower(), BOUNDED_INJECTION_MARKER)
else:
current[key] = "%s%s" % (value, BOUNDED_INJECTION_MARKER)
candidates["%s (%s)" % (parameter, key)] = re.sub(r"\b(%s\s*=\s*)%s" % (re.escape(parameter), re.escape(testableParameters[parameter])), r"\g<1>%s" % json.dumps(deserialized, separators=(',', ':') if ", " not in testableParameters[parameter] else None), parameters)
current[key] = original

walk()函数,通过递归的方式,检查json里面的每个值是否是符合某个数据类型.

如果该参数值能够反序列化,sqlmap会提示说

1
xx parameter is JSON deserializable. Do you want to inject inside?

例子:

比如通过表单的方式来传json类型的数据.

整个包不符合json类型,但是里面的参数值是可以被Json反序列化的.

尝试xml解析:

比如这类的数据包(为了方便看,xml参数已urldecode):

比如通过表单的方式来传xml类型的数据.

整个包不符合xml类型,但是里面的某个参数值是xml类型的数据.

解析到该类型数据,sqlmap会提示说

1
it appears that provided value for POST parameter 'xml' has boundaries. Do you want to inject inside? 

结束解析:

判断指定的参数有没在可测试的参数字典里面.

1
2
# 函数开始
testableParameters = OrderedDict()

函数开始的一开始,就定义了一个有序字典,用来存放可测试的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if conf.testParameter: # 通过-p指定可测试参数,如果给了
if not testableParameters: # 如果没有可测试参数
paramStr = ", ".join(test for test in conf.testParameter)

if len(conf.testParameter) > 1:
warnMsg = "provided parameters '%s' " % paramStr
warnMsg += "are not inside the %s" % place
logger.warn(warnMsg)
else:
parameter = conf.testParameter[0]

if not intersect(USER_AGENT_ALIASES + REFERER_ALIASES + HOST_ALIASES, parameter, True):
debugMsg = "provided parameter '%s' " % paramStr
debugMsg += "is not inside the %s" % place
logger.debug(debugMsg)

elif len(conf.testParameter) != len(testableParameters):
for parameter in conf.testParameter:
if parameter not in testableParameters:
debugMsg = "provided parameter '%s' " % parameter
debugMsg += "is not inside the %s" % place
logger.debug(debugMsg)

经过检验和解析后,testableParameters里存放着sqlmap认为可以测试的点.

例子:

一个普通的post包:

通过post传过去的参数只有sceneId

但是sqlmap认为可测试的点有很多

1
2
3
-------testableParameters--------
OrderedDict([(u'JSESSIONID', u'6006F1BD1A9B669150FBF263483BE49B'), (u'gr_user_id', u'6f346caa-beb4-49c1-bab6-4c684bb5680a'), (u'ajs_user_id', u'null'), (u'ajs_group_id', u'null'), (u'ajs_anonymous_id', u'%2265b5e8a7-ef15-4279-8897-0cf64a34baaa%22'), (u'zg_did', u'%7B%22did%22%3A%20%221693241ce78150-0e47fe537c96ac-4c322f7c-144000-1693241ce796db%22%7D'), (u'zg_153e3dce26ad42f2af4ae846edfdc259', u'%7B%22sid%22%3A%201551335266969%2C%22updated%22%3A%201551335266979%2C%22info%22%3A%201551326367365%2C%22superProperty%22%3A%20%22%7B%7D%22%2C%22platform%22%3A%20%22%7B%7D%22%2C%22utm%22%3A%20%22%7B%7D%22%2C%22referrerDomain%22%3A%20%22%22%2C%22landHref%22%3A%20%22http%3A%2F%2Fwotubbs.vanke.com%2Fwotuhome_node%2F%23%2F%22%7D'), (u'UM_distinctid', u'16955f9cc826fe-022ac9d6e823218-4c322f7c-144000-16955f9cc83645')])
-------testableParameters--------

可能就这是自动化工具与手工测试的差别


尝试对参数值解密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if testableParameters: # 有可测试的参数
for parameter, value in testableParameters.items():
if value and not value.isdigit():
for encoding in ("hex", "base64"):
try:
decoded = value.decode(encoding) # 尝试解码
if len(decoded) > MIN_ENCODED_LEN_CHECK and all(_ in string.printable for _ in decoded): #
# MIN_ENCODED_LEN_CHECK:5
warnMsg = "provided parameter '%s' " % parameter
warnMsg += "appears to be '%s' encoded" % encoding
logger.warn(warnMsg)
break
except:
pass

解密方式:

  • hex

  • base64


总结:

  • 参数里可能会包含html编码字符,例如__AMP__, __SEMICOLON__. 如果有的话,将参数提取出来.

  • 参数分隔:

    • Cookie里的参数,一般用;作为分隔符
    • Get、Post里的参数,一般用&作为分隔符
  • 参数如果含HTML标签,去掉HTML标签

  • 参数名和值分隔,一般用=分隔

  • urldecode参数名

  • 检验参数值,参数值被污染的情况:

    • 含有单引号'
    • 开头有3个9或者更多 (不知道为什么)
    • -数字 (类似id=2-1 这种payload吧)
    • 含以下关键词ANDORUNIONSELECTFROMCONCATinformation_schemaSLEEPDELAYFLOOR(RAND) (常见payload关键字)
    • 含google分析cookie前缀__UTM
  • 解析特殊情况的参数值:

    • 通过表单方式提交的xml数据
    • 通过表单方式提交的json数据
  • 尝试对参数值解密:base64, hex

  • 自动化工具发现的可测试的参数一般比手工测试发现得多.