/
heuristic.go
203 lines (175 loc) · 7.46 KB
/
heuristic.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
package sql
import (
"fmt"
"github.com/thoas/go-funk"
regexp "github.com/wasilibs/go-re2"
JieOutput "github.com/yhy0/Jie/pkg/output"
"github.com/yhy0/Jie/pkg/protocols/httpx"
"github.com/yhy0/Jie/pkg/util"
"github.com/yhy0/logging"
"strings"
"time"
)
/**
@author: yhy
@since: 2023/2/7
@desc: todo 应该获取全部参数,比如有的 post 请求,但 url 中也有参数
**/
// HeuristicCheckSqlInjection 启发式检测 sql 注入, 先过滤出有效参数,即不存在转型的参数, 之后在进行闭合检测
func (sql *Sqlmap) HeuristicCheckSqlInjection() {
// 避免POST请求出现参数重名,记录参数位置
var injectableParamsPos []int
// 通过闭合字符生成 payload, 看页面是否回显报错信息
randomTestString := getErrorBasedPreCheckPayload()
logging.Logger.Debugln(sql.Url, "开始启发式检测 sql 注入, payload:", randomTestString)
cast := false
var err error
var flag bool
if len(sql.DynamicPara) == 0 {
flag = true
}
errInject := false
// 检测是否存转型的参数, 转型参数代表无法注入
for pos, p := range sql.Variations.Params {
// 如果检测动态参数结果为空,则进行暴力检测,每个参数都检测;若不为空,则只对动态参数进行检测
if !flag && funk.Contains(sql.DynamicPara, p.Name) {
flag = true
}
if flag {
payload := sql.Variations.SetPayloadByIndex(p.Index, sql.Url, p.Value+randomTestString, sql.Method)
if payload == "" {
continue
}
logging.Logger.Debugln(sql.Url, payload)
var res *httpx.Response
if sql.Method == "GET" {
res, err = sql.Client.Request(payload, sql.Method, "", sql.Headers)
} else {
res, err = sql.Client.Request(sql.Url, sql.Method, payload, sql.Headers)
}
time.Sleep(time.Millisecond * 500)
if err != nil {
logging.Logger.Debugln(sql.Url, "checkIfInjectable Fuzz请求出错")
continue
}
for _, value := range FormatExceptionStrings {
if funk.Contains(res.Body, value) {
cast = true
logging.Logger.Debugf(sql.Url + " 参数: " + p.Name + " 因数值转型无法注入")
break
}
}
if cast {
cast = false
continue
}
// 这里出现 sql 报错信息则直接认为存在注入点,直接返回,后续不进行,减少流量。 验证交给人工/sql, 误报应该不多吧?
sql.DBMS = checkDBMSError(sql.Url, p.Name, payload, res)
if sql.DBMS != "" {
errInject = true
return
} else { // 尝试宽字节注入报错
payload = sql.Variations.SetPayloadByIndex(p.Index, sql.Url, p.Value+`%df'`, sql.Method)
if payload == "" {
continue
}
logging.Logger.Debugln(sql.Url, payload)
if sql.Method == "GET" {
res, err = sql.Client.Request(payload, sql.Method, "", sql.Headers)
} else {
res, err = sql.Client.Request(sql.Url, sql.Method, payload, sql.Headers)
}
if err != nil {
logging.Logger.Errorln(sql.Url, "宽字节注入出现错误", err)
continue
}
time.Sleep(time.Millisecond * 500)
sql.DBMS = checkDBMSError(sql.Url, p.Name, payload, res)
if sql.DBMS != "" {
errInject = true
return
}
}
injectableParamsPos = append(injectableParamsPos, pos)
logging.Logger.Debugf(sql.Url + " 参数: " + p.Name + " 未检测到转型,尝试注入检测")
// 这里也和 sql 一样 顺手检测一下xss、fi 漏洞
randStr1, randStr2 := util.RandomLetters(6), util.RandomLetters(6)
value := fmt.Sprintf("%s%s%s", randStr1, DummyNonSqliCheckAppendix, randStr2)
payload = sql.Variations.SetPayloadByIndex(p.Index, sql.Url, fmt.Sprintf("%s'%s", p.Value, value), sql.Method)
if payload == "" {
continue
}
if sql.Method == "GET" {
res, err = sql.Client.Request(payload, sql.Method, "", sql.Headers)
} else {
res, err = sql.Client.Request(sql.Url, sql.Method, payload, sql.Headers)
}
if err != nil {
logging.Logger.Debugln(sql.Url, " checkIfInjectable Fuzz请求出错, ", err)
continue
}
if funk.Contains(res.Body, value) {
JieOutput.OutChannel <- JieOutput.VulMessage{
DataType: "web_vul",
Plugin: "XSS",
VulnData: JieOutput.VulnData{
CreateTime: time.Now().Format("2006-01-02 15:04:05"),
Target: sql.Url,
Method: sql.Method,
Ip: "",
Param: p.Name,
Payload: fmt.Sprintf("%s \n", value),
Request: res.RequestDump,
Response: res.ResponseDump,
},
Level: JieOutput.Medium,
}
}
// 检测文件包含
re := regexp.MustCompile(FiErrorRegex)
matches := re.FindAllStringSubmatch(res.Body, -1)
for _, match := range matches {
if strings.Contains(strings.ToLower(match[0]), strings.ToLower(randStr1)) {
JieOutput.OutChannel <- JieOutput.VulMessage{
DataType: "web_vul",
Plugin: "FileInclude",
VulnData: JieOutput.VulnData{
CreateTime: time.Now().Format("2006-01-02 15:04:05"),
Target: sql.Url,
Method: sql.Method,
Ip: "",
Param: p.Name,
Payload: fmt.Sprintf("%s %s \n", value, match[0]),
Request: res.RequestDump,
Response: res.ResponseDump,
},
Level: JieOutput.Critical,
}
break
}
}
}
}
if len(injectableParamsPos) == 0 && !errInject {
logging.Logger.Debugln(sql.Url, "无可注入参数")
return
}
for _, pos := range injectableParamsPos {
sql.checkSqlInjection(pos)
}
}
func (sql *Sqlmap) checkSqlInjection(pos int) {
for _, closeType := range CloseType {
if sql.checkUnionBased(pos, closeType) {
return
}
}
for _, closeType := range CloseType {
if sql.checkBoolBased(pos, closeType) {
return
}
}
if sql.checkTimeBasedBlind(pos) {
return
}
}