数据库识别

简介

在SQL注入攻击中,最重要的是知道应用正在使用的数据库服务器。没有这一信息,就不可能修改查询以注入信息并获取自己所感兴趣的数据。

其中有两条思路,其一是通过报错信息来获取数据库信息。其二是通过不同数据库服务器所使用的SQL方言上的细微差异来判断。

本篇主要介绍第一种。

举个例子

大多数情况下,要了解后台数据库服务器,只需查看一条足够详细的错误消息即可。

例如一个单引号使数据库服务器将单引号后面的字符看做字符串而非SQL代码,会产生一条错误语句。对于Microsoft SQL Server来说,如上图所示。

再举个例子

MySQL数据库可能会产生如下报错:

这条错误信息同样可以清楚的显示出改数据库类型。

当然有时,关键信息并非来自数据库服务器本身,而是来自于访问数据库的技术。例如:

1
pg_query(): Query failed: ERROR: unterminated quoted string at or near "'" at character 69 in /var/www/php/somepge.php on line 20

这里并没有提及数据库服务器技术,但是有一个特定数据库产品所独有的错误代码。PHP使用pg_query函数对PostgreSQL数据库执行查询,因此可以推断出数据库是PostgreSQL。

获取标志信息

只获取数据库类型是不够的,还需要获取数据库版本。这里给出一个在返回各种数据库服务器时对应查询的表:

数据库服务器 查询
Microsoft SQL Server SELECT @@version
MySQL SELECT version()
MySQL SELECT @@version
Oracle SELECT banner FROM v$version
Oracle SELECT banner FROM v$version WHERE rownum=1
PostgreSQL SELECT version()

更多的错误信息可以通过Google来进行搜索,获取错误代码,函数名或者看上去难懂的字符串。可以帮助你更加快速地辨别后台数据库。

简单分析一下SQLMap是如何进行数据库识别的

SQLMap版本: 1.1.8

  • 在 ./lib/request/connect.py 中,getPage(**kwargs)函数用于获取URL连接,并返回页面信息。
  • 其中 processResponse(page, responseHeaders, status) 会调用 parseResponse 函数,从而调用 htmlParser 函数用于解析获取的页面信息。而 parseResponse 的注释是这样写的:使用要解析的页面来提供 htmlFp(通过Web应用程序返回的DBMS错误消息来确定后端的DBMS指纹)列表和文件绝对路径的设置。
  • 来看一下 htmlParser 函数,其中调用了 ERRORS_XML,这个文件就比较重要了,可以看到该文件位于 ./libexec/xml/errors.xml 中,而paths.ERRORS_XML这一变量的就是SQLMAP用来识别的指纹配置文件路径。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def htmlParser(page):
"""
This function calls a class that parses the input HTML page to
fingerprint the back-end database management system
"""

xmlfile = paths.ERRORS_XML
handler = HTMLHandler(page)
key = hash(page)

if key in kb.cache.parsedDbms:
retVal = kb.cache.parsedDbms[key]
if retVal:
handler._markAsErrorPage()
return retVal

parseXmlFile(xmlfile, handler)

if handler.dbms and handler.dbms not in kb.htmlFp:
kb.lastParserStatus = handler.dbms
kb.htmlFp.append(handler.dbms)
else:
kb.lastParserStatus = None

kb.cache.parsedDbms[key] = handler.dbms

# generic SQL warning/error messages
if re.search(r"SQL (warning|error|syntax)", page, re.I):
handler._markAsErrorPage()

return handler.dbms

这一配置文件其实比较简单,其实也就是一些对应数据库的正则。SQLMap在解析 errors.xml 的时候,然后根据 regexp 中的正则去匹配当前的页面信息然后去确定当前的数据库。

  • 可以看到 htmlParser 函数调用了HTMLHandler 类,这也是最终实现的类。这个类定义了解析传入页面后端 DBMS 指纹的方法。

至此,SQLMap大致的数据库指纹识别基本确定,当然这只是对于报错注入的一些判断,同样的还有其他注入方式的数据库判断。大家可以阅读一下SQLMap的源码,根据自己的需求添加一些规则使得工具变得更加强大顺手。

简单验证脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import requests
import argparse
import re

# 导入xml中的ElementTree方法,该方法类似于一个
# 轻量级的DOM,具有方便友好的API。代码可用性好,
# 速度快,消耗内存少。
import xml.etree.ElementTree as ET

headers = {
"user-agent": "Mozilla/4.0 (compatible; \
MSIE 7.0; Windows NT 5.1; 360SE)"}


def htmlParser(url):
r = requests.get(url, headers=headers)
html = str(r.content)

# 这里直接使用的 SQLMap 中的 errors.xml
# 规则集,也可以自己对规则集进行补充修改,使之
# 能够更好的匹配数据库。
tree = ET.parse('./errors.xml')
root = tree.getroot() # 获取根节点

for child in root: # 获取子节点
for sub in child: # 获取子孙节点
key = sub.attrib['regexp'] # 获取属性
try:
# 正则匹配页面中的信息
# 并打印出匹配后的数据库类型
if re.search(key, html) is not None:
print(child.attrib['value'])
except Exception:
pass


def main():
parser = argparse.ArgumentParser('python %prog ' + '-h <manual>')
parser.add_argument('-u', dest='tgt_url',
help='input the target url')

args = parser.parse_args()

url = args.tgt_url

if url:
if re.search(r"\'", url) is None:
url += "'"
htmlParser(url)


if __name__ == '__main__':
main()