Files
SamWaf/api/waf_log.go
T

368 lines
10 KiB
Go

package api
import (
"SamWaf/common/zlog"
"SamWaf/global"
"SamWaf/innerbean"
"SamWaf/model/common/response"
"SamWaf/model/request"
response2 "SamWaf/model/response"
"SamWaf/utils"
"SamWaf/wafdb"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"net/url"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)
type WafLogAPi struct {
}
func (w *WafLogAPi) GetDetailApi(c *gin.Context) {
var req request.WafAttackLogDetailReq
err := c.ShouldBind(&req)
if err == nil {
if global.GDATA_CURRENT_CHANGE {
//如果正在切换库 跳过
response.FailWithMessage("正在切换数据库请等待", c)
return
}
wafLog, _ := wafLogService.GetDetailApi(req)
response.OkWithDetailed(wafLog, "获取成功", c)
} else {
response.FailWithMessage("解析失败", c)
}
}
func (w *WafLogAPi) GetListApi(c *gin.Context) {
var req request.WafAttackLogSearch
err := c.ShouldBindJSON(&req)
if err == nil {
if global.GDATA_CURRENT_CHANGE {
//如果正在切换库 跳过
response.FailWithMessage("正在切换数据库请等待", c)
return
}
wafLogs, total, err2 := wafLogService.GetListApi(req)
if err2 != nil {
response.FailWithMessage("访问列表失败:"+err2.Error(), c)
} else {
response.OkWithDetailed(response.PageResult{
List: wafLogs,
Total: total,
PageIndex: req.PageIndex,
PageSize: req.PageSize,
}, "获取成功", c)
}
} else {
response.FailWithMessage("解析失败", c)
}
}
func (w *WafLogAPi) ExportDBApi(c *gin.Context) {
if global.GWAF_CAN_EXPORT_DOWNLOAD_LOG == false {
global.GQEQUE_MESSAGE_DB.Enqueue(innerbean.OperatorMessageInfo{
BaseMessageInfo: innerbean.BaseMessageInfo{OperaType: "导出失败"},
OperaCnt: "当前不允许导出",
})
response.FailWithMessage("当前不允许导出", c)
return
}
if global.GDATA_CURRENT_CHANGE {
//如果正在切换库 跳过
response.FailWithMessage("正在切换数据库请等待", c)
return
}
//TODO 必须再验证一次权限
//是否生成了 还没下载
if len(global.GWAF_RUNTIME_CURRENT_EXPORT_DB_LOG_FILE_PATH) > 0 {
response.FailWithMessage("文件还未下载请等待", c)
return
}
go func() {
currentDir := utils.GetCurrentDir()
downLoadDir := currentDir + "/download"
// 判断备份目录是否存在,不存在则创建
if _, err := os.Stat(downLoadDir); os.IsNotExist(err) {
if err := os.MkdirAll(downLoadDir, os.ModePerm); err != nil {
zlog.Error("创建下载目录失败:", err)
return
}
}
//处理老旧数据
duration := 30 * time.Minute
utils.DeleteOldFiles(downLoadDir, duration)
// 创建下载文件
downloadFileName := fmt.Sprintf("local_log_backup_%s.db", time.Now().Format("20060102150405"))
downloadFilePath := filepath.Join(downLoadDir, downloadFileName)
err := wafdb.BackupDatabase(global.GWAF_LOCAL_LOG_DB, downloadFilePath)
if err != nil {
global.GQEQUE_MESSAGE_DB.Enqueue(innerbean.OpResultMessageInfo{
BaseMessageInfo: innerbean.BaseMessageInfo{OperaType: "DOWNLOAD_LOG", Server: global.GWAF_CUSTOM_SERVER_NAME},
Msg: "导出失败",
Success: "true",
})
} else {
global.GWAF_RUNTIME_CURRENT_EXPORT_DB_LOG_FILE_PATH = downloadFilePath
//发送websocket 推送消息
global.GQEQUE_MESSAGE_DB.Enqueue(innerbean.ExportResultMessageInfo{
BaseMessageInfo: innerbean.BaseMessageInfo{OperaType: "DOWNLOAD_LOG", Server: global.GWAF_CUSTOM_SERVER_NAME},
Msg: "导出完毕",
Success: "true",
})
}
}()
}
func (w *WafLogAPi) DownloadApi(c *gin.Context) {
if global.GWAF_CAN_EXPORT_DOWNLOAD_LOG == false {
global.GQEQUE_MESSAGE_DB.Enqueue(innerbean.OperatorMessageInfo{
BaseMessageInfo: innerbean.BaseMessageInfo{OperaType: "下载失败"},
OperaCnt: "当前不允许下载",
})
c.JSON(http.StatusInternalServerError, gin.H{"message": "当前不允许下载"})
return
}
if len(global.GWAF_RUNTIME_CURRENT_EXPORT_DB_LOG_FILE_PATH) == 0 {
c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to download file,not find file"})
return
}
// 提供文件下载
c.FileAttachment(global.GWAF_RUNTIME_CURRENT_EXPORT_DB_LOG_FILE_PATH, "log.db")
global.GWAF_RUNTIME_CURRENT_EXPORT_DB_LOG_FILE_PATH = ""
// 下载完成后删除文件
err := os.Remove(global.GWAF_RUNTIME_CURRENT_EXPORT_DB_LOG_FILE_PATH)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to delete file"})
return
}
}
func (w *WafLogAPi) GetListByHostCodeApi(c *gin.Context) {
var req request.WafAttackLogSearch
err := c.ShouldBind(&req)
if err == nil {
if global.GDATA_CURRENT_CHANGE {
//如果正在切换库 跳过
response.FailWithMessage("正在切换数据库请等待", c)
return
}
wafLogs, total, _ := wafLogService.GetListByHostCodeApi(req)
response.OkWithDetailed(response.PageResult{
List: wafLogs,
Total: total,
PageIndex: req.PageIndex,
PageSize: req.PageSize,
}, "获取成功", c)
} else {
response.FailWithMessage("解析失败", c)
}
}
func (w *WafLogAPi) GetAllShareDbApi(c *gin.Context) {
wafShareList, _ := wafShareDbService.GetAllShareDbApi()
allShareDbRep := make([]response2.AllShareDbRep, len(wafShareList)) // 创建数组
for i, _ := range wafShareList {
allShareDbRep[i] = response2.AllShareDbRep{
StartTime: wafShareList[i].StartTime,
EndTime: wafShareList[i].EndTime,
FileName: wafShareList[i].FileName,
Cnt: wafShareList[i].Cnt,
}
}
response.OkWithDetailed(allShareDbRep, "获取成功", c)
}
// http 原始请求并进行脱敏处理
func (w *WafLogAPi) GetHttpCopyMaskApi(c *gin.Context) {
var req request.WafAttackLogDetailReq
err := c.ShouldBind(&req)
if err == nil {
if global.GDATA_CURRENT_CHANGE {
//如果正在切换库 跳过
response.FailWithMessage("正在切换数据库请等待", c)
return
}
wafLog, _ := wafLogService.GetDetailApi(req)
if req.OutputFormat == "curl" {
response.OkWithDetailed(GenerateCurlRequest(wafLog), "获取成功", c)
} else {
response.OkWithDetailed(GenerateRawHTTPRequest(wafLog), "获取成功", c)
}
} else {
response.FailWithMessage("解析失败", c)
}
}
// GetAttackIPListApi 获取风险数据列表
func (w *WafLogAPi) GetAttackIPListApi(c *gin.Context) {
var req request.WafAttackIpTagSearch
err := c.ShouldBindJSON(&req)
if err == nil {
ipAttackTags, total, err2 := wafLogService.GetAttackIpListApi(req)
if err2 != nil {
response.FailWithMessage("访问列表失败:"+err2.Error(), c)
} else {
response.OkWithDetailed(response.PageResult{
List: ipAttackTags,
Total: total,
PageIndex: req.PageIndex,
PageSize: req.PageSize,
}, "获取成功", c)
}
} else {
response.FailWithMessage("解析失败", c)
}
}
// GetAllIpTagApi 获取所有ip tag
func (w *WafLogAPi) GetAllIpTagApi(c *gin.Context) {
ipAttackTags, err2 := wafLogService.GetAllAttackIPTagListApi()
if err2 != nil {
response.FailWithMessage("访问ip tag 失败:"+err2.Error(), c)
} else {
response.OkWithDetailed(ipAttackTags, "获取成功", c)
}
}
func GenerateRawHTTPRequest(weblog innerbean.WebLog) string {
reqUrl := weblog.URL
if weblog.SrcURL != nil {
reqUrl = string(weblog.SrcURL)
}
parsedURL, err := url.Parse(reqUrl)
if err != nil {
return ""
}
// 构建请求行
pathWithQuery := parsedURL.Path
if parsedURL.RawQuery != "" {
pathWithQuery += "?" + parsedURL.RawQuery
}
// 根据协议确定 HTTP 版本
httpVersion := "HTTP/1.1"
if weblog.Scheme != "" {
httpVersion = weblog.Scheme
}
// 处理敏感头信息
maskedHeaders := maskSensitiveHeader(weblog.HEADER)
headers := strings.Split(maskedHeaders, "\n")
// 处理 Cookie
maskedCookies := maskSensitiveCookies(weblog.COOKIES)
if maskedCookies != "" {
cookieHeader := fmt.Sprintf("Cookie: %s", maskedCookies)
// 替换或添加 Cookie 头
cookieFound := false
for i, h := range headers {
if strings.HasPrefix(strings.TrimSpace(h), "Cookie:") {
headers[i] = cookieHeader
cookieFound = true
break
}
}
if !cookieFound {
headers = append(headers, cookieHeader)
}
}
// 确保 Host 头存在
host := parsedURL.Host
if host != "" {
hostExists := false
for _, h := range headers {
if strings.HasPrefix(strings.TrimSpace(strings.ToLower(h)), "host:") {
hostExists = true
break
}
}
if !hostExists {
headers = append(headers, fmt.Sprintf("Host: %s", host))
}
}
// 构建最终 header
var cleanHeaders []string
for _, h := range headers {
if trimmed := strings.TrimSpace(h); trimmed != "" {
cleanHeaders = append(cleanHeaders, trimmed)
}
}
// 构建完整请求
requestLines := []string{
fmt.Sprintf("%s %s %s",
weblog.METHOD,
pathWithQuery,
httpVersion,
),
}
requestLines = append(requestLines, cleanHeaders...)
// 添加 body(如果有)
if weblog.BODY != "" {
requestLines = append(requestLines, "", weblog.BODY)
}
return strings.Join(requestLines, "\n")
}
func GenerateCurlRequest(weblog innerbean.WebLog) string {
headers := strings.Split(weblog.HEADER, "\n")
maskedHeaders := maskSensitiveHeader(weblog.HEADER)
headers = strings.Split(maskedHeaders, "\n")
headerStrings := ""
for _, header := range headers {
headerStrings += fmt.Sprintf("-H '%s' ", strings.TrimSpace(header))
}
maskedCookies := maskSensitiveCookies(weblog.COOKIES)
reqUrl := weblog.URL
if weblog.SrcURL != nil {
reqUrl = string(weblog.SrcURL)
}
curlCommand := fmt.Sprintf(
"curl -X %s %s \\\n --url '%s' \\\n --cookie '%s' \\\n --data '%s'",
weblog.METHOD,
headerStrings,
reqUrl,
maskedCookies,
weblog.BODY,
)
return curlCommand
}
func maskSensitiveHeader(header string) string {
sensitiveKeys := []string{
"Authorization", "Token", "Api-Key", "Secret", "Access-Token", "X-Api-Key",
"X-Access-Token", "X-Secret", "Session-Key", "Set-Cookie",
}
maskedHeader := header
for _, key := range sensitiveKeys {
regex := regexp.MustCompile(fmt.Sprintf(`(?i)(%s):\s*[^\\n]+`, key))
maskedHeader = regex.ReplaceAllString(maskedHeader, "$1: [MASKED]")
}
return maskedHeader
}
func maskSensitiveCookies(cookies string) string {
cookieRegex := regexp.MustCompile(`(?i)(sessionid|auth|token|key|secret)=[^;]+`)
return cookieRegex.ReplaceAllString(cookies, "$1=[MASKED]")
}