SQLmap阅读笔记 -1
我使用的阅读工具:PyCharm
我的SQLmap版本:1.1.3.2#dev
前言:
国内我没有找到SQLmap的完整源码分析文章,就打算在我16岁的时候完成我人生的第一个小目标,读完SQLmap源码。
SQLmap是用来对付SQL漏洞的最佳工具,用Python编写。点击阅读原文查看官方GIT地址
这将是一个完整的系列且会稳定更新,作者高中生,学业繁忙。如果我断更了,我也很绝望。
这篇文章的定位是,会用PyCharm和SQLmap以及Python的入门人群,对小白将很不友好,建议小白先点亮前置技能,再来学习。
其实我之前写了一篇很照顾小白且很详细的分析笔记,然而被FB退稿了,我也很无奈啊。
我没看完,我是一边看一边写,算是直播,所以分析很可能出错和不严谨,求大佬不喷,给我点信心。
我会跳过我认为不重要的函数。
欢迎大家加我的QQ交流:1478023488
虎头:
阅读的准备工具:
PyCharm(一个很好用的IDE,只是用它就对了):https://www.jetbrains.com/pycharm/
SQLmap(SQLmap的源码都没有,你还真的只是来读文章的?):https://github.com/sqlmapproject/sqlmap
Python2.7(Python环境都没有,你是来搞笑的吗?):https://www.python.org/downloads/release/python-2712/
一个注入测试环境(一个注入测试环境):一个注入测试环境
用PyCharm打开SQLmap,然后开始设置运行参数。
run->Edit Configurations
第一个是运行文件的路径,请填sqlmap.py的路径
第二个是参数,平时怎么用就怎么写,建议加入--tamper
然后在main下个断点。 Debug模式运行
上面的蓝色三边形是运行的下一处代码。
下面的绿色三边形是运行到下一处断点。
红色四边形是停止运行。
好了,上车吧
SQLmap的框架
我们先在仔细看代码之前,先大略的看一遍代码,了解一下SQLmap的整体框架和结构,这会方便我们之后的仔细分析。
首先,我们可以发现,SQLmap的消息和提示,是用logging模块完成的。我们喜闻乐见的SQLmaplogo就是用这个模块输出的
配置信息在SQLmap目录/lib/core/log.py,现在我们即使不看具体代码,也可以通过logging这个模块猜到-v参数是怎么控制消息的输出等级的了吧。
然后我在sqlmap.py中的mian函数里面,看到了这两句代码:
sys.stdout = StdDbOut(conf.taskid, messagetype="stdout")
sys.stderr = StdDbOut(conf.taskid, messagetype="stderr")
看到这段代码,我是懵逼的。这?。。。作者你不按照基本法写代码啊!你居然赋值替换Python原生的sys.stdout和sys.stderr???
好吧,我查了半天资料,发现确实可以....
只要任何类重写了write()方法,就可以替换sys.stdout和sys.stderr
文档连接:https://docs.python.org/2/library/sys.html?highlight=sys#sys.stdout
我们暂时不管我们内心的懵逼之情,先跟进StdDbOut函数,来到SQLmap目录/lib/api.py看到StdDbOut的代码。开头没什么好说的,我们先来看write函数。
def write(self, value, status=CONTENT_STATUS.IN_PROGRESS, content_type=None):
开头就又冒出了个CONTENT_STATUS:
我们跟进,跳到SQLmap目录/lib/core/enums.py
发现一堆class.....
这熟悉的定义,这么多这种模式的class....
莫非...你是传说中的enum了?(其实文件名就已经暴露一切了)
看起来作者拿class当enum,存了一堆配置信息。
引用一下百度对enum的解释,详细信息自己去查。
enum是计算机编程语言中的一种数据类型。枚举类型:在实际问题中,有些变量的取值被限定在一个有限的范围内。例如,一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等。如果把这些量说明为整型,字符型或其它类型显然是不妥当的。
为此,C语言提供了一种称为“枚举”的类型。在“枚举”类型的定义中列举出所有可能的取值,被说明为该“枚举”类型的变量取值不能超过定义的范围。应该说明的是,枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何基本类型。
好了,我们回到SQLmap目录/lib/api.py,看完剩下的write函数。我们又发现了一个新类名....kb
if kb.partRun is not None:
content_type = PART_RUN_CONTENT_TYPES.get(kb.partRun)
kb又是什么鬼....算了,我们继续跟进!
来到SQLmap目录/lib/core/data.py
我们发现AttribDict赋值给了kb,我们只能继续跟进AttribDict函数
来到SQLmap目录/lib/core/datatype.py
好吧,我们先看第一行类的定义,发现AttribDict继承了Python原生dict,看起来作者自己封装了一个dict。但是,为啥要自己在封装一个????
我们先继续往下看
等等,我们抓到了一只野生的深拷贝函数,去掉头就可以吃,富含....呸!
看起来和自带的深拷贝函数没有区别....才怪啊!
首先,我们都知道Python原生的dist的key是不可变的,dict的key不能为list,dict,set.,value可以为任意。
另外在普及一下浅拷贝和深拷贝区别:
copy.copy 浅拷贝 只拷贝父对象,不会拷贝对象的内部的子对象。
copy.deepcopy 深拷贝 拷贝对象及其子对象
举个例子:
默认赋值操作是浅拷贝,所以我们只修改了C,但是P的值也同意改变了,你可以理解为C和P都只是一个指针,它们指向同一个数据,另外也可以看到他们的ID是一样的。
深拷贝就不会出现这种情况。
可能作者需要一个key能修改的dist且默认都是深拷贝,这个函数把凡是不能直接拷贝的比如id,私有变量,内建函数都特殊处理,其他的都用直接拷贝。看起来这就是作者大佬自己写一个dict的目的了。
好了,我们应该回头继续分析了,我也不知道跟进了多少函数,我们回到sqlmap.py文件。
开头的几个函数没什么好讲的
checkEnvironment():就是检查系统环境
setPaths(modulePath()):检测SQLmap关键文件的完整性,可读可写等等
banner():输出我们喜闻乐见的logo
我们先跟进cmdLineParser().__dict__,来到SQLmap目录/lib/parse/cmdine.py
if not argv:
argv = sys.argv
checkSystemEncoding()
_ = getUnicode(os.path.basename(argv[0]),encoding=sys.getfilesystemencoding() or UNICODE_ENCODING)
开头首先判断有没有传递argv进来,没有的话,就默认拿sys.argv作为默认值。
我们从这里就可以猜到,这个函数是用来处理我们传递的指令的。checkSystemEncoding()和剩下的一句,都是处理编码的:
def checkSystemEncoding():
if sys.getdefaultencoding() == "cp720":
try:
codecs.lookup("cp720")
except LookupError:
errMsg = "there is a known Python issue (#1616979) related "
errMsg += "to support for charset 'cp720'. Please visit "
errMsg +="'http://blog.oneortheother.info/tip/python-fix-cp720-encoding/index.html'"
errMsg += "and follow the instructions to be able to fix it"
logger.critical(errMsg)
warnMsg = "temporary switching to charset 'cp1256'"
logger.warn(warnMsg)
reload(sys)
sys.setdefaultencoding("cp1256")
看起来不支持cp720编码,如果是cp720,就临时切换为cp1256。应该是为了兼容日文系统。
我们来到parser = OptionParser(usage=usage)这句,SQLmap的作者用OptionParser模块处理的命令。在然后下面就是用OptionParser初始化各种参数,差不多千行,没什么分析的价值。
我们回到sqlmap.py,跟进cmdLineOptions.update。又来到了SQLmap目录/lib/core/data.py。发现cmdLineOptions是AttrDict的一个实例,至于AttrDict就是之前我们分析的那个作者自己封装的dist。而update方法是AttrDict的父类dict的方法,没有重写,接受一个dict或者mapping object为参数,更新调用者,在这里就是拿cmdLineParser更新cmdLineOptions。同时,我们看到cmdLineParser有一个__dict__。
那么关系我们就理清楚了,cmdLineOptions通过update方法,用cmdLineParser的__dict__属性构建了一个新对象。
在这里,我先科普一下__dict__属性还有类和父类的继承关系,本来在分析AttrDict的时候就该讲的,然而不想讲的,但是不讲就说不清,果然躲不了啊:
首先,可以看到,我创建了一个新class,然后又创建了它的两个子类,分别是CNM和FUCK。然后我通过子类CNM给W赋值为20,然后我们全部输出。发现只有子类的W改变了。
在然后我们通过他们的父类hello给W赋值为30,然后输出,发现除了CNM还是20.父类hello和它的子类FUCK都发生了改变,为什么会这样呢?
因为一个对象的属性查找顺序遵循首先查找实例对象自己,然后是类,接着是类的父类。具体我们要来了解__dict__属性。
__dict__是一个dist,key是属性名,value为属性值。Python的实例有自己的__dict__,它对应的类也有自己的__dict__
这里我输出了它们的__dist__属性,我们可以看到,CNM这个子类的__dist__里面有一个key为W,value为20的参数,所以他输出的值是20,父类的W是30,所以父类输出的是30。
那么,FUCK的__dist__里面没有W,为什么也会输出30呢?
因为一个对象的属性查找顺序遵循首先查找实例对象自己,然后是类,接着是类的父类。同样,父类的方法也会被子类继承,这就是之前作者重写AttrDict的基础
OK,现在我们知道了__dist__这个方法的功能和知道了父类和子类的关系,我们在学一个相当Python的Python的技巧,我们就明白这一行代码的意思了。
cmdLineOptions.update(cmdLineParser().__dict__)
我们回来继续看这行代码,但是我们注意到,cmdLineParser是一个函数啊......
函数怎么会有__dict__方法呢???????
我试着去掉了__dict__方法,如图:
发现报了一个类型错误。
我们之前跟进过cmdLineParser函数,知道那是用OptionParser模块写的一个处理命令的函数,我们先去看它的返回值类型。
我们可以看到返回类型的不同,.update方法只支持dist类型。
这个时候我们就可以理解为什么作者要自己封装一个AttrDict并在里面写了一个__dist__,作者用__dist__把返回类型转换为dist类型,然后传递给cmdLineOptions(cmdLineOptions父类是AttrDict)用update更新为一个class类型。
一个设计和思想极其伟大的创意!
蛇尾
以上只是对SQLmap源码的简单的一个框架分析,我们可以看出来,作者用OptionParser模块处理用户传递进来的参数。同时自己写了一个dist的子类AttrDict来替代原生dist的功能。SQLmap的全部dist都是用AttrDict来做的,同时作者也覆盖重写sys.stdout和sys.stderr。为什么覆盖重写目前还没找到原因。
框架因为分析的代码很少,暂时还给不出来。我们仔细看看我们分析了main的几行代码.....好像,10行都没有???????
但是根据我们已经分析的代码,我们心中应该也有个模型了-。-
没有的话,等我下篇文章写吧-。-
补一句,SQLmap的源码和作者简直是大佬,能开无双那种-。-
猪屁股
好了,下篇文章会在星期6写出来。
假如我没懒癌发作的话!
还有我没被拒稿和被你们喷的话
有错误直接说,但是求不喷,我还是很玻璃心的-。-
*本文作者:温酒,转载请注明来自Freebuf.COM
版权声明
本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。
发表评论