书写是为了更好地思考 writing-is-better-thinking——摘自刘未鹏的博客
感觉大学里学得很杂,每次学完后一搁置就忘了许多,以后多写写博客吧
简介
直观感受(需要翻墙):How to hack a website using sqlmap on Kali Linux - YouTube
第一篇将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,则会优先读取原来测得的结果,而不会运行到有些检测函数中去