14 自动化注入神器(一):sqlmap的设计思路解析 你好,我是王昊天。

从古至今,人们为了方便自己的生活,发明出各种各样的工具。就拿扫地来说,这是我小时候最讨厌的家务活动,因为扫地时扬起的灰尘会让我十分难受,而且有的死角很难被打扫干净。扫地机器人的出现给我们带来了极大的便利,我们只要拥有它,就不需要再亲自扫地了。

在前几节课中,我们学习了SQL注入的原理和方法,相信你已经小试牛刀了。不知道你在做注入测试的时候是否会觉得困难呢?反正我学的时候是遇到了不少困难,比如,绕过技巧多种多样,我们几乎不可能全部记住它们,就算记住了我们一一去尝试也需要很多的时间,费时又伤神。

就像我们刚才说过的,当问题出现时,我们常常会创造出一种工具,来解决对应的问题。那么有没有一款工具,能帮我们自动去进行注入测试呢?答案是肯定的,这个工具就是sqlmap。

这节课呢,我们正式开启sqlmap学习之旅,深入探究这款自动化注入神器的实现原理。首先我们要对一些知识有所了解,包括如何获取软件的代码,如何搭建软件的运行环境,以及软件文件功能等。接下来,我们会对sqlmap的工作流程做一个整体的介绍,这会为我们后续学习sqlmap打好基础。

sqlmap

图片

我们先来看看到底什么是sqlmap。

sqlmap是一个帮助我们自动检测sql注入是否存在的测试工具,它会使用不同注入方法进行测试,并将测试结果展示给我们,供我们利用。这些方法都是我们之前学过的内容,包括联合注入、时延注入、布尔盲注、报错注入和堆查询注入。如果你感兴趣,可以去上节课寻找更加详细的介绍。

因为它的持续更新和维护,所以大家普遍认为它既方便又好用,从图片中我们可以看到超过4k的fork数量,以及高频的源码更新。就像一枚硬币的两面,这种优势也会伴随一些问题,比如给我们阅读源代码增加了难度,而我们要想真正去理解、掌握这款工具,就必须要迎难而上,对它的源代码进行剖析。

图片

代码获取

我们用如下命令将sqlmap的源代码克隆下来。 git clone https://github.com/sqlmapproject/sqlmap cd sqlmap

因为该过程需要访问外网,所以克隆的速度会较为缓慢,当然我们可以借助一些代理工具来加速这个过程。

这里我们分析的sqlmap版本是最新的版本1.6,它于2022年初发布,相比于上一代1.5.12版本,只是修复了几个编码异常,并且替换版本信息,并没有什么重大的改动。因此学习两个大版本的代码都是可以的。

图片图片

在获取到源代码之后,我们还需要配置软件的运行环境,这样sqlmap才能顺利的运行。下面让我们进入到环境搭建这一步骤。

环境搭建

这节课,我们采用的分析环境是python3.10.1,可以利用下面的命令新建一个虚拟的解释环境,这样做有利于运行环境的隔离,防止其他环境因素干扰sqlmap的执行过程。 python3 -m venv venv

配置文件

在做完环境搭建之后,sqlmap已经具有运行能力了。但是我们还需要知道哪个文件才是它的配置文件,即哪个文件会对它的运行产生影响。所以呢,接下来我们就来看看,sqlmap中有哪些重要的文件,它们又有哪些功能。

首先我们一起看下sqlmap的配置文件

sqlmap.conf : /# 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注入语句为例: SELECT id FROM users WHERE name = ‘$name’;

想要对其进行注入,就需要将name参数闭合,这在前两节课中有过较深入的探讨。

对于sqlmap而言,data.xml.boundaries.xml就是用于闭合参数的配置文件,通过该文件我们可以确定闭合元素的字符和位置等等信息,了解该文件有助于我们理解sqlmap真正发送的测试payload的格式,关于payload格式这部分的内容我们会在下一讲具体的讲解。 <?xml version=”1.0” encoding=”UTF-8”?> <!– Tag: How to prepend and append to the test ' ' string. Sub-tag: 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: 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结构方便你了解。

AND boolean-based blind - WHERE or HAVING clause (MySQL comment) 1 3 1 1 1 AND [INFERENCE] AND [RANDNUM]=[RANDNUM] /# AND [RANDNUM]=[RANDNUM1] </response>
MySQL
</test> 可以看到它的结构内部定义攻击类型、方式、生效位置、关联数据库等等信息。- 至此,我们大致了解了sqlmap工作的底层依赖。下一步只需要分析和掌握sqlmap的运行过程,日后在使用中我们就可以更加得心应手。 sqlmap作为一个规模较大的工具,在启动过程中首先会执行一些初始化操作,下面就让我们一起学习sqlmap的初始化过程,通过这部分内容你可以明白sqlmap在启动之初做了哪些事情。 ## 初始化过程 sqlmap的初始化过程包含四个步骤:环境初始化、命令行参数的解析、全局变量的赋值,以及运行环境的检查。我们将结合代码和图片注释,顺着sqlmap代码运行的顺序,详细讲述每一个步骤。下面让我们从环境初始化开始吧。 **环境初始化** 首先我们会发现,sqlmap的入口文件是sqlmap.py,这个文件的main如下所示: 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函数里查看。 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的结果信息、注入信息、控制信息都是通过它们进行读写来完成程序的执行的。这部分内容我们会在下节课展开。 from lib.core.data import conf from lib.core.data import kb 在这里,为了你更好地理解它们,我们先来看看这两个数据结构是什么,这里我们可以找到lib.core.data.py文件进行观察。 /# 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内容数据的拷贝操作会被大大优化。 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函数。 dirtyPatches() /# 补丁函数 resolveCrossReferences() /# 消除交叉引用 checkEnvironment() /# 检查环境 setPaths(modulePath()) /# 如果使用py2exe 作为file获取程序路径的替代 设置绝对路径 banner() 我们可以看到,程序在进入到try语句之后,首先会执行几个函数。 第一个函数是dirtyPatches() ,它是一个补丁函数。 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 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()。 def checkEnvironment(): try: os.path.isdir(modulePath()) except UnicodeEncodeError: errMsg = “your system does not properly handle non-ASCII paths. “ errMsg += “Please move the sqlmap’s 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()) 是用来给项目中的文件夹和文件设置绝对路径的。 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://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Web%e6%bc%8f%e6%b4%9e%e6%8c%96%e6%8e%98%e5%ae%9e%e6%88%98/assets/ffc5dd6318d7422bac514b844c795142.jpg) 下方是banner函数的代码,你在构建自己工具的时候也可以考虑引入这种有趣的小函数,为你的工具增添个性化色彩,让它看起来又酷又实用。 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) **命令行参数解析** 经过上面的函数执行之后,基本环境已经配置好了,程序将开始处理用户的输入,包括有命令行参数以及配置的文件参数。 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方法,这个方法是一个重要的前期配置方法,里面包含大量的配置操作,这些操作我们可以进入函数中观察。 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 其实我们只需要根据后面的注释,了解每个函数大概实现的功能就可以了,它们有些与初始化配置文件相关,有些与实际攻击过程相关。实际上,这里只是会初步解析命令行传入的参数,并不涉及到大部分函数的调用过程,在这里我们不需要理解的很细致,我们的核心关注点应该在注入逻辑上。 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://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Web%e6%bc%8f%e6%b4%9e%e6%8c%96%e6%8e%98%e5%ae%9e%e6%88%98/assets/e22b18fbee4f4843b20542e1224409c2.jpg) 这是我整理的一幅sqlmap工作原理图,在本节课我们学习了sqlmap工作前的初始化流程,这些内容可以帮助你认识到sqlmap设计时的底层逻辑。下节课我们将正式开始学习sqlmap的自动化注入功能,它的起点是预注入,也是我们在前几节课程中所讲的起手式阶段。 ## 思考 sqlmap的初始化流程有什么值得改进的地方吗? 欢迎在评论区留下你的思考,我们下节课再见。 # 参考资料 https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Web%e6%bc%8f%e6%b4%9e%e6%8c%96%e6%8e%98%e5%ae%9e%e6%88%98/14%20%e8%87%aa%e5%8a%a8%e5%8c%96%e6%b3%a8%e5%85%a5%e7%a5%9e%e5%99%a8%ef%bc%88%e4%b8%80%ef%bc%89%ef%bc%9asqlmap%e7%9a%84%e8%ae%be%e8%ae%a1%e6%80%9d%e8%b7%af%e8%a7%a3%e6%9e%90.md * any list {:toc}