fix:owasp detail

#79
This commit is contained in:
samwaf
2025-05-29 10:26:48 +08:00
parent 2973c6d7d3
commit c3a30805a1
3 changed files with 286 additions and 105 deletions

View File

@@ -23,7 +23,10 @@ func (waf *WafEngine) CheckOwasp(r *http.Request, weblogbean *innerbean.WebLog,
isInteeruption, interruption, err := global.GWAF_OWASP.ProcessRequest(r, *weblogbean)
if err == nil && isInteeruption {
result.IsBlock = true
result.Title = "OWASP:" + strconv.Itoa(interruption.RuleID)
// 使用中断对象中的详细信息
if interruption.Data != "" {
result.Title = "OWASP:" + strconv.Itoa(interruption.RuleID) + interruption.Data
}
result.Content = "访问不合法"
weblogbean.RISK_LEVEL = 2
}

View File

@@ -858,13 +858,13 @@ func (waf *WafEngine) modifyResponse() func(*http.Response) error {
wafwebcache.StoreWebDataCache(resp, waf.HostTarget[host], cacheConfig, weblogfrist)
}
zlog.Debug("TEST_Challenge", weblogfrist.HOST, weblogfrist.URL)
if !isStaticAssist {
datetimeNow := time.Now()
weblogfrist.TimeSpent = datetimeNow.UnixNano()/1e6 - weblogfrist.UNIX_ADD_TIME
// 根据配置决定是否检查HTTP响应代码并重定向到本地
if strings.HasPrefix(weblogfrist.URL, global.GSSL_HTTP_CHANGLE_PATH) {
zlog.Debug("TEST_Challenge", weblogfrist.HOST, weblogfrist.URL)
if global.GCONFIG_RECORD_SSLHTTP_CHECK == 0 || resp.StatusCode == 404 || resp.StatusCode == 301 || resp.StatusCode == 302 {
//如果远端HTTP01不存在挑战验证文件那么我们映射到走本地再试一下
//或者配置为不检查HTTP响应代码直接走本地

View File

@@ -3,145 +3,323 @@ package wafowasp
import (
"SamWaf/common/zlog"
"SamWaf/innerbean"
"encoding/json"
"bytes"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"github.com/corazawaf/coraza/v3"
"github.com/corazawaf/coraza/v3/collection"
"github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes"
"github.com/corazawaf/coraza/v3/types"
"github.com/corazawaf/coraza/v3/types/variables"
"net/http"
"strconv"
"testing"
)
// CustomLogger 自定义日志记录器,用于集成 zlog
type CustomLogger struct{}
func (l *CustomLogger) Error(msg string, fields map[string]interface{}) {
zlog.Error(msg, fields)
}
type WafOWASP struct {
IsActive bool // 是否激活 WAF
WAF coraza.WAF // 对 Coraza WAF 实例的引用
logger *CustomLogger
}
// InitOwasp 测试用
func InitOwasp(currentDir string) coraza.WAF {
cfg := coraza.NewWAFConfig().
WithDirectivesFromFile(currentDir + "/data/owasp/coraza.conf").
WithDirectivesFromFile(currentDir + "/data/owasp/coreruleset/crs-setup.conf").
WithDirectivesFromFile(currentDir + "/data/owasp/coreruleset/rules/*.conf")
// First we initialize our waf and our seclang parser
wafOwasp, err := coraza.NewWAF(cfg)
// Now we parse our rules
if err != nil {
fmt.Println(err)
}
return wafOwasp
}
// NewWafOWASP 创建新的 WAF 实例
func NewWafOWASP(isActive bool, currentDir string) *WafOWASP {
// 初始化一个新的 WAF 实例
cfg := coraza.NewWAFConfig().
WithDirectivesFromFile(currentDir + "/data/owasp/coraza.conf").
WithDirectivesFromFile(currentDir + "/data/owasp/coreruleset/crs-setup.conf").
WithDirectivesFromFile(currentDir + "/data/owasp/coreruleset/rules/*.conf")
// First we initialize our waf and our seclang parser
waf, err := coraza.NewWAF(cfg)
// Now we parse our rules
if err != nil {
fmt.Println(err)
zlog.Error("Failed to initialize WAF", map[string]interface{}{"error": err.Error()})
return nil
}
logger := &CustomLogger{}
return &WafOWASP{
IsActive: isActive,
WAF: waf,
logger: logger,
}
}
// ProcessRequest 处理 HTTP 请求
func (w *WafOWASP) ProcessRequest(r *http.Request, weblog innerbean.WebLog) (bool, *types.Interruption, error) {
// 只有在 WAF 激活时才处理请求
if w.IsActive {
tx := w.WAF.NewTransaction()
defer tx.Close()
if !w.IsActive {
return false, nil, nil
}
// 添加请求头
for key, values := range r.Header {
for _, value := range values {
tx.AddRequestHeader(key, value)
}
tx := w.WAF.NewTransaction()
defer func() {
if err := tx.Close(); err != nil {
w.logger.Error("Failed to close transaction", map[string]interface{}{"error": err.Error()})
}
// 添加请求行信息
tx.ProcessURI(r.URL.RequestURI(), r.Method, r.Proto)
}()
// 如果有请求体,则读取并写入事务
if weblog.BODY != "" {
if _, _, err := tx.WriteRequestBody([]byte(weblog.BODY)); err != nil {
return false, nil, fmt.Errorf("error writing request body: %v", err)
}
// 1. 处理连接信息
if err := w.processConnection(tx, weblog, r); err != nil {
return false, nil, fmt.Errorf("connection processing error: %v", err)
}
// 2. 处理请求行
if err := w.processRequestLine(tx, r); err != nil {
return false, nil, fmt.Errorf("request line processing error: %v", err)
}
// 3. 处理请求头
if err := w.processRequestHeaders(tx, r); err != nil {
return false, nil, fmt.Errorf("request headers processing error: %v", err)
}
// 4. 检查请求头阶段的中断
if it := tx.ProcessRequestHeaders(); it != nil {
return w.handleInterruption(tx, it)
}
// 5. 处理请求体
if err := w.processRequestBody(tx, r, weblog); err != nil {
return false, nil, fmt.Errorf("request body processing error: %v", err)
}
// 6. 检查请求体阶段的中断
if it, err := tx.ProcessRequestBody(); err != nil {
return false, nil, fmt.Errorf("request body processing error: %v", err)
} else if it != nil {
return w.handleInterruption(tx, it)
}
// 7. 检查最终的中断状态
if tx.IsInterrupted() {
if it := tx.Interruption(); it != nil {
return w.handleInterruption(tx, it)
}
// 处理请求头和请求体
if it := tx.ProcessRequestHeaders(); it != nil {
return false, nil, fmt.Errorf("error ProcessRequestHeaders: %v", it)
}
if _, err := tx.ProcessRequestBody(); err != nil {
return false, nil, fmt.Errorf("request body processing error: %v", err)
}
interrupted := tx.IsInterrupted()
if interrupted {
//显示详细信息
txState := tx.(plugintypes.TransactionState)
collections := make([][]string, 0)
// we transform this into collection, key, index, value
txState.Variables().All(func(_ variables.RuleVariable, v collection.Collection) bool {
for index, md := range v.FindAll() {
collections = append(collections, []string{
v.Name(),
md.Key(),
strconv.Itoa(index),
md.Value(),
})
}
return true
})
jsdata, err := json.Marshal(collections)
if err != nil {
fmt.Printf("Error marshaling %s\n", err)
}
md := [][]string{}
for _, m := range tx.MatchedRules() {
msg := m.Message()
if msg == "" {
continue
}
md = append(md, []string{strconv.Itoa(m.Rule().ID()), msg})
}
matchedData, err := json.Marshal(md)
if err != nil {
fmt.Printf("Error marshaling %s\n", err)
}
result := map[string]interface{}{
"transaction_id": tx.ID(),
"collections": string(jsdata),
"matched_data": string(matchedData),
"rules_matched_total": strconv.Itoa(len(tx.MatchedRules())),
"audit_log": `{"error": "not implemented"}`,
"disruptive_action": "none",
"disruptive_rule": "-",
"duration": 0,
}
zlog.Error("OWASP Detail", result)
if it := tx.Interruption(); it != nil {
result["disruptive_action"] = it.Action
result["disruptive_rule"] = it.RuleID
}
return interrupted, tx.Interruption(), nil
} else {
return interrupted, nil, fmt.Errorf("request body processing error: %v")
return true, nil, nil
}
return false, nil, nil
}
// processConnection 处理连接信息
func (w *WafOWASP) processConnection(tx types.Transaction, weblog innerbean.WebLog, r *http.Request) error {
clientIP := weblog.SRC_IP
if clientIP == "" {
clientIP = r.RemoteAddr
if idx := strings.LastIndex(clientIP, ":"); idx != -1 {
clientIP = clientIP[:idx]
}
}
return false, nil, fmt.Errorf("error")
clientPort := 0
if weblog.SRC_PORT != "" {
if port, err := strconv.Atoi(weblog.SRC_PORT); err == nil {
clientPort = port
}
}
// 获取服务器信息
serverIP := "127.0.0.1"
serverPort := 80
if r.Host != "" {
host := r.Host
if strings.Contains(host, ":") {
parts := strings.Split(host, ":")
if len(parts) == 2 {
serverIP = parts[0]
if port, err := strconv.Atoi(parts[1]); err == nil {
serverPort = port
}
}
} else {
serverIP = host
if r.TLS != nil {
serverPort = 443
}
}
}
tx.ProcessConnection(clientIP, clientPort, serverIP, serverPort)
return nil
}
type testLogOutput struct {
t *testing.T
// processRequestLine 处理请求行
func (w *WafOWASP) processRequestLine(tx types.Transaction, r *http.Request) error {
// 构建完整的 URI
uri := r.URL.RequestURI()
if uri == "" {
uri = r.URL.Path
if r.URL.RawQuery != "" {
uri += "?" + r.URL.RawQuery
}
}
tx.ProcessURI(uri, r.Method, r.Proto)
return nil
}
func (l testLogOutput) Write(p []byte) (int, error) {
fmt.Println(string(p))
return len(p), nil
// processRequestHeaders 处理请求头
func (w *WafOWASP) processRequestHeaders(tx types.Transaction, r *http.Request) error {
// 添加所有请求头
for name, values := range r.Header {
for _, value := range values {
tx.AddRequestHeader(name, value)
}
}
// 确保 Host 头存在
if r.Host != "" && r.Header.Get("Host") == "" {
tx.AddRequestHeader("Host", r.Host)
}
return nil
}
// processRequestBody 处理请求体
func (w *WafOWASP) processRequestBody(tx types.Transaction, r *http.Request, weblog innerbean.WebLog) error {
var bodyData []byte
var err error
// 优先使用 weblog 中的 BODY
if weblog.BODY != "" {
bodyData = []byte(weblog.BODY)
} else if r.Body != nil {
// 从请求中读取 body
bodyData, err = io.ReadAll(r.Body)
if err != nil {
return fmt.Errorf("failed to read request body: %v", err)
}
// 重新设置 body以便后续处理
r.Body = io.NopCloser(bytes.NewReader(bodyData))
}
if len(bodyData) > 0 {
// 写入请求体数据,让 Coraza 自动解析
if _, _, err := tx.WriteRequestBody(bodyData); err != nil {
return fmt.Errorf("failed to write request body: %v", err)
}
}
return nil
}
// handleInterruption 处理中断情况
func (w *WafOWASP) handleInterruption(tx types.Transaction, it *types.Interruption) (bool, *types.Interruption, error) {
// 收集详细信息用于日志记录
details := w.collectTransactionDetails(tx)
// 记录详细的日志信息
w.logger.Error("OWASP WAF Interruption", details)
// 将详细信息添加到中断对象中,以便返回给用户
if it != nil {
// 将匹配的规则信息转换为字符串
var ruleDetails string
if matchedRules, ok := details["matched_rules"].([]map[string]interface{}); ok && len(matchedRules) > 0 {
for i, rule := range matchedRules {
ruleDetails += fmt.Sprintf("规则ID: %v, 消息: %v", rule["id"], rule["message"])
if i < len(matchedRules)-1 {
ruleDetails += "\n"
}
}
// 将规则详情添加到中断数据中
it.Data = ruleDetails
}
}
return true, it, nil
}
// collectTransactionDetails 收集事务详细信息
func (w *WafOWASP) collectTransactionDetails(tx types.Transaction) map[string]interface{} {
details := map[string]interface{}{
"transaction_id": tx.ID(),
"interrupted": tx.IsInterrupted(),
}
// 收集匹配的规则信息
matchedRules := []map[string]interface{}{}
for _, rule := range tx.MatchedRules() {
if rule.Message() != "" {
matchedRules = append(matchedRules, map[string]interface{}{
"id": rule.Rule().ID(),
"message": rule.Message(),
"file": rule.Rule().File(),
"line": rule.Rule().Line(),
})
}
}
details["matched_rules"] = matchedRules
details["rules_matched_total"] = len(matchedRules)
// 收集变量信息(仅在需要时)
if txState, ok := tx.(plugintypes.TransactionState); ok {
collections := []map[string]interface{}{}
txState.Variables().All(func(_ variables.RuleVariable, v collection.Collection) bool {
for index, md := range v.FindAll() {
collections = append(collections, map[string]interface{}{
"collection": v.Name(),
"key": md.Key(),
"index": index,
"value": md.Value(),
})
}
return true
})
details["collections"] = collections
}
// 收集中断信息
if it := tx.Interruption(); it != nil {
details["interruption"] = map[string]interface{}{
"action": it.Action,
"rule_id": it.RuleID,
"status": it.Status,
}
}
return details
}
// 处理响应
func (w *WafOWASP) ProcessResponse(tx types.Transaction, statusCode int, headers http.Header, body []byte) (*types.Interruption, error) {
if !w.IsActive {
return nil, nil
}
// 添加响应头
for name, values := range headers {
for _, value := range values {
tx.AddResponseHeader(name, value)
}
}
// 处理响应头
if it := tx.ProcessResponseHeaders(statusCode, "HTTP/1.1"); it != nil {
return it, nil
}
// 写入响应体
if len(body) > 0 {
if _, _, err := tx.WriteResponseBody(body); err != nil {
return nil, fmt.Errorf("failed to write response body: %v", err)
}
}
// 处理响应体
if it, err := tx.ProcessResponseBody(); err != nil {
return nil, fmt.Errorf("response body processing error: %v", err)
} else if it != nil {
return it, nil
}
return nil, nil
}