forked from GhostTroops/scan4all
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfilefuzz.go
341 lines (319 loc) · 10.2 KB
/
filefuzz.go
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
package brute
import (
"context"
_ "embed"
"github.com/antlabs/strsim"
"github.com/hktalent/scan4all/lib/util"
"log"
"net/url"
"regexp"
"strings"
"sync"
"sync/atomic"
"time"
)
// 备份、敏感文件后缀
//go:embed dicts/bakSuffix.txt
var bakSuffix string
// 备份、敏感文件 http头类型 ContentType 检测
//go:embed dicts/fuzzContentType1.txt
var fuzzct string
// 敏感文件前缀
//go:embed dicts/prefix.txt
var szPrefix string
var (
ret = []string{} // 敏感信息文件字典
prefix, suffix []string // 敏感信息字典: 前缀、后缀
)
// 生成敏感信息字典
func InitGeneral() int {
szPrefix = util.GetVal4File("prefix", szPrefix)
prefix = strings.Split(strings.TrimSpace(szPrefix), "\n")
suffix = strings.Split(strings.TrimSpace(bakSuffix), "\n")
for i := 0; i < len(prefix); i++ {
for j := 0; j < len(suffix); j++ {
ret = append(ret, "/"+prefix[i]+suffix[j])
}
}
eableFileFuzz = !util.GetValAsBool("enablFileFuzz")
return len(ret)
}
// 请求url并返回自定义对象
func reqPage(u string) (*util.Page, *util.Response, error) {
page := &util.Page{Url: &u}
var method = "GET"
for _, ext := range suffix {
if strings.HasSuffix(u, ext) {
page.IsBackUpPath = true
method = "HEAD" // 节约请求时间
}
}
header := make(map[string]string)
header["Accept"] = "*/*"
header["Connection"] = "close"
header["Pragma"] = "no-cache"
// fuzz check Shiro CVE_2016_4437
header["Cookie"] = "JSESSIONID=" + RandStr4Cookie + ";rememberMe=123"
if req, err := util.HttpRequset(u, method, "", false, header); err == nil && nil != req && nil != req.Header {
//if pkg.IntInSlice(req.StatusCode, []int{301, 302, 307, 308}) {
// 简单粗暴效率高
if 300 <= req.StatusCode && req.StatusCode < 400 {
page.Is302 = true
}
page.StatusCode = req.StatusCode
page.Resqonse = req
page.Header = req.Header
page.BodyLen = len(req.Body)
page.Title = Gettitle(req.Body)
page.LocationUrl = &req.Location
// 敏感文件头信息检测
page.IsBackUpPage = CheckBakPage(req)
// https://zh.m.wikipedia.org/zh-hans/HTTP_403
// 403 Forbidden 是HTTP协议中的一个HTTP状态码(Status Code)。403状态码意为服务器成功解析请求但是客户端没有访问该资源的权限
page.Is403 = req.StatusCode == 403
return page, req, err
} else {
return page, nil, err
}
}
// 敏感文件头信息检测:
// 检测头信息是否有敏感文件、本份文件、流文件等敏感信息
func CheckBakPage(req *util.Response) bool {
if x0, ok := (*req.Header)["Content-Type"]; ok && 0 < len(x0) {
x0B := []byte(x0[0])
for _, reg := range regs {
// 找到对应等正则
if r1, ok := regsMap[reg]; ok {
if r1.Match(x0B) {
return true
}
}
}
}
return false
}
// 备份、敏感文件 http头类型 ContentType 检测,正则
var regs []string
var (
regsMap = make(map[string]*regexp.Regexp) // fuzz 正则库
eableFileFuzz = false // 是否开启fuzz
)
// 初始化字典、数组等
func init() {
util.RegInitFunc(func() {
bakSuffix = util.GetVal4File("bakSuffix", bakSuffix)
fuzzct = util.GetVal4File("fuzzct", fuzzct)
InitGeneral()
regs = strings.Split(strings.TrimSpace(fuzzct), "\n")
var err error
// 初始化多时候一次性编译,否则会影响效率
for _, reg := range regs {
regsMap[reg], err = regexp.Compile(reg)
if nil != err {
log.Println(reg, " regexp.Compile error: ", err)
}
}
//regs = append(regs, ret...)
})
}
// 绝对404请求文件前缀
//var file_not_support = "file_not_support"
// 绝对404请求文件
//var RandStr = file_not_support + "_scan4all"
// 随机10个字符串
var RandStr4Cookie = util.RandStringRunes(10)
// 重写了fuzz:优化流程、优化算法、修复线程安全bug、增加智能功能
func FileFuzz(u string, indexStatusCode int, indexContentLength int, indexbody string) ([]string, []string) {
if eableFileFuzz || util.TestRepeat(u, "FileFuzz") {
return []string{}, []string{}
}
u01, err := url.Parse(strings.TrimSpace(u))
if nil == err {
u = u01.Scheme + "://" + u01.Host + "/"
}
var (
//path404 = RandStr // 绝对404页面路径
errorTimes int32 = 0 // 错误计数器,> 20则退出fuzz
technologies []string // 指纹数据
path []string // 成功页面路径
)
url404, url404req, err, ok := util.TestIs404Page(u) //reqPage(u + path404)
if err == nil && ok {
go util.CheckHeader(url404req.Header, u)
// 跳过当前目标所有的fuzz,后续所有的fuzz都无意义了
if 200 == url404.StatusCode || 301 == url404.StatusCode || 302 == url404.StatusCode {
return []string{}, []string{}
}
// 其实这里无论状态码是什么,都是404
// 所有异常页面 > 400 > 500都做异常页面fuzz指纹
// 提高精准度,可以只考虑404
//if url404req.StatusCode > 400 {
if url404req.StatusCode == 404 {
technologies = Addfingerprints404(technologies, url404req, url404) //基于404页面文件扫描指纹添加
StudyErrPageAI(url404req, url404, "") // 异常页面学习
} else {
return []string{}, []string{}
}
} else {
return []string{}, []string{}
}
var wg sync.WaitGroup
// 中途控制关闭当前目标所有fuzz
ctx, stop := context.WithCancel(util.Ctx_global)
// 控制 fuzz 线程数
var ch = make(chan struct{}, util.Fuzzthreads)
// 异步接收结果
var async_data = make(chan []string, 64)
var async_technologies = make(chan []string, 64)
defer func() {
close(ch)
close(async_data)
close(async_technologies)
}()
log.Printf("start fuzz: %s for", u)
go func() {
for {
select {
case <-ctx.Done():
return
case x1 := <-async_data:
path = append(path, x1...)
if len(path) > 40 {
stop() //发停止指令
atomic.AddInt32(&errorTimes, 21)
}
case x2 := <-async_technologies:
technologies = append(technologies, x2...)
default:
<-time.After(time.Duration(100) * time.Millisecond)
}
}
}()
for _, payload := range filedic {
// 接收到停止信号
if atomic.LoadInt32(&errorTimes) >= 20 {
break
}
//log.Println(u, " ", payload)
endP := u[len(u)-1:] == "/"
ch <- struct{}{}
wg.Add(1)
go func(payload string) {
defer func() {
wg.Done() // 控制所有线程结束
<-ch // 并发控制
}()
for {
select {
case _, ok := <-ch:
if !ok {
stop()
return
}
case <-ctx.Done(): // 00-捕获所有线程关闭信号,并退出,close for all
atomic.AddInt32(&errorTimes, 21)
return
default:
//if _, ok := noRpt.Load(szKey001Over); ok {
// stop()
// return
//}
// 01-异常>20关闭所有fuzz
if atomic.LoadInt32(&errorTimes) >= 20 {
stop() //发停止指令
return
}
// 修复url,默认 认为 payload 不包含/
szUrl := u + payload
if strings.HasPrefix(payload, "/") && endP {
szUrl = u + payload[1:]
}
log.Printf("start fuzz: %s", szUrl)
if fuzzPage, req, err := reqPage(szUrl); err == nil && 0 < len(req.Body) {
go util.CheckHeader(req.Header, u)
// 02-状态码和req1相同,且与req1相似度>9.5,关闭所有fuzz
fXsd := strsim.Compare(url404req.Body, req.Body)
bBig95 := 9.5 < fXsd
if url404.StatusCode == fuzzPage.StatusCode && bBig95 {
stop() //发停止指令
atomic.AddInt32(&errorTimes, 21)
return
}
var path1, technologies1 = []string{}, []string{}
// 03-异常页面(>400),或相似度与404匹配
if fuzzPage.StatusCode >= 400 || bBig95 || fuzzPage.StatusCode != 200 {
// 03.01-异常页面指纹匹配
technologies = Addfingerprints404(technologies, req, fuzzPage) //基于404页面文件扫描指纹添加
// 03.02-与绝对404相似度低于0.8,添加body 404 body list
// 03.03-添加404titlelist
if 0.8 > fXsd {
StudyErrPageAI(req, fuzzPage, "") // 异常页面学习
}
// 04-403: 403 by pass
if fuzzPage.Is403 && !url404.Is403 {
a11 := ByPass403(&u, &payload, &wg)
// 表示 ByPass403 成功了, 结果、控制台输出点什么?
if 0 < len(a11) {
async_data <- a11
}
}
return
}
// 当前和绝对404不等于404,后续的比较也没有意义了,都等于[200,301,302]都没有意义了,都说明没有fuzz成功
if url404.StatusCode != 404 && url404.StatusCode == fuzzPage.StatusCode {
return
}
// 05-跳转检测,即便是跳转,如果和绝对404不一样,说明检测成功
//if CheckDirckt(fuzzPage, req) && url404.StatusCode != fuzzPage.StatusCode {
// return
//}
// 1、状态码和绝对404一样 2、智能识别算出来
is404Page := url404.StatusCode == fuzzPage.StatusCode || CheckIsErrPageAI(req, fuzzPage)
// 06-成功页面, 非异常页面
if !is404Page {
// 1、指纹匹配
technologies1 = Addfingerprintsnormal(payload, technologies1, req, fuzzPage) // 基于200页面文件扫描指纹添加
// 2、成功fuzz路径结果添加
path1 = append(path1, *fuzzPage.Url)
}
if 0 < len(path1) {
async_data <- path1
}
if 0 < len(technologies1) {
async_technologies <- technologies1
}
} else { // 这里应该元子操作
atomic.AddInt32(&errorTimes, 1)
}
return
}
}
}(payload)
}
// 默认情况等待所有结束
wg.Wait()
stop() //发停止指令
log.Printf("fuzz is over: %s\n", u)
return path, technologies
}
// html跳转
var reg1 = regexp.MustCompile("(?i)<meta.*http-equiv\\s*=\\s*\"refresh\".*content\\s*=\\s*\"5;\\s*url=")
// js跳转
var reg2 = regexp.MustCompile("(window|self|top)\\.location\\.href\\s*=")
// 跳转检测
// 1、状态码跳转:301 代表永久性转移(Permanently Moved);302 redirect: 302 代表暂时性转移(Temporarily Moved )
// 2、html刷新跳转
// 3、js 跳转
func CheckDirckt(fuzzPage *util.Page, req *util.Response) bool {
if nil == fuzzPage || nil == req {
return false
}
data := []byte(req.Body)
// 01 redirect:
if 302 == req.StatusCode || 301 == req.StatusCode {
return true
} else if 0 < len(data) && (0 < len(reg1.Find(data)) || 0 < len(reg2.Find(data))) { // html刷新跳转;js 跳转
return true
}
return false
}