This commit is contained in:
unknown
2026-02-04 20:27:13 +08:00
commit 3b042d1dad
9410 changed files with 1488147 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
# WAF
A basic WAF for TeaWeb.
## Config Constructions
~~~
WAF
Inbound
Rule Groups
Rule Sets
Rules
Checkpoint Param <Operator> Value
Outbound
Rule Groups
...
~~~
## Apply WAF
~~~
Request --> WAF --> Backends
/
Response <-- WAF <----
~~~
## Coding
~~~go
waf := teawaf.NewWAF()
// add rule groups here
err := waf.Init()
if err != nil {
return
}
waf.Start()
// match http request
// (req *http.Request, responseWriter http.ResponseWriter)
goNext, ruleSet, _ := waf.MatchRequest(req, responseWriter)
if ruleSet != nil {
log.Println("meet rule set:", ruleSet.Name, "action:", ruleSet.Action)
}
if !goNext {
return
}
// stop the waf
// waf.Stop()
~~~

View File

@@ -0,0 +1,46 @@
package waf
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
)
type AllowScope = string
const (
AllowScopeGroup AllowScope = "group"
AllowScopeServer AllowScope = "server"
AllowScopeGlobal AllowScope = "global"
)
type AllowAction struct {
BaseAction
Scope AllowScope `yaml:"scope" json:"scope"`
}
func (this *AllowAction) Init(waf *WAF) error {
return nil
}
func (this *AllowAction) Code() string {
return ActionAllow
}
func (this *AllowAction) IsAttack() bool {
return false
}
func (this *AllowAction) WillChange() bool {
return true
}
func (this *AllowAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
// do nothing
return PerformResult{
ContinueRequest: true,
GoNextGroup: this.Scope == AllowScopeGroup,
IsAllowed: true,
AllowScope: this.Scope,
}
}

View File

@@ -0,0 +1,17 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package waf
type BaseAction struct {
currentActionId int64
}
// ActionId 读取ActionId
func (this *BaseAction) ActionId() int64 {
return this.currentActionId
}
// SetActionId 设置Id
func (this *BaseAction) SetActionId(actionId int64) {
this.currentActionId = actionId
}

View File

@@ -0,0 +1,148 @@
package waf
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/bytepool"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/rands"
"io"
"net/http"
"os"
"path/filepath"
"regexp"
"time"
)
// url client configure
var urlPrefixReg = regexp.MustCompile("^(?i)(http|https)://")
var httpClient = utils.SharedHttpClient(5 * time.Second)
type BlockAction struct {
BaseAction
StatusCode int `yaml:"statusCode" json:"statusCode"`
Body string `yaml:"body" json:"body"` // supports HTML
URL string `yaml:"url" json:"url"`
Timeout int32 `yaml:"timeout" json:"timeout"`
TimeoutMax int32 `yaml:"timeoutMax" json:"timeoutMax"`
Scope string `yaml:"scope" json:"scope"`
FailBlockScopeAll bool `yaml:"failBlockScopeAll" json:"failBlockScopeAll"`
}
func (this *BlockAction) Init(waf *WAF) error {
if waf.DefaultBlockAction != nil {
if this.StatusCode <= 0 {
this.StatusCode = waf.DefaultBlockAction.StatusCode
}
if len(this.Body) == 0 {
this.Body = waf.DefaultBlockAction.Body
}
if len(this.URL) == 0 {
this.URL = waf.DefaultBlockAction.URL
}
if this.Timeout <= 0 {
this.Timeout = waf.DefaultBlockAction.Timeout
this.TimeoutMax = waf.DefaultBlockAction.TimeoutMax // 只有没有填写封锁时长的时候才会使用默认的封锁时长最大值
}
this.FailBlockScopeAll = waf.DefaultBlockAction.FailBlockScopeAll
}
return nil
}
func (this *BlockAction) Code() string {
return ActionBlock
}
func (this *BlockAction) IsAttack() bool {
return true
}
func (this *BlockAction) WillChange() bool {
return true
}
func (this *BlockAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
// 加入到黑名单
var timeout = this.Timeout
if timeout <= 0 {
timeout = 300 // 默认封锁300秒
}
// 随机时长
var timeoutMax = this.TimeoutMax
if timeoutMax > timeout {
timeout = timeout + int32(rands.Int64()%int64(timeoutMax-timeout+1))
}
SharedIPBlackList.RecordIP(IPTypeAll, this.Scope, request.WAFServerId(), request.WAFRemoteIP(), time.Now().Unix()+int64(timeout), waf.Id, waf.UseLocalFirewall && (this.FailBlockScopeAll || this.Scope == firewallconfigs.FirewallScopeGlobal), group.Id, set.Id, "")
if writer != nil {
// close the connection
defer request.WAFClose()
// output response
if this.StatusCode > 0 {
request.ProcessResponseHeaders(writer.Header(), this.StatusCode)
writer.WriteHeader(this.StatusCode)
} else {
request.ProcessResponseHeaders(writer.Header(), http.StatusForbidden)
writer.WriteHeader(http.StatusForbidden)
}
if len(this.URL) > 0 {
if urlPrefixReg.MatchString(this.URL) {
req, err := http.NewRequest(http.MethodGet, this.URL, nil)
if err != nil {
logs.Error(err)
return PerformResult{}
}
req.Header.Set("User-Agent", teaconst.GlobalProductName+"/"+teaconst.Version)
resp, err := httpClient.Do(req)
if err != nil {
logs.Error(err)
return PerformResult{}
}
defer func() {
_ = resp.Body.Close()
}()
for k, v := range resp.Header {
for _, v1 := range v {
writer.Header().Add(k, v1)
}
}
var buf = bytepool.Pool1k.Get()
_, _ = io.CopyBuffer(writer, resp.Body, buf.Bytes)
bytepool.Pool1k.Put(buf)
} else {
var path = this.URL
if !filepath.IsAbs(this.URL) {
path = Tea.Root + string(os.PathSeparator) + path
}
data, err := os.ReadFile(path)
if err != nil {
logs.Error(err)
return PerformResult{}
}
_, _ = writer.Write(data)
}
return PerformResult{}
}
if len(this.Body) > 0 {
_, _ = writer.Write([]byte(this.Body))
} else {
_, _ = writer.Write([]byte("The request is blocked by " + teaconst.ProductName))
}
}
return PerformResult{}
}

View File

@@ -0,0 +1,196 @@
package waf
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
wafutils "github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/types"
"net/http"
"net/url"
"strings"
"time"
)
const (
CaptchaSeconds = 600 // 10 minutes
CaptchaPath = "/WAF/VERIFY/CAPTCHA"
)
type CaptchaAction struct {
BaseAction
Life int32 `yaml:"life" json:"life"`
MaxFails int `yaml:"maxFails" json:"maxFails"` // 最大失败次数
FailBlockTimeout int `yaml:"failBlockTimeout" json:"failBlockTimeout"` // 失败拦截时间
FailBlockScopeAll bool `yaml:"failBlockScopeAll" json:"failBlockScopeAll"` // 是否全局有效
CountLetters int8 `yaml:"countLetters" json:"countLetters"`
CaptchaType firewallconfigs.CaptchaType `yaml:"captchaType" json:"captchaType"`
UIIsOn bool `yaml:"uiIsOn" json:"uiIsOn"` // 是否使用自定义UI
UITitle string `yaml:"uiTitle" json:"uiTitle"` // 消息标题
UIPrompt string `yaml:"uiPrompt" json:"uiPrompt"` // 消息提示
UIButtonTitle string `yaml:"uiButtonTitle" json:"uiButtonTitle"` // 按钮标题
UIShowRequestId bool `yaml:"uiShowRequestId" json:"uiShowRequestId"` // 是否显示请求ID
UICss string `yaml:"uiCss" json:"uiCss"` // CSS样式
UIFooter string `yaml:"uiFooter" json:"uiFooter"` // 页脚
UIBody string `yaml:"uiBody" json:"uiBody"` // 内容轮廓
OneClickUIIsOn bool `yaml:"oneClickUIIsOn" json:"oneClickUIIsOn"` // 是否使用自定义UI
OneClickUITitle string `yaml:"oneClickUITitle" json:"oneClickUITitle"` // 消息标题
OneClickUIPrompt string `yaml:"oneClickUIPrompt" json:"oneClickUIPrompt"` // 消息提示
OneClickUIShowRequestId bool `yaml:"oneClickUIShowRequestId" json:"oneClickUIShowRequestId"` // 是否显示请求ID
OneClickUICss string `yaml:"oneClickUICss" json:"oneClickUICss"` // CSS样式
OneClickUIFooter string `yaml:"oneClickUIFooter" json:"oneClickUIFooter"` // 页脚
OneClickUIBody string `yaml:"oneClickUIBody" json:"oneClickUIBody"` // 内容轮廓
SlideUIIsOn bool `yaml:"sliceUIIsOn" json:"sliceUIIsOn"` // 是否使用自定义UI
SlideUITitle string `yaml:"slideUITitle" json:"slideUITitle"` // 消息标题
SlideUIPrompt string `yaml:"slideUIPrompt" json:"slideUIPrompt"` // 消息提示
SlideUIShowRequestId bool `yaml:"SlideUIShowRequestId" json:"SlideUIShowRequestId"` // 是否显示请求ID
SlideUICss string `yaml:"slideUICss" json:"slideUICss"` // CSS样式
SlideUIFooter string `yaml:"slideUIFooter" json:"slideUIFooter"` // 页脚
SlideUIBody string `yaml:"slideUIBody" json:"slideUIBody"` // 内容轮廓
GeeTestConfig *firewallconfigs.GeeTestConfig `yaml:"geeTestConfig" json:"geeTestConfig"` // 极验设置 MUST be struct
Lang string `yaml:"lang" json:"lang"` // 语言zh-CN, en-US ...
AddToWhiteList bool `yaml:"addToWhiteList" json:"addToWhiteList"` // 是否加入到白名单
Scope string `yaml:"scope" json:"scope"`
}
func (this *CaptchaAction) Init(waf *WAF) error {
if waf.DefaultCaptchaAction != nil {
if this.Life <= 0 {
this.Life = waf.DefaultCaptchaAction.Life
}
if this.MaxFails <= 0 {
this.MaxFails = waf.DefaultCaptchaAction.MaxFails
}
if this.FailBlockTimeout <= 0 {
this.FailBlockTimeout = waf.DefaultCaptchaAction.FailBlockTimeout
}
this.FailBlockScopeAll = waf.DefaultCaptchaAction.FailBlockScopeAll
if this.CountLetters <= 0 {
this.CountLetters = waf.DefaultCaptchaAction.CountLetters
}
this.UIIsOn = waf.DefaultCaptchaAction.UIIsOn
if len(this.UITitle) == 0 {
this.UITitle = waf.DefaultCaptchaAction.UITitle
}
if len(this.UIPrompt) == 0 {
this.UIPrompt = waf.DefaultCaptchaAction.UIPrompt
}
if len(this.UIButtonTitle) == 0 {
this.UIButtonTitle = waf.DefaultCaptchaAction.UIButtonTitle
}
this.UIShowRequestId = waf.DefaultCaptchaAction.UIShowRequestId
if len(this.UICss) == 0 {
this.UICss = waf.DefaultCaptchaAction.UICss
}
if len(this.UIFooter) == 0 {
this.UIFooter = waf.DefaultCaptchaAction.UIFooter
}
if len(this.UIBody) == 0 {
this.UIBody = waf.DefaultCaptchaAction.UIBody
}
if len(this.Lang) == 0 {
this.Lang = waf.DefaultCaptchaAction.Lang
}
if len(this.CaptchaType) == 0 {
this.CaptchaType = waf.DefaultCaptchaAction.CaptchaType
}
}
return nil
}
func (this *CaptchaAction) Code() string {
return ActionCaptcha
}
func (this *CaptchaAction) IsAttack() bool {
return false
}
func (this *CaptchaAction) WillChange() bool {
return true
}
func (this *CaptchaAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, req requests.Request, writer http.ResponseWriter) PerformResult {
// 是否在白名单中
if SharedIPWhiteList.Contains(wafutils.ComposeIPType(set.Id, req), this.Scope, req.WAFServerId(), req.WAFRemoteIP()) {
return PerformResult{
ContinueRequest: true,
}
}
// 检查Cookie值
var fullCookieName = captchaCookiePrefix + "_" + types.String(set.Id)
cookie, err := req.WAFRaw().Cookie(fullCookieName)
if err == nil && cookie != nil && len(cookie.Value) > 0 {
var info = &AllowCookieInfo{}
err = info.Decode(cookie.Value)
if err == nil && set.Id == info.SetId && info.ExpiresAt > fasttime.Now().Unix() {
// 重新记录到白名单
SharedIPWhiteList.RecordIP(wafutils.ComposeIPType(set.Id, req), this.Scope, req.WAFServerId(), req.WAFRemoteIP(), info.ExpiresAt, waf.Id, false, group.Id, set.Id, "")
return PerformResult{
ContinueRequest: true,
}
}
}
var refURL = req.WAFRaw().URL.String()
// 覆盖配置
if strings.HasPrefix(refURL, CaptchaPath) {
var info = req.WAFRaw().URL.Query().Get("info")
if len(info) > 0 {
var oldArg = &InfoArg{}
decodeErr := oldArg.Decode(info)
if decodeErr == nil && oldArg.IsValid() {
refURL = oldArg.URL
} else {
// 兼容老版本
m, err := utils.SimpleDecryptMap(info)
if err == nil && m != nil {
refURL = m.GetString("url")
}
}
}
}
var captchaConfig = &InfoArg{
ActionId: this.ActionId(),
Timestamp: time.Now().Unix(),
URL: refURL,
PolicyId: waf.Id,
GroupId: group.Id,
SetId: set.Id,
UseLocalFirewall: waf.UseLocalFirewall && (this.FailBlockScopeAll || this.Scope == firewallconfigs.AllowScopeGlobal),
}
info, err := utils.SimpleEncryptObject(captchaConfig)
if err != nil {
remotelogs.Error("WAF_CAPTCHA_ACTION", "encode captcha config failed: "+err.Error())
return PerformResult{
ContinueRequest: true,
}
}
// 占用一次失败次数
CaptchaIncreaseFails(req, this, waf.Id, group.Id, set.Id, CaptchaPageCodeInit, waf.UseLocalFirewall && (this.FailBlockScopeAll || this.Scope == firewallconfigs.FirewallScopeGlobal))
req.DisableStat()
req.ProcessResponseHeaders(writer.Header(), http.StatusTemporaryRedirect)
http.Redirect(writer, req.WAFRaw(), CaptchaPath+"?info="+url.QueryEscape(info)+"&from="+url.QueryEscape(refURL), http.StatusTemporaryRedirect)
return PerformResult{}
}

View File

@@ -0,0 +1,13 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package waf
import "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
type ActionCategory = string
const (
ActionCategoryAllow ActionCategory = firewallconfigs.HTTPFirewallActionCategoryAllow
ActionCategoryBlock ActionCategory = firewallconfigs.HTTPFirewallActionCategoryBlock
ActionCategoryVerify ActionCategory = firewallconfigs.HTTPFirewallActionCategoryVerify
)

View File

@@ -0,0 +1,10 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package waf
import "github.com/iwind/TeaGo/maps"
type ActionConfig struct {
Code string `yaml:"code" json:"code"`
Options maps.Map `yaml:"options" json:"options"`
}

View File

@@ -0,0 +1,13 @@
package waf
import "reflect"
// ActionDefinition action definition
type ActionDefinition struct {
Name string
Code ActionString
Description string
Category string // category: block, verify, allow
Instance ActionInterface
Type reflect.Type
}

View File

@@ -0,0 +1,86 @@
package waf
import (
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/types"
"net/http"
"net/url"
"time"
)
const (
Get302Path = "/WAF/VERIFY/GET"
)
// Get302Action
// 原理: origin url --> 302 verify url --> origin url
// TODO 将来支持meta refresh验证
type Get302Action struct {
BaseAction
Life int32 `yaml:"life" json:"life"`
Scope string `yaml:"scope" json:"scope"`
}
func (this *Get302Action) Init(waf *WAF) error {
return nil
}
func (this *Get302Action) Code() string {
return ActionGet302
}
func (this *Get302Action) IsAttack() bool {
return false
}
func (this *Get302Action) WillChange() bool {
return true
}
func (this *Get302Action) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
// 仅限于Get
if request.WAFRaw().Method != http.MethodGet {
return PerformResult{
ContinueRequest: true,
}
}
// 是否已经在白名单中
if SharedIPWhiteList.Contains("set:"+types.String(set.Id), this.Scope, request.WAFServerId(), request.WAFRemoteIP()) {
return PerformResult{
ContinueRequest: true,
}
}
var m = InfoArg{
URL: request.WAFRaw().URL.String(),
Timestamp: time.Now().Unix(),
Life: this.Life,
Scope: this.Scope,
PolicyId: waf.Id,
GroupId: group.Id,
SetId: set.Id,
UseLocalFirewall: false,
}
info, err := utils.SimpleEncryptObject(m)
if err != nil {
remotelogs.Error("WAF_GET_302_ACTION", "encode info failed: "+err.Error())
return PerformResult{
ContinueRequest: true,
}
}
request.DisableStat()
request.ProcessResponseHeaders(writer.Header(), http.StatusFound)
http.Redirect(writer, request.WAFRaw(), Get302Path+"?info="+url.QueryEscape(info), http.StatusFound)
flusher, ok := writer.(http.Flusher)
if ok {
flusher.Flush()
}
return PerformResult{}
}

View File

@@ -0,0 +1,58 @@
package waf
import (
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/types"
"net/http"
)
type GoGroupAction struct {
BaseAction
GroupId string `yaml:"groupId" json:"groupId"`
}
func (this *GoGroupAction) Init(waf *WAF) error {
return nil
}
func (this *GoGroupAction) Code() string {
return ActionGoGroup
}
func (this *GoGroupAction) IsAttack() bool {
return false
}
func (this *GoGroupAction) WillChange() bool {
return true
}
func (this *GoGroupAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
var nextGroup = waf.FindRuleGroup(types.Int64(this.GroupId))
if nextGroup == nil || !nextGroup.IsOn {
return PerformResult{
ContinueRequest: true,
GoNextSet: true,
}
}
b, _, nextSet, err := nextGroup.MatchRequest(request)
if err != nil {
remotelogs.Error("WAF", "GO_GROUP_ACTION: "+err.Error())
return PerformResult{
ContinueRequest: true,
GoNextSet: true,
}
}
if !b {
return PerformResult{
ContinueRequest: true,
GoNextSet: true,
}
}
return nextSet.PerformActions(waf, nextGroup, request, writer)
}

View File

@@ -0,0 +1,64 @@
package waf
import (
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/types"
"net/http"
)
type GoSetAction struct {
BaseAction
GroupId string `yaml:"groupId" json:"groupId"`
SetId string `yaml:"setId" json:"setId"`
}
func (this *GoSetAction) Init(waf *WAF) error {
return nil
}
func (this *GoSetAction) Code() string {
return ActionGoSet
}
func (this *GoSetAction) IsAttack() bool {
return false
}
func (this *GoSetAction) WillChange() bool {
return true
}
func (this *GoSetAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
var nextGroup = waf.FindRuleGroup(types.Int64(this.GroupId))
if nextGroup == nil || !nextGroup.IsOn {
return PerformResult{
ContinueRequest: true,
GoNextSet: true,
}
}
var nextSet = nextGroup.FindRuleSet(types.Int64(this.SetId))
if nextSet == nil || !nextSet.IsOn {
return PerformResult{
ContinueRequest: true,
GoNextSet: true,
}
}
b, _, err := nextSet.MatchRequest(request)
if err != nil {
remotelogs.Error("WAF", "GO_GROUP_ACTION: "+err.Error())
return PerformResult{
ContinueRequest: true,
GoNextSet: true,
}
}
if !b {
return PerformResult{
ContinueRequest: true,
GoNextSet: true,
}
}
return nextSet.PerformActions(waf, nextGroup, request, writer)
}

View File

@@ -0,0 +1,4 @@
package waf
type Action struct {
}

View File

@@ -0,0 +1,31 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package waf
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
)
type ActionInterface interface {
// Init 初始化
Init(waf *WAF) error
// ActionId 读取ActionId
ActionId() int64
// SetActionId 设置ID
SetActionId(id int64)
// Code 代号
Code() string
// IsAttack 是否为拦截攻击动作
IsAttack() bool
// WillChange determine if the action will change the request
WillChange() bool
// Perform the action
Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult
}

View File

@@ -0,0 +1,159 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package waf
import (
"crypto/md5"
"fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/types"
"net/http"
"strings"
"time"
)
type JSCookieAction struct {
BaseAction
Life int32 `yaml:"life" json:"life"`
MaxFails int `yaml:"maxFails" json:"maxFails"` // 最大失败次数
FailBlockTimeout int `yaml:"failBlockTimeout" json:"failBlockTimeout"` // 失败拦截时间
Scope string `yaml:"scope" json:"scope"`
FailBlockScopeAll bool `yaml:"failBlockScopeAll" json:"failBlockScopeAll"`
}
func (this *JSCookieAction) Init(waf *WAF) error {
if waf.DefaultJSCookieAction != nil {
if this.Life <= 0 {
this.Life = waf.DefaultJSCookieAction.Life
}
if this.MaxFails <= 0 {
this.MaxFails = waf.DefaultJSCookieAction.MaxFails
}
if this.FailBlockTimeout <= 0 {
this.FailBlockTimeout = waf.DefaultJSCookieAction.FailBlockTimeout
}
if len(this.Scope) == 0 {
this.Scope = waf.DefaultJSCookieAction.Scope
}
this.FailBlockScopeAll = waf.DefaultJSCookieAction.FailBlockScopeAll
}
if len(this.Scope) == 0 {
this.Scope = firewallconfigs.FirewallScopeGlobal
}
return nil
}
func (this *JSCookieAction) Code() string {
return ActionJavascriptCookie
}
func (this *JSCookieAction) IsAttack() bool {
return false
}
func (this *JSCookieAction) WillChange() bool {
return true
}
func (this *JSCookieAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, req requests.Request, writer http.ResponseWriter) PerformResult {
// 是否在白名单中
if SharedIPWhiteList.Contains("set:"+types.String(set.Id), this.Scope, req.WAFServerId(), req.WAFRemoteIP()) {
return PerformResult{
ContinueRequest: true,
}
}
nodeConfig, err := nodeconfigs.SharedNodeConfig()
if err != nil {
return PerformResult{
ContinueRequest: true,
}
}
var life = this.Life
if life <= 0 {
life = 3600
}
// 检查Cookie
var cookieName = "ge_js_validator_" + types.String(set.Id)
cookie, err := req.WAFRaw().Cookie(cookieName)
if err == nil && cookie != nil {
var cookieValue = cookie.Value
if len(cookieValue) > 10 {
var pieces = strings.Split(cookieValue, "@")
if len(pieces) == 3 {
var timestamp = pieces[0]
var sum = pieces[2]
if types.Int64(timestamp) >= time.Now().Unix()-int64(life) && fmt.Sprintf("%x", md5.Sum([]byte(timestamp+"@"+types.String(set.Id)+"@"+nodeConfig.NodeId))) == sum {
return PerformResult{
ContinueRequest: true,
}
}
}
}
}
req.ProcessResponseHeaders(writer.Header(), http.StatusOK)
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
writer.Header().Set("Cache-Control", "no-cache")
var timestamp = types.String(time.Now().Unix())
var cookieValue = timestamp + "@" + types.String(set.Id) + "@" + fmt.Sprintf("%x", md5.Sum([]byte(timestamp+"@"+types.String(set.Id)+"@"+nodeConfig.NodeId)))
var respHTML = `<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="UTF-8"/>
<script type="text/javascript">
document.cookie = "` + cookieName + `=` + cookieValue + `; path=/; max-age=` + types.String(life) + `;";
window.location.reload();
</script>
</head>
<body>
</body>
</html>`
writer.Header().Set("Content-Length", types.String(len(respHTML)))
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte(respHTML))
// 记录失败次数
this.increaseFails(req, waf.Id, group.Id, set.Id, waf.UseLocalFirewall && (this.FailBlockScopeAll || this.Scope == firewallconfigs.FirewallScopeGlobal))
return PerformResult{}
}
func (this *JSCookieAction) increaseFails(req requests.Request, policyId int64, groupId int64, setId int64, useLocalFirewall bool) (goNext bool) {
var maxFails = this.MaxFails
var failBlockTimeout = this.FailBlockTimeout
if maxFails <= 0 {
maxFails = 10 // 默认10次
} else if maxFails <= 5 {
maxFails = 5 // 不能小于3防止意外刷新出现
}
if failBlockTimeout <= 0 {
failBlockTimeout = 1800 // 默认1800s
}
var key = "WAF:JS_COOKIE:FAILS:" + req.WAFRemoteIP() + ":" + types.String(req.WAFServerId()) + ":" + req.WAFRaw().URL.String()
var countFails = counters.SharedCounter.IncreaseKey(key, 300)
if int(countFails) >= maxFails {
SharedIPBlackList.RecordIP(IPTypeAll, firewallconfigs.FirewallScopeServer, req.WAFServerId(), req.WAFRemoteIP(), time.Now().Unix()+int64(failBlockTimeout), policyId, useLocalFirewall, groupId, setId, "JS_COOKIE验证连续失败超过"+types.String(maxFails)+"次")
return false
}
return true
}

View File

@@ -0,0 +1,32 @@
package waf
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
)
type LogAction struct {
BaseAction
}
func (this *LogAction) Init(waf *WAF) error {
return nil
}
func (this *LogAction) Code() string {
return ActionLog
}
func (this *LogAction) IsAttack() bool {
return false
}
func (this *LogAction) WillChange() bool {
return false
}
func (this *LogAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
return PerformResult{
ContinueRequest: true,
}
}

View File

@@ -0,0 +1,95 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package waf
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/utils/goman"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/types"
"net/http"
"time"
)
type notifyTask struct {
ServerId int64
HttpFirewallPolicyId int64
HttpFirewallRuleGroupId int64
HttpFirewallRuleSetId int64
CreatedAt int64
}
var notifyChan = make(chan *notifyTask, 128)
func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventLoaded, func() {
goman.New(func() {
rpcClient, err := rpc.SharedRPC()
if err != nil {
remotelogs.Error("WAF_NOTIFY_ACTION", "create rpc client failed: "+err.Error())
return
}
for task := range notifyChan {
_, err = rpcClient.FirewallRPC.NotifyHTTPFirewallEvent(rpcClient.Context(), &pb.NotifyHTTPFirewallEventRequest{
ServerId: task.ServerId,
HttpFirewallPolicyId: task.HttpFirewallPolicyId,
HttpFirewallRuleGroupId: task.HttpFirewallRuleGroupId,
HttpFirewallRuleSetId: task.HttpFirewallRuleSetId,
CreatedAt: task.CreatedAt,
})
if err != nil {
remotelogs.Error("WAF_NOTIFY_ACTION", "notify failed: "+err.Error())
}
}
})
})
}
type NotifyAction struct {
BaseAction
}
func (this *NotifyAction) Init(waf *WAF) error {
return nil
}
func (this *NotifyAction) Code() string {
return ActionNotify
}
func (this *NotifyAction) IsAttack() bool {
return false
}
// WillChange determine if the action will change the request
func (this *NotifyAction) WillChange() bool {
return false
}
// Perform the action
func (this *NotifyAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
select {
case notifyChan <- &notifyTask{
ServerId: request.WAFServerId(),
HttpFirewallPolicyId: types.Int64(waf.Id),
HttpFirewallRuleGroupId: types.Int64(group.Id),
HttpFirewallRuleSetId: types.Int64(set.Id),
CreatedAt: time.Now().Unix(),
}:
default:
}
return PerformResult{
ContinueRequest: true,
}
}

View File

@@ -0,0 +1,77 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package waf
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
)
type PageAction struct {
BaseAction
UseDefault bool `yaml:"useDefault" json:"useDefault"`
Status int `yaml:"status" json:"status"`
Body string `yaml:"body" json:"body"`
}
func (this *PageAction) Init(waf *WAF) error {
if waf.DefaultPageAction != nil {
if this.Status <= 0 || this.UseDefault {
this.Status = waf.DefaultPageAction.Status
}
if len(this.Body) == 0 || this.UseDefault {
this.Body = waf.DefaultPageAction.Body
}
}
if this.Status <= 0 {
this.Status = http.StatusForbidden
}
return nil
}
func (this *PageAction) Code() string {
return ActionPage
}
func (this *PageAction) IsAttack() bool {
return false
}
// WillChange determine if the action will change the request
func (this *PageAction) WillChange() bool {
return true
}
// Perform the action
func (this *PageAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
if writer == nil {
return PerformResult{}
}
request.ProcessResponseHeaders(writer.Header(), this.Status)
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
writer.WriteHeader(this.Status)
var body = this.Body
if len(body) == 0 {
body = `<!DOCTYPE html>
<html lang="en">
<head>
<title>403 Forbidden</title>
<style>
address { line-height: 1.8; }
</style>
</head>
<body>
<h1>403 Forbidden By WAF</h1>
<address>Connection: ${remoteAddr} (Client) -&gt; ${serverAddr} (Server)</address>
<address>Request ID: ${requestId}</address>
</body>
</html>`
}
_, _ = writer.Write([]byte(request.Format(body)))
return PerformResult{}
}

View File

@@ -0,0 +1,147 @@
package waf
import (
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/bytepool"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/types"
"io"
"net/http"
"time"
)
type Post307Action struct {
Life int32 `yaml:"life" json:"life"`
Scope string `yaml:"scope" json:"scope"`
BaseAction
}
func (this *Post307Action) Init(waf *WAF) error {
return nil
}
func (this *Post307Action) Code() string {
return ActionPost307
}
func (this *Post307Action) IsAttack() bool {
return false
}
func (this *Post307Action) WillChange() bool {
return true
}
func (this *Post307Action) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
const cookieName = "WAF_VALIDATOR_ID"
// 仅限于POST
if request.WAFRaw().Method != http.MethodPost {
return PerformResult{
ContinueRequest: true,
}
}
// 是否已经在白名单中
if SharedIPWhiteList.Contains("set:"+types.String(set.Id), this.Scope, request.WAFServerId(), request.WAFRemoteIP()) {
return PerformResult{
ContinueRequest: true,
}
}
// 判断是否有Cookie
cookie, cookieErr := request.WAFRaw().Cookie(cookieName)
if cookieErr == nil && cookie != nil {
var remoteIP string
var life int64
var setId int64
var policyId int64
var groupId int64
var timestamp int64
var infoArg = &InfoArg{}
var success bool
decodeErr := infoArg.Decode(cookie.Value)
if decodeErr == nil && infoArg.IsValid() {
success = true
remoteIP = infoArg.RemoteIP
life = int64(infoArg.Life)
setId = infoArg.SetId
policyId = infoArg.PolicyId
groupId = infoArg.GroupId
timestamp = infoArg.Timestamp
} else {
// 兼容老版本
m, decodeMapErr := utils.SimpleDecryptMap(cookie.Value)
if decodeMapErr == nil {
success = true
remoteIP = m.GetString("remoteIP")
timestamp = m.GetInt64("timestamp")
life = m.GetInt64("life")
setId = m.GetInt64("setId")
groupId = m.GetInt64("groupId")
policyId = m.GetInt64("policyId")
}
}
if success && remoteIP == request.WAFRemoteIP() && time.Now().Unix() < timestamp+10 {
if life <= 0 {
life = 600 // 默认10分钟
}
SharedIPWhiteList.RecordIP("set:"+types.String(setId), this.Scope, request.WAFServerId(), request.WAFRemoteIP(), time.Now().Unix()+life, policyId, false, groupId, setId, "")
return PerformResult{
ContinueRequest: true,
}
}
}
var m = &InfoArg{
Timestamp: time.Now().Unix(),
Life: this.Life,
Scope: this.Scope,
PolicyId: waf.Id,
GroupId: group.Id,
SetId: set.Id,
RemoteIP: request.WAFRemoteIP(),
UseLocalFirewall: false,
}
info, err := utils.SimpleEncryptObject(m)
if err != nil {
remotelogs.Error("WAF_POST_307_ACTION", "encode info failed: "+err.Error())
return PerformResult{
ContinueRequest: true,
}
}
// 清空请求内容
var req = request.WAFRaw()
if req.ContentLength > 0 && req.Body != nil {
var buf = bytepool.Pool16k.Get()
_, _ = io.CopyBuffer(io.Discard, req.Body, buf.Bytes)
bytepool.Pool16k.Put(buf)
_ = req.Body.Close()
}
// 设置Cookie
http.SetCookie(writer, &http.Cookie{
Name: cookieName,
Path: "/",
MaxAge: 10,
Value: info,
})
request.DisableStat()
request.ProcessResponseHeaders(writer.Header(), http.StatusTemporaryRedirect)
http.Redirect(writer, request.WAFRaw(), request.WAFRaw().URL.String(), http.StatusTemporaryRedirect)
flusher, ok := writer.(http.Flusher)
if ok {
flusher.Flush()
}
return PerformResult{}
}

View File

@@ -0,0 +1,254 @@
package waf
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ipconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/utils/goman"
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/types"
"net/http"
"strings"
"time"
)
type recordIPTask struct {
ip string
listId int64
expiresAt int64
level string
serverId int64
reason string
sourceServerId int64
sourceHTTPFirewallPolicyId int64
sourceHTTPFirewallRuleGroupId int64
sourceHTTPFirewallRuleSetId int64
}
var recordIPTaskChan = make(chan *recordIPTask, 512)
func init() {
if !teaconst.IsMain {
return
}
var memGB = memutils.SystemMemoryGB()
if memGB > 16 {
recordIPTaskChan = make(chan *recordIPTask, 4<<10)
} else if memGB > 8 {
recordIPTaskChan = make(chan *recordIPTask, 2<<10)
} else if memGB > 4 {
recordIPTaskChan = make(chan *recordIPTask, 1<<10)
}
events.On(events.EventLoaded, func() {
goman.New(func() {
rpcClient, err := rpc.SharedRPC()
if err != nil {
remotelogs.Error("WAF_RECORD_IP_ACTION", "create rpc client failed: "+err.Error())
return
}
const maxItems = 512 // 每次上传的最大IP数
for {
var pbItemMap = map[string]*pb.CreateIPItemsRequest_IPItem{} // ip => IPItem
func() {
for {
select {
case task := <-recordIPTaskChan:
var ipType = "ipv4"
if strings.Contains(task.ip, ":") {
ipType = "ipv6"
}
var reason = task.reason
if len(reason) == 0 {
reason = "触发WAF规则自动加入"
}
pbItemMap[task.ip] = &pb.CreateIPItemsRequest_IPItem{
IpListId: task.listId,
Value: task.ip,
IpFrom: task.ip,
IpTo: "",
ExpiredAt: task.expiresAt,
Reason: reason,
Type: ipType,
EventLevel: task.level,
ServerId: task.serverId,
SourceNodeId: teaconst.NodeId,
SourceServerId: task.sourceServerId,
SourceHTTPFirewallPolicyId: task.sourceHTTPFirewallPolicyId,
SourceHTTPFirewallRuleGroupId: task.sourceHTTPFirewallRuleGroupId,
SourceHTTPFirewallRuleSetId: task.sourceHTTPFirewallRuleSetId,
}
if len(pbItemMap) >= maxItems {
return
}
default:
return
}
}
}()
if len(pbItemMap) > 0 {
var pbItems = []*pb.CreateIPItemsRequest_IPItem{}
for _, pbItem := range pbItemMap {
pbItems = append(pbItems, pbItem)
}
for i := 0; i < 5; /* max tries */ i++ {
_, err = rpcClient.IPItemRPC.CreateIPItems(rpcClient.Context(), &pb.CreateIPItemsRequest{IpItems: pbItems})
if err != nil {
remotelogs.Error("WAF_RECORD_IP_ACTION", "create ip item failed: "+err.Error())
time.Sleep(1 * time.Second)
} else {
break
}
}
} else {
time.Sleep(1 * time.Second)
}
}
})
})
}
type RecordIPAction struct {
BaseAction
Type string `yaml:"type" json:"type"`
IPListId int64 `yaml:"ipListId" json:"ipListId"`
IPListIsDeleted bool `yaml:"ipListIsDeleted" json:"ipListIsDeleted"`
Level string `yaml:"level" json:"level"`
Timeout int32 `yaml:"timeout" json:"timeout"`
Scope string `yaml:"scope" json:"scope"`
}
func (this *RecordIPAction) Init(waf *WAF) error {
return nil
}
func (this *RecordIPAction) Code() string {
return ActionRecordIP
}
func (this *RecordIPAction) IsAttack() bool {
return this.Type == ipconfigs.IPListTypeBlack
}
func (this *RecordIPAction) WillChange() bool {
return this.Type == ipconfigs.IPListTypeBlack
}
func (this *RecordIPAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
var ipListId = this.IPListId
if ipListId <= 0 || firewallconfigs.IsGlobalListId(ipListId) {
// server or policy list ids
switch this.Type {
case ipconfigs.IPListTypeWhite:
ipListId = waf.AllowListId
case ipconfigs.IPListTypeBlack:
ipListId = waf.DenyListId
case ipconfigs.IPListTypeGrey:
ipListId = waf.GreyListId
}
// global list ids
if ipListId <= 0 {
ipListId = firewallconfigs.FindGlobalListIdWithType(this.Type)
if ipListId <= 0 {
ipListId = firewallconfigs.GlobalBlackListId
}
}
}
// 是否已删除
var ipListIsAvailable = (firewallconfigs.IsGlobalListId(ipListId)) || (ipListId > 0 && !this.IPListIsDeleted && !ExistDeletedIPList(ipListId))
// 是否在本地白名单中
if SharedIPWhiteList.Contains("set:"+types.String(set.Id), this.Scope, request.WAFServerId(), request.WAFRemoteIP()) {
return PerformResult{
ContinueRequest: true,
IsAllowed: true,
AllowScope: AllowScopeGlobal,
}
}
var timeout = this.Timeout
var isForever = false
if timeout <= 0 {
isForever = true
timeout = 86400 // 1天
}
var expiresAt = time.Now().Unix() + int64(timeout)
var isGrey bool
var isWhite bool
if this.Type == ipconfigs.IPListTypeBlack {
request.ProcessResponseHeaders(writer.Header(), http.StatusForbidden)
writer.WriteHeader(http.StatusForbidden)
request.WAFClose()
// 先加入本地的黑名单
if ipListIsAvailable {
SharedIPBlackList.Add(IPTypeAll, this.Scope, request.WAFServerId(), request.WAFRemoteIP(), expiresAt)
}
} else if this.Type == ipconfigs.IPListTypeGrey {
isGrey = true
} else {
isWhite = true
// 加入本地白名单
if ipListIsAvailable {
SharedIPWhiteList.Add("set:"+types.String(set.Id), this.Scope, request.WAFServerId(), request.WAFRemoteIP(), expiresAt)
}
}
// 上报
if ipListId > 0 && ipListIsAvailable {
var serverId int64
if this.Scope == firewallconfigs.FirewallScopeServer {
serverId = request.WAFServerId()
}
var realExpiresAt = expiresAt
if isForever {
realExpiresAt = 0
}
select {
case recordIPTaskChan <- &recordIPTask{
ip: request.WAFRemoteIP(),
listId: ipListId,
expiresAt: realExpiresAt,
level: this.Level,
serverId: serverId,
sourceServerId: request.WAFServerId(),
sourceHTTPFirewallPolicyId: waf.Id,
sourceHTTPFirewallRuleGroupId: group.Id,
sourceHTTPFirewallRuleSetId: set.Id,
}:
default:
}
}
return PerformResult{
ContinueRequest: isWhite || isGrey,
IsAllowed: isWhite,
AllowScope: AllowScopeGlobal,
}
}

View File

@@ -0,0 +1,44 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package waf
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
)
type RedirectAction struct {
BaseAction
Status int `yaml:"status" json:"status"`
URL string `yaml:"url" json:"url"`
}
func (this *RedirectAction) Init(waf *WAF) error {
if this.Status <= 0 {
this.Status = http.StatusTemporaryRedirect
}
return nil
}
func (this *RedirectAction) Code() string {
return ActionRedirect
}
func (this *RedirectAction) IsAttack() bool {
return false
}
// WillChange determine if the action will change the request
func (this *RedirectAction) WillChange() bool {
return true
}
// Perform the action
func (this *RedirectAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
request.ProcessResponseHeaders(writer.Header(), this.Status)
writer.Header().Set("Location", this.URL)
writer.WriteHeader(this.Status)
return PerformResult{}
}

View File

@@ -0,0 +1,34 @@
package waf
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
)
type TagAction struct {
BaseAction
Tags []string `yaml:"tags" json:"tags"`
}
func (this *TagAction) Init(waf *WAF) error {
return nil
}
func (this *TagAction) Code() string {
return ActionTag
}
func (this *TagAction) IsAttack() bool {
return false
}
func (this *TagAction) WillChange() bool {
return false
}
func (this *TagAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
return PerformResult{
ContinueRequest: true,
}
}

View File

@@ -0,0 +1,109 @@
package waf
import "reflect"
type ActionString = string
const (
ActionLog ActionString = "log" // allow and log
ActionBlock ActionString = "block" // block
ActionCaptcha ActionString = "captcha" // block and show captcha
ActionJavascriptCookie ActionString = "js_cookie" // js cookie
ActionNotify ActionString = "notify" // 告警
ActionGet302 ActionString = "get_302" // 针对GET的302重定向认证
ActionPost307 ActionString = "post_307" // 针对POST的307重定向认证
ActionRecordIP ActionString = "record_ip" // 记录IP
ActionTag ActionString = "tag" // 标签
ActionPage ActionString = "page" // 显示网页
ActionRedirect ActionString = "redirect" // 跳转
ActionAllow ActionString = "allow" // allow
ActionGoGroup ActionString = "go_group" // go to next rule group
ActionGoSet ActionString = "go_set" // go to next rule set
)
var AllActions = []*ActionDefinition{
{
Name: "阻止",
Code: ActionBlock,
Instance: new(BlockAction),
Type: reflect.TypeOf(new(BlockAction)).Elem(),
},
{
Name: "允许通过",
Code: ActionAllow,
Instance: new(AllowAction),
Type: reflect.TypeOf(new(AllowAction)).Elem(),
},
{
Name: "允许并记录日志",
Code: ActionLog,
Instance: new(LogAction),
Type: reflect.TypeOf(new(LogAction)).Elem(),
},
{
Name: "Captcha验证码",
Code: ActionCaptcha,
Instance: new(CaptchaAction),
Type: reflect.TypeOf(new(CaptchaAction)).Elem(),
},
{
Name: "JS Cookie验证",
Code: ActionJavascriptCookie,
Instance: new(JSCookieAction),
Type: reflect.TypeOf(new(JSCookieAction)).Elem(),
},
{
Name: "告警",
Code: ActionNotify,
Instance: new(NotifyAction),
Type: reflect.TypeOf(new(NotifyAction)).Elem(),
},
{
Name: "GET 302",
Code: ActionGet302,
Instance: new(Get302Action),
Type: reflect.TypeOf(new(Get302Action)).Elem(),
},
{
Name: "POST 307",
Code: ActionPost307,
Instance: new(Post307Action),
Type: reflect.TypeOf(new(Post307Action)).Elem(),
},
{
Name: "记录IP",
Code: ActionRecordIP,
Instance: new(RecordIPAction),
Type: reflect.TypeOf(new(RecordIPAction)).Elem(),
},
{
Name: "标签",
Code: ActionTag,
Instance: new(TagAction),
Type: reflect.TypeOf(new(TagAction)).Elem(),
},
{
Name: "显示页面",
Code: ActionPage,
Instance: new(PageAction),
Type: reflect.TypeOf(new(PageAction)).Elem(),
},
{
Name: "跳转",
Code: ActionRedirect,
Instance: new(RedirectAction),
Type: reflect.TypeOf(new(RedirectAction)).Elem(),
},
{
Name: "跳到下一个规则分组",
Code: ActionGoGroup,
Instance: new(GoGroupAction),
Type: reflect.TypeOf(new(GoGroupAction)).Elem(),
},
{
Name: "跳到下一个规则集",
Code: ActionGoSet,
Instance: new(GoSetAction),
Type: reflect.TypeOf(new(GoSetAction)).Elem(),
},
}

View File

@@ -0,0 +1,51 @@
package waf
import (
"encoding/json"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/iwind/TeaGo/maps"
"reflect"
"sync/atomic"
)
var seedActionId int64 = 1
func FindActionInstance(action ActionString, options maps.Map) ActionInterface {
for _, def := range AllActions {
if def.Code == action {
if def.Type != nil {
// create new instance
var ptrValue = reflect.New(def.Type)
var instance = ptrValue.Interface().(ActionInterface)
instance.SetActionId(atomic.AddInt64(&seedActionId, 1))
if len(options) > 0 {
optionsJSON, err := json.Marshal(options)
if err != nil {
remotelogs.Error("WAF_FindActionInstance", "encode options to json failed: "+err.Error())
} else {
err = json.Unmarshal(optionsJSON, instance)
if err != nil {
remotelogs.Error("WAF_FindActionInstance", "decode options from json failed: "+err.Error())
}
}
}
return instance
}
// return shared instance
return def.Instance
}
}
return nil
}
func FindActionName(action ActionString) string {
for _, def := range AllActions {
if def.Code == action {
return def.Name
}
}
return ""
}

View File

@@ -0,0 +1,40 @@
package waf_test
import (
"github.com/TeaOSLab/EdgeNode/internal/waf"
"github.com/iwind/TeaGo/assert"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"runtime"
"testing"
)
func TestFindActionInstance(t *testing.T) {
a := assert.NewAssertion(t)
t.Logf("ActionBlock: %p", waf.FindActionInstance(waf.ActionBlock, nil))
t.Logf("ActionBlock: %p", waf.FindActionInstance(waf.ActionBlock, nil))
t.Logf("ActionGoGroup: %p", waf.FindActionInstance(waf.ActionGoGroup, nil))
t.Logf("ActionGoGroup: %p", waf.FindActionInstance(waf.ActionGoGroup, nil))
t.Logf("ActionGoSet: %p", waf.FindActionInstance(waf.ActionGoSet, nil))
t.Logf("ActionGoSet: %p", waf.FindActionInstance(waf.ActionGoSet, nil))
t.Logf("ActionGoSet: %#v", waf.FindActionInstance(waf.ActionGoSet, maps.Map{"groupId": "a", "setId": "b"}))
a.IsTrue(waf.FindActionInstance(waf.ActionGoSet, nil) != waf.FindActionInstance(waf.ActionGoSet, nil))
}
func TestFindActionInstance_Options(t *testing.T) {
//t.Logf("%p", FindActionInstance(ActionBlock, maps.Map{}))
//t.Logf("%p", FindActionInstance(ActionBlock, maps.Map{}))
//logs.PrintAsJSON(FindActionInstance(ActionBlock, maps.Map{}), t)
logs.PrintAsJSON(waf.FindActionInstance(waf.ActionBlock, maps.Map{
"timeout": 3600,
}), t)
}
func BenchmarkFindActionInstance(b *testing.B) {
runtime.GOMAXPROCS(1)
for i := 0; i < b.N; i++ {
waf.FindActionInstance(waf.ActionGoSet, nil)
}
}

View File

@@ -0,0 +1,46 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package waf
import (
"encoding/base64"
"encoding/binary"
"errors"
"github.com/TeaOSLab/EdgeNode/internal/utils"
)
type AllowCookieInfo struct {
SetId int64
ExpiresAt int64
}
func (this *AllowCookieInfo) Encode() (string, error) {
if this.SetId < 0 {
this.SetId = 0
}
if this.ExpiresAt < 0 {
this.ExpiresAt = 0
}
var result = make([]byte, 16)
binary.BigEndian.PutUint64(result, uint64(this.SetId))
binary.BigEndian.PutUint64(result[8:], uint64(this.ExpiresAt))
return base64.StdEncoding.EncodeToString(utils.SimpleEncrypt(result)), nil
}
func (this *AllowCookieInfo) Decode(encodedString string) error {
data, err := base64.StdEncoding.DecodeString(encodedString)
if err != nil {
return err
}
var result = utils.SimpleDecrypt(data)
if len(result) != 16 {
return errors.New("unexpected data length")
}
this.SetId = int64(binary.BigEndian.Uint64(result[:8]))
this.ExpiresAt = int64(binary.BigEndian.Uint64(result[8:16]))
return nil
}

View File

@@ -0,0 +1,35 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package waf_test
import (
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/TeaOSLab/EdgeNode/internal/waf"
"github.com/iwind/TeaGo/assert"
"github.com/iwind/TeaGo/types"
"testing"
)
func TestAllowCookieInfo_Encode(t *testing.T) {
var a = assert.NewAssertion(t)
var info = &waf.AllowCookieInfo{
SetId: 123,
ExpiresAt: fasttime.Now().Unix(),
}
data, err := info.Encode()
if err != nil {
t.Fatal(err)
}
t.Log("encrypted: ["+types.String(len(data))+"]", data)
var info2 = &waf.AllowCookieInfo{}
err = info2.Decode(data)
if err != nil {
t.Fatal(err)
}
t.Logf("%+v", info2)
a.IsTrue(info.SetId == info2.SetId)
a.IsTrue(info.ExpiresAt == info2.ExpiresAt)
}

View File

@@ -0,0 +1,50 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package waf
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/types"
"time"
)
type CaptchaPageCode = string
const (
CaptchaPageCodeInit CaptchaPageCode = "init"
CaptchaPageCodeShow CaptchaPageCode = "show"
CaptchaPageCodeImage CaptchaPageCode = "image"
CaptchaPageCodeSubmit CaptchaPageCode = "submit"
)
// CaptchaIncreaseFails 增加Captcha失败次数以便后续操作
func CaptchaIncreaseFails(req requests.Request, actionConfig *CaptchaAction, policyId int64, groupId int64, setId int64, pageCode CaptchaPageCode, useLocalFirewall bool) (goNext bool) {
var maxFails = actionConfig.MaxFails
var failBlockTimeout = actionConfig.FailBlockTimeout
if maxFails > 0 && failBlockTimeout > 0 {
if maxFails <= 3 {
maxFails = 3 // 不能小于3防止意外刷新出现
}
var countFails = counters.SharedCounter.IncreaseKey(CaptchaCacheKey(req, pageCode), 300)
if int(countFails) >= maxFails {
SharedIPBlackList.RecordIP(IPTypeAll, firewallconfigs.FirewallScopeServer, req.WAFServerId(), req.WAFRemoteIP(), time.Now().Unix()+int64(failBlockTimeout), policyId, useLocalFirewall, groupId, setId, "CAPTCHA验证连续失败超过"+types.String(maxFails)+"次")
return false
}
}
return true
}
// CaptchaDeleteCacheKey 清除计数
func CaptchaDeleteCacheKey(req requests.Request) {
counters.SharedCounter.ResetKey(CaptchaCacheKey(req, CaptchaPageCodeInit))
counters.SharedCounter.ResetKey(CaptchaCacheKey(req, CaptchaPageCodeShow))
counters.SharedCounter.ResetKey(CaptchaCacheKey(req, CaptchaPageCodeImage))
counters.SharedCounter.ResetKey(CaptchaCacheKey(req, CaptchaPageCodeSubmit))
}
// CaptchaCacheKey 获取Captcha缓存Key
func CaptchaCacheKey(req requests.Request, pageCode CaptchaPageCode) string {
return "WAF:CAPTCHA:FAILS:" + pageCode + ":" + req.WAFRemoteIP() + ":" + types.String(req.WAFServerId())
}

View File

@@ -0,0 +1,71 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package waf
import (
"bytes"
"github.com/dchest/captcha"
"github.com/iwind/TeaGo/rands"
"io"
"time"
)
// CaptchaGenerator captcha generator
type CaptchaGenerator struct {
store captcha.Store
}
func NewCaptchaGenerator() *CaptchaGenerator {
return &CaptchaGenerator{
store: captcha.NewMemoryStore(100_000, 5*time.Minute),
}
}
// NewCaptcha create new captcha
func (this *CaptchaGenerator) NewCaptcha(length int) (captchaId string) {
captchaId = rands.HexString(16)
if length <= 0 || length > 20 {
length = 4
}
this.store.Set(captchaId, captcha.RandomDigits(length))
return
}
// WriteImage write image to front writer
func (this *CaptchaGenerator) WriteImage(w io.Writer, id string, width, height int) error {
var d = this.store.Get(id, false)
if d == nil {
return captcha.ErrNotFound
}
_, err := captcha.NewImage(id, d, width, height).WriteTo(w)
return err
}
// Verify user input
func (this *CaptchaGenerator) Verify(id string, digits string) bool {
var countDigits = len(digits)
if countDigits == 0 {
return false
}
var value = this.store.Get(id, true)
if len(value) != countDigits {
return false
}
var nb = make([]byte, countDigits)
for i := 0; i < countDigits; i++ {
var d = digits[i]
if d >= '0' && d <= '9' {
nb[i] = d - '0'
}
}
return bytes.Equal(nb, value)
}
// Get captcha data
func (this *CaptchaGenerator) Get(id string) []byte {
return this.store.Get(id, false)
}

View File

@@ -0,0 +1,87 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package waf_test
import (
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/TeaOSLab/EdgeNode/internal/waf"
"github.com/iwind/TeaGo/assert"
"github.com/iwind/TeaGo/types"
"runtime"
"strings"
"testing"
"time"
)
func TestCaptchaGenerator_NewCaptcha(t *testing.T) {
var a = assert.NewAssertion(t)
var generator = waf.NewCaptchaGenerator()
var captchaId = generator.NewCaptcha(6)
t.Log("captchaId:", captchaId)
var digits = generator.Get(captchaId)
var s []string
for _, digit := range digits {
s = append(s, types.String(digit))
}
t.Log(strings.Join(s, " "))
a.IsTrue(generator.Verify(captchaId, strings.Join(s, "")))
a.IsFalse(generator.Verify(captchaId, strings.Join(s, "")))
}
func TestCaptchaGenerator_NewCaptcha_UTF8(t *testing.T) {
var a = assert.NewAssertion(t)
var generator = waf.NewCaptchaGenerator()
var captchaId = generator.NewCaptcha(6)
t.Log("captchaId:", captchaId)
var digits = generator.Get(captchaId)
var s []string
for _, digit := range digits {
s = append(s, types.String(digit))
}
t.Log(strings.Join(s, " "))
a.IsFalse(generator.Verify(captchaId, "中文真的很长"))
}
func TestCaptchaGenerator_NewCaptcha_Memory(t *testing.T) {
runtime.GC()
var stat1 = &runtime.MemStats{}
runtime.ReadMemStats(stat1)
var generator = waf.NewCaptchaGenerator()
for i := 0; i < 1_000_000; i++ {
generator.NewCaptcha(6)
}
if testutils.IsSingleTesting() {
time.Sleep(1 * time.Second)
}
runtime.GC()
var stat2 = &runtime.MemStats{}
runtime.ReadMemStats(stat2)
t.Log((stat2.HeapInuse-stat1.HeapInuse)>>10, "KiB")
_ = generator
}
func BenchmarkNewCaptchaGenerator(b *testing.B) {
runtime.GOMAXPROCS(4)
var generator = waf.NewCaptchaGenerator()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
generator.NewCaptcha(6)
}
})
}

View File

@@ -0,0 +1,70 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package waf_test
import (
"bytes"
"fmt"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/dchest/captcha"
"runtime"
"testing"
"time"
)
func TestCaptchaMemory(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var stat1 = &runtime.MemStats{}
runtime.ReadMemStats(stat1)
var count = 5_000
var before = time.Now()
for i := 0; i < count; i++ {
var id = captcha.NewLen(6)
var writer = &bytes.Buffer{}
err := captcha.WriteImage(writer, id, 200, 100)
if err != nil {
t.Fatal(err)
}
captcha.VerifyString(id, "abc")
}
var stat2 = &runtime.MemStats{}
runtime.ReadMemStats(stat2)
t.Log((stat2.HeapInuse-stat1.HeapInuse)>>20, "MB", fmt.Sprintf("%.0f QPS", float64(count)/time.Since(before).Seconds()))
}
func BenchmarkCaptcha_VerifyCode_100_50(b *testing.B) {
runtime.GOMAXPROCS(4)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
var id = captcha.NewLen(6)
var writer = &bytes.Buffer{}
err := captcha.WriteImage(writer, id, 100, 50)
if err != nil {
b.Fatal(err)
}
}
})
}
func BenchmarkCaptcha_VerifyCode_200_100(b *testing.B) {
runtime.GOMAXPROCS(4)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
var id = captcha.NewLen(6)
var writer = &bytes.Buffer{}
err := captcha.WriteImage(writer, id, 200, 100)
if err != nil {
b.Fatal(err)
}
_ = id
}
})
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,194 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"regexp"
)
// CCCheckpoint ${cc.arg}
// TODO implement more traffic rules
type CCCheckpoint struct {
Checkpoint
}
func (this *CCCheckpoint) Init() {
}
func (this *CCCheckpoint) Start() {
}
func (this *CCCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = 0
periodString, ok := options["period"]
if !ok {
return
}
var period = types.Int(periodString)
if period < 1 {
return
}
if period > 7*86400 {
period = 7 * 86400
}
var userType = types.String(options["userType"])
var userField = types.String(options["userField"])
var userIndex = types.Int(options["userIndex"])
if param == "requests" { // requests
var key = ""
switch userType {
case "ip":
key = req.WAFRemoteIP()
case "cookie":
if len(userField) == 0 {
key = req.WAFRemoteIP()
} else {
cookie, _ := req.WAFRaw().Cookie(userField)
if cookie != nil {
v := cookie.Value
if userIndex > 0 && len(v) > userIndex {
v = v[userIndex:]
}
key = "USER@" + userType + "@" + userField + "@" + v
}
}
case "get":
if len(userField) == 0 {
key = req.WAFRemoteIP()
} else {
v := req.WAFRaw().URL.Query().Get(userField)
if userIndex > 0 && len(v) > userIndex {
v = v[userIndex:]
}
key = "USER@" + userType + "@" + userField + "@" + v
}
case "post":
if len(userField) == 0 {
key = req.WAFRemoteIP()
} else {
v := req.WAFRaw().PostFormValue(userField)
if userIndex > 0 && len(v) > userIndex {
v = v[userIndex:]
}
key = "USER@" + userType + "@" + userField + "@" + v
}
case "header":
if len(userField) == 0 {
key = req.WAFRemoteIP()
} else {
v := req.WAFRaw().Header.Get(userField)
if userIndex > 0 && len(v) > userIndex {
v = v[userIndex:]
}
key = "USER@" + userType + "@" + userField + "@" + v
}
default:
key = req.WAFRemoteIP()
}
if len(key) == 0 {
key = req.WAFRemoteIP()
}
value = counters.SharedCounter.IncreaseKey(types.String(ruleId)+"@WAF_CC@"+key, types.Int(period))
}
return
}
func (this *CCCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *CCCheckpoint) ParamOptions() *ParamOptions {
option := NewParamOptions()
option.AddParam("请求数", "requests")
return option
}
func (this *CCCheckpoint) Options() []OptionInterface {
options := []OptionInterface{}
// period
{
option := NewFieldOption("统计周期", "period")
option.Value = "60"
option.RightLabel = "秒"
option.Size = 8
option.MaxLength = 8
option.Validate = func(value string) (ok bool, message string) {
if regexp.MustCompile(`^\d+$`).MatchString(value) {
ok = true
return
}
message = "周期需要是一个整数数字"
return
}
options = append(options, option)
}
// type
{
option := NewOptionsOption("用户识别读取来源", "userType")
option.Size = 10
option.SetOptions([]maps.Map{
{
"name": "IP",
"value": "ip",
},
{
"name": "Cookie",
"value": "cookie",
},
{
"name": "URL参数",
"value": "get",
},
{
"name": "POST参数",
"value": "post",
},
{
"name": "HTTP Header",
"value": "header",
},
})
options = append(options, option)
}
// user field
{
option := NewFieldOption("用户识别字段", "userField")
option.Comment = "识别用户的唯一性字段在用户读取来源不是IP时使用"
options = append(options, option)
}
// user value index
{
option := NewFieldOption("字段读取位置", "userIndex")
option.Size = 5
option.MaxLength = 5
option.Comment = "读取用户识别字段的位置从0开始比如user12345的数字ID 12345的位置就是5在用户读取来源不是IP时使用"
options = append(options, option)
}
return options
}
func (this *CCCheckpoint) Stop() {
}
func (this *CCCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheDisabled
}

View File

@@ -0,0 +1,99 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package checkpoints
import (
"fmt"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
wafutils "github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"path/filepath"
"strings"
)
// CC2Checkpoint 新的CC
type CC2Checkpoint struct {
Checkpoint
}
func (this *CC2Checkpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
var keys = options.GetSlice("keys")
var keyValues = []string{}
var hasRemoteAddr = false
for _, key := range keys {
if key == "${remoteAddr}" || key == "${rawRemoteAddr}" {
hasRemoteAddr = true
}
keyValues = append(keyValues, req.Format(types.String(key)))
}
if len(keyValues) == 0 {
return
}
var period = options.GetInt("period")
if period <= 0 {
period = 60
} else if period > 7*86400 {
period = 7 * 86400
}
/**var threshold = options.GetInt64("threshold")
if threshold <= 0 {
threshold = 1000
}**/
if options.GetBool("ignoreCommonFiles") {
var rawReq = req.WAFRaw()
if len(rawReq.Referer()) > 0 {
var ext = filepath.Ext(rawReq.URL.Path)
if len(ext) > 0 && utils.IsCommonFileExtension(ext) {
return
}
}
}
var ccKey = "WAF-CC-" + types.String(ruleId) + "-" + strings.Join(keyValues, "@")
var ccValue = counters.SharedCounter.IncreaseKey(ccKey, period)
value = ccValue
// 基于指纹统计
var enableFingerprint = true
if options.Has("enableFingerprint") && !options.GetBool("enableFingerprint") {
enableFingerprint = false
}
if hasRemoteAddr && enableFingerprint {
var fingerprint = req.WAFFingerprint()
if len(fingerprint) > 0 {
var fpKeyValues = []string{}
for _, key := range keys {
if key == "${remoteAddr}" || key == "${rawRemoteAddr}" {
fpKeyValues = append(fpKeyValues, fmt.Sprintf("%x", fingerprint))
continue
}
fpKeyValues = append(fpKeyValues, req.Format(types.String(key)))
}
var fpCCKey = "WAF-CC-" + types.String(ruleId) + "-" + strings.Join(fpKeyValues, "@")
var fpValue = counters.SharedCounter.IncreaseKey(fpCCKey, period)
if fpValue > ccValue {
value = fpValue
}
}
}
return
}
func (this *CC2Checkpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *CC2Checkpoint) CacheLife() wafutils.CacheLife {
return wafutils.CacheDisabled
}

View File

@@ -0,0 +1,43 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/maps"
"net/http"
"testing"
)
func TestCCCheckpoint_RequestValue(t *testing.T) {
raw, err := http.NewRequest(http.MethodGet, "http://teaos.cn/", nil)
if err != nil {
t.Fatal(err)
}
req := requests.NewTestRequest(raw)
req.WAFRaw().RemoteAddr = "127.0.0.1"
checkpoint := new(CCCheckpoint)
checkpoint.Init()
checkpoint.Start()
options := maps.Map{
"period": "5",
}
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
req.WAFRaw().RemoteAddr = "127.0.0.2"
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
req.WAFRaw().RemoteAddr = "127.0.0.1"
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
req.WAFRaw().RemoteAddr = "127.0.0.2"
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
req.WAFRaw().RemoteAddr = "127.0.0.2"
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
req.WAFRaw().RemoteAddr = "127.0.0.2"
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
}

View File

@@ -0,0 +1,59 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
)
type Checkpoint struct {
priority int
}
func (this *Checkpoint) Init() {
}
func (this *Checkpoint) IsRequest() bool {
return true
}
func (this *Checkpoint) IsComposed() bool {
return false
}
func (this *Checkpoint) ParamOptions() *ParamOptions {
return nil
}
func (this *Checkpoint) Options() []OptionInterface {
return nil
}
func (this *Checkpoint) Start() {
}
func (this *Checkpoint) Stop() {
}
func (this *Checkpoint) SetPriority(priority int) {
this.priority = priority
}
func (this *Checkpoint) Priority() int {
return this.priority
}
func (this *Checkpoint) RequestBodyIsEmpty(req requests.Request) bool {
if req.WAFRaw().ContentLength == 0 {
return true
}
var method = req.WAFRaw().Method
if method == http.MethodHead || method == http.MethodGet {
return true
}
return false
}

View File

@@ -0,0 +1,11 @@
package checkpoints
// CheckpointDefinition check point definition
type CheckpointDefinition struct {
Name string
Description string
Prefix string
HasParams bool // has sub params
Instance CheckpointInterface
Priority int
}

View File

@@ -0,0 +1,46 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
// CheckpointInterface Check Point
type CheckpointInterface interface {
// Init initialize
Init()
// IsRequest is request?
IsRequest() bool
// IsComposed is composed?
IsComposed() bool
// RequestValue get request value
RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error)
// ResponseValue get response value
ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error)
// ParamOptions param option list
ParamOptions() *ParamOptions
// Options options
Options() []OptionInterface
// Start start
Start()
// Stop stop
Stop()
// SetPriority set priority
SetPriority(priority int)
// Priority get priority
Priority() int
// CacheLife regexp cache life
CacheLife() utils.CacheLife
}

View File

@@ -0,0 +1,5 @@
package checkpoints
type OptionInterface interface {
Type() string
}

View File

@@ -0,0 +1,26 @@
package checkpoints
// attach option
type FieldOption struct {
Name string
Code string
Value string // default value
IsRequired bool
Size int
Comment string
Placeholder string
RightLabel string
MaxLength int
Validate func(value string) (ok bool, message string)
}
func NewFieldOption(name string, code string) *FieldOption {
return &FieldOption{
Name: name,
Code: code,
}
}
func (this *FieldOption) Type() string {
return "field"
}

View File

@@ -0,0 +1,30 @@
package checkpoints
import "github.com/iwind/TeaGo/maps"
type OptionsOption struct {
Name string
Code string
Value string // default value
IsRequired bool
Size int
Comment string
RightLabel string
Validate func(value string) (ok bool, message string)
Options []maps.Map
}
func NewOptionsOption(name string, code string) *OptionsOption {
return &OptionsOption{
Name: name,
Code: code,
}
}
func (this *OptionsOption) Type() string {
return "options"
}
func (this *OptionsOption) SetOptions(options []maps.Map) {
this.Options = options
}

View File

@@ -0,0 +1,21 @@
package checkpoints
type KeyValue struct {
Name string `json:"name"`
Value string `json:"value"`
}
type ParamOptions struct {
Options []*KeyValue `json:"options"`
}
func NewParamOptions() *ParamOptions {
return &ParamOptions{}
}
func (this *ParamOptions) AddParam(name string, value string) {
this.Options = append(this.Options, &KeyValue{
Name: name,
Value: value,
})
}

View File

@@ -0,0 +1,60 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
// RequestAllCheckpoint ${requestAll}
type RequestAllCheckpoint struct {
Checkpoint
}
func (this *RequestAllCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
var valueBytes = [][]byte{}
if len(req.WAFRaw().RequestURI) > 0 {
valueBytes = append(valueBytes, []byte(req.WAFRaw().RequestURI))
} else if req.WAFRaw().URL != nil {
valueBytes = append(valueBytes, []byte(req.WAFRaw().URL.RequestURI()))
}
if this.RequestBodyIsEmpty(req) {
value = valueBytes
return
}
if req.WAFRaw().Body != nil {
var bodyData = req.WAFGetCacheBody()
hasRequestBody = true
if len(bodyData) == 0 {
data, err := req.WAFReadBody(req.WAFMaxRequestSize()) // read body
if err != nil {
return "", hasRequestBody, err, nil
}
bodyData = data
req.WAFSetCacheBody(data)
req.WAFRestoreBody(data)
}
if len(bodyData) > 0 {
valueBytes = append(valueBytes, bodyData)
}
}
value = valueBytes
return
}
func (this *RequestAllCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = ""
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestAllCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,76 @@
package checkpoints
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/types"
"io"
"net/http"
"runtime"
"strings"
"testing"
)
func TestRequestAllCheckpoint_RequestValue(t *testing.T) {
req, err := http.NewRequest(http.MethodPost, "http://teaos.cn/hello/world", bytes.NewBuffer([]byte("123456")))
if err != nil {
t.Fatal(err)
}
checkpoint := new(RequestAllCheckpoint)
v, _, sysErr, userErr := checkpoint.RequestValue(requests.NewTestRequest(req), "", nil, 1)
if sysErr != nil {
t.Fatal(sysErr)
}
if userErr != nil {
t.Fatal(userErr)
}
if v != nil {
vv, ok := v.([][]byte)
if ok {
for _, v2 := range vv {
t.Log(string(v2), ":", v2)
}
}
}
body, err := io.ReadAll(req.Body)
if err != nil {
t.Fatal(err)
}
t.Log(string(body))
}
func TestRequestAllCheckpoint_RequestValue_Max(t *testing.T) {
req, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte(strings.Repeat("123456", 10240000))))
if err != nil {
t.Fatal(err)
}
checkpoint := new(RequestBodyCheckpoint)
value, _, err, _ := checkpoint.RequestValue(requests.NewTestRequest(req), "", nil, 1)
if err != nil {
t.Fatal(err)
}
t.Log("value bytes:", len(types.String(value)))
body, err := io.ReadAll(req.Body)
if err != nil {
t.Fatal(err)
}
t.Log("raw bytes:", len(body))
}
func BenchmarkRequestAllCheckpoint_RequestValue(b *testing.B) {
runtime.GOMAXPROCS(1)
req, err := http.NewRequest(http.MethodPost, "http://teaos.cn/hello/world", bytes.NewBuffer(bytes.Repeat([]byte("HELLO"), 1024)))
if err != nil {
b.Fatal(err)
}
checkpoint := new(RequestAllCheckpoint)
for i := 0; i < b.N; i++ {
_, _, _, _ = checkpoint.RequestValue(requests.NewTestRequest(req), "", nil, 1)
}
}

View File

@@ -0,0 +1,26 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestArgCheckpoint struct {
Checkpoint
}
func (this *RequestArgCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return req.WAFRaw().URL.Query().Get(param), hasRequestBody, nil, nil
}
func (this *RequestArgCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestArgCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,21 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
"testing"
)
func TestArgParam_RequestValue(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodGet, "http://teaos.cn/?name=lu", nil)
if err != nil {
t.Fatal(err)
}
req := requests.NewTestRequest(rawReq)
checkpoint := new(RequestArgCheckpoint)
t.Log(checkpoint.RequestValue(req, "name", nil, 1))
t.Log(checkpoint.ResponseValue(req, nil, "name", nil, 1))
t.Log(checkpoint.RequestValue(req, "name2", nil, 1))
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestArgsCheckpoint struct {
Checkpoint
}
func (this *RequestArgsCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.WAFRaw().URL.RawQuery
return
}
func (this *RequestArgsCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestArgsCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,50 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
// RequestBodyCheckpoint ${requestBody}
type RequestBodyCheckpoint struct {
Checkpoint
}
func (this *RequestBodyCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.RequestBodyIsEmpty(req) {
value = ""
return
}
if req.WAFRaw().Body == nil {
value = ""
return
}
var bodyData = req.WAFGetCacheBody()
hasRequestBody = true
if len(bodyData) == 0 {
data, err := req.WAFReadBody(req.WAFMaxRequestSize()) // read body
if err != nil {
return "", hasRequestBody, err, nil
}
bodyData = data
req.WAFSetCacheBody(data)
req.WAFRestoreBody(data)
}
return bodyData, hasRequestBody, nil, nil
}
func (this *RequestBodyCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestBodyCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheDisabled
}

View File

@@ -0,0 +1,48 @@
package checkpoints
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/types"
"io"
"net/http"
"strings"
"testing"
)
func TestRequestBodyCheckpoint_RequestValue(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte("123456")))
if err != nil {
t.Fatal(err)
}
var req = requests.NewTestRequest(rawReq)
checkpoint := new(RequestBodyCheckpoint)
t.Log(checkpoint.RequestValue(req, "", nil, 1))
body, err := io.ReadAll(rawReq.Body)
if err != nil {
t.Fatal(err)
}
t.Log(string(body))
t.Log(string(req.WAFGetCacheBody()))
}
func TestRequestBodyCheckpoint_RequestValue_Max(t *testing.T) {
req, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte(strings.Repeat("123456", 10240000))))
if err != nil {
t.Fatal(err)
}
checkpoint := new(RequestBodyCheckpoint)
value, _, err, _ := checkpoint.RequestValue(requests.NewTestRequest(req), "", nil, 1)
if err != nil {
t.Fatal(err)
}
t.Log("value bytes:", len(types.String(value)))
body, err := io.ReadAll(req.Body)
if err != nil {
t.Fatal(err)
}
t.Log("raw bytes:", len(body))
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestCNAMECheckpoint struct {
Checkpoint
}
func (this *RequestCNAMECheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.Format("${cname}")
return
}
func (this *RequestCNAMECheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestCNAMECheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestContentTypeCheckpoint struct {
Checkpoint
}
func (this *RequestContentTypeCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.WAFRaw().Header.Get("Content-Type")
return
}
func (this *RequestContentTypeCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestContentTypeCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,33 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestCookieCheckpoint struct {
Checkpoint
}
func (this *RequestCookieCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
cookie, err := req.WAFRaw().Cookie(param)
if err != nil {
value = ""
return
}
value = cookie.Value
return
}
func (this *RequestCookieCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestCookieCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,33 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"net/url"
"strings"
)
type RequestCookiesCheckpoint struct {
Checkpoint
}
func (this *RequestCookiesCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
var cookies = []string{}
for _, cookie := range req.WAFRaw().Cookies() {
cookies = append(cookies, url.QueryEscape(cookie.Name)+"="+url.QueryEscape(cookie.Value))
}
value = strings.Join(cookies, "&")
return
}
func (this *RequestCookiesCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestCookiesCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,54 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"net/url"
)
// RequestFormArgCheckpoint ${requestForm.arg}
type RequestFormArgCheckpoint struct {
Checkpoint
}
func (this *RequestFormArgCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
hasRequestBody = true
if this.RequestBodyIsEmpty(req) {
value = ""
return
}
if req.WAFRaw().Body == nil {
value = ""
return
}
var bodyData = req.WAFGetCacheBody()
if len(bodyData) == 0 {
data, err := req.WAFReadBody(req.WAFMaxRequestSize()) // read body
if err != nil {
return "", hasRequestBody, err, nil
}
bodyData = data
req.WAFSetCacheBody(data)
req.WAFRestoreBody(data)
}
// TODO improve performance
values, _ := url.ParseQuery(string(bodyData))
return values.Get(param), hasRequestBody, nil, nil
}
func (this *RequestFormArgCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestFormArgCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,32 @@
package checkpoints
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"io"
"net/http"
"net/url"
"testing"
)
func TestRequestFormArgCheckpoint_RequestValue(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte("name=lu&age=20&encoded="+url.QueryEscape("<strong>ENCODED STRING</strong>"))))
if err != nil {
t.Fatal(err)
}
req := requests.NewTestRequest(rawReq)
req.WAFRaw().Header.Set("Content-Type", "application/x-www-form-urlencoded")
checkpoint := new(RequestFormArgCheckpoint)
t.Log(checkpoint.RequestValue(req, "name", nil, 1))
t.Log(checkpoint.RequestValue(req, "age", nil, 1))
t.Log(checkpoint.RequestValue(req, "Hello", nil, 1))
t.Log(checkpoint.RequestValue(req, "encoded", nil, 1))
body, err := io.ReadAll(req.WAFRaw().Body)
if err != nil {
t.Fatal(err)
}
t.Log(string(body))
}

View File

@@ -0,0 +1,45 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
)
type RequestGeneralHeaderLengthCheckpoint struct {
Checkpoint
}
func (this *RequestGeneralHeaderLengthCheckpoint) IsComposed() bool {
return true
}
func (this *RequestGeneralHeaderLengthCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = false
var headers = options.GetSlice("headers")
if len(headers) == 0 {
return
}
var length = options.GetInt("length")
for _, header := range headers {
v := req.WAFRaw().Header.Get(types.String(header))
if len(v) > length {
value = true
break
}
}
return
}
func (this *RequestGeneralHeaderLengthCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return
}
func (this *RequestGeneralHeaderLengthCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheDisabled
}

View File

@@ -0,0 +1,30 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestGeoCityNameCheckpoint struct {
Checkpoint
}
func (this *RequestGeoCityNameCheckpoint) IsComposed() bool {
return false
}
func (this *RequestGeoCityNameCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.Format("${geo.city.name}")
return
}
func (this *RequestGeoCityNameCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return this.RequestValue(req, param, options, ruleId)
}
func (this *RequestGeoCityNameCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,30 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestGeoCountryNameCheckpoint struct {
Checkpoint
}
func (this *RequestGeoCountryNameCheckpoint) IsComposed() bool {
return false
}
func (this *RequestGeoCountryNameCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.Format("${geo.country.name}")
return
}
func (this *RequestGeoCountryNameCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return this.RequestValue(req, param, options, ruleId)
}
func (this *RequestGeoCountryNameCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,30 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestGeoProvinceNameCheckpoint struct {
Checkpoint
}
func (this *RequestGeoProvinceNameCheckpoint) IsComposed() bool {
return false
}
func (this *RequestGeoProvinceNameCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.Format("${geo.province.name}")
return
}
func (this *RequestGeoProvinceNameCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return this.RequestValue(req, param, options, ruleId)
}
func (this *RequestGeoProvinceNameCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,33 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"strings"
)
type RequestHeaderCheckpoint struct {
Checkpoint
}
func (this *RequestHeaderCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
v, found := req.WAFRaw().Header[param]
if !found {
value = ""
return
}
value = strings.Join(v, ";")
return
}
func (this *RequestHeaderCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestHeaderCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,37 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestHeaderMaxLengthCheckpoint struct {
Checkpoint
}
func (this *RequestHeaderMaxLengthCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
var maxLen int
for _, v := range req.WAFRaw().Header {
for _, subV := range v {
var l = len(subV)
if l > maxLen {
maxLen = l
}
}
}
value = maxLen
return
}
func (this *RequestHeaderMaxLengthCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestHeaderMaxLengthCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,32 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"strings"
)
type RequestHeaderNamesCheckpoint struct {
Checkpoint
}
func (this *RequestHeaderNamesCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
var headerNames = []string{}
for k := range req.WAFRaw().Header {
headerNames = append(headerNames, k)
}
value = strings.Join(headerNames, "\n")
return
}
func (this *RequestHeaderNamesCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestHeaderNamesCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,23 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package checkpoints_test
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/checkpoints"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
"testing"
)
func TestRequestHeaderNamesCheckpoint_RequestValue(t *testing.T) {
var checkpoint = &checkpoints.RequestHeaderNamesCheckpoint{}
rawReq, err := http.NewRequest(http.MethodGet, "https://example.com", nil)
if err != nil {
t.Fatal(err)
}
rawReq.Header.Set("Accept", "text/html")
rawReq.Header.Set("User-Agent", "Chrome")
rawReq.Header.Set("Accept-Encoding", "br, gzip")
var req = requests.NewTestRequest(rawReq)
t.Log(checkpoint.RequestValue(req, "", nil, 0))
}

View File

@@ -0,0 +1,36 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"sort"
"strings"
)
type RequestHeadersCheckpoint struct {
Checkpoint
}
func (this *RequestHeadersCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
var headers = []string{}
for k, v := range req.WAFRaw().Header {
for _, subV := range v {
headers = append(headers, k+": "+subV)
}
}
sort.Strings(headers)
value = strings.Join(headers, "\n")
return
}
func (this *RequestHeadersCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestHeadersCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,33 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package checkpoints
import (
"net/http"
"runtime"
"sort"
"strings"
"testing"
)
func BenchmarkRequestHeadersCheckpoint_RequestValue(b *testing.B) {
runtime.GOMAXPROCS(1)
var header = http.Header{
"Content-Type": []string{"keep-alive"},
"User-Agent": []string{"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"},
"Accept-Encoding": []string{"gzip, deflate, br"},
"Referer": []string{"https://goedge.cn/"},
}
for i := 0; i < b.N; i++ {
var headers = []string{}
for k, v := range header {
for _, subV := range v {
headers = append(headers, k+": "+subV)
}
}
sort.Strings(headers)
_ = strings.Join(headers, "\n")
}
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestHostCheckpoint struct {
Checkpoint
}
func (this *RequestHostCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.WAFRaw().Host
return
}
func (this *RequestHostCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestHostCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,20 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
"testing"
)
func TestRequestHostCheckpoint_RequestValue(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodGet, "https://teaos.cn/?name=lu", nil)
if err != nil {
t.Fatal(err)
}
req := requests.NewTestRequest(rawReq)
req.WAFRaw().Header.Set("Host", "cloud.teaos.cn")
checkpoint := new(RequestHostCheckpoint)
t.Log(checkpoint.RequestValue(req, "", nil, 1))
}

View File

@@ -0,0 +1,31 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestIsCNAMECheckpoint struct {
Checkpoint
}
func (this *RequestIsCNAMECheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if req.Format("${cname}") == req.Format("${host}") {
value = 1
} else {
value = 0
}
return
}
func (this *RequestIsCNAMECheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestIsCNAMECheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,30 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestISPNameCheckpoint struct {
Checkpoint
}
func (this *RequestISPNameCheckpoint) IsComposed() bool {
return false
}
func (this *RequestISPNameCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.Format("${isp.name}")
return
}
func (this *RequestISPNameCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return this.RequestValue(req, param, options, ruleId)
}
func (this *RequestISPNameCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,54 @@
package checkpoints
import (
"encoding/json"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
wafutils "github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"strings"
)
// RequestJSONArgCheckpoint ${requestJSON.arg}
type RequestJSONArgCheckpoint struct {
Checkpoint
}
func (this *RequestJSONArgCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
var bodyData = req.WAFGetCacheBody()
hasRequestBody = true
if len(bodyData) == 0 {
data, err := req.WAFReadBody(req.WAFMaxRequestSize()) // read body
if err != nil {
return "", hasRequestBody, err, nil
}
bodyData = data
req.WAFSetCacheBody(data)
defer req.WAFRestoreBody(data)
}
// TODO improve performance
var m any = nil
err := json.Unmarshal(bodyData, &m)
if err != nil || m == nil {
return "", hasRequestBody, nil, err
}
value = utils.Get(m, strings.Split(param, "."))
if value != nil {
return value, hasRequestBody, nil, err
}
return "", hasRequestBody, nil, nil
}
func (this *RequestJSONArgCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestJSONArgCheckpoint) CacheLife() wafutils.CacheLife {
return wafutils.CacheMiddleLife
}

View File

@@ -0,0 +1,99 @@
package checkpoints
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"io"
"net/http"
"testing"
)
func TestRequestJSONArgCheckpoint_RequestValue_Map(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte(`
{
"name": "lu",
"age": 20,
"books": [ "PHP", "Golang", "Python" ]
}
`)))
if err != nil {
t.Fatal(err)
}
req := requests.NewTestRequest(rawReq)
//req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
checkpoint := new(RequestJSONArgCheckpoint)
t.Log(checkpoint.RequestValue(req, "name", nil, 1))
t.Log(checkpoint.RequestValue(req, "age", nil, 1))
t.Log(checkpoint.RequestValue(req, "Hello", nil, 1))
t.Log(checkpoint.RequestValue(req, "", nil, 1))
t.Log(checkpoint.RequestValue(req, "books", nil, 1))
t.Log(checkpoint.RequestValue(req, "books.1", nil, 1))
body, err := io.ReadAll(req.WAFRaw().Body)
if err != nil {
t.Fatal(err)
}
t.Log(string(body))
}
func TestRequestJSONArgCheckpoint_RequestValue_Array(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte(`
[{
"name": "lu",
"age": 20,
"books": [ "PHP", "Golang", "Python" ]
}]
`)))
if err != nil {
t.Fatal(err)
}
req := requests.NewTestRequest(rawReq)
//req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
checkpoint := new(RequestJSONArgCheckpoint)
t.Log(checkpoint.RequestValue(req, "0.name", nil, 1))
t.Log(checkpoint.RequestValue(req, "0.age", nil, 1))
t.Log(checkpoint.RequestValue(req, "0.Hello", nil, 1))
t.Log(checkpoint.RequestValue(req, "", nil, 1))
t.Log(checkpoint.RequestValue(req, "0.books", nil, 1))
t.Log(checkpoint.RequestValue(req, "0.books.1", nil, 1))
body, err := io.ReadAll(req.WAFRaw().Body)
if err != nil {
t.Fatal(err)
}
t.Log(string(body))
}
func TestRequestJSONArgCheckpoint_RequestValue_Error(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte(`
[{
"name": "lu",
"age": 20,
"books": [ "PHP", "Golang", "Python" ]
}]
`)))
if err != nil {
t.Fatal(err)
}
req := requests.NewTestRequest(rawReq)
//req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
checkpoint := new(RequestJSONArgCheckpoint)
t.Log(checkpoint.RequestValue(req, "0.name", nil, 1))
t.Log(checkpoint.RequestValue(req, "0.age", nil, 1))
t.Log(checkpoint.RequestValue(req, "0.Hello", nil, 1))
t.Log(checkpoint.RequestValue(req, "", nil, 1))
t.Log(checkpoint.RequestValue(req, "0.books", nil, 1))
t.Log(checkpoint.RequestValue(req, "0.books.1", nil, 1))
body, err := io.ReadAll(req.WAFRaw().Body)
if err != nil {
t.Fatal(err)
}
t.Log(string(body))
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestLengthCheckpoint struct {
Checkpoint
}
func (this *RequestLengthCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.WAFRaw().ContentLength
return
}
func (this *RequestLengthCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestLengthCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestMethodCheckpoint struct {
Checkpoint
}
func (this *RequestMethodCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.WAFRaw().Method
return
}
func (this *RequestMethodCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestMethodCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,26 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestPathCheckpoint struct {
Checkpoint
}
func (this *RequestPathCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return req.WAFRaw().URL.Path, false, nil, nil
}
func (this *RequestPathCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestPathCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,18 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
"testing"
)
func TestRequestPathCheckpoint_RequestValue(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodGet, "http://teaos.cn/index?name=lu", nil)
if err != nil {
t.Fatal(err)
}
req := requests.NewTestRequest(rawReq)
checkpoint := new(RequestPathCheckpoint)
t.Log(checkpoint.RequestValue(req, "", nil, 1))
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestProtoCheckpoint struct {
Checkpoint
}
func (this *RequestProtoCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.WAFRaw().Proto
return
}
func (this *RequestProtoCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestProtoCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,33 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"net"
)
type RequestRawRemoteAddrCheckpoint struct {
Checkpoint
}
func (this *RequestRawRemoteAddrCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
host, _, err := net.SplitHostPort(req.WAFRaw().RemoteAddr)
if err == nil {
value = host
} else {
value = req.WAFRaw().RemoteAddr
}
return
}
func (this *RequestRawRemoteAddrCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestRawRemoteAddrCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestRefererCheckpoint struct {
Checkpoint
}
func (this *RequestRefererCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.WAFRaw().Referer()
return
}
func (this *RequestRefererCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestRefererCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,104 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package checkpoints
import (
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"net/url"
)
// RequestRefererBlockCheckpoint 防盗链
type RequestRefererBlockCheckpoint struct {
Checkpoint
}
// RequestValue 计算checkpoint值
// 选项allowEmpty, allowSameDomain, allowDomains
func (this *RequestRefererBlockCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
var checkOrigin = options.GetBool("checkOrigin")
var referer = req.WAFRaw().Referer()
if len(referer) == 0 && checkOrigin {
var origin = req.WAFRaw().Header.Get("Origin")
if len(origin) > 0 && origin != "null" {
referer = "https://" + origin // 因为Origin都只有域名部分所以为了下面的URL 分析需要加上https://
}
}
if len(referer) == 0 {
if options.GetBool("allowEmpty") {
value = 1
return
}
value = 0
return
}
u, err := url.Parse(referer)
if err != nil {
value = 0
return
}
var host = u.Host
if options.GetBool("allowSameDomain") && host == req.WAFRaw().Host {
value = 1
return
}
// allow domains
var allowDomains = options.GetSlice("allowDomains")
var allowDomainStrings = []string{}
for _, domain := range allowDomains {
allowDomainStrings = append(allowDomainStrings, types.String(domain))
}
// deny domains
var denyDomains = options.GetSlice("denyDomains")
var denyDomainStrings = []string{}
for _, domain := range denyDomains {
denyDomainStrings = append(denyDomainStrings, types.String(domain))
}
if len(allowDomainStrings) == 0 {
if len(denyDomainStrings) > 0 {
if configutils.MatchDomains(denyDomainStrings, host) {
value = 0
} else {
value = 1
}
return
}
value = 0
return
}
if configutils.MatchDomains(allowDomainStrings, host) {
if len(denyDomainStrings) > 0 {
if configutils.MatchDomains(denyDomainStrings, host) {
value = 0
} else {
value = 1
}
return
}
value = 1
return
} else {
value = 0
}
return
}
func (this *RequestRefererBlockCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return
}
func (this *RequestRefererBlockCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,44 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestRefererOriginCheckpoint struct {
Checkpoint
}
func (this *RequestRefererOriginCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
var s []string
var referer = req.WAFRaw().Referer()
if len(referer) > 0 {
s = append(s, referer)
}
var origin = req.WAFRaw().Header.Get("Origin")
if len(origin) > 0 {
s = append(s, origin)
}
if len(s) > 0 {
value = s
} else {
value = ""
}
return
}
func (this *RequestRefererOriginCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestRefererOriginCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,38 @@
package checkpoints_test
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/checkpoints"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
"testing"
)
func TestRequestRefererOriginCheckpoint_RequestValue(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodGet, "https://example.com", nil)
if err != nil {
t.Fatal(err)
}
var req = requests.NewTestRequest(rawReq)
var checkpoint = &checkpoints.RequestRefererOriginCheckpoint{}
{
t.Log(checkpoint.RequestValue(req, "", nil, 0))
}
{
rawReq.Header.Set("Referer", "https://example.com/hello.yaml")
t.Log(checkpoint.RequestValue(req, "", nil, 0))
}
{
rawReq.Header.Set("Origin", "https://example.com/world.yaml")
t.Log(checkpoint.RequestValue(req, "", nil, 0))
}
{
rawReq.Header.Del("Referer")
rawReq.Header.Set("Origin", "https://example.com/world.yaml")
t.Log(checkpoint.RequestValue(req, "", nil, 0))
}
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestRemoteAddrCheckpoint struct {
Checkpoint
}
func (this *RequestRemoteAddrCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.WAFRemoteIP()
return
}
func (this *RequestRemoteAddrCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestRemoteAddrCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,34 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"net"
)
type RequestRemotePortCheckpoint struct {
Checkpoint
}
func (this *RequestRemotePortCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
_, port, err := net.SplitHostPort(req.WAFRaw().RemoteAddr)
if err == nil {
value = types.Int(port)
} else {
value = 0
}
return
}
func (this *RequestRemotePortCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestRemotePortCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,32 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestRemoteUserCheckpoint struct {
Checkpoint
}
func (this *RequestRemoteUserCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
username, _, ok := req.WAFRaw().BasicAuth()
if !ok {
value = ""
return
}
value = username
return
}
func (this *RequestRemoteUserCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestRemoteUserCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestSchemeCheckpoint struct {
Checkpoint
}
func (this *RequestSchemeCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.Format("${scheme}")
return
}
func (this *RequestSchemeCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestSchemeCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,18 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
"testing"
)
func TestRequestSchemeCheckpoint_RequestValue(t *testing.T) {
rawReq, err := http.NewRequest(http.MethodGet, "https://teaos.cn/?name=lu", nil)
if err != nil {
t.Fatal(err)
}
req := requests.NewTestRequest(rawReq)
checkpoint := new(RequestSchemeCheckpoint)
t.Log(checkpoint.RequestValue(req, "", nil, 1))
}

View File

@@ -0,0 +1,234 @@
package checkpoints
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"io"
"net/http"
"path/filepath"
"regexp"
"strconv"
"strings"
)
var multipartHeaderRegexp = regexp.MustCompile(`(?i)(?:^|\r\n)--+\w+\r\n((([\w-]+: .+)\r\n)+)`)
var multipartHeaderContentRangeRegexp = regexp.MustCompile(`/(\d+)`)
// RequestUploadCheckpoint ${requestUpload.arg}
type RequestUploadCheckpoint struct {
Checkpoint
}
func (this *RequestUploadCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.RequestBodyIsEmpty(req) {
value = ""
return
}
value = ""
if param == "minSize" || param == "maxSize" {
value = 0
}
if req.WAFRaw().Method != http.MethodPost {
return
}
if req.WAFRaw().Body == nil {
return
}
hasRequestBody = true
var requestContentLength = req.WAFRaw().ContentLength
var fields []string
var minSize int64
var maxSize int64
var names []string
var extensions []string
if requestContentLength <= req.WAFMaxRequestSize() { // full read
if req.WAFRaw().MultipartForm == nil {
var bodyData = req.WAFGetCacheBody()
if len(bodyData) == 0 {
data, err := req.WAFReadBody(req.WAFMaxRequestSize())
if err != nil {
sysErr = err
return
}
bodyData = data
req.WAFSetCacheBody(data)
defer req.WAFRestoreBody(data)
}
var oldBody = req.WAFRaw().Body
req.WAFRaw().Body = io.NopCloser(bytes.NewBuffer(bodyData))
err := req.WAFRaw().ParseMultipartForm(req.WAFMaxRequestSize())
if err == nil {
for field, files := range req.WAFRaw().MultipartForm.File {
if param == "field" {
fields = append(fields, field)
} else if param == "minSize" {
for _, file := range files {
if minSize == 0 || minSize > file.Size {
minSize = file.Size
}
}
} else if param == "maxSize" {
for _, file := range files {
if maxSize < file.Size {
maxSize = file.Size
}
}
} else if param == "name" {
for _, file := range files {
if !lists.ContainsString(names, file.Filename) {
names = append(names, file.Filename)
}
}
} else if param == "ext" {
for _, file := range files {
if len(file.Filename) > 0 {
exit := strings.ToLower(filepath.Ext(file.Filename))
if !lists.ContainsString(extensions, exit) {
extensions = append(extensions, exit)
}
}
}
}
}
}
// 还原
req.WAFRaw().Body = oldBody
if err != nil {
userErr = err
return
}
if req.WAFRaw().MultipartForm == nil {
return
}
}
} else { // read first part
var bodyData = req.WAFGetCacheBody()
if len(bodyData) == 0 {
data, err := req.WAFReadBody(req.WAFMaxRequestSize())
if err != nil {
sysErr = err
return
}
bodyData = data
req.WAFSetCacheBody(data)
defer req.WAFRestoreBody(data)
}
var subMatches = multipartHeaderRegexp.FindAllSubmatch(bodyData, -1)
for _, subMatch := range subMatches {
var headers = bytes.Split(subMatch[1], []byte{'\r', '\n'})
var partContentLength int64 = -1
for _, header := range headers {
if len(header) > 2 {
var kv = bytes.SplitN(header, []byte{':'}, 2)
if len(kv) == 2 {
var k = kv[0]
var v = kv[1]
switch string(bytes.ToLower(k)) {
case "content-disposition":
var props = bytes.Split(v, []byte{';', ' '})
for _, prop := range props {
var propKV = bytes.SplitN(prop, []byte{'='}, 2)
if len(propKV) == 2 {
var propValue = string(propKV[1])
switch string(propKV[0]) {
case "name":
if param == "field" {
propValue, _ = strconv.Unquote(propValue)
fields = append(fields, propValue)
}
case "filename":
if param == "name" {
propValue, _ = strconv.Unquote(propValue)
names = append(names, propValue)
} else if param == "ext" {
propValue, _ = strconv.Unquote(propValue)
extensions = append(extensions, strings.ToLower(filepath.Ext(propValue)))
}
}
}
}
case "content-range":
if partContentLength <= 0 {
var contentRange = multipartHeaderContentRangeRegexp.FindSubmatch(v)
if len(contentRange) >= 2 {
partContentLength = types.Int64(string(contentRange[1]))
}
}
case "content-length":
if partContentLength <= 0 {
partContentLength = types.Int64(string(v))
}
}
}
}
}
// minSize & maxSize
if partContentLength > 0 {
if param == "minSize" && (minSize == 0 /** not set yet **/ || partContentLength < minSize) {
minSize = partContentLength
} else if param == "maxSize" && partContentLength > maxSize {
maxSize = partContentLength
}
}
}
}
if param == "field" { // field
value = strings.Join(fields, ",")
} else if param == "minSize" { // minSize
if minSize == 0 && requestContentLength > 0 {
minSize = requestContentLength
}
value = minSize
} else if param == "maxSize" { // maxSize
if maxSize == 0 && requestContentLength > 0 {
maxSize = requestContentLength
}
value = maxSize
} else if param == "name" { // name
value = strings.Join(names, ",")
} else if param == "ext" { // ext
value = strings.Join(extensions, ",")
}
return
}
func (this *RequestUploadCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestUploadCheckpoint) ParamOptions() *ParamOptions {
option := NewParamOptions()
option.AddParam("最小文件尺寸", "minSize")
option.AddParam("最大文件尺寸", "maxSize")
option.AddParam("扩展名(如.txt)", "ext")
option.AddParam("原始文件名", "name")
option.AddParam("表单字段名", "field")
return option
}
func (this *RequestUploadCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,102 @@
package checkpoints
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"io"
"mime/multipart"
"net/http"
"testing"
)
func TestRequestUploadCheckpoint_RequestValue(t *testing.T) {
body := bytes.NewBuffer([]byte{})
writer := multipart.NewWriter(body)
{
part, err := writer.CreateFormField("name")
if err == nil {
_, err := part.Write([]byte("lu"))
if err != nil {
t.Fatal(err)
}
}
}
{
part, err := writer.CreateFormField("age")
if err == nil {
_, err = part.Write([]byte("20"))
if err != nil {
t.Fatal(err)
}
}
}
{
part, err := writer.CreateFormFile("myFile", "hello.txt")
if err == nil {
_, err = part.Write([]byte("Hello, World!"))
if err != nil {
t.Fatal(err)
}
}
}
{
part, err := writer.CreateFormFile("myFile2", "hello.PHP")
if err == nil {
_, err = part.Write([]byte("Hello, World, PHP!"))
if err != nil {
t.Fatal(err)
}
}
}
{
part, err := writer.CreateFormFile("myFile3", "hello.asp")
if err == nil {
_, err = part.Write([]byte("Hello, World, ASP Pages!"))
if err != nil {
t.Fatal(err)
}
}
}
{
part, err := writer.CreateFormFile("myFile4", "hello.asp")
if err == nil {
_, err = part.Write([]byte("Hello, World, ASP Pages!"))
if err != nil {
t.Fatal(err)
}
}
}
err := writer.Close()
if err != nil {
t.Fatal(err)
}
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn/", body)
if err != nil {
t.Fatal()
}
req := requests.NewTestRequest(rawReq)
req.WAFRaw().Header.Add("Content-Type", writer.FormDataContentType())
checkpoint := new(RequestUploadCheckpoint)
t.Log(checkpoint.RequestValue(req, "field", nil, 1))
t.Log(checkpoint.RequestValue(req, "minSize", nil, 1))
t.Log(checkpoint.RequestValue(req, "maxSize", nil, 1))
t.Log(checkpoint.RequestValue(req, "name", nil, 1))
t.Log(checkpoint.RequestValue(req, "ext", nil, 1))
data, err := io.ReadAll(req.WAFRaw().Body)
if err != nil {
t.Fatal(err)
}
t.Log(string(data))
}

View File

@@ -0,0 +1,31 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestURICheckpoint struct {
Checkpoint
}
func (this *RequestURICheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if len(req.WAFRaw().RequestURI) > 0 {
value = req.WAFRaw().RequestURI
} else if req.WAFRaw().URL != nil {
value = req.WAFRaw().URL.RequestURI()
}
return
}
func (this *RequestURICheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestURICheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,26 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestURLCheckpoint struct {
Checkpoint
}
func (this *RequestURLCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return req.Format("${requestURL}"), hasRequestBody, nil, nil
}
func (this *RequestURLCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestURLCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
type RequestUserAgentCheckpoint struct {
Checkpoint
}
func (this *RequestUserAgentCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = req.WAFRaw().UserAgent()
return
}
func (this *RequestUserAgentCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *RequestUserAgentCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,52 @@
package checkpoints
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"io"
)
// ResponseBodyCheckpoint ${responseBody}
type ResponseBodyCheckpoint struct {
Checkpoint
}
func (this *ResponseBodyCheckpoint) IsRequest() bool {
return false
}
func (this *ResponseBodyCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = ""
return
}
func (this *ResponseBodyCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if resp.ContentLength == 0 {
value = ""
return
}
value = ""
if resp != nil && resp.Body != nil {
if len(resp.BodyData) > 0 {
value = string(resp.BodyData)
return
}
body, err := io.ReadAll(resp.Body)
if err != nil {
sysErr = err
return
}
resp.BodyData = body
_ = resp.Body.Close()
value = body
resp.Body = io.NopCloser(bytes.NewBuffer(body))
}
return
}
func (this *ResponseBodyCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,29 @@
package checkpoints
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"io"
"net/http"
"testing"
)
func TestResponseBodyCheckpoint_ResponseValue(t *testing.T) {
resp := requests.NewResponse(new(http.Response))
resp.StatusCode = 200
resp.Header = http.Header{}
resp.Header.Set("Hello", "World")
resp.Body = io.NopCloser(bytes.NewBuffer([]byte("Hello, World")))
checkpoint := new(ResponseBodyCheckpoint)
t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1))
t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1))
t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1))
t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1))
data, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
t.Log("after read:", string(data))
}

View File

@@ -0,0 +1,33 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
// ResponseBytesSentCheckpoint ${bytesSent}
type ResponseBytesSentCheckpoint struct {
Checkpoint
}
func (this *ResponseBytesSentCheckpoint) IsRequest() bool {
return false
}
func (this *ResponseBytesSentCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = 0
return
}
func (this *ResponseBytesSentCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = 0
if resp != nil {
value = resp.ContentLength
}
return
}
func (this *ResponseBytesSentCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheShortLife
}

View File

@@ -0,0 +1,49 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
)
type ResponseGeneralHeaderLengthCheckpoint struct {
Checkpoint
}
func (this *ResponseGeneralHeaderLengthCheckpoint) IsRequest() bool {
return false
}
func (this *ResponseGeneralHeaderLengthCheckpoint) IsComposed() bool {
return true
}
func (this *ResponseGeneralHeaderLengthCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return
}
func (this *ResponseGeneralHeaderLengthCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = false
headers := options.GetSlice("headers")
if len(headers) == 0 {
return
}
length := options.GetInt("length")
for _, header := range headers {
v := req.WAFRaw().Header.Get(types.String(header))
if len(v) > length {
value = true
break
}
}
return
}
func (this *ResponseGeneralHeaderLengthCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,34 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
// ResponseHeaderCheckpoint ${responseHeader.arg}
type ResponseHeaderCheckpoint struct {
Checkpoint
}
func (this *ResponseHeaderCheckpoint) IsRequest() bool {
return false
}
func (this *ResponseHeaderCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = ""
return
}
func (this *ResponseHeaderCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if resp != nil && resp.Header != nil {
value = resp.Header.Get(param)
} else {
value = ""
}
return
}
func (this *ResponseHeaderCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,17 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
"testing"
)
func TestResponseHeaderCheckpoint_ResponseValue(t *testing.T) {
resp := requests.NewResponse(new(http.Response))
resp.StatusCode = 200
resp.Header = http.Header{}
resp.Header.Set("Hello", "World")
checkpoint := new(ResponseHeaderCheckpoint)
t.Log(checkpoint.ResponseValue(nil, resp, "Hello", nil, 1))
}

View File

@@ -0,0 +1,32 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
// ResponseStatusCheckpoint ${bytesSent}
type ResponseStatusCheckpoint struct {
Checkpoint
}
func (this *ResponseStatusCheckpoint) IsRequest() bool {
return false
}
func (this *ResponseStatusCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
value = 0
return
}
func (this *ResponseStatusCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if resp != nil {
value = resp.StatusCode
}
return
}
func (this *ResponseStatusCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheLongLife
}

View File

@@ -0,0 +1,15 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"net/http"
"testing"
)
func TestResponseStatusCheckpoint_ResponseValue(t *testing.T) {
resp := requests.NewResponse(new(http.Response))
resp.StatusCode = 200
checkpoint := new(ResponseStatusCheckpoint)
t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1))
}

View File

@@ -0,0 +1,27 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
// SampleRequestCheckpoint just a sample checkpoint, copy and change it for your new checkpoint
type SampleRequestCheckpoint struct {
Checkpoint
}
func (this *SampleRequestCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return
}
func (this *SampleRequestCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
if this.IsRequest() {
return this.RequestValue(req, param, options, ruleId)
}
return
}
func (this *SampleRequestCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

View File

@@ -0,0 +1,28 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
"github.com/iwind/TeaGo/maps"
)
// SampleResponseCheckpoint just a sample checkpoint, copy and change it for your new checkpoint
type SampleResponseCheckpoint struct {
Checkpoint
}
func (this *SampleResponseCheckpoint) IsRequest() bool {
return false
}
func (this *SampleResponseCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, sysErr error, userErr error) {
return
}
func (this *SampleResponseCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
return
}
func (this *SampleResponseCheckpoint) CacheLife() utils.CacheLife {
return utils.CacheMiddleLife
}

Some files were not shown because too many files have changed in this diff Show More