sqlmap学习及代码简析

书写是为了更好地思考 writing-is-better-thinking——摘自刘未鹏的博客

感觉大学里学得很杂,每次学完后一搁置就忘了许多,以后多写写博客吧


简介

直观感受(需要翻墙):How to hack a website using sqlmap on Kali Linux - YouTube

sqlmap用户手册

SQLMAP 实例COOKBOOK

第一篇将sqlmap的文档(doc/README.pdf)进行了选择性地翻译,能够了解很多基本参数
第二篇是一个白帽子自己实战例子,均使用了sqlmap,使用起来不难,但找到这些漏洞的过程与技巧就不得而知了…


简单使用方法

python sqlmap.py -u "http://xx.com?a=1&b=2&c=3"  --tables

这样就可以对a,b,c三个GET参数进行检测了,如果能够注入,会显示得到的表名
稍微多控制一点:

python sqlmap.py -u "http://xx.com?a=1&b=2&c=3" -p a --referer="http://xxx.com" --exclude-sysdbs --tables --level 3

这样可以指定注入点为a(-p a)
指定跳转来源(–referer=”http://xxx.com”)
去除数据库系统的表(–exclude-sysdbs)
指定注入尝试的级别(–level 3)
还有一些常用的参数(–dbms,-D,-C,–dump,–cookie,–user-agent,–data)等

可以在本机的建的测试网页上进行尝试
也可以下一些渗透测试演练系统练习:[TOP10]十大渗透测试演练系统
目前用过DVWA,练习XSS等感觉还不错


源码简单分析

(本着学习一下sqlmap原理,了解其软件结构的目的而来)
目前使用的是sqlmap0.9(顺带推荐一下python的IDE:PyCharm,与Android Studio一个风格,都是JetBrains的产品)


1.路径设置

sqlmap.py入口

if __name__ == "__main__":
    main()

然后开始了一系列关于路径的设置:

paths.SQLMAP_ROOT_PATH = modulePath()
        setPaths()

其中modulePath()是为了得到程序的目录,并处理了被py2exe转换过的特殊情况
setPaths()在common.py中,用于设置项目的目录(绝对路径),可以看出其与源码中文件夹是对应的:

    # sqlmap paths
    paths.SQLMAP_EXTRAS_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "extra")
    paths.SQLMAP_PROCS_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "procs")
    paths.SQLMAP_SHELL_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "shell")
    paths.SQLMAP_TAMPER_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "tamper")
    paths.SQLMAP_WAF_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "waf")
    paths.SQLMAP_TXT_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "txt")
    paths.SQLMAP_UDF_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "udf")
    paths.SQLMAP_XML_PATH = os.path.join(paths.SQLMAP_ROOT_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后输出到的文件夹:

_ = os.path.join(os.path.expanduser("~"), ".sqlmap")
    paths.SQLMAP_OUTPUT_PATH = getUnicode(paths.get("SQLMAP_OUTPUT_PATH", os.path.join(_, "output")), encoding=sys.getfilesystemencoding())
    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")

接着是sqlmap中一些文件,有一些是供我们自定义时用的:

    # sqlmap files
    paths.OS_SHELL_HISTORY = os.path.join(_, "os.hst")
    paths.SQL_SHELL_HISTORY = os.path.join(_, "sql.hst")
    paths.SQLMAP_SHELL_HISTORY = os.path.join(_, "sqlmap.hst")
    paths.GITHUB_HISTORY = os.path.join(_, "github.hst")
    paths.SQLMAP_CONFIG = os.path.join(paths.SQLMAP_ROOT_PATH, "sqlmap-%s.conf" % randomStr())
    paths.COMMON_COLUMNS = os.path.join(paths.SQLMAP_TXT_PATH, "common-columns.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.zip")
    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.LIVE_TESTS_XML = os.path.join(paths.SQLMAP_XML_PATH, "livetests.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")

接着检测这些必须要有的路径是否存在,文件(txt,xml,zip)是否可读:

for path in paths.values():
        if any(path.endswith(_) for _ in (".txt", ".xml", ".zip")):
            checkFile(path)

检测可读文件的关键代码:

try:
        with open(filename, "rb") as f:
                pass
        except:
            valid = False

2.解析cmdline

cmdLineParser()

首先检查系统编码:

    checkSystemEncoding()

接着设置我们在手册上看到的那些参数:
参数有如下分类:

target options,request options,optimization options,injection options,detection options,techniques options,fingerprint options,enumeration options,brute force options,User-defined function options,file system options,takeover options,windows registry options,general options,miscellaneous options,Hidden and/or experimental options

将这些参数组加到parser中:

parser.add_option_group(target)
...

接着是一些Dirty hack(不符合设计原理 / 不易维护 / 不易调整 / 不够健壮 / 不够美观的处理),其中-hh即为帮助的参数,用于列出命令的:

# Dirty hack to display longer options without breaking into two lines
        def _(self, *args):
            _ = parser.formatter._format_option_strings(*args)
            if len(_) > MAX_HELP_OPTION_LENGTH:
                _ = ("%%.%ds.." % (MAX_HELP_OPTION_LENGTH - parser.formatter.indent_increment)) % _
            return _
parser.formatter._format_option_strings = parser.formatter.format_option_strings
        parser.formatter.format_option_strings = type(parser.formatter.format_option_strings)(_, parser, type(parser))

        # Dirty hack for making a short option -hh
        option = parser.get_option("--hh")
        option._short_opts = ["-hh"]
        option._long_opts = []

        # Dirty hack for inherent help message of switch -h
        option = parser.get_option("-h")
        option.help = option.help.capitalize().replace("this help", "basic help")

接着用正则和判断来处理我们输入的参数(扩展助记参数),检测其是否符合格式:

        # Hide non-basic options in basic help case
        for i in xrange(len(argv)):
            if argv[i] == "-hh":
                argv[i] = "-h"
            elif re.match(r"\A\d+!\Z", argv[i]) and argv[max(0, i - 1)] == "--threads" or re.match(r"\A--threads.+\d+!\Z", argv[i]):
                argv[i] = argv[i][:-1]
                conf.skipThreadCheck = True
            elif argv[i] == "--version":
                print VERSION_STRING.split('/')[-1]
                raise SystemExit
            elif argv[i] == "-h":
                advancedHelp = False
                for group in parser.option_groups[:]:
                    found = False
                    for option in group.option_list:
                        if option.dest not in BASIC_HELP_ITEMS:
                            option.help = SUPPRESS_HELP
                        else:
                            found = True
                    if not found:
                        parser.option_groups.remove(group)

        try:
            (args, _) = parser.parse_args(argv)
        except UnicodeEncodeError, ex:
            print "\n[!] %s" % ex.object.encode("unicode-escape")
            raise SystemExit
        except SystemExit:
            if "-h" in argv and not advancedHelp:
                print "\n[!] to see full list of options run with '-hh'"
            raise

        # Expand given mnemonic options (e.g. -z "ign,flu,bat")
        for i in xrange(len(argv) - 1):
            if argv[i] == "-z":
                expandMnemonics(argv[i + 1], parser, args)

        if args.dummy:
            args.url = args.url or DUMMY_URL

        if not any((args.direct, args.url, args.logFile, args.bulkFile, args.googleDork, args.configFile, \
            args.requestFile, args.updateAll, args.smokeTest, args.liveTest, args.wizard, args.dependencies, \
            args.purgeOutput, args.pickledOptions, args.sitemapUrl)):
            errMsg = "missing a mandatory option (-d, -u, -l, -m, -r, -g, -c, -x, --wizard, --update, --purge-output or --dependencies), "
            errMsg += "use -h for basic or -hh for advanced help"
            parser.error(errMsg)

        return args

    except (OptionError, TypeError), e:
        parser.error(e)

    except SystemExit:
        # Protection against Windows dummy double clicking
        if IS_WIN:
            print "\nPress Enter to continue...",
            raw_input()
        raise

    debugMsg = "parsing command line"
    logger.debug(debugMsg)

之后初始化选项,显示我们在控制台看到的图案和基本信息

initOptions(cmdLineOptions)
...
banner()
...
init()

3.开始运行

start() 在controller.py中
可以参考这篇:Python:SQLMap源码精读—start函数
不过和此版本的代码稍有不同,以下为个人阅读分析:
总功能:检查URL的稳定性,GET,POST,Cookie,User-Agent参数,检查他们是否是动态变化的,是否会对SQL注入有影响
设置目标环境,新建存储本次操作的文件夹,文件和db,将目标所需要的参数(url,method,data,cookie,headers)设置好

...
conf.url = targetUrl
            conf.method = targetMethod.upper() if targetMethod else targetMethod
            conf.data = targetData
            conf.cookie = targetCookie
            conf.httpHeaders = list(initialHeaders)
            conf.httpHeaders.extend(targetHeaders or [])

            initTargetEnv()
            parseTargetUrl()
            ...
            setupTargetEnv()

检测waf(Web应用防火墙):

checkWaf()

检测目标url是否稳定:
方法:间隔1s访问同一个URL,比较两次内容是否一样

checkStability()

稳定后构造注入用的包请求:
根据–level来选择测试哪些头部
检测参数是否是动态变化的
找到可能可以注入的参数


...
orderList = (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER, PLACE.URI, PLACE.POST, PLACE.GET)
...
# --level >= 3
                    skip = (place == PLACE.USER_AGENT and conf.level < 3)
                    skip |= (place == PLACE.REFERER and conf.level < 3)
...

按照需求开始测试
测DB类型及版本
测UNION 等等
保存结果和显示

...
check = heuristicCheckSqlInjection(place, parameter)
...
injection = checkSqlInjection(place, parameter, value)
...
_saveToResultsFile()
_saveToHashDB()
_showInjections()
_selectInjection()
...

4.额外

如果之前已经对某个网页运行过sqlmap,则会优先读取原来测得的结果,而不会运行到有些检测函数中去

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值