阅读DSSS.py 并修改成支持python3.6
项目地址: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的更多相关文章
- 百度编辑器UEditor修改成支持物理路径
一.前言 我虽然工作了2年.有快1年没有做后台的开发了.最近要写个新项目用到富文本编辑器,然后选择用了百度的UEditor.在使用过程中感觉有些不太好.然后就自己手动改一下源码,写得不好请见谅.这只是 ...
- Live555 的一个缺陷–例子不能支持多线程(已经修改成支持多线程)
我对Live555进行了一次封装,但是Live555 是单线程的,里面定义的全局变量太多,我封装好dll库后,在客户端调用,因为多个对话框中要使用码流,我就定义了多个对象从设备端接收码流,建立多个连接 ...
- 如何将python3.6软件的py文件打包成exe程序
在我们完成一个Python项目或一个程序时,希望将Python的py文件打包成在Windows系统下直接可以运行的exe程序.在浏览网上的资料来看,有利用pyinstaller和cx_Freeze进行 ...
- HTMLTestRunner修改成Python3版本
修改前:HTMLTestRunner下载地址:http://tungwaiyip.info/software/HTMLTestRunner.html BSTestRunner 下载地址:htt ...
- 用启动器py成功解决python2和python3同时共存且同时运行的问题
缘起:之前一直用PHP来开发微信公众号后台,最近正在学习python,而且看到微信官方也把公众号后台的示例代码换成了python的,但是示例中用的web.py需要用到python2,而我自己的电脑上装 ...
- 阿里云 rds python sdk不支持python3处理
阿里云文档中心的python版本aliyun-python-sdk-rds不支持python3处理 问题:默认情况下文档中心的python版本只支持python2,不兼容python3版本 需要稍微修 ...
- ubuntu将python3设为默认后再安装支持python3.x的包
简介: ubuntu默认python2.7版本,如果想要装python3.x版本,请记住python2.7版本一定不能卸载!!!但是即使我 python3.x版本安装成功,当运行python脚本时,系 ...
- Linux --- vim 安装、支持python3的配置、插件自动补全YCM的安装配置及全过程错误总结
1.git(用来下载vim和相关插件) sudo apt-get install git 2,cmake(用来编译clang-llvm) sudo apt-get install build-esse ...
- 使用supervisor支持Python3程序 (解决找不到Module的问题)
Supervisor是python2写就的一款强大的运维工具(其实现在已经支持Python3了 https://github.com/Supervisor/supervisor)那么怎么利用Super ...
随机推荐
- 如何在win10查看wifi密码
tep1 找到wifi图标 step 2 右键点击打开网络共享中心 没有啦!!
- 基于python的接口自动化测试+ddt数据驱动
在测试接口时,一个接口会先写好测试用例,这个用例主要针对功能,传入参时考虑到各种场景,正常的,异常的,如:参数缺省,参数传一个六位数字写用例时考虑边界情况等. 一个接口设计用例时有可能会十几条到几十条 ...
- [转]C++ 初始化列表的初始化顺序
构造函数初始化列表仅用于初始化成员的值,并不指定这些初始化执行的次序.成员被初始化的次序就是定义成员的次序.第一个被定义的成员先被初始化,依次类推.一般,初始化的顺序无关紧要,然而,如果一个成员是根据 ...
- C语言程序设计(基础)- 第4周作业
一.PTA作业 完成PTA第四周作业中8个题目,并将其中4个题目的思路列在博客中. 1.7-1 计算分段函数[1] 2.7-2 A除以B 3.7-6 阶梯电价 4.7-7 出租车计价 随笔具体书写内容 ...
- Beta 第五天
今天遇到的困难: 前端大部分代码由我们放逐的组员完成,这影响到了我们解决"Fragment碎片刷新时总产生的固定位置"的进程,很难找到源码对应 新加入的成员对界面代码不熟悉. 我们 ...
- Beta阶段总结分析报告
1 讨论照片 2 Postmortem结果 二手交易平台项目Postmortem结果 整理:程环宇 设想和目标 1. 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有 ...
- PHP处理上传文件
HTML中使用type = 'file'类型的表单可以向服务器上传文件: 上传文件的表单必须在form中定义enctyp = 'multipart/form-data': HTML代码如下: < ...
- Python内置函数(12)——str
英文文档: class str(object='') class str(object=b'', encoding='utf-8', errors='strict') Return a string ...
- axure 预览"HTTP/1.1 302 Found"
使用Axure编辑原型时,点击预览出现"HTTP/1.1 302 Found" 第一想到的就是重新安装Axure和检查原型文件是否损坏,验证后证明前Axure和.rp文件都是完好的 ...
- Django之views系统
Django的View(视图)简介 一个视图函数(类),简称视图,是一个简单的Python 函数(类),它接受Web请求并且返回Web响应. 响应可以是一张网页的HTML内容,一个重定向,一个404错 ...