mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-07-13 21:02:44 +08:00
perf: 优化Check.go的代码,添加注释,规范输出
This commit is contained in:
parent
9296ad0846
commit
7f62d4a835
@ -15,182 +15,227 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// API配置常量
|
||||||
ceyeApi = "a78a1cb49d91fe09e01876078d1868b2"
|
const (
|
||||||
ceyeDomain = "7wtusr.ceye.io"
|
ceyeApi = "a78a1cb49d91fe09e01876078d1868b2" // Ceye平台的API密钥
|
||||||
|
ceyeDomain = "7wtusr.ceye.io" // Ceye平台的域名
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Task 定义单个POC检测任务的结构体
|
||||||
type Task struct {
|
type Task struct {
|
||||||
Req *http.Request
|
Req *http.Request // HTTP请求对象
|
||||||
Poc *Poc
|
Poc *Poc // POC检测脚本
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckMultiPoc 并发执行多个POC检测
|
||||||
|
// 参数说明:
|
||||||
|
// - req: HTTP请求对象
|
||||||
|
// - pocs: POC检测脚本列表
|
||||||
|
// - workers: 并发工作协程数量
|
||||||
func CheckMultiPoc(req *http.Request, pocs []*Poc, workers int) {
|
func CheckMultiPoc(req *http.Request, pocs []*Poc, workers int) {
|
||||||
tasks := make(chan Task)
|
if workers <= 0 {
|
||||||
|
workers = 1 // 确保至少有一个工作协程
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks := make(chan Task, len(pocs)) // 使用带缓冲的通道,避免阻塞
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// 启动工作协程池
|
||||||
for i := 0; i < workers; i++ {
|
for i := 0; i < workers; i++ {
|
||||||
go func() {
|
go func() {
|
||||||
for task := range tasks {
|
for task := range tasks {
|
||||||
isVul, _, name := executePoc(task.Req, task.Poc)
|
// 执行POC检测
|
||||||
if isVul {
|
isVulnerable, details, vulName := executePoc(task.Req, task.Poc)
|
||||||
result := fmt.Sprintf("[+] PocScan %s %s %s", task.Req.URL, task.Poc.Name, name)
|
|
||||||
|
if isVulnerable {
|
||||||
|
// 格式化输出结果
|
||||||
|
result := fmt.Sprintf("[+] [发现漏洞] 目标: %s\n"+
|
||||||
|
" 漏洞类型: %s\n"+
|
||||||
|
" 漏洞名称: %s\n"+
|
||||||
|
" 详细信息: %s",
|
||||||
|
task.Req.URL,
|
||||||
|
task.Poc.Name,
|
||||||
|
vulName,
|
||||||
|
details)
|
||||||
|
|
||||||
Common.LogSuccess(result)
|
Common.LogSuccess(result)
|
||||||
}
|
}
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 分发任务
|
||||||
for _, poc := range pocs {
|
for _, poc := range pocs {
|
||||||
task := Task{
|
wg.Add(1)
|
||||||
|
tasks <- Task{
|
||||||
Req: req,
|
Req: req,
|
||||||
Poc: poc,
|
Poc: poc,
|
||||||
}
|
}
|
||||||
wg.Add(1)
|
|
||||||
tasks <- task
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 等待所有任务完成
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
close(tasks)
|
close(tasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// executePoc 执行单个POC检测
|
||||||
func executePoc(oReq *http.Request, p *Poc) (bool, error, string) {
|
func executePoc(oReq *http.Request, p *Poc) (bool, error, string) {
|
||||||
c := NewEnvOption()
|
// 初始化环境配置
|
||||||
c.UpdateCompileOptions(p.Set)
|
config := NewEnvOption()
|
||||||
|
config.UpdateCompileOptions(p.Set)
|
||||||
|
|
||||||
|
// 处理额外的设置项
|
||||||
if len(p.Sets) > 0 {
|
if len(p.Sets) > 0 {
|
||||||
var setMap StrMap
|
var setMap StrMap
|
||||||
for _, item := range p.Sets {
|
for _, item := range p.Sets {
|
||||||
|
value := ""
|
||||||
if len(item.Value) > 0 {
|
if len(item.Value) > 0 {
|
||||||
setMap = append(setMap, StrItem{item.Key, item.Value[0]})
|
value = item.Value[0]
|
||||||
} else {
|
|
||||||
setMap = append(setMap, StrItem{item.Key, ""})
|
|
||||||
}
|
}
|
||||||
|
setMap = append(setMap, StrItem{item.Key, value})
|
||||||
}
|
}
|
||||||
c.UpdateCompileOptions(setMap)
|
config.UpdateCompileOptions(setMap)
|
||||||
}
|
}
|
||||||
env, err := NewEnv(&c)
|
|
||||||
|
// 创建执行环境
|
||||||
|
env, err := NewEnv(&config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("[-] %s environment creation error: %s\n", p.Name, err)
|
return false, fmt.Errorf("[-] 创建%s的执行环境失败: %v", p.Name, err), ""
|
||||||
return false, err, ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 解析请求
|
||||||
req, err := ParseRequest(oReq)
|
req, err := ParseRequest(oReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("[-] %s ParseRequest error: %s\n", p.Name, err)
|
return false, fmt.Errorf("[-] 解析%s的请求失败: %v", p.Name, err), ""
|
||||||
return false, err, ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化变量映射
|
||||||
variableMap := make(map[string]interface{})
|
variableMap := make(map[string]interface{})
|
||||||
defer func() { variableMap = nil }()
|
defer func() { variableMap = nil }()
|
||||||
variableMap["request"] = req
|
variableMap["request"] = req
|
||||||
|
|
||||||
|
// 处理设置项
|
||||||
for _, item := range p.Set {
|
for _, item := range p.Set {
|
||||||
k, expression := item.Key, item.Value
|
key, expression := item.Key, item.Value
|
||||||
if expression == "newReverse()" {
|
if expression == "newReverse()" {
|
||||||
if !Common.DnsLog {
|
if !Common.DnsLog {
|
||||||
return false, nil, ""
|
return false, nil, ""
|
||||||
}
|
}
|
||||||
variableMap[k] = newReverse()
|
variableMap[key] = newReverse()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err, _ = evalset(env, variableMap, k, expression)
|
if err, _ = evalset(env, variableMap, key, expression); err != nil {
|
||||||
if err != nil {
|
Common.LogError(fmt.Sprintf("[-] 执行%s的设置项失败: %v", p.Name, err))
|
||||||
fmt.Printf("[-] %s evalset error: %v\n", p.Name, err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
success := false
|
|
||||||
//爆破模式,比如tomcat弱口令
|
// 处理爆破模式
|
||||||
if len(p.Sets) > 0 {
|
if len(p.Sets) > 0 {
|
||||||
success, err = clusterpoc(oReq, p, variableMap, req, env)
|
success, err := clusterpoc(oReq, p, variableMap, req, env)
|
||||||
return success, nil, ""
|
return success, err, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理单个规则的函数
|
||||||
DealWithRule := func(rule Rules) (bool, error) {
|
DealWithRule := func(rule Rules) (bool, error) {
|
||||||
Headers := cloneMap(rule.Headers)
|
Headers := cloneMap(rule.Headers)
|
||||||
var (
|
|
||||||
flag, ok bool
|
// 替换变量
|
||||||
)
|
for varName, varValue := range variableMap {
|
||||||
for k1, v1 := range variableMap {
|
if _, isMap := varValue.(map[string]string); isMap {
|
||||||
_, isMap := v1.(map[string]string)
|
|
||||||
if isMap {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
value := fmt.Sprintf("%v", v1)
|
strValue := fmt.Sprintf("%v", varValue)
|
||||||
for k2, v2 := range Headers {
|
|
||||||
if !strings.Contains(v2, "{{"+k1+"}}") {
|
// 替换Header中的变量
|
||||||
continue
|
for headerKey, headerValue := range Headers {
|
||||||
|
if strings.Contains(headerValue, "{{"+varName+"}}") {
|
||||||
|
Headers[headerKey] = strings.ReplaceAll(headerValue, "{{"+varName+"}}", strValue)
|
||||||
}
|
}
|
||||||
Headers[k2] = strings.ReplaceAll(v2, "{{"+k1+"}}", value)
|
|
||||||
}
|
}
|
||||||
rule.Path = strings.ReplaceAll(rule.Path, "{{"+k1+"}}", value)
|
|
||||||
rule.Body = strings.ReplaceAll(rule.Body, "{{"+k1+"}}", value)
|
// 替换Path和Body中的变量
|
||||||
|
rule.Path = strings.ReplaceAll(rule.Path, "{{"+varName+"}}", strValue)
|
||||||
|
rule.Body = strings.ReplaceAll(rule.Body, "{{"+varName+"}}", strValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 构建请求路径
|
||||||
if oReq.URL.Path != "" && oReq.URL.Path != "/" {
|
if oReq.URL.Path != "" && oReq.URL.Path != "/" {
|
||||||
req.Url.Path = fmt.Sprint(oReq.URL.Path, rule.Path)
|
req.Url.Path = fmt.Sprint(oReq.URL.Path, rule.Path)
|
||||||
} else {
|
} else {
|
||||||
req.Url.Path = rule.Path
|
req.Url.Path = rule.Path
|
||||||
}
|
}
|
||||||
// 某些poc没有区分path和query,需要处理
|
|
||||||
req.Url.Path = strings.ReplaceAll(req.Url.Path, " ", "%20")
|
req.Url.Path = strings.ReplaceAll(req.Url.Path, " ", "%20")
|
||||||
//req.Url.Path = strings.ReplaceAll(req.Url.Path, "+", "%20")
|
|
||||||
|
|
||||||
newRequest, err := http.NewRequest(rule.Method, fmt.Sprintf("%s://%s%s", req.Url.Scheme, req.Url.Host, string([]rune(req.Url.Path))), strings.NewReader(rule.Body))
|
// 创建新的请求
|
||||||
|
newRequest, err := http.NewRequest(
|
||||||
|
rule.Method,
|
||||||
|
fmt.Sprintf("%s://%s%s", req.Url.Scheme, req.Url.Host, string([]rune(req.Url.Path))),
|
||||||
|
strings.NewReader(rule.Body),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//fmt.Println("[-] newRequest error: ",err)
|
return false, fmt.Errorf("创建新请求失败: %v", err)
|
||||||
return false, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
newRequest.Header = oReq.Header.Clone()
|
newRequest.Header = oReq.Header.Clone()
|
||||||
for k, v := range Headers {
|
for k, v := range Headers {
|
||||||
newRequest.Header.Set(k, v)
|
newRequest.Header.Set(k, v)
|
||||||
}
|
}
|
||||||
Headers = nil
|
Headers = nil
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
resp, err := DoRequest(newRequest, rule.FollowRedirects)
|
resp, err := DoRequest(newRequest, rule.FollowRedirects)
|
||||||
newRequest = nil
|
newRequest = nil
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
variableMap["response"] = resp
|
variableMap["response"] = resp
|
||||||
// 先判断响应页面是否匹配search规则
|
|
||||||
|
// 执行搜索规则
|
||||||
if rule.Search != "" {
|
if rule.Search != "" {
|
||||||
result := doSearch(rule.Search, GetHeader(resp.Headers)+string(resp.Body))
|
result := doSearch(rule.Search, GetHeader(resp.Headers)+string(resp.Body))
|
||||||
if len(result) > 0 { // 正则匹配成功
|
if len(result) == 0 {
|
||||||
for k, v := range result {
|
|
||||||
variableMap[k] = v
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
for k, v := range result {
|
||||||
|
variableMap[k] = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 执行表达式
|
||||||
out, err := Evaluate(env, rule.Expression, variableMap)
|
out, err := Evaluate(env, rule.Expression, variableMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
//如果false不继续执行后续rule
|
|
||||||
// 如果最后一步执行失败,就算前面成功了最终依旧是失败
|
if flag, ok := out.Value().(bool); ok {
|
||||||
flag, ok = out.Value().(bool)
|
return flag, nil
|
||||||
if !ok {
|
|
||||||
flag = false
|
|
||||||
}
|
}
|
||||||
return flag, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理规则组的函数
|
||||||
DealWithRules := func(rules []Rules) bool {
|
DealWithRules := func(rules []Rules) bool {
|
||||||
successFlag := false
|
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
flag, err := DealWithRule(rule)
|
flag, err := DealWithRule(rule)
|
||||||
if err != nil || !flag { //如果false不继续执行后续rule
|
if err != nil || !flag {
|
||||||
successFlag = false // 如果其中一步为flag,则直接break
|
return false
|
||||||
break
|
|
||||||
}
|
}
|
||||||
successFlag = true
|
|
||||||
}
|
}
|
||||||
return successFlag
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 执行检测规则
|
||||||
|
success := false
|
||||||
if len(p.Rules) > 0 {
|
if len(p.Rules) > 0 {
|
||||||
success = DealWithRules(p.Rules)
|
success = DealWithRules(p.Rules)
|
||||||
} else {
|
} else {
|
||||||
for _, item := range p.Groups {
|
for _, item := range p.Groups {
|
||||||
name, rules := item.Key, item.Value
|
name, rules := item.Key, item.Value
|
||||||
success = DealWithRules(rules)
|
if success = DealWithRules(rules); success {
|
||||||
if success {
|
return true, nil, name
|
||||||
return success, nil, name
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -198,18 +243,25 @@ func executePoc(oReq *http.Request, p *Poc) (bool, error, string) {
|
|||||||
return success, nil, ""
|
return success, nil, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// doSearch 在响应体中执行正则匹配并提取命名捕获组
|
||||||
func doSearch(re string, body string) map[string]string {
|
func doSearch(re string, body string) map[string]string {
|
||||||
|
// 编译正则表达式
|
||||||
r, err := regexp.Compile(re)
|
r, err := regexp.Compile(re)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("[-] regexp.Compile error: ", err)
|
Common.LogError(fmt.Sprintf("正则表达式编译失败: %v", err))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 执行正则匹配
|
||||||
result := r.FindStringSubmatch(body)
|
result := r.FindStringSubmatch(body)
|
||||||
names := r.SubexpNames()
|
names := r.SubexpNames()
|
||||||
|
|
||||||
|
// 处理匹配结果
|
||||||
if len(result) > 1 && len(names) > 1 {
|
if len(result) > 1 && len(names) > 1 {
|
||||||
paramsMap := make(map[string]string)
|
paramsMap := make(map[string]string)
|
||||||
for i, name := range names {
|
for i, name := range names {
|
||||||
if i > 0 && i <= len(result) {
|
if i > 0 && i <= len(result) {
|
||||||
|
// 特殊处理Cookie头
|
||||||
if strings.HasPrefix(re, "Set-Cookie:") && strings.Contains(name, "cookie") {
|
if strings.HasPrefix(re, "Set-Cookie:") && strings.Contains(name, "cookie") {
|
||||||
paramsMap[name] = optimizeCookies(result[i])
|
paramsMap[name] = optimizeCookies(result[i])
|
||||||
} else {
|
} else {
|
||||||
@ -222,36 +274,61 @@ func doSearch(re string, body string) map[string]string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func optimizeCookies(rawCookie string) (output string) {
|
// optimizeCookies 优化Cookie字符串,移除不必要的属性
|
||||||
// Parse the cookies
|
func optimizeCookies(rawCookie string) string {
|
||||||
parsedCookie := strings.Split(rawCookie, "; ")
|
var output strings.Builder
|
||||||
for _, c := range parsedCookie {
|
|
||||||
nameVal := strings.Split(c, "=")
|
// 解析Cookie键值对
|
||||||
if len(nameVal) >= 2 {
|
pairs := strings.Split(rawCookie, "; ")
|
||||||
switch strings.ToLower(nameVal[0]) {
|
for _, pair := range pairs {
|
||||||
case "expires", "max-age", "path", "domain", "version", "comment", "secure", "samesite", "httponly":
|
nameVal := strings.SplitN(pair, "=", 2)
|
||||||
continue
|
if len(nameVal) < 2 {
|
||||||
}
|
continue
|
||||||
output += fmt.Sprintf("%s=%s; ", nameVal[0], strings.Join(nameVal[1:], "="))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 跳过Cookie属性
|
||||||
|
switch strings.ToLower(nameVal[0]) {
|
||||||
|
case "expires", "max-age", "path", "domain",
|
||||||
|
"version", "comment", "secure", "samesite", "httponly":
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建Cookie键值对
|
||||||
|
if output.Len() > 0 {
|
||||||
|
output.WriteString("; ")
|
||||||
|
}
|
||||||
|
output.WriteString(nameVal[0])
|
||||||
|
output.WriteString("=")
|
||||||
|
output.WriteString(strings.Join(nameVal[1:], "="))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return output.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newReverse 创建新的反连检测对象
|
||||||
func newReverse() *Reverse {
|
func newReverse() *Reverse {
|
||||||
|
// 检查DNS日志功能是否启用
|
||||||
if !Common.DnsLog {
|
if !Common.DnsLog {
|
||||||
return &Reverse{}
|
return &Reverse{}
|
||||||
}
|
}
|
||||||
letters := "1234567890abcdefghijklmnopqrstuvwxyz"
|
|
||||||
|
// 生成随机子域名
|
||||||
|
const (
|
||||||
|
letters = "1234567890abcdefghijklmnopqrstuvwxyz"
|
||||||
|
subdomainLength = 8
|
||||||
|
)
|
||||||
randSource := rand.New(rand.NewSource(time.Now().UnixNano()))
|
randSource := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
sub := RandomStr(randSource, letters, 8)
|
subdomain := RandomStr(randSource, letters, subdomainLength)
|
||||||
//if true {
|
|
||||||
// //默认不开启dns解析
|
// 构建URL
|
||||||
// return &Reverse{}
|
urlStr := fmt.Sprintf("http://%s.%s", subdomain, ceyeDomain)
|
||||||
//}
|
u, err := url.Parse(urlStr)
|
||||||
urlStr := fmt.Sprintf("http://%s.%s", sub, ceyeDomain)
|
if err != nil {
|
||||||
u, _ := url.Parse(urlStr)
|
Common.LogError(fmt.Sprintf("解析反连URL失败: %v", err))
|
||||||
|
return &Reverse{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回反连检测配置
|
||||||
return &Reverse{
|
return &Reverse{
|
||||||
Url: urlStr,
|
Url: urlStr,
|
||||||
Domain: u.Hostname(),
|
Domain: u.Hostname(),
|
||||||
@ -260,270 +337,368 @@ func newReverse() *Reverse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clusterpoc 执行集群POC检测,支持批量参数组合测试
|
||||||
func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{}, req *Request, env *cel.Env) (success bool, err error) {
|
func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{}, req *Request, env *cel.Env) (success bool, err error) {
|
||||||
var strMap StrMap
|
var strMap StrMap // 存储成功的参数组合
|
||||||
var tmpnum int
|
var shiroKeyCount int // shiro key测试计数
|
||||||
for i, rule := range p.Rules {
|
|
||||||
|
// 遍历POC规则
|
||||||
|
for ruleIndex, rule := range p.Rules {
|
||||||
|
// 检查是否需要进行参数Fuzz测试
|
||||||
if !isFuzz(rule, p.Sets) {
|
if !isFuzz(rule, p.Sets) {
|
||||||
|
// 不需要Fuzz,直接发送请求
|
||||||
success, err = clustersend(oReq, variableMap, req, env, rule)
|
success, err = clustersend(oReq, variableMap, req, env, rule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if success {
|
if !success {
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成参数组合
|
||||||
setsMap := Combo(p.Sets)
|
setsMap := Combo(p.Sets)
|
||||||
ruleHash := make(map[string]struct{})
|
ruleHash := make(map[string]struct{}) // 用于去重的规则哈希表
|
||||||
look:
|
|
||||||
for j, item := range setsMap {
|
// 遍历参数组合
|
||||||
//shiro默认只跑10key
|
paramLoop:
|
||||||
if p.Name == "poc-yaml-shiro-key" && !Common.PocFull && j >= 10 {
|
for comboIndex, paramCombo := range setsMap {
|
||||||
if item[1] == "cbc" {
|
// Shiro Key测试特殊处理:默认只测试10个key
|
||||||
|
if p.Name == "poc-yaml-shiro-key" && !Common.PocFull && comboIndex >= 10 {
|
||||||
|
if paramCombo[1] == "cbc" {
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
if tmpnum == 0 {
|
if shiroKeyCount == 0 {
|
||||||
tmpnum = j
|
shiroKeyCount = comboIndex
|
||||||
}
|
}
|
||||||
if j-tmpnum >= 10 {
|
if comboIndex-shiroKeyCount >= 10 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rule1 := cloneRules(rule)
|
|
||||||
var flag1 bool
|
// 克隆规则以避免相互影响
|
||||||
var tmpMap StrMap
|
currentRule := cloneRules(rule)
|
||||||
var payloads = make(map[string]interface{})
|
var hasReplacement bool
|
||||||
var tmpexpression string
|
var currentParams StrMap
|
||||||
for i, one := range p.Sets {
|
payloads := make(map[string]interface{})
|
||||||
key, expression := one.Key, item[i]
|
var payloadExpr string
|
||||||
|
|
||||||
|
// 计算所有参数的实际值
|
||||||
|
for i, set := range p.Sets {
|
||||||
|
key, expr := set.Key, paramCombo[i]
|
||||||
if key == "payload" {
|
if key == "payload" {
|
||||||
tmpexpression = expression
|
payloadExpr = expr
|
||||||
}
|
}
|
||||||
_, output := evalset1(env, variableMap, key, expression)
|
_, output := evalset1(env, variableMap, key, expr)
|
||||||
payloads[key] = output
|
payloads[key] = output
|
||||||
}
|
}
|
||||||
for _, one := range p.Sets {
|
|
||||||
flag := false
|
// 替换规则中的参数
|
||||||
key := one.Key
|
for _, set := range p.Sets {
|
||||||
|
paramReplaced := false
|
||||||
|
key := set.Key
|
||||||
value := fmt.Sprintf("%v", payloads[key])
|
value := fmt.Sprintf("%v", payloads[key])
|
||||||
for k2, v2 := range rule1.Headers {
|
|
||||||
if strings.Contains(v2, "{{"+key+"}}") {
|
// 替换Header中的参数
|
||||||
rule1.Headers[k2] = strings.ReplaceAll(v2, "{{"+key+"}}", value)
|
for headerKey, headerVal := range currentRule.Headers {
|
||||||
flag = true
|
if strings.Contains(headerVal, "{{"+key+"}}") {
|
||||||
|
currentRule.Headers[headerKey] = strings.ReplaceAll(headerVal, "{{"+key+"}}", value)
|
||||||
|
paramReplaced = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.Contains(rule1.Path, "{{"+key+"}}") {
|
|
||||||
rule1.Path = strings.ReplaceAll(rule1.Path, "{{"+key+"}}", value)
|
// 替换Path中的参数
|
||||||
flag = true
|
if strings.Contains(currentRule.Path, "{{"+key+"}}") {
|
||||||
|
currentRule.Path = strings.ReplaceAll(currentRule.Path, "{{"+key+"}}", value)
|
||||||
|
paramReplaced = true
|
||||||
}
|
}
|
||||||
if strings.Contains(rule1.Body, "{{"+key+"}}") {
|
|
||||||
rule1.Body = strings.ReplaceAll(rule1.Body, "{{"+key+"}}", value)
|
// 替换Body中的参数
|
||||||
flag = true
|
if strings.Contains(currentRule.Body, "{{"+key+"}}") {
|
||||||
|
currentRule.Body = strings.ReplaceAll(currentRule.Body, "{{"+key+"}}", value)
|
||||||
|
paramReplaced = true
|
||||||
}
|
}
|
||||||
if flag {
|
|
||||||
flag1 = true
|
// 记录替换的参数
|
||||||
|
if paramReplaced {
|
||||||
|
hasReplacement = true
|
||||||
if key == "payload" {
|
if key == "payload" {
|
||||||
var flag2 bool
|
// 处理payload的特殊情况
|
||||||
for k, v := range variableMap {
|
hasVarInPayload := false
|
||||||
if strings.Contains(tmpexpression, k) {
|
for varKey, varVal := range variableMap {
|
||||||
flag2 = true
|
if strings.Contains(payloadExpr, varKey) {
|
||||||
tmpMap = append(tmpMap, StrItem{k, fmt.Sprintf("%v", v)})
|
hasVarInPayload = true
|
||||||
|
currentParams = append(currentParams, StrItem{varKey, fmt.Sprintf("%v", varVal)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if flag2 {
|
if hasVarInPayload {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tmpMap = append(tmpMap, StrItem{key, value})
|
currentParams = append(currentParams, StrItem{key, value})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !flag1 {
|
|
||||||
|
// 如果没有参数被替换,跳过当前组合
|
||||||
|
if !hasReplacement {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
has := md5.Sum([]byte(fmt.Sprintf("%v", rule1)))
|
|
||||||
md5str := fmt.Sprintf("%x", has)
|
// 规则去重
|
||||||
if _, ok := ruleHash[md5str]; ok {
|
ruleDigest := md5.Sum([]byte(fmt.Sprintf("%v", currentRule)))
|
||||||
|
ruleMD5 := fmt.Sprintf("%x", ruleDigest)
|
||||||
|
if _, exists := ruleHash[ruleMD5]; exists {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ruleHash[md5str] = struct{}{}
|
ruleHash[ruleMD5] = struct{}{}
|
||||||
success, err = clustersend(oReq, variableMap, req, env, rule1)
|
|
||||||
|
// 发送请求并处理结果
|
||||||
|
success, err = clustersend(oReq, variableMap, req, env, currentRule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if success {
|
if success {
|
||||||
if rule.Continue {
|
// 处理成功情况
|
||||||
|
if currentRule.Continue {
|
||||||
|
// 特殊POC的输出处理
|
||||||
if p.Name == "poc-yaml-backup-file" || p.Name == "poc-yaml-sql-file" {
|
if p.Name == "poc-yaml-backup-file" || p.Name == "poc-yaml-sql-file" {
|
||||||
Common.LogSuccess(fmt.Sprintf("[+] PocScan %s://%s%s %s", req.Url.Scheme, req.Url.Host, req.Url.Path, p.Name))
|
Common.LogSuccess(fmt.Sprintf("[+] 检测到漏洞 %s://%s%s %s",
|
||||||
|
req.Url.Scheme, req.Url.Host, req.Url.Path, p.Name))
|
||||||
} else {
|
} else {
|
||||||
Common.LogSuccess(fmt.Sprintf("[+] PocScan %s://%s%s %s %v", req.Url.Scheme, req.Url.Host, req.Url.Path, p.Name, tmpMap))
|
Common.LogSuccess(fmt.Sprintf("[+] 检测到漏洞 %s://%s%s %s 参数:%v",
|
||||||
|
req.Url.Scheme, req.Url.Host, req.Url.Path, p.Name, currentParams))
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
strMap = append(strMap, tmpMap...)
|
|
||||||
if i == len(p.Rules)-1 {
|
// 记录成功的参数组合
|
||||||
Common.LogSuccess(fmt.Sprintf("[+] PocScan %s://%s%s %s %v", req.Url.Scheme, req.Url.Host, req.Url.Path, p.Name, strMap))
|
strMap = append(strMap, currentParams...)
|
||||||
//防止后续继续打印poc成功信息
|
if ruleIndex == len(p.Rules)-1 {
|
||||||
|
Common.LogSuccess(fmt.Sprintf("[+] 检测到漏洞 %s://%s%s %s 参数:%v",
|
||||||
|
req.Url.Scheme, req.Url.Host, req.Url.Path, p.Name, strMap))
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
break look
|
break paramLoop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !success {
|
if !success {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if rule.Continue {
|
if rule.Continue {
|
||||||
//防止后续继续打印poc成功信息
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return success, nil
|
return success, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isFuzz 检查规则是否包含需要Fuzz测试的参数
|
||||||
func isFuzz(rule Rules, Sets ListMap) bool {
|
func isFuzz(rule Rules, Sets ListMap) bool {
|
||||||
for _, one := range Sets {
|
// 遍历所有参数
|
||||||
key := one.Key
|
for _, param := range Sets {
|
||||||
for _, v := range rule.Headers {
|
key := param.Key
|
||||||
if strings.Contains(v, "{{"+key+"}}") {
|
paramPattern := "{{" + key + "}}"
|
||||||
|
|
||||||
|
// 检查Headers中是否包含参数
|
||||||
|
for _, headerValue := range rule.Headers {
|
||||||
|
if strings.Contains(headerValue, paramPattern) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.Contains(rule.Path, "{{"+key+"}}") {
|
|
||||||
|
// 检查Path中是否包含参数
|
||||||
|
if strings.Contains(rule.Path, paramPattern) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if strings.Contains(rule.Body, "{{"+key+"}}") {
|
|
||||||
|
// 检查Body中是否包含参数
|
||||||
|
if strings.Contains(rule.Body, paramPattern) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func Combo(input ListMap) (output [][]string) {
|
// Combo 生成参数组合
|
||||||
if len(input) > 1 {
|
func Combo(input ListMap) [][]string {
|
||||||
output = Combo(input[1:])
|
if len(input) == 0 {
|
||||||
output = MakeData(output, input[0].Value)
|
return nil
|
||||||
} else {
|
|
||||||
for _, i := range input[0].Value {
|
|
||||||
output = append(output, []string{i})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return
|
|
||||||
|
// 处理只有一个参数的情况
|
||||||
|
if len(input) == 1 {
|
||||||
|
output := make([][]string, 0, len(input[0].Value))
|
||||||
|
for _, value := range input[0].Value {
|
||||||
|
output = append(output, []string{value})
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归处理多个参数的情况
|
||||||
|
subCombos := Combo(input[1:])
|
||||||
|
return MakeData(subCombos, input[0].Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeData(base [][]string, nextData []string) (output [][]string) {
|
// MakeData 将新的参数值与已有的组合进行组合
|
||||||
for i := range base {
|
func MakeData(base [][]string, nextData []string) [][]string {
|
||||||
for _, j := range nextData {
|
// 预分配足够的空间
|
||||||
output = append(output, append([]string{j}, base[i]...))
|
output := make([][]string, 0, len(base)*len(nextData))
|
||||||
|
|
||||||
|
// 遍历已有组合和新参数值
|
||||||
|
for _, existingCombo := range base {
|
||||||
|
for _, newValue := range nextData {
|
||||||
|
// 创建新组合
|
||||||
|
newCombo := make([]string, 0, len(existingCombo)+1)
|
||||||
|
newCombo = append(newCombo, newValue)
|
||||||
|
newCombo = append(newCombo, existingCombo...)
|
||||||
|
output = append(output, newCombo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
|
||||||
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clustersend 执行单个规则的HTTP请求和响应检测
|
||||||
func clustersend(oReq *http.Request, variableMap map[string]interface{}, req *Request, env *cel.Env, rule Rules) (bool, error) {
|
func clustersend(oReq *http.Request, variableMap map[string]interface{}, req *Request, env *cel.Env, rule Rules) (bool, error) {
|
||||||
for k1, v1 := range variableMap {
|
// 替换请求中的变量
|
||||||
_, isMap := v1.(map[string]string)
|
for varName, varValue := range variableMap {
|
||||||
if isMap {
|
// 跳过map类型的变量
|
||||||
|
if _, isMap := varValue.(map[string]string); isMap {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
value := fmt.Sprintf("%v", v1)
|
|
||||||
for k2, v2 := range rule.Headers {
|
strValue := fmt.Sprintf("%v", varValue)
|
||||||
if strings.Contains(v2, "{{"+k1+"}}") {
|
varPattern := "{{" + varName + "}}"
|
||||||
rule.Headers[k2] = strings.ReplaceAll(v2, "{{"+k1+"}}", value)
|
|
||||||
|
// 替换Headers中的变量
|
||||||
|
for headerKey, headerValue := range rule.Headers {
|
||||||
|
if strings.Contains(headerValue, varPattern) {
|
||||||
|
rule.Headers[headerKey] = strings.ReplaceAll(headerValue, varPattern, strValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rule.Path = strings.ReplaceAll(strings.TrimSpace(rule.Path), "{{"+k1+"}}", value)
|
|
||||||
rule.Body = strings.ReplaceAll(strings.TrimSpace(rule.Body), "{{"+k1+"}}", value)
|
// 替换Path和Body中的变量
|
||||||
|
rule.Path = strings.ReplaceAll(strings.TrimSpace(rule.Path), varPattern, strValue)
|
||||||
|
rule.Body = strings.ReplaceAll(strings.TrimSpace(rule.Body), varPattern, strValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 构建完整请求路径
|
||||||
if oReq.URL.Path != "" && oReq.URL.Path != "/" {
|
if oReq.URL.Path != "" && oReq.URL.Path != "/" {
|
||||||
req.Url.Path = fmt.Sprint(oReq.URL.Path, rule.Path)
|
req.Url.Path = fmt.Sprint(oReq.URL.Path, rule.Path)
|
||||||
} else {
|
} else {
|
||||||
req.Url.Path = rule.Path
|
req.Url.Path = rule.Path
|
||||||
}
|
}
|
||||||
// 某些poc没有区分path和query,需要处理
|
|
||||||
|
// URL编码处理
|
||||||
req.Url.Path = strings.ReplaceAll(req.Url.Path, " ", "%20")
|
req.Url.Path = strings.ReplaceAll(req.Url.Path, " ", "%20")
|
||||||
//req.Url.Path = strings.ReplaceAll(req.Url.Path, "+", "%20")
|
|
||||||
//
|
// 创建新的HTTP请求
|
||||||
newRequest, err := http.NewRequest(rule.Method, fmt.Sprintf("%s://%s%s", req.Url.Scheme, req.Url.Host, req.Url.Path), strings.NewReader(rule.Body))
|
reqURL := fmt.Sprintf("%s://%s%s", req.Url.Scheme, req.Url.Host, req.Url.Path)
|
||||||
|
newRequest, err := http.NewRequest(rule.Method, reqURL, strings.NewReader(rule.Body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//fmt.Println("[-] newRequest error:",err)
|
return false, fmt.Errorf("[-] 创建HTTP请求失败: %v", err)
|
||||||
return false, err
|
|
||||||
}
|
}
|
||||||
|
defer func() { newRequest = nil }() // 及时释放资源
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
newRequest.Header = oReq.Header.Clone()
|
newRequest.Header = oReq.Header.Clone()
|
||||||
for k, v := range rule.Headers {
|
for key, value := range rule.Headers {
|
||||||
newRequest.Header.Set(k, v)
|
newRequest.Header.Set(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
resp, err := DoRequest(newRequest, rule.FollowRedirects)
|
resp, err := DoRequest(newRequest, rule.FollowRedirects)
|
||||||
newRequest = nil
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, fmt.Errorf("[-] 发送请求失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新响应到变量映射
|
||||||
variableMap["response"] = resp
|
variableMap["response"] = resp
|
||||||
// 先判断响应页面是否匹配search规则
|
|
||||||
|
// 执行搜索规则
|
||||||
if rule.Search != "" {
|
if rule.Search != "" {
|
||||||
result := doSearch(rule.Search, GetHeader(resp.Headers)+string(resp.Body))
|
searchContent := GetHeader(resp.Headers) + string(resp.Body)
|
||||||
if result != nil && len(result) > 0 { // 正则匹配成功
|
result := doSearch(rule.Search, searchContent)
|
||||||
for k, v := range result {
|
|
||||||
variableMap[k] = v
|
if result != nil && len(result) > 0 {
|
||||||
|
// 将搜索结果添加到变量映射
|
||||||
|
for key, value := range result {
|
||||||
|
variableMap[key] = value
|
||||||
}
|
}
|
||||||
//return false, nil
|
|
||||||
} else {
|
} else {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 执行CEL表达式
|
||||||
out, err := Evaluate(env, rule.Expression, variableMap)
|
out, err := Evaluate(env, rule.Expression, variableMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "Syntax error") {
|
if strings.Contains(err.Error(), "Syntax error") {
|
||||||
fmt.Println(rule.Expression, err)
|
Common.LogError(fmt.Sprintf("[-] CEL表达式语法错误 [%s]: %v", rule.Expression, err))
|
||||||
}
|
}
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
//fmt.Println(fmt.Sprintf("%v, %s", out, out.Type().TypeName()))
|
|
||||||
if fmt.Sprintf("%v", out) == "false" { //如果false不继续执行后续rule
|
// 检查表达式执行结果
|
||||||
return false, err // 如果最后一步执行失败,就算前面成功了最终依旧是失败
|
if fmt.Sprintf("%v", out) == "false" {
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
return true, err
|
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cloneRules 深度复制Rules结构体
|
||||||
|
// 参数:
|
||||||
|
// - tags: 原始Rules结构体
|
||||||
|
// 返回: 复制后的新Rules结构体
|
||||||
func cloneRules(tags Rules) Rules {
|
func cloneRules(tags Rules) Rules {
|
||||||
cloneTags := Rules{}
|
return Rules{
|
||||||
cloneTags.Method = tags.Method
|
Method: tags.Method,
|
||||||
cloneTags.Path = tags.Path
|
Path: tags.Path,
|
||||||
cloneTags.Body = tags.Body
|
Body: tags.Body,
|
||||||
cloneTags.Search = tags.Search
|
Search: tags.Search,
|
||||||
cloneTags.FollowRedirects = tags.FollowRedirects
|
FollowRedirects: tags.FollowRedirects,
|
||||||
cloneTags.Expression = tags.Expression
|
Expression: tags.Expression,
|
||||||
cloneTags.Headers = cloneMap(tags.Headers)
|
Headers: cloneMap(tags.Headers),
|
||||||
return cloneTags
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cloneMap 深度复制字符串映射
|
||||||
func cloneMap(tags map[string]string) map[string]string {
|
func cloneMap(tags map[string]string) map[string]string {
|
||||||
cloneTags := make(map[string]string)
|
cloneTags := make(map[string]string, len(tags))
|
||||||
for k, v := range tags {
|
for key, value := range tags {
|
||||||
cloneTags[k] = v
|
cloneTags[key] = value
|
||||||
}
|
}
|
||||||
return cloneTags
|
return cloneTags
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalset(env *cel.Env, variableMap map[string]interface{}, k string, expression string) (err error, output string) {
|
// evalset 执行CEL表达式并处理特殊类型结果
|
||||||
|
func evalset(env *cel.Env, variableMap map[string]interface{}, k string, expression string) (error, string) {
|
||||||
out, err := Evaluate(env, expression, variableMap)
|
out, err := Evaluate(env, expression, variableMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
variableMap[k] = expression
|
variableMap[k] = expression
|
||||||
} else {
|
return err, expression
|
||||||
switch value := out.Value().(type) {
|
|
||||||
case *UrlType:
|
|
||||||
variableMap[k] = UrlTypeToString(value)
|
|
||||||
case int64:
|
|
||||||
variableMap[k] = int(value)
|
|
||||||
default:
|
|
||||||
variableMap[k] = fmt.Sprintf("%v", out)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return err, fmt.Sprintf("%v", variableMap[k])
|
|
||||||
|
// 根据不同类型处理输出
|
||||||
|
switch value := out.Value().(type) {
|
||||||
|
case *UrlType:
|
||||||
|
variableMap[k] = UrlTypeToString(value)
|
||||||
|
case int64:
|
||||||
|
variableMap[k] = int(value)
|
||||||
|
default:
|
||||||
|
variableMap[k] = fmt.Sprintf("%v", out)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Sprintf("%v", variableMap[k])
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalset1(env *cel.Env, variableMap map[string]interface{}, k string, expression string) (err error, output string) {
|
// evalset1 执行CEL表达式的简化版本
|
||||||
|
func evalset1(env *cel.Env, variableMap map[string]interface{}, k string, expression string) (error, string) {
|
||||||
out, err := Evaluate(env, expression, variableMap)
|
out, err := Evaluate(env, expression, variableMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
variableMap[k] = expression
|
variableMap[k] = expression
|
||||||
@ -533,6 +708,7 @@ func evalset1(env *cel.Env, variableMap map[string]interface{}, k string, expres
|
|||||||
return err, fmt.Sprintf("%v", variableMap[k])
|
return err, fmt.Sprintf("%v", variableMap[k])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckInfoPoc 检查POC信息并返回别名
|
||||||
func CheckInfoPoc(infostr string) string {
|
func CheckInfoPoc(infostr string) string {
|
||||||
for _, poc := range info.PocDatas {
|
for _, poc := range info.PocDatas {
|
||||||
if strings.Contains(infostr, poc.Name) {
|
if strings.Contains(infostr, poc.Name) {
|
||||||
@ -542,11 +718,12 @@ func CheckInfoPoc(infostr string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetHeader(header map[string]string) (output string) {
|
// GetHeader 将HTTP头转换为字符串格式
|
||||||
|
func GetHeader(header map[string]string) string {
|
||||||
|
var builder strings.Builder
|
||||||
for name, values := range header {
|
for name, values := range header {
|
||||||
line := fmt.Sprintf("%s: %s\n", name, values)
|
builder.WriteString(fmt.Sprintf("%s: %s\n", name, values))
|
||||||
output = output + line
|
|
||||||
}
|
}
|
||||||
output = output + "\r\n"
|
builder.WriteString("\r\n")
|
||||||
return
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user