feat:custom blocking page

#115 #IB0ULW
This commit is contained in:
samwaf
2025-02-08 08:27:34 +08:00
parent 70c775530a
commit 0599206789
32 changed files with 610 additions and 85 deletions

View File

@@ -103,7 +103,11 @@ SamWaf is a lightweight, open-source web application firewall for small companie
- Data obfuscation
- Supports global one-click configuration
- Supports OWASP CRS
- Automatic SSL certificate application and renewal
- Bulk SSL certificate expiration check
- IPv6 support
- Customizable blocking page
-
# Usage Instructions
**It is strongly recommended to conduct thorough testing in a test environment before deploying to production. If any issues arise, please provide feedback promptly.**
## Download the Latest Version

View File

@@ -101,6 +101,10 @@ SamWaf网站防火墙是一款适用于小公司、工作室和个人网站的
- 通讯日志加密
- 信息脱敏保存
- 支持OWASP CRS规则集
- 自动SSL证书申请以及续签
- SSL证书批量检测到期情况
- 支持IPV6
- 支持自定义拦截界面
# 使用说明

View File

@@ -33,6 +33,7 @@ type APIGroup struct {
WafSslExpireApi
WafHttpAuthBaseApi
WafTaskApi
WafBlockingPageApi
}
var APIGroupAPP = new(APIGroup)
@@ -78,4 +79,6 @@ var (
wafHttpAuthBaseService = waf_service.WafHttpAuthBaseServiceApp
TaskService = waf_service.WafTaskServiceApp
wafBlockingPageService = waf_service.WafBlockingPageServiceApp
)

133
api/waf_blockingpage_api.go Normal file
View File

@@ -0,0 +1,133 @@
package api
import (
"SamWaf/enums"
"SamWaf/global"
"SamWaf/model"
"SamWaf/model/common/response"
"SamWaf/model/request"
"SamWaf/model/spec"
"errors"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type WafBlockingPageApi struct {
}
func (w *WafBlockingPageApi) AddApi(c *gin.Context) {
var req request.WafBlockingPageAddReq
err := c.ShouldBindJSON(&req)
if err == nil {
cnt := wafBlockingPageService.CheckIsExistApi(req)
if cnt == 0 {
err = wafBlockingPageService.AddApi(req)
if err == nil {
w.NotifyWaf(req.HostCode)
response.OkWithMessage("添加成功", c)
} else {
response.FailWithMessage("添加失败", c)
}
return
} else {
response.FailWithMessage("当前记录已经存在", c)
return
}
} else {
response.FailWithMessage("解析失败", c)
}
}
func (w *WafBlockingPageApi) GetDetailApi(c *gin.Context) {
var req request.WafBlockingPageDetailReq
err := c.ShouldBind(&req)
if err == nil {
bean := wafBlockingPageService.GetDetailApi(req)
response.OkWithDetailed(bean, "获取成功", c)
} else {
response.FailWithMessage("解析失败", c)
}
}
func (w *WafBlockingPageApi) GetListApi(c *gin.Context) {
var req request.WafBlockingPageSearchReq
err := c.ShouldBindJSON(&req)
if err == nil {
BlockingPage, total, _ := wafBlockingPageService.GetListApi(req)
response.OkWithDetailed(response.PageResult{
List: BlockingPage,
Total: total,
PageIndex: req.PageIndex,
PageSize: req.PageSize,
}, "获取成功", c)
} else {
response.FailWithMessage("解析失败", c)
}
}
func (w *WafBlockingPageApi) DelApi(c *gin.Context) {
var req request.WafBlockingPageDelReq
err := c.ShouldBind(&req)
if err == nil {
bean := wafBlockingPageService.GetDetailByIdApi(req.Id)
if bean.Id == "" {
response.FailWithMessage("未找到信息", c)
return
}
err = wafBlockingPageService.DelApi(req)
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
response.FailWithMessage("请检测参数", c)
} else if err != nil {
response.FailWithMessage("发生错误", c)
} else {
w.NotifyWaf(bean.HostCode)
response.OkWithMessage("删除成功", c)
}
} else {
response.FailWithMessage("解析失败", c)
}
}
func (w *WafBlockingPageApi) ModifyApi(c *gin.Context) {
var req request.WafBlockingPageEditReq
err := c.ShouldBindJSON(&req)
if err == nil {
bean := wafBlockingPageService.GetDetailByIdApi(req.Id)
err = wafBlockingPageService.ModifyApi(req)
if err != nil {
response.FailWithMessage("编辑发生错误"+err.Error(), c)
} else {
w.NotifyWaf(req.HostCode)
if bean.HostCode != req.HostCode && bean.HostCode != "" {
//老的主机编码
w.NotifyWaf(bean.HostCode)
}
response.OkWithMessage("编辑成功", c)
}
} else {
response.FailWithMessage("解析失败", c)
}
}
func (w *WafBlockingPageApi) NotifyWaf(hostCode string) {
var blockingPageList []model.BlockingPage
global.GWAF_LOCAL_DB.Where("host_code=? ", hostCode).Find(&blockingPageList)
blockingPageMap := map[string]model.BlockingPage{}
if len(blockingPageList) > 0 {
for i := 0; i < len(blockingPageList); i++ {
if blockingPageList[i].BlockingType == "not_match_website" {
blockingPageMap["not_match_website"] = blockingPageList[i]
} else if blockingPageList[i].BlockingType == "other_block" {
blockingPageMap["other_block"] = blockingPageList[i]
}
}
}
var chanInfo = spec.ChanCommonHost{
HostCode: hostCode,
Type: enums.ChanTypeBlockingPage,
Content: blockingPageMap,
}
global.GWAF_CHAN_MSG <- chanInfo
}

View File

@@ -7,9 +7,11 @@ import (
)
func TestCodeGeneration(t *testing.T) {
// 定义字段信息的字符串
// 唯一校验定义字段信息的字符串
fieldDefs := []string{
"TaskName:task_name:string",
"BlockingPageName:blocking_page_name:string",
"BlockingType:blocking_type:string",
"HostCode:host_code:string",
}
// 构造 `uniFields` 列表
@@ -27,6 +29,6 @@ func TestCodeGeneration(t *testing.T) {
}
}
fields := GetStructFields(model.Task{})
CodeGeneration("Task", fields, uniFields)
fields := GetStructFields(model.BlockingPage{})
CodeGeneration("BlockingPage", fields, uniFields)
}

View File

@@ -14,4 +14,5 @@ const (
ChanTypeLoadBalance
ChanTypeSSL
ChanTypeHttpauth
ChanTypeBlockingPage
)

View File

@@ -0,0 +1,4 @@
package global
// GLOBAL_DEFAULT_BLOCK_INFO 默认被拦截的HTML的内容
var GLOBAL_DEFAULT_BLOCK_INFO string = "<!DOCTYPE html>\n <html lang=\"zh-CN\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>您的访问被阻止</title>\n <style>\n /* 基本样式 */\n body {\n font-family: 'Arial', sans-serif;\n background-color: #f4f7fa;\n margin: 0;\n padding: 0;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n color: #333;\n }\n \n /* 页面内容的容器 */\n .container {\n background-color: #ffffff;\n border-radius: 8px;\n padding: 40px;\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);\n text-align: center;\n max-width: 600px;\n width: 100%;\n }\n \n /* 标题样式 */\n h1 {\n font-size: 2.5em;\n color: #d9534f;\n margin: 0;\n }\n \n /* 访问识别码样式 */\n h3 {\n font-size: 1.5em;\n color: #5bc0de;\n margin: 20px 0;\n }\n \n /* 提示文字 */\n .message {\n font-size: 1.1em;\n color: #999;\n margin-bottom: 30px;\n }\n \n /* 返回首页按钮样式 */\n .back-btn {\n background-color: #5cb85c;\n color: white;\n border: none;\n padding: 10px 20px;\n border-radius: 5px;\n text-decoration: none;\n font-size: 1.1em;\n }\n \n .back-btn:hover {\n background-color: #4cae4c;\n }\n \n .back-btn:active {\n background-color: #398439;\n }\n \n /* 响应式设计 */\n @media (max-width: 768px) {\n .container {\n padding: 30px;\n }\n \n h1 {\n font-size: 2em;\n }\n \n h3 {\n font-size: 1.3em;\n }\n \n .message {\n font-size: 1em;\n }\n }\n\t\t /* 版权信息 */\n .footer {\n margin-top: 30px;\n font-size: 0.9em;\n color: #777;\n }\n </style>\n </head>\n <body>\n <div class=\"container\">\n <h1>您的访问被阻止</h1>\n <p class=\"message\">由于安全策略,您的请求被阻止。<br>如果有疑问,请将下面的访问识别码发给管理员,以便进一步排查。</p>\n <h3>访问识别码:[[.SAMWAF_REQ_UUID]]</h3>\n\t\t <div class=\"footer\">\n 安全防护由SamWAF提供。\n </div>\n </div> \n </body>\n </html>"

View File

@@ -493,6 +493,12 @@ func (m *wafSystenService) run() {
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.LoadHost(host)
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.StartAllProxyServer()
break
case enums.ChanTypeBlockingPage:
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostTarget[globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostCode[msg.HostCode]].Mux.Lock()
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostTarget[globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostCode[msg.HostCode]].BlockingPage = msg.Content.(map[string]model.BlockingPage)
zlog.Debug("远程配置", zap.Any("配置自定义拦截界面信息", msg.Content.(map[string]model.BlockingPage)))
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostTarget[globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.HostCode[msg.HostCode]].Mux.Unlock()
break
}
//end switch

14
model/blocking_page.go Normal file
View File

@@ -0,0 +1,14 @@
package model
import "SamWaf/model/baseorm"
// BlockingPage 自定义拦截模板界面
type BlockingPage struct {
baseorm.BaseOrm
BlockingPageName string `json:"blocking_page_name"` //自定义拦截模板页面名称
BlockingType string `json:"blocking_type"` //自定义类型 被拦截
HostCode string `json:"host_code"` //适用于某个网站唯一码
ResponseCode string `json:"response_code"` //响应代码 默认403
ResponseHeader string `json:"response_header"` //响应Header头信息JSON
ResponseContent string `json:"response_content"` //响应内容
}

View File

@@ -0,0 +1,30 @@
package request
import "SamWaf/model/common/request"
type WafBlockingPageAddReq struct {
BlockingPageName string `json:"blocking_page_name" form:"blocking_page_name"`
BlockingType string `json:"blocking_type" form:"blocking_type"`
HostCode string `json:"host_code" form:"host_code"`
ResponseCode string `json:"response_code" form:"response_code"`
ResponseHeader string `json:"response_header" form:"response_header"`
ResponseContent string `json:"response_content" form:"response_content"`
}
type WafBlockingPageEditReq struct {
Id string `json:"id"`
BlockingPageName string `json:"blocking_page_name" form:"blocking_page_name"`
BlockingType string `json:"blocking_type" form:"blocking_type"`
HostCode string `json:"host_code" form:"host_code"`
ResponseCode string `json:"response_code" form:"response_code"`
ResponseHeader string `json:"response_header" form:"response_header"`
ResponseContent string `json:"response_content" form:"response_content"`
}
type WafBlockingPageDetailReq struct {
Id string `json:"id" form:"id"`
}
type WafBlockingPageDelReq struct {
Id string `json:"id" form:"id"`
}
type WafBlockingPageSearchReq struct {
request.PageInfo
}

View File

@@ -22,12 +22,14 @@ type HostSafe struct {
UrlWhiteLists []model.URLAllowList //url 白名单
LdpUrlLists []model.LDPUrl //url 隐私保护
IPBlockLists []model.IPBlockList //ip 黑名单
UrlBlockLists []model.URLBlockList //url 黑名单
LoadBalanceLists []model.LoadBalance //负载均衡
LoadBalanceRuntime *LoadBalanceRuntime //负载运行时
AntiCCBean model.AntiCC //抵御CC
HttpAuthBases []model.HttpAuthBase //HTTP AUTH校验
IPBlockLists []model.IPBlockList //ip 黑名单
UrlBlockLists []model.URLBlockList //url 黑名单
LoadBalanceLists []model.LoadBalance //负载均衡
LoadBalanceRuntime *LoadBalanceRuntime //负载运行时
AntiCCBean model.AntiCC //抵御CC
HttpAuthBases []model.HttpAuthBase //HTTP AUTH校验
BlockingPage map[string]model.BlockingPage //自定义拦截界面
}
// 负载处理运行对象

View File

@@ -31,6 +31,7 @@ type ApiGroup struct {
WafSslExpireRouter
WafHttpAuthBaseRouter
WafTaskRouter
WafBlockingPageRouter
}
type PublicApiGroup struct {
LoginRouter

View File

@@ -0,0 +1,19 @@
package router
import (
"SamWaf/api"
"github.com/gin-gonic/gin"
)
type WafBlockingPageRouter struct {
}
func (receiver *WafBlockingPageRouter) InitWafBlockingPageRouter(group *gin.RouterGroup) {
api := api.APIGroupAPP.WafBlockingPageApi
router := group.Group("")
router.POST("/samwaf/wafhost/blockingpage/add", api.AddApi)
router.POST("/samwaf/wafhost/blockingpage/list", api.GetListApi)
router.GET("/samwaf/wafhost/blockingpage/detail", api.GetDetailApi)
router.POST("/samwaf/wafhost/blockingpage/edit", api.ModifyApi)
router.GET("/samwaf/wafhost/blockingpage/del", api.DelApi)
}

View File

@@ -0,0 +1,187 @@
package waf_service
import (
"SamWaf/customtype"
"SamWaf/global"
"SamWaf/model"
"SamWaf/model/baseorm"
"SamWaf/model/request"
"errors"
uuid "github.com/satori/go.uuid"
"time"
)
type WafBlockingPageService struct{}
var WafBlockingPageServiceApp = new(WafBlockingPageService)
func (receiver *WafBlockingPageService) AddApi(req request.WafBlockingPageAddReq) error {
var bean = &model.BlockingPage{
BaseOrm: baseorm.BaseOrm{
Id: uuid.NewV4().String(),
USER_CODE: global.GWAF_USER_CODE,
Tenant_ID: global.GWAF_TENANT_ID,
CREATE_TIME: customtype.JsonTime(time.Now()),
UPDATE_TIME: customtype.JsonTime(time.Now()),
},
BlockingPageName: req.BlockingPageName,
BlockingType: req.BlockingType,
HostCode: req.HostCode,
ResponseCode: req.ResponseCode,
ResponseHeader: req.ResponseHeader,
ResponseContent: req.ResponseContent,
}
global.GWAF_LOCAL_DB.Create(bean)
return nil
}
func (receiver *WafBlockingPageService) CheckIsExistApi(req request.WafBlockingPageAddReq) int {
var total int64 = 0
/*where条件*/
var whereField = ""
var whereValues []interface{}
//where字段
whereField = ""
if len(req.BlockingPageName) > 0 {
if len(whereField) > 0 {
whereField = whereField + " and "
}
whereField = whereField + " blocking_page_name=? "
}
if len(req.BlockingType) > 0 {
if len(whereField) > 0 {
whereField = whereField + " and "
}
whereField = whereField + " blocking_type=? "
}
if len(req.HostCode) > 0 {
if len(whereField) > 0 {
whereField = whereField + " and "
}
whereField = whereField + " host_code=? "
}
//where字段赋值
if len(req.BlockingPageName) > 0 {
if len(whereField) > 0 {
whereValues = append(whereValues, req.BlockingPageName)
}
}
if len(req.BlockingType) > 0 {
if len(whereField) > 0 {
whereValues = append(whereValues, req.BlockingType)
}
}
if len(req.HostCode) > 0 {
if len(whereField) > 0 {
whereValues = append(whereValues, req.HostCode)
}
}
global.GWAF_LOCAL_DB.Model(&model.BlockingPage{}).Where(whereField, whereValues...).Count(&total)
return int(total)
}
func (receiver *WafBlockingPageService) ModifyApi(req request.WafBlockingPageEditReq) error {
// 根据唯一字段生成查询条件只有在UniFields不为空时才进行存在性检查
var total int64 = 0
/*where条件*/
var whereField = ""
var whereValues []interface{}
//where字段
whereField = ""
if len(req.BlockingPageName) > 0 {
if len(whereField) > 0 {
whereField = whereField + " and "
}
whereField = whereField + " blocking_page_name=? "
}
if len(req.BlockingType) > 0 {
if len(whereField) > 0 {
whereField = whereField + " and "
}
whereField = whereField + " blocking_type=? "
}
if len(req.HostCode) > 0 {
if len(whereField) > 0 {
whereField = whereField + " and "
}
whereField = whereField + " host_code=? "
}
//where字段赋值
if len(req.BlockingPageName) > 0 {
whereValues = append(whereValues, req.BlockingPageName)
}
if len(req.BlockingType) > 0 {
whereValues = append(whereValues, req.BlockingType)
}
if len(req.HostCode) > 0 {
whereValues = append(whereValues, req.HostCode)
}
global.GWAF_LOCAL_DB.Model(&model.BlockingPage{}).Where(whereField, whereValues...).Count(&total)
// 查询是否已存在记录
var bean model.BlockingPage
global.GWAF_LOCAL_DB.Model(&model.BlockingPage{}).Where(whereField, whereValues...).Limit(1).Find(&bean)
if int(total) > 0 && bean.Id != "" && bean.Id != req.Id {
return errors.New("当前记录已经存在")
}
beanMap := map[string]interface{}{
"BlockingPageName": req.BlockingPageName,
"BlockingType": req.BlockingType,
"HostCode": req.HostCode,
"ResponseCode": req.ResponseCode,
"ResponseHeader": req.ResponseHeader,
"ResponseContent": req.ResponseContent,
"UPDATE_TIME": customtype.JsonTime(time.Now()),
}
err := global.GWAF_LOCAL_DB.Model(model.BlockingPage{}).Where("id = ?", req.Id).Updates(beanMap).Error
return err
}
func (receiver *WafBlockingPageService) GetDetailApi(req request.WafBlockingPageDetailReq) model.BlockingPage {
var bean model.BlockingPage
global.GWAF_LOCAL_DB.Where("id=?", req.Id).Find(&bean)
return bean
}
func (receiver *WafBlockingPageService) GetDetailByIdApi(id string) model.BlockingPage {
var bean model.BlockingPage
global.GWAF_LOCAL_DB.Where("id=?", id).Find(&bean)
return bean
}
func (receiver *WafBlockingPageService) GetListApi(req request.WafBlockingPageSearchReq) ([]model.BlockingPage, int64, error) {
var list []model.BlockingPage
var total int64 = 0
global.GWAF_LOCAL_DB.Model(&model.BlockingPage{}).Limit(req.PageSize).Offset(req.PageSize * (req.PageIndex - 1)).Find(&list)
global.GWAF_LOCAL_DB.Model(&model.BlockingPage{}).Count(&total)
return list, total, nil
}
func (receiver *WafBlockingPageService) DelApi(req request.WafBlockingPageDelReq) error {
var bean model.BlockingPage
err := global.GWAF_LOCAL_DB.Where("id = ?", req.Id).First(&bean).Error
if err != nil {
return err
}
err = global.GWAF_LOCAL_DB.Where("id = ?", req.Id).Delete(model.BlockingPage{}).Error
return err
}

View File

@@ -157,6 +157,10 @@ func InitCoreDb(currentDir string) {
//任务
db.AutoMigrate(&model.Task{})
//自定义拦截界面
db.AutoMigrate(&model.BlockingPage{})
global.GWAF_LOCAL_DB.Callback().Query().Before("gorm:query").Register("tenant_plugin:before_query", before_query)
global.GWAF_LOCAL_DB.Callback().Query().Before("gorm:update").Register("tenant_plugin:before_update", before_update)

View File

@@ -14,7 +14,7 @@ import (
*
检测白名单 ip
*/
func (waf *WafEngine) CheckAllowIP(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe) detection.Result {
func (waf *WafEngine) CheckAllowIP(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe, globalHostTarget *wafenginmodel.HostSafe) detection.Result {
result := detection.Result{
JumpGuardResult: false,
IsBlock: false,

View File

@@ -15,7 +15,7 @@ import (
检测允许的URL
返回是否满足条件
*/
func (waf *WafEngine) CheckAllowURL(r *http.Request, weblogbean innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe) detection.Result {
func (waf *WafEngine) CheckAllowURL(r *http.Request, weblogbean innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe, globalHostTarget *wafenginmodel.HostSafe) detection.Result {
result := detection.Result{
JumpGuardResult: false,
IsBlock: false,

View File

@@ -13,7 +13,7 @@ import (
*
检测爬虫
*/
func (waf *WafEngine) CheckBot(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe) detection.Result {
func (waf *WafEngine) CheckBot(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe, globalHostTarget *wafenginmodel.HostSafe) detection.Result {
result := detection.Result{
JumpGuardResult: false,
IsBlock: false,

View File

@@ -15,7 +15,7 @@ import (
*
检测cc
*/
func (waf *WafEngine) CheckCC(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe) detection.Result {
func (waf *WafEngine) CheckCC(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe, globalHostTarget *wafenginmodel.HostSafe) detection.Result {
result := detection.Result{
JumpGuardResult: false,
IsBlock: false,

View File

@@ -15,7 +15,7 @@ import (
检测不允许访问的 ip
返回是否满足条件
*/
func (waf *WafEngine) CheckDenyIP(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe) detection.Result {
func (waf *WafEngine) CheckDenyIP(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe, globalHostTarget *wafenginmodel.HostSafe) detection.Result {
result := detection.Result{
JumpGuardResult: false,
IsBlock: false,

View File

@@ -15,7 +15,7 @@ import (
检测不允许访问的 url
返回是否满足条件
*/
func (waf *WafEngine) CheckDenyURL(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe) detection.Result {
func (waf *WafEngine) CheckDenyURL(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe, globalHostTarget *wafenginmodel.HostSafe) detection.Result {
result := detection.Result{
JumpGuardResult: false,
IsBlock: false,

View File

@@ -10,7 +10,7 @@ import (
"strconv"
)
func (waf *WafEngine) CheckOwasp(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe) detection.Result {
func (waf *WafEngine) CheckOwasp(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe, globalHostTarget *wafenginmodel.HostSafe) detection.Result {
result := detection.Result{
JumpGuardResult: false,
IsBlock: false,

View File

@@ -13,7 +13,7 @@ import (
*
检测Rce
*/
func (waf *WafEngine) CheckRce(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe) detection.Result {
func (waf *WafEngine) CheckRce(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe, globalHostTarget *wafenginmodel.HostSafe) detection.Result {
result := detection.Result{
JumpGuardResult: false,
IsBlock: false,

View File

@@ -14,7 +14,7 @@ import (
*
检测rule
*/
func (waf *WafEngine) CheckRule(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe) detection.Result {
func (waf *WafEngine) CheckRule(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe, globalHostTarget *wafenginmodel.HostSafe) detection.Result {
result := detection.Result{
JumpGuardResult: false,
IsBlock: false,

View File

@@ -13,7 +13,7 @@ import (
*
检测扫描工具
*/
func (waf *WafEngine) CheckSan(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe) detection.Result {
func (waf *WafEngine) CheckSan(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe, globalHostTarget *wafenginmodel.HostSafe) detection.Result {
result := detection.Result{
JumpGuardResult: false,
IsBlock: false,

View File

@@ -12,7 +12,7 @@ import (
*
检测敏感词
*/
func (waf *WafEngine) CheckSensitive(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe) detection.Result {
func (waf *WafEngine) CheckSensitive(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe, globalHostTarget *wafenginmodel.HostSafe) detection.Result {
result := detection.Result{
JumpGuardResult: false,
IsBlock: false,

View File

@@ -13,7 +13,7 @@ import (
*
检测sqli
*/
func (waf *WafEngine) CheckSql(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe) detection.Result {
func (waf *WafEngine) CheckSql(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe, globalHostTarget *wafenginmodel.HostSafe) detection.Result {
result := detection.Result{
JumpGuardResult: false,
IsBlock: false,

View File

@@ -13,7 +13,7 @@ import (
*
检测xss
*/
func (waf *WafEngine) CheckXss(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe) detection.Result {
func (waf *WafEngine) CheckXss(r *http.Request, weblogbean *innerbean.WebLog, formValue url.Values, hostTarget *wafenginmodel.HostSafe, globalHostTarget *wafenginmodel.HostSafe) detection.Result {
result := detection.Result{
JumpGuardResult: false,
IsBlock: false,

View File

@@ -0,0 +1,135 @@
package wafenginecore
import (
"SamWaf/common/zlog"
"SamWaf/global"
"SamWaf/innerbean"
"SamWaf/model/wafenginmodel"
"SamWaf/utils"
"bytes"
"encoding/json"
"fmt"
"go.uber.org/zap"
"net/http"
"strconv"
"text/template"
"time"
)
func renderTemplate(templateContent string, data map[string]interface{}) ([]byte, error) {
tmpl, err := template.New("blockingPageTemplate").Delims("[[", "]]").Parse(templateContent)
if err != nil {
return nil, err
}
var renderedCode bytes.Buffer
err = tmpl.Execute(&renderedCode, data)
if err != nil {
return nil, err
}
return renderedCode.Bytes(), nil
}
// EchoErrorInfo ruleName 对内记录 blockInfo 对外展示
func EchoErrorInfo(w http.ResponseWriter, r *http.Request, weblogbean innerbean.WebLog, ruleName string, blockInfo string, hostsafe *wafenginmodel.HostSafe, globalHostSafe *wafenginmodel.HostSafe, isLog bool) {
resBytes := []byte("")
var responseCode int = 403
renderData := map[string]interface{}{
"SAMWAF_REQ_UUID": weblogbean.REQ_UUID,
"SAMWAF_BLOCK_INFO": blockInfo,
}
// 处理 hostsafe 的模板
if blockingPage, ok := hostsafe.BlockingPage["other_block"]; ok {
// 设置 HTTP header
var headers []map[string]string
if err := json.Unmarshal([]byte(blockingPage.ResponseHeader), &headers); err == nil {
for _, header := range headers {
if name, ok := header["name"]; ok {
if value, ok := header["value"]; ok && value != "" {
w.Header().Set(name, value)
}
}
}
}
// 渲染模板
renderedBytes, err := renderTemplate(blockingPage.ResponseContent, renderData)
if err == nil {
resBytes = renderedBytes
} else {
resBytes = []byte(blockingPage.ResponseContent)
}
// 设置响应码
if code, err := strconv.Atoi(blockingPage.ResponseCode); err == nil {
responseCode = code
}
} else if globalBlockingPage, ok := globalHostSafe.BlockingPage["other_block"]; ok {
// 处理 globalHostSafe 的模板
// 设置 HTTP header
var headers []map[string]string
if err := json.Unmarshal([]byte(globalBlockingPage.ResponseHeader), &headers); err == nil {
for _, header := range headers {
if name, ok := header["name"]; ok {
if value, ok := header["value"]; ok && value != "" {
w.Header().Set(name, value)
}
}
}
}
// 渲染模板
renderedBytes, err := renderTemplate(globalBlockingPage.ResponseContent, renderData)
if err == nil {
resBytes = renderedBytes
} else {
resBytes = []byte(globalBlockingPage.ResponseContent)
}
// 设置响应码
if code, err := strconv.Atoi(globalBlockingPage.ResponseCode); err == nil {
responseCode = code
}
} else {
// 默认的阻止页面
renderedBytes, err := renderTemplate(global.GLOBAL_DEFAULT_BLOCK_INFO, renderData)
if err == nil {
resBytes = renderedBytes
} else {
resBytes = []byte(global.GLOBAL_DEFAULT_BLOCK_INFO)
}
}
w.WriteHeader(responseCode)
_, err := w.Write(resBytes)
if err != nil {
zlog.Debug("write fail:", zap.Any("", err))
return
}
if isLog {
go func() {
// 发送推送消息
global.GQEQUE_MESSAGE_DB.Enqueue(innerbean.RuleMessageInfo{
BaseMessageInfo: innerbean.BaseMessageInfo{OperaType: "命中保护规则", Server: global.GWAF_CUSTOM_SERVER_NAME},
Domain: weblogbean.HOST,
RuleInfo: ruleName,
Ip: fmt.Sprintf("%s (%s)", weblogbean.SRC_IP, utils.GetCountry(weblogbean.SRC_IP)),
})
}()
datetimeNow := time.Now()
weblogbean.TimeSpent = datetimeNow.UnixNano()/1e6 - weblogbean.UNIX_ADD_TIME
// 记录响应body
weblogbean.RES_BODY = string(resBytes)
weblogbean.RULE = ruleName
weblogbean.ACTION = "阻止"
weblogbean.STATUS = "阻止访问"
weblogbean.STATUS_CODE = 403
weblogbean.TASK_FLAG = 1
weblogbean.GUEST_IDENTIFICATION = "可疑用户"
global.GQEQUE_LOG_DB.Enqueue(weblogbean)
}
}

View File

@@ -180,18 +180,6 @@ func (waf *WafEngine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
region := utils.GetCountry(clientIP)
// 检测是否已经被CC封禁
ccCacheKey := enums.CACHE_CCVISITBAN_PRE + clientIP
if global.GCACHE_WAFCACHE.IsKeyExist(ccCacheKey) {
visitIPError := fmt.Sprintf("当前IP已经被CC封禁IP:%s 归属地区:%s", clientIP, region)
global.GQEQUE_MESSAGE_DB.Enqueue(innerbean.OperatorMessageInfo{
BaseMessageInfo: innerbean.BaseMessageInfo{OperaType: "CC封禁提醒"},
OperaCnt: visitIPError,
})
EchoErrorInfoNoLog(w, r, "当前IP由于访问频次太高暂时无法访问")
return
}
currentDay, _ := strconv.Atoi(time.Now().Format("20060102"))
//URL 解码
@@ -242,6 +230,19 @@ func (waf *WafEngine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Println("解码失败:", weblogbean.BODY)
}
}
// 检测是否已经被CC封禁
ccCacheKey := enums.CACHE_CCVISITBAN_PRE + clientIP
if global.GCACHE_WAFCACHE.IsKeyExist(ccCacheKey) {
visitIPError := fmt.Sprintf("当前IP已经被CC封禁IP:%s 归属地区:%s", clientIP, region)
global.GQEQUE_MESSAGE_DB.Enqueue(innerbean.OperatorMessageInfo{
BaseMessageInfo: innerbean.BaseMessageInfo{OperaType: "CC封禁提醒"},
OperaCnt: visitIPError,
})
EchoErrorInfo(w, r, weblogbean, "", "当前IP由于访问频次太高暂时无法访问", hostTarget, waf.HostTarget[waf.HostCode[global.GWAF_GLOBAL_HOST_CODE]], false)
return
}
if host == hostTarget.Host.Host+":80" && !strings.HasPrefix(weblogbean.URL, global.GSSL_HTTP_CHANGLE_PATH) && hostTarget.Host.AutoJumpHTTPS == 1 && hostTarget.Host.Ssl == 1 {
// 重定向到 HTTPS 版本的 URL
targetHttpsUrl := fmt.Sprintf("%s%s%s", "https://", r.Host, r.URL.Path)
@@ -260,18 +261,19 @@ func (waf *WafEngine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if hostTarget.Host.GUARD_STATUS == 1 {
//一系列检测逻辑
handleBlock := func(checkFunc func(*http.Request, *innerbean.WebLog, url.Values, *wafenginmodel.HostSafe) detection.Result) bool {
detectionResult := checkFunc(r, &weblogbean, formValues, hostTarget)
handleBlock := func(checkFunc func(*http.Request, *innerbean.WebLog, url.Values, *wafenginmodel.HostSafe, *wafenginmodel.HostSafe) detection.Result) bool {
detectionResult := checkFunc(r, &weblogbean, formValues, hostTarget, waf.HostTarget[waf.HostCode[global.GWAF_GLOBAL_HOST_CODE]])
if detectionResult.IsBlock {
decrementMonitor(hostCode)
EchoErrorInfo(w, r, weblogbean, detectionResult.Title, detectionResult.Content)
EchoErrorInfo(w, r, weblogbean, detectionResult.Title, detectionResult.Content, hostTarget, waf.HostTarget[waf.HostCode[global.GWAF_GLOBAL_HOST_CODE]], true)
return true
}
return false
}
detectionWhiteResult := waf.CheckAllowIP(r, &weblogbean, formValues, hostTarget)
globalHostSafe := waf.HostTarget[waf.HostCode[global.GWAF_GLOBAL_HOST_CODE]]
detectionWhiteResult := waf.CheckAllowIP(r, &weblogbean, formValues, hostTarget, globalHostSafe)
if detectionWhiteResult.JumpGuardResult == false {
detectionWhiteResult = waf.CheckAllowURL(r, weblogbean, formValues, hostTarget)
detectionWhiteResult = waf.CheckAllowURL(r, weblogbean, formValues, hostTarget, globalHostSafe)
}
if detectionWhiteResult.JumpGuardResult == false {
@@ -496,50 +498,6 @@ func (waf *WafEngine) getClientIP(r *http.Request, headers ...string) (error, st
return errors.New("invalid IP address (not IPv4 or IPv6): " + ip), "", ""
}
// EchoErrorInfo ruleName 对内记录 blockInfo 对外展示
func EchoErrorInfo(w http.ResponseWriter, r *http.Request, weblogbean innerbean.WebLog, ruleName string, blockInfo string) {
go func() {
//发送推送消息
global.GQEQUE_MESSAGE_DB.Enqueue(innerbean.RuleMessageInfo{
BaseMessageInfo: innerbean.BaseMessageInfo{OperaType: "命中保护规则", Server: global.GWAF_CUSTOM_SERVER_NAME},
Domain: weblogbean.HOST,
RuleInfo: ruleName,
Ip: fmt.Sprintf("%s (%s)", weblogbean.SRC_IP, utils.GetCountry(weblogbean.SRC_IP)),
})
}()
resBytes := []byte("<html><head><title>您的访问被阻止</title></head><body><center><h1>" + blockInfo + "</h1> <br> 访问识别码:<h3>" + weblogbean.REQ_UUID + "</h3></center></body> </html>")
w.WriteHeader(403)
_, err := w.Write(resBytes)
if err != nil {
zlog.Debug("write fail:", zap.Any("", err))
return
}
datetimeNow := time.Now()
weblogbean.TimeSpent = datetimeNow.UnixNano()/1e6 - weblogbean.UNIX_ADD_TIME
//记录响应body
weblogbean.RES_BODY = string(resBytes)
weblogbean.RULE = ruleName
weblogbean.ACTION = "阻止"
weblogbean.STATUS = "阻止访问"
weblogbean.STATUS_CODE = 403
weblogbean.TASK_FLAG = 1
weblogbean.GUEST_IDENTIFICATION = "可疑用户"
global.GQEQUE_LOG_DB.Enqueue(weblogbean)
}
// EchoErrorInfoNoLog 屏蔽不记录日志
func EchoErrorInfoNoLog(w http.ResponseWriter, r *http.Request, blockInfo string) {
resBytes := []byte("<html><head><title>您的访问被阻止</title></head><body><center><h1>" + blockInfo + "</h1> </h3></center></body> </html>")
w.WriteHeader(403)
_, err := w.Write(resBytes)
if err != nil {
zlog.Debug("write fail:", zap.Any("", err))
return
}
}
func (waf *WafEngine) errorResponse() func(http.ResponseWriter, *http.Request, error) {
return func(w http.ResponseWriter, req *http.Request, err error) {

View File

@@ -166,6 +166,21 @@ func (waf *WafEngine) LoadHost(inHost model.Hosts) []innerbean.ServerRunTime {
//查询HTTP AUTH
var httpAuthList []model.HttpAuthBase
global.GWAF_LOCAL_DB.Where("host_code=? ", inHost.Code).Find(&httpAuthList)
//查询自定义拦截界面
var blockingPageList []model.BlockingPage
global.GWAF_LOCAL_DB.Where("host_code=? ", inHost.Code).Find(&blockingPageList)
blockingPageMap := map[string]model.BlockingPage{}
if len(blockingPageList) > 0 {
for i := 0; i < len(blockingPageList); i++ {
if blockingPageList[i].BlockingType == "not_match_website" {
blockingPageMap["not_match_website"] = blockingPageList[i]
} else if blockingPageList[i].BlockingType == "other_block" {
blockingPageMap["other_block"] = blockingPageList[i]
}
}
}
//初始化主机host
hostsafe := &wafenginmodel.HostSafe{
LoadBalanceRuntime: &wafenginmodel.LoadBalanceRuntime{
@@ -188,6 +203,7 @@ func (waf *WafEngine) LoadHost(inHost model.Hosts) []innerbean.ServerRunTime {
UrlBlockLists: urlblocklist,
AntiCCBean: anticcBean,
HttpAuthBases: httpAuthList,
BlockingPage: blockingPageMap,
}
hostsafe.Mux.Lock()
defer hostsafe.Mux.Unlock()

View File

@@ -61,6 +61,8 @@ func (web *WafWebManager) initRouter(r *gin.Engine) {
router.ApiGroupApp.InitWafSslExpireRouter(RouterGroup)
router.ApiGroupApp.InitWafHttpAuthBaseRouter(RouterGroup)
router.ApiGroupApp.InitWafTaskRouter(RouterGroup)
router.ApiGroupApp.InitWafBlockingPageRouter(RouterGroup)
}
//r.Use(middleware.GinGlobalExceptionMiddleWare())
if global.GWAF_RELEASE == "true" {