2020-10-08 15:06:42 +08:00
|
|
|
|
package waf
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2023-08-11 14:38:00 +08:00
|
|
|
|
"fmt"
|
2021-09-30 11:30:58 +08:00
|
|
|
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
2021-07-18 15:51:49 +08:00
|
|
|
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
2021-12-02 16:08:25 +08:00
|
|
|
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
2020-10-08 15:06:42 +08:00
|
|
|
|
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
2021-07-18 15:51:49 +08:00
|
|
|
|
"github.com/iwind/TeaGo/lists"
|
2020-10-08 15:06:42 +08:00
|
|
|
|
"github.com/iwind/TeaGo/maps"
|
2022-01-29 21:43:42 +08:00
|
|
|
|
"github.com/iwind/TeaGo/types"
|
2021-07-18 15:51:49 +08:00
|
|
|
|
"net/http"
|
2021-10-06 08:56:38 +08:00
|
|
|
|
"sort"
|
2020-10-08 15:06:42 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type RuleConnector = string
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
RuleConnectorAnd = "and"
|
|
|
|
|
|
RuleConnectorOr = "or"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type RuleSet struct {
|
2021-11-16 16:11:05 +08:00
|
|
|
|
Id int64 `yaml:"id" json:"id"`
|
2021-07-18 15:51:49 +08:00
|
|
|
|
Code string `yaml:"code" json:"code"`
|
|
|
|
|
|
IsOn bool `yaml:"isOn" json:"isOn"`
|
|
|
|
|
|
Name string `yaml:"name" json:"name"`
|
|
|
|
|
|
Description string `yaml:"description" json:"description"`
|
|
|
|
|
|
Rules []*Rule `yaml:"rules" json:"rules"`
|
|
|
|
|
|
Connector RuleConnector `yaml:"connector" json:"connector"` // rules connector
|
|
|
|
|
|
Actions []*ActionConfig `yaml:"actions" json:"actions"`
|
2021-12-02 16:08:25 +08:00
|
|
|
|
IgnoreLocal bool `yaml:"ignoreLocal" json:"ignoreLocal"`
|
2020-10-08 15:06:42 +08:00
|
|
|
|
|
2021-07-18 15:51:49 +08:00
|
|
|
|
actionCodes []string
|
|
|
|
|
|
actionInstances []ActionInterface
|
2020-10-08 15:06:42 +08:00
|
|
|
|
|
2024-01-20 20:54:41 +08:00
|
|
|
|
hasAllowActions bool
|
|
|
|
|
|
allowScope string
|
|
|
|
|
|
|
2020-10-08 15:06:42 +08:00
|
|
|
|
hasRules bool
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func NewRuleSet() *RuleSet {
|
|
|
|
|
|
return &RuleSet{
|
|
|
|
|
|
IsOn: true,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-18 15:51:49 +08:00
|
|
|
|
func (this *RuleSet) Init(waf *WAF) error {
|
2020-10-08 15:06:42 +08:00
|
|
|
|
this.hasRules = len(this.Rules) > 0
|
|
|
|
|
|
if this.hasRules {
|
|
|
|
|
|
for _, rule := range this.Rules {
|
|
|
|
|
|
err := rule.Init()
|
|
|
|
|
|
if err != nil {
|
2023-08-11 14:38:00 +08:00
|
|
|
|
return fmt.Errorf("init rule '%s %s %s' failed: %w", rule.Param, rule.Operator, types.String(rule.Value), err)
|
2020-10-08 15:06:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-10-24 17:57:07 +08:00
|
|
|
|
|
|
|
|
|
|
// sort by priority
|
|
|
|
|
|
sort.Slice(this.Rules, func(i, j int) bool {
|
|
|
|
|
|
return this.Rules[i].Priority > this.Rules[j].Priority
|
|
|
|
|
|
})
|
2020-10-08 15:06:42 +08:00
|
|
|
|
}
|
2021-07-18 15:51:49 +08:00
|
|
|
|
|
|
|
|
|
|
// action codes
|
|
|
|
|
|
var actionCodes = []string{}
|
|
|
|
|
|
for _, action := range this.Actions {
|
2024-01-20 20:54:41 +08:00
|
|
|
|
if action.Code == ActionAllow {
|
|
|
|
|
|
this.hasAllowActions = true
|
|
|
|
|
|
if action.Options != nil {
|
|
|
|
|
|
this.allowScope = action.Options.GetString("scope")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-07-18 15:51:49 +08:00
|
|
|
|
if !lists.ContainsString(actionCodes, action.Code) {
|
|
|
|
|
|
actionCodes = append(actionCodes, action.Code)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
this.actionCodes = actionCodes
|
|
|
|
|
|
|
|
|
|
|
|
// action instances
|
|
|
|
|
|
this.actionInstances = []ActionInterface{}
|
|
|
|
|
|
for _, action := range this.Actions {
|
2022-05-21 11:17:53 +08:00
|
|
|
|
var instance = FindActionInstance(action.Code, action.Options)
|
2021-07-18 15:51:49 +08:00
|
|
|
|
if instance == nil {
|
|
|
|
|
|
remotelogs.Error("WAF_RULE_SET", "can not find instance for action '"+action.Code+"'")
|
2021-10-25 19:42:12 +08:00
|
|
|
|
continue
|
2021-07-18 15:51:49 +08:00
|
|
|
|
}
|
2021-10-25 19:42:12 +08:00
|
|
|
|
|
2021-07-18 15:51:49 +08:00
|
|
|
|
err := instance.Init(waf)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
remotelogs.Error("WAF_RULE_SET", "init action '"+action.Code+"' failed: "+err.Error())
|
2021-10-25 19:42:12 +08:00
|
|
|
|
continue
|
2021-07-18 15:51:49 +08:00
|
|
|
|
}
|
2021-10-25 19:42:12 +08:00
|
|
|
|
|
|
|
|
|
|
this.actionInstances = append(this.actionInstances, instance)
|
2022-05-21 11:17:53 +08:00
|
|
|
|
waf.AddAction(instance)
|
2021-07-18 15:51:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-06 09:39:57 +08:00
|
|
|
|
// sort actions
|
|
|
|
|
|
sort.Slice(this.actionInstances, func(i, j int) bool {
|
|
|
|
|
|
var instance1 = this.actionInstances[i]
|
|
|
|
|
|
if !instance1.WillChange() {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
if instance1.Code() == ActionRecordIP {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2020-10-08 15:06:42 +08:00
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (this *RuleSet) AddRule(rule ...*Rule) {
|
|
|
|
|
|
this.Rules = append(this.Rules, rule...)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-07-18 15:51:49 +08:00
|
|
|
|
// AddAction 添加动作
|
|
|
|
|
|
func (this *RuleSet) AddAction(code string, options maps.Map) {
|
|
|
|
|
|
if options == nil {
|
|
|
|
|
|
options = maps.Map{}
|
|
|
|
|
|
}
|
|
|
|
|
|
this.Actions = append(this.Actions, &ActionConfig{
|
|
|
|
|
|
Code: code,
|
|
|
|
|
|
Options: options,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// HasSpecialActions 除了Allow之外是否还有别的动作
|
|
|
|
|
|
func (this *RuleSet) HasSpecialActions() bool {
|
|
|
|
|
|
for _, action := range this.Actions {
|
|
|
|
|
|
if action.Code != ActionAllow {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// HasAttackActions 检查是否含有攻击防御动作
|
|
|
|
|
|
func (this *RuleSet) HasAttackActions() bool {
|
|
|
|
|
|
for _, action := range this.actionInstances {
|
|
|
|
|
|
if action.IsAttack() {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (this *RuleSet) ActionCodes() []string {
|
|
|
|
|
|
return this.actionCodes
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-20 20:54:41 +08:00
|
|
|
|
func (this *RuleSet) PerformActions(waf *WAF, group *RuleGroup, req requests.Request, writer http.ResponseWriter) PerformResult {
|
2021-10-03 08:35:28 +08:00
|
|
|
|
if len(waf.Mode) != 0 && waf.Mode != firewallconfigs.FirewallModeDefend {
|
2024-01-20 20:54:41 +08:00
|
|
|
|
return PerformResult{
|
|
|
|
|
|
ContinueRequest: true,
|
|
|
|
|
|
}
|
2021-09-30 11:30:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-20 20:54:41 +08:00
|
|
|
|
var isAllowed = this.hasAllowActions
|
|
|
|
|
|
var allowScope = this.allowScope
|
|
|
|
|
|
var continueRequest bool
|
|
|
|
|
|
var goNextGroup bool
|
|
|
|
|
|
var goNextSet bool
|
|
|
|
|
|
|
2021-07-18 15:51:49 +08:00
|
|
|
|
// 先执行allow
|
|
|
|
|
|
for _, instance := range this.actionInstances {
|
|
|
|
|
|
if !instance.WillChange() {
|
2022-08-25 16:44:44 +08:00
|
|
|
|
continueRequest = req.WAFOnAction(instance)
|
|
|
|
|
|
if !continueRequest {
|
2024-01-20 20:54:41 +08:00
|
|
|
|
return PerformResult{
|
|
|
|
|
|
IsAllowed: isAllowed,
|
|
|
|
|
|
AllowScope: allowScope,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
var performResult = instance.Perform(waf, group, this, req, writer)
|
|
|
|
|
|
continueRequest = performResult.ContinueRequest
|
|
|
|
|
|
goNextSet = performResult.GoNextSet
|
|
|
|
|
|
if performResult.IsAllowed {
|
|
|
|
|
|
isAllowed = true
|
|
|
|
|
|
allowScope = performResult.AllowScope
|
|
|
|
|
|
goNextGroup = performResult.GoNextGroup
|
2021-07-18 15:51:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 再执行block|verify
|
|
|
|
|
|
for _, instance := range this.actionInstances {
|
|
|
|
|
|
// 只执行第一个可能改变请求的动作,其余的都会被忽略
|
|
|
|
|
|
if instance.WillChange() {
|
2022-08-25 16:44:44 +08:00
|
|
|
|
continueRequest = req.WAFOnAction(instance)
|
|
|
|
|
|
if !continueRequest {
|
2024-01-20 20:54:41 +08:00
|
|
|
|
return PerformResult{
|
|
|
|
|
|
IsAllowed: isAllowed,
|
|
|
|
|
|
AllowScope: allowScope,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
var performResult = instance.Perform(waf, group, this, req, writer)
|
|
|
|
|
|
continueRequest = performResult.ContinueRequest
|
|
|
|
|
|
goNextSet = performResult.GoNextSet
|
|
|
|
|
|
if performResult.IsAllowed {
|
|
|
|
|
|
isAllowed = true
|
|
|
|
|
|
allowScope = performResult.AllowScope
|
|
|
|
|
|
goNextGroup = performResult.GoNextGroup
|
|
|
|
|
|
}
|
|
|
|
|
|
return PerformResult{
|
|
|
|
|
|
ContinueRequest: performResult.ContinueRequest,
|
|
|
|
|
|
GoNextGroup: goNextGroup,
|
|
|
|
|
|
GoNextSet: performResult.GoNextSet,
|
|
|
|
|
|
IsAllowed: isAllowed,
|
|
|
|
|
|
AllowScope: allowScope,
|
2021-07-18 15:51:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-20 20:54:41 +08:00
|
|
|
|
return PerformResult{
|
|
|
|
|
|
ContinueRequest: true,
|
|
|
|
|
|
GoNextGroup: goNextGroup,
|
|
|
|
|
|
GoNextSet: goNextSet,
|
|
|
|
|
|
IsAllowed: isAllowed,
|
|
|
|
|
|
AllowScope: allowScope,
|
|
|
|
|
|
}
|
2021-07-18 15:51:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-16 17:05:37 +08:00
|
|
|
|
func (this *RuleSet) MatchRequest(req requests.Request) (b bool, hasRequestBody bool, err error) {
|
2021-12-02 16:08:25 +08:00
|
|
|
|
// 是否忽略局域网IP
|
|
|
|
|
|
if this.IgnoreLocal && utils.IsLocalIP(req.WAFRemoteIP()) {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return false, hasRequestBody, nil
|
2021-12-02 16:08:25 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-08 15:06:42 +08:00
|
|
|
|
if !this.hasRules {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return false, hasRequestBody, nil
|
2020-10-08 15:06:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
switch this.Connector {
|
|
|
|
|
|
case RuleConnectorAnd:
|
|
|
|
|
|
for _, rule := range this.Rules {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
b1, hasCheckRequestBody, err1 := rule.MatchRequest(req)
|
|
|
|
|
|
if hasCheckRequestBody {
|
|
|
|
|
|
hasRequestBody = true
|
|
|
|
|
|
}
|
2020-10-08 15:06:42 +08:00
|
|
|
|
if err1 != nil {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return false, hasRequestBody, err1
|
2020-10-08 15:06:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
if !b1 {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return false, hasRequestBody, nil
|
2020-10-08 15:06:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return true, hasRequestBody, nil
|
2020-10-08 15:06:42 +08:00
|
|
|
|
case RuleConnectorOr:
|
|
|
|
|
|
for _, rule := range this.Rules {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
b1, hasCheckRequestBody, err1 := rule.MatchRequest(req)
|
|
|
|
|
|
if hasCheckRequestBody {
|
|
|
|
|
|
hasRequestBody = true
|
|
|
|
|
|
}
|
2020-10-08 15:06:42 +08:00
|
|
|
|
if err1 != nil {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return false, hasRequestBody, err1
|
2020-10-08 15:06:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
if b1 {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return true, hasRequestBody, nil
|
2020-10-08 15:06:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
default: // same as And
|
|
|
|
|
|
for _, rule := range this.Rules {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
b1, hasCheckRequestBody, err1 := rule.MatchRequest(req)
|
|
|
|
|
|
if hasCheckRequestBody {
|
|
|
|
|
|
hasRequestBody = true
|
|
|
|
|
|
}
|
2020-10-08 15:06:42 +08:00
|
|
|
|
if err1 != nil {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return false, hasRequestBody, err1
|
2020-10-08 15:06:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
if !b1 {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return false, hasRequestBody, nil
|
2020-10-08 15:06:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return true, hasRequestBody, nil
|
2020-10-08 15:06:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-16 17:05:37 +08:00
|
|
|
|
func (this *RuleSet) MatchResponse(req requests.Request, resp *requests.Response) (b bool, hasRequestBody bool, err error) {
|
2020-10-08 15:06:42 +08:00
|
|
|
|
if !this.hasRules {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return false, hasRequestBody, nil
|
2020-10-08 15:06:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
switch this.Connector {
|
|
|
|
|
|
case RuleConnectorAnd:
|
|
|
|
|
|
for _, rule := range this.Rules {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
b1, hasCheckRequestBody, err1 := rule.MatchResponse(req, resp)
|
|
|
|
|
|
if hasCheckRequestBody {
|
|
|
|
|
|
hasRequestBody = true
|
|
|
|
|
|
}
|
2020-10-08 15:06:42 +08:00
|
|
|
|
if err1 != nil {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return false, hasRequestBody, err1
|
2020-10-08 15:06:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
if !b1 {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return false, hasRequestBody, nil
|
2020-10-08 15:06:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return true, hasRequestBody, nil
|
2020-10-08 15:06:42 +08:00
|
|
|
|
case RuleConnectorOr:
|
|
|
|
|
|
for _, rule := range this.Rules {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
// 对于OR连接符,只需要判断最先匹配的一条规则中的hasRequestBody即可
|
|
|
|
|
|
b1, hasCheckRequestBody, err1 := rule.MatchResponse(req, resp)
|
2020-10-08 15:06:42 +08:00
|
|
|
|
if err1 != nil {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return false, hasCheckRequestBody, err1
|
2020-10-08 15:06:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
if b1 {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return true, hasCheckRequestBody, nil
|
2020-10-08 15:06:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
default: // same as And
|
|
|
|
|
|
for _, rule := range this.Rules {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
b1, hasCheckRequestBody, err1 := rule.MatchResponse(req, resp)
|
|
|
|
|
|
if hasCheckRequestBody {
|
|
|
|
|
|
hasRequestBody = true
|
|
|
|
|
|
}
|
2020-10-08 15:06:42 +08:00
|
|
|
|
if err1 != nil {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return false, hasRequestBody, err1
|
2020-10-08 15:06:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
if !b1 {
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return false, hasRequestBody, nil
|
2020-10-08 15:06:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-07-16 17:05:37 +08:00
|
|
|
|
return true, hasRequestBody, nil
|
2020-10-08 15:06:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|