You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

721 lines
32 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 14自动化注入神器sqlmap的设计思路解析
你好,我是王昊天。
从古至今,人们为了方便自己的生活,发明出各种各样的工具。就拿扫地来说,这是我小时候最讨厌的家务活动,因为扫地时扬起的灰尘会让我十分难受,而且有的死角很难被打扫干净。扫地机器人的出现给我们带来了极大的便利,我们只要拥有它,就不需要再亲自扫地了。
在前几节课中我们学习了SQL注入的原理和方法相信你已经小试牛刀了。不知道你在做注入测试的时候是否会觉得困难呢反正我学的时候是遇到了不少困难比如绕过技巧多种多样我们几乎不可能全部记住它们就算记住了我们一一去尝试也需要很多的时间费时又伤神。
就像我们刚才说过的当问题出现时我们常常会创造出一种工具来解决对应的问题。那么有没有一款工具能帮我们自动去进行注入测试呢答案是肯定的这个工具就是sqlmap。
这节课呢我们正式开启sqlmap学习之旅深入探究这款自动化注入神器的实现原理。**首先我们要对一些知识有所了解包括如何获取软件的代码如何搭建软件的运行环境以及软件文件功能等。接下来我们会对sqlmap的工作流程做一个整体的介绍这会为我们后续学习sqlmap打好基础。**
## sqlmap
![图片](https://static001.geekbang.org/resource/image/34/9a/347a42c49613eea753d176b53396ba9a.png?wh=1895x662)
我们先来看看到底什么是sqlmap。
sqlmap是一个帮助我们自动检测sql注入是否存在的测试工具它会使用不同注入方法进行测试并将测试结果展示给我们供我们利用。这些方法都是我们之前学过的内容包括联合注入、时延注入、布尔盲注、报错注入和堆查询注入。如果你感兴趣可以去上节课寻找更加详细的介绍。
因为它的持续更新和维护所以大家普遍认为它既方便又好用从图片中我们可以看到超过4k的fork数量以及高频的源码更新。就像一枚硬币的两面这种优势也会伴随一些问题比如给我们阅读源代码增加了难度而我们要想真正去理解、掌握这款工具就必须要迎难而上对它的源代码进行剖析。
![图片](https://static001.geekbang.org/resource/image/2e/ca/2e17e69bf2856256754fc409ab2ebaca.png?wh=1751x480)
**代码获取**
我们用如下命令将sqlmap的源代码克隆下来。
```bash
git clone https://github.com/sqlmapproject/sqlmap
cd sqlmap
```
因为该过程需要访问外网,所以克隆的速度会较为缓慢,当然我们可以借助一些代理工具来加速这个过程。
这里我们分析的sqlmap版本是最新的版本1.6它于2022年初发布相比于上一代1.5.12版本,只是修复了几个编码异常,并且替换版本信息,并没有什么重大的改动。因此学习两个大版本的代码都是可以的。
![图片](https://static001.geekbang.org/resource/image/35/80/35e9758f3c123549d2a25488c9224580.png?wh=1296x363)![图片](https://static001.geekbang.org/resource/image/65/e6/65ff8f7d9aca2ac1b79afbf44548fbe6.png?wh=1243x347)
在获取到源代码之后我们还需要配置软件的运行环境这样sqlmap才能顺利的运行。下面让我们进入到环境搭建这一步骤。
**环境搭建**
这节课我们采用的分析环境是python3.10.1可以利用下面的命令新建一个虚拟的解释环境这样做有利于运行环境的隔离防止其他环境因素干扰sqlmap的执行过程。
```bash
python3 -m venv venv
```
**配置文件**
在做完环境搭建之后sqlmap已经具有运行能力了。但是我们还需要知道哪个文件才是它的配置文件即哪个文件会对它的运行产生影响。所以呢接下来我们就来看看sqlmap中有哪些重要的文件它们又有哪些功能。
首先我们一起看下sqlmap的配置文件`sqlmap.conf`
```python
# At least one of these options has to be specified to set the source to
# get target URLs from.
[Target]
# Target URL.
# Example: http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2
url =
# Direct connection to the database.
# Examples:
# mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME
# oracle://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_SID
direct =
# Parse targets from Burp or WebScarab logs
# Valid: Burp proxy (http://portswigger.net/suite/) requests log file path
# or WebScarab proxy (http://www.owasp.org/index.php/Category:OWASP_WebScarab_Project)
# 'conversations/' folder path
logFile =
# Scan multiple targets enlisted in a given textual file
bulkFile =
# Load HTTP request from a file
# Example (file content): POST /login.jsp HTTP/1.1\nHost: example.com\nUser-Agent: Mozilla/4.0\n\nuserid=joe&password=guessme
requestFile =
# At least one of these options has to be specified to set the source to
# get target URLs from.
[Target]
# Target URL.
# Example: http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2
url =
# Direct connection to the database.
# Examples:
# mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME
# oracle://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_SID
direct =
# Parse targets from Burp or WebScarab logs
# Valid: Burp proxy (http://portswigger.net/suite/) requests log file path
# or WebScarab proxy (http://www.owasp.org/index.php/Category:OWASP_WebScarab_Project)
# 'conversations/' folder path
logFile =
# Scan multiple targets enlisted in a given textual file
bulkFile =
# Load HTTP request from a file
# Example (file content): POST /login.jsp HTTP/1.1\nHost: example.com\nUser-Agent: Mozilla/4.0\n\nuserid=joe&password=guessme
requestFile =
......
# Force back-end DBMS operating system to provided value. If this option is
# set, the back-end DBMS identification process will be minimized as
# needed.
# If not set, sqlmap will detect back-end DBMS operating system
# automatically by default.
# Valid: linux, windows
os =
......
```
由于配置文件很长,所以这里没有全部展示出来。我们需要知道的是,**配置文件的参数配置可以对sqlmap程序运行的流程产生影响**。举个例子在配置文件中可以设置OS配置项sqlmap就无需探测操作系统的类型了这样就可以帮助我们在一些特定情况下优化sqlmap的执行速度。
看完配置文件之后我们回忆一下之前学过的内容在做SQL注入时都需要经历哪些步骤呢
我们知道一般来说SQL注入攻击可以分为三步首先是寻找注入点之后要选择闭合参数的位置最后要根据需求选择合适的payload来实现我们的注入攻击操作。
sqlmap和手动SQL注入的思想是相似的程序可以直接从请求的参数中获取到注入点位置的信息而闭合参数的位置和payload的选择则会因为它们的多样性变得略微复杂下面就让我们一起来看看与之相关的两个配置文件。
我们先来看sqlmap中闭合参数的配置内容这里举个例子帮助你来理解什么是闭合参数。以一个典型的SQL注入语句为例
```python
SELECT id FROM users WHERE name = '$name';
```
想要对其进行注入就需要将name参数闭合这在前两节课中有过较深入的探讨。
对于sqlmap而言data.xml.boundaries.xml就是用于闭合参数的配置文件通过该文件我们可以确定闭合元素的字符和位置等等信息了解该文件有助于我们理解sqlmap真正发送的测试payload的格式关于payload格式这部分的内容我们会在下一讲具体的讲解。
```plain
<?xml version="1.0" encoding="UTF-8"?>
<!--
Tag: <boundary>
How to prepend and append to the test ' <payload><comment> ' string.
Sub-tag: <level>
From which level check for this test.
Valid values:
1: Always (<100 requests)
2: Try a bit harder (100-200 requests)
3: Good number of requests (200-500 requests)
4: Extensive test (500-1000 requests)
5: You have plenty of time (>1000 requests)
Sub-tag: <clause>
In which clause the payload can work.
NOTE: for instance, there are some payload that do not have to be
tested as soon as it has been identified whether or not the
injection is within a WHERE clause condition.
......
```
而关于payload这一部分就让我们看一看sqlmap中payload的配置文件夹`data.xml.payloads/`。这个文件夹储存有不同注入攻击类型的payload信息。
该文件夹下的每一个文件都与程序中一个重要的结构test息息相关它是一个payload的基本信息单元每个test里面包含了一个完整的payload需要的信息 sqlmap发送的攻击载荷就是在此基础上进行加工得出的如下代码就是这个文件夹下的`boolean_blind.xml`文件的部分内容这里我们选取了完整的test结构方便你了解。
```python
<test>
<title>AND boolean-based blind - WHERE or HAVING clause (MySQL comment)</title>
<stype>1</stype>
<level>3</level>
<risk>1</risk>
<clause>1</clause>
<where>1</where>
<vector>AND [INFERENCE]</vector>
<request>
<payload>AND [RANDNUM]=[RANDNUM]</payload>
<comment>#</comment>
</request>
<respons e>
<comparison>AND [RANDNUM]=[RANDNUM1]</comparison>
</response>
<details>
<dbms>MySQL</dbms>
</details>
</test>
```
可以看到它的结构内部定义攻击类型、方式、生效位置、关联数据库等等信息。
至此我们大致了解了sqlmap工作的底层依赖。下一步只需要分析和掌握sqlmap的运行过程日后在使用中我们就可以更加得心应手。
sqlmap作为一个规模较大的工具在启动过程中首先会执行一些初始化操作下面就让我们一起学习sqlmap的初始化过程通过这部分内容你可以明白sqlmap在启动之初做了哪些事情。
## 初始化过程
sqlmap的初始化过程包含四个步骤环境初始化、命令行参数的解析、全局变量的赋值以及运行环境的检查。我们将结合代码和图片注释顺着sqlmap代码运行的顺序详细讲述每一个步骤。下面让我们从环境初始化开始吧。
**环境初始化**
首先我们会发现sqlmap的入口文件是sqlmap.py这个文件的main如下所示
```python
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass
except SystemExit:
raise
except:
traceback.print_exc()
finally:
if threading.active_count() > 1:
os._exit(getattr(os, "_exitcode", 0))
else:
sys.exit(getattr(os, "_exitcode", 0))
else:
__import__("lib.controller.controller")
```
根据main函数的名字我们可以知道程序的核心逻辑一定被封装在了mian函数里面。因此我们可以进入到mian函数里查看。
```python
def main():
try:
dirtyPatches()
resolveCrossReferences()
checkEnvironment()
setPaths(modulePath())
banner()
args = cmdLineParser()
cmdLineOptions.update(args.__dict__ if hasattr(args, "__dict__") else args)
initOptions(cmdLineOptions)
if checkPipedInput():
conf.batch = True
if conf.get("api"):
from lib.utils.api import StdDbOut
from lib.utils.api import setRestAPILog
sys.stdout = StdDbOut(conf.taskid, messagetype="stdout")
sys.stderr = StdDbOut(conf.taskid, messagetype="stderr")
setRestAPILog()
conf.showTime = True
dataToStdout("[!] legal disclaimer: %s\n\n" % LEGAL_DISCLAIMER, forceOutput=True)
dataToStdout("[*] starting @ %s\n\n" % time.strftime("%X /%Y-%m-%d/"), forceOutput=True)
init()
```
进入到mian函数之后我们就会发现环境初始化初始化过程的代码就在这里该过程引入了两个重要数据容器的映射conf和kb。他们都是全局的环境变量是存储程序运行中间数据和信息数据的容器其中sqlmap的结果信息、注入信息、控制信息都是通过它们进行读写来完成程序的执行的。这部分内容我们会在下节课展开。
```python
from lib.core.data import conf
from lib.core.data import kb
```
在这里为了你更好地理解它们我们先来看看这两个数据结构是什么这里我们可以找到lib.core.data.py文件进行观察。
```python
# object to share within function and classes command
# line options and settings
conf = AttribDict()
# object to share within function and classes results
kb = AttribDict()
```
可以发现,这两个数据结构是作者自己封装的函数,封装的主要目的是实现深拷贝(`__deepcopy__` 这个魔法函数。通过这种方式Python内容数据的拷贝操作会被大大优化。
```python
class AttribDict(dict):
......
def __deepcopy__(self, memo):
retVal = self.__class__()
memo[id(self)] = retVal
for attr in dir(self):
if not attr.startswith('_'):
value = getattr(self, attr)
if not isinstance(value, (types.BuiltinFunctionType, types.FunctionType, types.MethodType)):
setattr(retVal, attr, copy.deepcopy(value, memo))
for key, value in self.items():
retVal.__setitem__(key, copy.deepcopy(value, memo))
return retVal
```
接下来我们继续观察main函数。
```python
dirtyPatches() # 补丁函数
resolveCrossReferences() # 消除交叉引用
checkEnvironment() # 检查环境
setPaths(modulePath()) # 如果使用py2exe 作为file获取程序路径的替代 设置绝对路径
banner()
```
我们可以看到程序在进入到try语句之后首先会执行几个函数。
第一个函数是dirtyPatches() ,它是一个补丁函数。
```python
def dirtyPatches():
"""
Place for "dirty" Python related patches
"""
# accept overly long result lines (e.g. SQLi results in HTTP header responses)
_http_client._MAXLINE = 1 * 1024 * 1024
# prevent double chunked encoding in case of sqlmap chunking (Note: Python3 does it automatically if 'Content-length' is missing)
if six.PY3:
if not hasattr(_http_client.HTTPConnection, "__send_output"):
_http_client.HTTPConnection.__send_output = _http_client.HTTPConnection._send_output
def _send_output(self, *args, **kwargs):
if conf.get("chunked") and "encode_chunked" in kwargs:
kwargs["encode_chunked"] = False
self.__send_output(*args, **kwargs)
_http_client.HTTPConnection._send_output = _send_output
# add support for inet_pton() on Windows OS
if IS_WIN:
from thirdparty.wininetpton import win_inet_pton
# Reference: https://github.com/nodejs/node/issues/12786#issuecomment-298652440
codecs.register(lambda name: codecs.lookup("utf-8") if name == "cp65001" else None)
# Reference: http://bugs.python.org/issue17849
if hasattr(_http_client, "LineAndFileWrapper"):
def _(self, *args):
return self._readline()
_http_client.LineAndFileWrapper._readline = _http_client.LineAndFileWrapper.readline
_http_client.LineAndFileWrapper.readline = _
# to prevent too much "guessing" in case of binary data retrieval
thirdparty.chardet.universaldetector.MINIMUM_THRESHOLD = 0.90
match = re.search(r" --method[= ](\w+)", " ".join(sys.argv))
if match and match.group(1).upper() != PLACE.POST:
PLACE.CUSTOM_POST = PLACE.CUSTOM_POST.replace("POST", "%s (body)" % match.group(1))
# https://github.com/sqlmapproject/sqlmap/issues/4314
try:
os.urandom(1)
except NotImplementedError:
if six.PY3:
os.urandom = lambda size: bytes(random.randint(0, 255) for _ in range(size))
else:
os.urandom = lambda size: "".join(chr(random.randint(0, 255)) for _ in xrange(size))
```
之所称它为补丁函数呢是因为这个函数功能主要用来处理sqlmap的一些历史信息。例如支持Python2升级Python3、支持Windows平台的inet\_pton()函数、限制httplib的最大行长度等等。这些操作对sqlmap的实际功能影响并不是特别大属于保证用户体验和系统设置的正常选项我们不需要过多关心。
接下来是resolveCrossReferences函数它的作用是消除交叉引用。我们通过下面这个示例理解下什么是交叉引用。
```python
```python
a.py
from b import b_var
a_var = 1
b.py
from a import a_var
b_var = 2
```
```
这就是一个交叉引用的示例,文件`a.py`和`b.py`互相引用导致了运行的python解释器报错。为了避免交叉引用引起这样的问题这里我们选择通过`resolveCrossReferences()`函数来消除交叉引用。
```python
def resolveCrossReferences():
“””
Place for cross-reference resolution
“””
lib.core.threads.isDigit = isDigit
lib.core.threads.readInput = readInput
lib.core.common.getPageTemplate = getPageTemplate
lib.core.convert.filterNone = filterNone
lib.core.convert.isListLike = isListLike
lib.core.convert.shellExec = shellExec
lib.core.convert.singleTimeWarnMessage = singleTimeWarnMessage
lib.core.option._pympTempLeakPatch = pympTempLeakPatch
lib.request.connect.setHTTPHandlers = _setHTTPHandlers
lib.utils.search.setHTTPHandlers = _setHTTPHandlers
lib.controller.checks.setVerbosity = setVerbosity
lib.utils.sqlalchemy.getSafeExString = getSafeExString
thirdparty.ansistrm.ansistrm.stdoutEncode = stdoutEncode
```
sqlmap在消除交叉引用之后程序会开始运行检查环境的函数checkEnvironment()。
```python
def checkEnvironment():
try:
os.path.isdir(modulePath())
except UnicodeEncodeError:
errMsg = your system does not properly handle non-ASCII paths.
errMsg += Please move the sqlmaps directory to the other location
logger.critical(errMsg)
raise SystemExit
if LooseVersion(VERSION) < LooseVersion(1.0):
errMsg = your runtime environment (e.g. PYTHONPATH) is
errMsg += broken. Please make sure that you are not running
errMsg += newer versions of sqlmap with runtime scripts for older
errMsg += versions
logger.critical(errMsg)
raise SystemExit
# Patch for pip (import) environment
if sqlmap.sqlmap in sys.modules:
for _ in (cmdLineOptions, conf, kb):
globals()[_] = getattr(sys.modules[lib.core.data], _)
for _ in (SqlmapBaseException, SqlmapShellQuitException, SqlmapSilentQuitException, SqlmapUserQuitException):
globals()[_] = getattr(sys.modules[lib.core.exception], _)
```
checkEnvironment函数会对当前的环境进行初步检查检查的内容包括存放sqlmap的路径是否包含非ASCII字符以及sqlmap的版本是否小于1.0。
下一个函数`setPaths(modulePath())`是用来给项目中的文件夹和文件设置绝对路径的。
```python
def setPaths(rootPath):
"""
Sets absolute paths for project directories and files
"""
paths.SQLMAP_ROOT_PATH = rootPath
# sqlmap paths
paths.SQLMAP_DATA_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "data")
paths.SQLMAP_EXTRAS_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "extra")
paths.SQLMAP_SETTINGS_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "lib", "core", "settings.py")
paths.SQLMAP_TAMPER_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "tamper")
paths.SQLMAP_PROCS_PATH = os.path.join(paths.SQLMAP_DATA_PATH, "procs")
paths.SQLMAP_SHELL_PATH = os.path.join(paths.SQLMAP_DATA_PATH, "shell")
paths.SQLMAP_TXT_PATH = os.path.join(paths.SQLMAP_DATA_PATH, "txt")
paths.SQLMAP_UDF_PATH = os.path.join(paths.SQLMAP_DATA_PATH, "udf")
paths.SQLMAP_XML_PATH = os.path.join(paths.SQLMAP_DATA_PATH, "xml")
paths.SQLMAP_XML_BANNER_PATH = os.path.join(paths.SQLMAP_XML_PATH, "banner")
paths.SQLMAP_XML_PAYLOADS_PATH = os.path.join(paths.SQLMAP_XML_PATH, "payloads")
# sqlmap files
paths.COMMON_COLUMNS = os.path.join(paths.SQLMAP_TXT_PATH, "common-columns.txt")
paths.COMMON_FILES = os.path.join(paths.SQLMAP_TXT_PATH, "common-files.txt")
paths.COMMON_TABLES = os.path.join(paths.SQLMAP_TXT_PATH, "common-tables.txt")
paths.COMMON_OUTPUTS = os.path.join(paths.SQLMAP_TXT_PATH, 'common-outputs.txt')
paths.SQL_KEYWORDS = os.path.join(paths.SQLMAP_TXT_PATH, "keywords.txt")
paths.SMALL_DICT = os.path.join(paths.SQLMAP_TXT_PATH, "smalldict.txt")
paths.USER_AGENTS = os.path.join(paths.SQLMAP_TXT_PATH, "user-agents.txt")
paths.WORDLIST = os.path.join(paths.SQLMAP_TXT_PATH, "wordlist.tx_")
paths.ERRORS_XML = os.path.join(paths.SQLMAP_XML_PATH, "errors.xml")
paths.BOUNDARIES_XML = os.path.join(paths.SQLMAP_XML_PATH, "boundaries.xml")
paths.QUERIES_XML = os.path.join(paths.SQLMAP_XML_PATH, "queries.xml")
paths.GENERIC_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "generic.xml")
paths.MSSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "mssql.xml")
paths.MYSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "mysql.xml")
paths.ORACLE_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "oracle.xml")
paths.PGSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "postgresql.xml")
for path in paths.values():
if any(path.endswith(_) for _ in (".txt", ".xml", ".tx_")):
checkFile(path)
if IS_WIN:
# Reference: https://pureinfotech.com/list-environment-variables-windows-10/
if os.getenv("LOCALAPPDATA"):
paths.SQLMAP_HOME_PATH = os.path.expandvars("%LOCALAPPDATA%\\sqlmap")
elif os.getenv("USERPROFILE"):
paths.SQLMAP_HOME_PATH = os.path.expandvars("%USERPROFILE%\\Local Settings\\sqlmap")
else:
paths.SQLMAP_HOME_PATH = os.path.join(os.path.expandvars(os.path.expanduser("~")), "sqlmap")
else:
paths.SQLMAP_HOME_PATH = os.path.join(os.path.expandvars(os.path.expanduser("~")), ".sqlmap")
if not os.path.isdir(paths.SQLMAP_HOME_PATH):
if "XDG_DATA_HOME" in os.environ:
paths.SQLMAP_HOME_PATH = os.path.join(os.environ["XDG_DATA_HOME"], "sqlmap")
else:
paths.SQLMAP_HOME_PATH = os.path.join(os.path.expandvars(os.path.expanduser("~")), ".local", "share", "sqlmap")
paths.SQLMAP_OUTPUT_PATH = getUnicode(paths.get("SQLMAP_OUTPUT_PATH", os.path.join(paths.SQLMAP_HOME_PATH, "output")), encoding=sys.getfilesystemencoding() or UNICODE_ENCODING)
paths.SQLMAP_DUMP_PATH = os.path.join(paths.SQLMAP_OUTPUT_PATH, "%s", "dump")
paths.SQLMAP_FILES_PATH = os.path.join(paths.SQLMAP_OUTPUT_PATH, "%s", "files")
# History files
paths.SQLMAP_HISTORY_PATH = getUnicode(os.path.join(paths.SQLMAP_HOME_PATH, "history"), encoding=sys.getfilesystemencoding() or UNICODE_ENCODING)
paths.API_SHELL_HISTORY = os.path.join(paths.SQLMAP_HISTORY_PATH, "api.hst")
paths.OS_SHELL_HISTORY = os.path.join(paths.SQLMAP_HISTORY_PATH, "os.hst")
paths.SQL_SHELL_HISTORY = os.path.join(paths.SQLMAP_HISTORY_PATH, "sql.hst")
paths.SQLMAP_SHELL_HISTORY = os.path.join(paths.SQLMAP_HISTORY_PATH, "sqlmap.hst")
paths.GITHUB_HISTORY = os.path.join(paths.SQLMAP_HISTORY_PATH, "github.hst")
```
设置完绝对路径后我们可以看到sqlmap运行了一个banner函数它对于sqlmap的运行没有实际的作用但是作者可以通过banner函数绘制一幅字符画是不是很酷
![图片](https://static001.geekbang.org/resource/image/a5/c2/a5dc76a4f24686649413121e108d58c2.png?wh=1556x290)
下方是banner函数的代码你在构建自己工具的时候也可以考虑引入这种有趣的小函数为你的工具增添个性化色彩让它看起来又酷又实用。
```python
def banner():
"""
This function prints sqlmap banner with its version
"""
if not any(_ in sys.argv for _ in ("--version", "--api")) and not conf.get("disableBanner"):
result = BANNER
if not IS_TTY or any(_ in sys.argv for _ in ("--disable-coloring", "--disable-colouring")):
result = clearColors(result)
elif IS_WIN:
coloramainit()
dataToStdout(result, forceOutput=True)
```
**命令行参数解析**
经过上面的函数执行之后,基本环境已经配置好了,程序将开始处理用户的输入,包括有命令行参数以及配置的文件参数。
```python
def main():
"""
Main function of sqlmap when running from command line.
"""
try:
dirtyPatches()
resolveCrossReferences()
checkEnvironment()
setPaths(modulePath())
banner()
# Store original command line options for possible later restoration
args = cmdLineParser()
cmdLineOptions.update(args.__dict__ if hasattr(args, "__dict__") else args)
initOptions(cmdLineOptions)
```
**全局变量赋值与环境检查**
在接收用户的输入参数之后程序开始进入init方法这个方法是一个重要的前期配置方法里面包含大量的配置操作这些操作我们可以进入函数中观察。
```python
def init():
_useWizardInterface() # 启动引导模式
setVerbosity() # 设置默认的日志输出详细度
_saveConfig() # 保存当前扫描的配置
_setRequestFromFile() # 解析 request file 的文件内容
_cleanupOptions() # 为 conf 中的参数赋初值
_cleanupEnvironment()
_purge() # 清空 sqlmap 相关信息
_checkDependencies() # 检查是否缺失依赖
_createHomeDirectories() # 创建 output、history 目录
_createTemporaryDirectory() # 创建临时目录
_basicOptionValidation() # 验证部分参数值是否符合预期
_setProxyList() # 解析 proxy file 的文件内容
_setTorProxySettings() # 设置 tor 代理
_setDNSServer() # 创建 DNS 服务器
_adjustLoggingFormatter() # 初始化日志格式化工具
_setMultipleTargets() # 解析 burp log 的文件内容
_listTamperingFunctions() # 输出 tamper 的详细信息
_setTamperingFunctions() # 设置后续要调用的 tamper
_setPreprocessFunctions() # 设置处理请求的函数
_setPostprocessFunctions() # 设置处理响应的函数
_setTrafficOutputFP() # 创建 trafficFile 并获取文件句柄
_setupHTTPCollector() # 创建 HAR 文件
_setHttpChunked() # 设置 chunked
_checkWebSocket() # 检查 websocket 环境是否正常
parseTargetDirect() # 解析数据库链接
if any((conf.url, conf.logFile, conf.bulkFile, conf.requestFile, conf.googleDork, conf.stdinPipe)):
_setHostname() # 设置 conf 中的 hostname
_setHTTPTimeout() # 设置请求最大超时时间
_setHTTPExtraHeaders() # 设置请求的 headers
_setHTTPCookies() # 设置请求的 cookies
_setHTTPReferer() # 设置请求的 referer
_setHTTPHost() # 设置请求的 host
_setHTTPUserAgent() # 设置请求的 UA
_setHTTPAuthentication() # 设置请求的认证信息
_setHTTPHandlers() # 设置对应的请求处理类
_setDNSCache() # 设置 dns 缓存
_setSocketPreConnect()
_setSafeVisit()
_doSearch() # 处理 Google Dork 解析
_setStdinPipeTargets() # 从 pipeline 中获取 targets
_setBulkMultipleTargets() # 从文本中获取 targets
_checkTor() # 检查 tor 代理
_setCrawler() # 设置爬虫信息
_findPageForms() # 寻找页面中的表单
_setDBMS() # 设置 DBMS
_setTechnique() # 设置检测类型
_setThreads() # 设置线程数
_setOS() # 设置操作系统类型
_setWriteFile() # 设置文件写入信息
_setMetasploit() # 设置 MSF 信息
_setDBMSAuthentication() # 设置 DBMS 的认证信息
loadBoundaries() # 加载 Boundaries
loadPayloads() # 加载 Payloads
_setPrefixSuffix() # 设置新的 prefix 和sufix
update() # 更新 sqlmap
_loadQueries() # 加载 queries
```
其实我们只需要根据后面的注释,了解每个函数大概实现的功能就可以了,它们有些与初始化配置文件相关,有些与实际攻击过程相关。实际上,这里只是会初步解析命令行传入的参数,并不涉及到大部分函数的调用过程,在这里我们不需要理解的很细致,我们的核心关注点应该在注入逻辑上。
```python
init()
if not conf.updateAll:
# Postponed imports (faster start)
if conf.smokeTest:
from lib.core.testing import smokeTest
os._exitcode = 1 - (smokeTest() or 0)
elif conf.vulnTest:
from lib.core.testing import vulnTest
os._exitcode = 1 - (vulnTest() or 0)
else:
from lib.controller.controller import start
if conf.profile:
from lib.core.profiling import profile
globals()["start"] = start
profile()
else:
try:
if conf.crawlDepth and conf.bulkFile:
targets = getFileItems(conf.bulkFile)
for i in xrange(len(targets)):
target = None
try:
kb.targets = OrderedSet()
target = targets[i]
if not re.search(r"(?i)\Ahttp[s]*://", target)
target = "http://%s" % target
infoMsg = "starting crawler for target URL '%s' (%d/%d)" % (target, i + 1, len(targets))
logger.info(infoMsg)
crawl(target)
except Exception as ex:
if target and not isinstance(ex, SqlmapUserQuitException):
errMsg = "problem occurred while crawling '%s' ('%s')" % (target, getSafeExString(ex))
logger.error(errMsg)
else:
raise
else:
if kb.targets:
start()
```
学习完毕init方法之后我们就完成了整个初始化过程。
## 总结
这节课我们学习了自动化SQL注入测试工具sqlmap的设计思路。
作为业内知名且常用的SQL自动化注入工具sqlmap已经持续维护了超过10年的时间。作为有梦想的工程师我们不仅需要掌握如何使用sqlmap更要学习它的设计思想和工作原理站在巨人的肩膀上才能帮助我们看的更远。
sqlmap的代码量十分庞大因为掌握其核心设计思想和工作原理就十分重要这会成为我们深入代码逻辑探索的风向标。因此在了解如何获取sqlmap代码以及如何搭建sqlmap的运行环境后我们进一步学习了sqlmap设计原理的相关知识比如sqlmap的配置文件介绍等。
接下来与大部分开源软件的设计思路类似sqlmap在真正开始工作前需要做大量的初始化工作因此我们对sqlmap的初始化流程进行了梳理。经过分析总结出它主要包含有全局变量初始化、命令行参数解析、全局变量赋值以及环境检查这四个通用的初始化步骤然后我们又具体学习了这四个流程是如何实现的。
在学习完sqlmap的初始化后很快我们就会进入sqlmap的工作流程学习这时我们需要重新审视sqlmap的设计架构了解我们所处的位置。
![图片](https://static001.geekbang.org/resource/image/0f/bc/0fbeefeec2d41ffd8949e164076180bc.png?wh=1436x768)
这是我整理的一幅sqlmap工作原理图在本节课我们学习了sqlmap工作前的初始化流程这些内容可以帮助你认识到sqlmap设计时的底层逻辑。下节课我们将正式开始学习sqlmap的自动化注入功能它的起点是预注入也是我们在前几节课程中所讲的起手式阶段。
## 思考
sqlmap的初始化流程有什么值得改进的地方吗
欢迎在评论区留下你的思考,我们下节课再见。