mirror of
https://gitee.com/samwaf/SamWaf.git
synced 2026-10-31 02:01:44 +08:00
321 lines
11 KiB
Go
321 lines
11 KiB
Go
package innerbean
|
||
|
||
import (
|
||
// ⚠️ 依赖约束:本包只能依赖 ipset 这个叶子包,绝不能 import SamWaf/utils。
|
||
// utils/rulehelper.go 里有 innerbean.NewRuleFunc(),即 utils → innerbean 已经成立,
|
||
// 反向引用会形成 import cycle 直接编译不过。
|
||
"SamWaf/wafenginecore/ipset"
|
||
"bytes"
|
||
"net"
|
||
"strings"
|
||
)
|
||
|
||
// RuleFunc 规则函数助手,提供各种通用的规则判断函数
|
||
// 使用方式: 在DataContext中注册为 "RF",然后在规则中调用
|
||
// 例如: RF.IPInRange(MF.SRC_IP, "192.168.0.0", "192.168.1.254")
|
||
type RuleFunc struct{}
|
||
|
||
// NewRuleFunc 创建规则函数助手实例
|
||
func NewRuleFunc() *RuleFunc {
|
||
return &RuleFunc{}
|
||
}
|
||
|
||
// ================== IP 相关函数 ==================
|
||
|
||
// IPInRange 判断IP是否在指定范围内(包含起始和结束IP)
|
||
// ip: 要检查的IP地址
|
||
// startIP: 起始IP地址
|
||
// endIP: 结束IP地址
|
||
// 返回: true表示IP在范围内,false表示不在
|
||
// 使用示例: RF.IPInRange(MF.SRC_IP, "172.16.0.0", "172.20.255.254")
|
||
func (rf *RuleFunc) IPInRange(ip, startIP, endIP string) bool {
|
||
parsedIP := net.ParseIP(ip)
|
||
parsedStart := net.ParseIP(startIP)
|
||
parsedEnd := net.ParseIP(endIP)
|
||
|
||
if parsedIP == nil || parsedStart == nil || parsedEnd == nil {
|
||
return false
|
||
}
|
||
|
||
// 确保都是相同格式(IPv4或IPv6)
|
||
parsedIP = parsedIP.To16()
|
||
parsedStart = parsedStart.To16()
|
||
parsedEnd = parsedEnd.To16()
|
||
|
||
// IP >= startIP && IP <= endIP
|
||
return bytes.Compare(parsedIP, parsedStart) >= 0 && bytes.Compare(parsedIP, parsedEnd) <= 0
|
||
}
|
||
|
||
// IPInCIDR 判断IP是否在指定的CIDR网段内
|
||
// ip: 要检查的IP地址
|
||
// cidr: CIDR格式的网段,如 "192.168.1.0/24"
|
||
// 返回: true表示IP在网段内,false表示不在
|
||
// 使用示例: RF.IPInCIDR(MF.SRC_IP, "192.168.1.0/24")
|
||
func (rf *RuleFunc) IPInCIDR(ip, cidr string) bool {
|
||
parsedIP := net.ParseIP(ip)
|
||
if parsedIP == nil {
|
||
return false
|
||
}
|
||
|
||
_, ipNet, err := net.ParseCIDR(cidr)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
|
||
return ipNet.Contains(parsedIP)
|
||
}
|
||
|
||
// IPMatch 判断IP是否匹配一个「IP模式」,一次覆盖全部写法
|
||
// ip: 要检查的IP地址
|
||
// pattern: IP模式,支持以下写法
|
||
//
|
||
// 单IP: RF.IPMatch(MF.SRC_IP, "1.2.3.4")
|
||
// CIDR网段: RF.IPMatch(MF.SRC_IP, "10.0.0.0/8")
|
||
// IPv4通配: RF.IPMatch(MF.SRC_IP, "10.10.*.*") 按八位组,* 可在任意位置(10.*.1.* 也合法)
|
||
// IPv6通配: RF.IPMatch(MF.SRC_IP, "2001:db8:*:*:*:*:*:*") 必须写满8段,不能与 :: 混用
|
||
// 闭区间: RF.IPMatch(MF.SRC_IP, "1.2.3.4-1.2.3.99")
|
||
//
|
||
// 返回: true表示匹配。IP 或模式非法时返回 false。
|
||
// 模式解析结果带缓存,每请求求值不会重复解析字符串。
|
||
func (rf *RuleFunc) IPMatch(ip, pattern string) bool {
|
||
return ipset.MatchPatternStrCached(ip, pattern)
|
||
}
|
||
|
||
// IPInGroup 判断IP是否在指定的「IP组」内
|
||
// ip: 要检查的IP地址
|
||
// groupCodeOrName: IP组的短码或名称(在 网站防护-IP组 页面维护)
|
||
// 返回: true表示IP在该组内;组不存在时返回 false
|
||
// 使用示例: RF.IPInGroup(MF.SRC_IP, "办公室出口")
|
||
//
|
||
// IP组是可跨站点复用的IP集合,组内容变更后对所有引用方(含本规则)立即生效,无需重启。
|
||
func (rf *RuleFunc) IPInGroup(ip, groupCodeOrName string) bool {
|
||
return ipset.LookupGroupMatcher(groupCodeOrName).Contains(net.ParseIP(strings.TrimSpace(ip)))
|
||
}
|
||
|
||
// IPInRanges 判断IP是否在多个范围中的任意一个(类似SQL的IN操作)
|
||
// ip: 要检查的IP地址
|
||
// ranges: IP范围列表,支持 IPMatch 的全部写法(单IP / CIDR / 通配符 / "startIP-endIP" 区间)
|
||
// 返回: true表示IP在任意一个范围内,false表示都不在
|
||
// 使用示例: RF.IPInRanges(MF.SRC_IP, "172.16.0.0-172.20.255.254", "192.168.0.0/24", "10.10.*.*")
|
||
//
|
||
// 行为变化说明:本函数原先对「非 CIDR 也非区间」的项做纯字符串相等比较,
|
||
// 现在改为 IP 语义比较。影响是 "1.2.3.04"、"::ffff:1.2.3.4" 这类等价写法现在能正确命中,
|
||
// 而把非 IP 字符串当参数传入时不再可能命中(原先只有 SRC_IP 恰好等于该串时为 true)。
|
||
func (rf *RuleFunc) IPInRanges(ip string, ranges ...string) bool {
|
||
for _, r := range ranges {
|
||
if ipset.MatchPatternStrCached(ip, r) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// IPEquals 判断两个IP是否相等(支持IPv4和IPv6的标准化比较)
|
||
// ip1: 第一个IP地址
|
||
// ip2: 第二个IP地址
|
||
// 返回: true表示相等,false表示不相等
|
||
func (rf *RuleFunc) IPEquals(ip1, ip2 string) bool {
|
||
parsedIP1 := net.ParseIP(ip1)
|
||
parsedIP2 := net.ParseIP(ip2)
|
||
|
||
if parsedIP1 == nil || parsedIP2 == nil {
|
||
return ip1 == ip2 // 如果无法解析,直接字符串比较
|
||
}
|
||
|
||
return parsedIP1.Equal(parsedIP2)
|
||
}
|
||
|
||
// ================== 字符串相关函数 ==================
|
||
|
||
// In 判断值是否在给定的列表中(类似SQL的IN操作)
|
||
// value: 要检查的值
|
||
// list: 可能的值列表
|
||
// 返回: true表示值在列表中,false表示不在
|
||
// 使用示例: RF.In(MF.METHOD, "GET", "POST", "PUT")
|
||
func (rf *RuleFunc) In(value string, list ...string) bool {
|
||
for _, item := range list {
|
||
if value == item {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// InIgnoreCase 判断值是否在给定的列表中(忽略大小写,类似SQL的IN操作)
|
||
// value: 要检查的值
|
||
// list: 可能的值列表
|
||
// 返回: true表示值在列表中,false表示不在
|
||
// 使用示例: RF.InIgnoreCase(MF.METHOD, "get", "post", "put")
|
||
func (rf *RuleFunc) InIgnoreCase(value string, list ...string) bool {
|
||
valueLower := strings.ToLower(value)
|
||
for _, item := range list {
|
||
if valueLower == strings.ToLower(item) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// ContainsAny 判断字符串是否包含给定列表中的任意一个
|
||
// value: 要检查的字符串
|
||
// list: 要搜索的子串列表
|
||
// 返回: true表示包含至少一个,false表示一个都不包含
|
||
// 使用示例: RF.ContainsAny(MF.USER_AGENT, "bot", "spider", "crawler")
|
||
func (rf *RuleFunc) ContainsAny(value string, list ...string) bool {
|
||
for _, item := range list {
|
||
if strings.Contains(value, item) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// ContainsAnyIgnoreCase 判断字符串是否包含给定列表中的任意一个(忽略大小写)
|
||
// value: 要检查的字符串
|
||
// list: 要搜索的子串列表
|
||
// 返回: true表示包含至少一个,false表示一个都不包含
|
||
// 使用示例: RF.ContainsAnyIgnoreCase(MF.USER_AGENT, "Bot", "Spider", "Crawler")
|
||
func (rf *RuleFunc) ContainsAnyIgnoreCase(value string, list ...string) bool {
|
||
valueLower := strings.ToLower(value)
|
||
for _, item := range list {
|
||
if strings.Contains(valueLower, strings.ToLower(item)) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// ContainsAll 判断字符串是否包含给定列表中的全部
|
||
// value: 要检查的字符串
|
||
// list: 要搜索的子串列表
|
||
// 返回: true表示包含全部,false表示至少缺少一个
|
||
// 使用示例: RF.ContainsAll(MF.URL, "/admin", ".php")
|
||
func (rf *RuleFunc) ContainsAll(value string, list ...string) bool {
|
||
for _, item := range list {
|
||
if !strings.Contains(value, item) {
|
||
return false
|
||
}
|
||
}
|
||
return len(list) > 0
|
||
}
|
||
|
||
// StartsWithAny 判断字符串是否以给定列表中的任意一个开头
|
||
// value: 要检查的字符串
|
||
// list: 可能的前缀列表
|
||
// 返回: true表示匹配至少一个前缀,false表示一个都不匹配
|
||
// 使用示例: RF.StartsWithAny(MF.URL, "/admin", "/api", "/manage")
|
||
func (rf *RuleFunc) StartsWithAny(value string, list ...string) bool {
|
||
for _, item := range list {
|
||
if strings.HasPrefix(value, item) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// EndsWithAny 判断字符串是否以给定列表中的任意一个结尾
|
||
// value: 要检查的字符串
|
||
// list: 可能的后缀列表
|
||
// 返回: true表示匹配至少一个后缀,false表示一个都不匹配
|
||
// 使用示例: RF.EndsWithAny(MF.URL, ".php", ".asp", ".jsp")
|
||
func (rf *RuleFunc) EndsWithAny(value string, list ...string) bool {
|
||
for _, item := range list {
|
||
if strings.HasSuffix(value, item) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// ================== 数值比较函数 ==================
|
||
|
||
// IntInRange 判断整数是否在指定范围内(包含边界)
|
||
// value: 要检查的整数
|
||
// min: 最小值
|
||
// max: 最大值
|
||
// 返回: true表示在范围内,false表示不在
|
||
// 使用示例: RF.IntInRange(MF.STATUS_CODE, 400, 499)
|
||
func (rf *RuleFunc) IntInRange(value, min, max int64) bool {
|
||
return value >= min && value <= max
|
||
}
|
||
|
||
// IntIn 判断整数是否在给定的列表中
|
||
// value: 要检查的整数
|
||
// list: 可能的值列表
|
||
// 返回: true表示在列表中,false表示不在
|
||
// 使用示例: RF.IntIn(MF.STATUS_CODE, 200, 201, 204)
|
||
func (rf *RuleFunc) IntIn(value int64, list ...int64) bool {
|
||
for _, item := range list {
|
||
if value == item {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// ================== 逻辑辅助函数 ==================
|
||
|
||
// Not 逻辑非
|
||
// value: 布尔值
|
||
// 返回: 取反后的值
|
||
// 使用示例: RF.Not(RF.IPInRange(MF.SRC_IP, "192.168.0.0", "192.168.1.254"))
|
||
func (rf *RuleFunc) Not(value bool) bool {
|
||
return !value
|
||
}
|
||
|
||
// IsEmpty 判断字符串是否为空
|
||
// value: 要检查的字符串
|
||
// 返回: true表示为空,false表示不为空
|
||
func (rf *RuleFunc) IsEmpty(value string) bool {
|
||
return value == ""
|
||
}
|
||
|
||
// IsNotEmpty 判断字符串是否不为空
|
||
// value: 要检查的字符串
|
||
// 返回: true表示不为空,false表示为空
|
||
func (rf *RuleFunc) IsNotEmpty(value string) bool {
|
||
return value != ""
|
||
}
|
||
|
||
// LengthBetween 判断字符串长度是否在指定范围内
|
||
// value: 要检查的字符串
|
||
// min: 最小长度
|
||
// max: 最大长度
|
||
// 返回: true表示长度在范围内,false表示不在
|
||
func (rf *RuleFunc) LengthBetween(value string, min, max int64) bool {
|
||
length := int64(len(value))
|
||
return length >= min && length <= max
|
||
}
|
||
|
||
// ================== 规则动作标记函数 ==================
|
||
//
|
||
// 这几个方法本身不做任何事情(no-op),只是让规则的 then 块能声明"命中之后干什么"。
|
||
// 引擎走的是 FetchMatchingRules(只求值 when,不执行 then),动作是在规则加载阶段
|
||
// 由 utils.ExtractRuleActions 从规则文本里解析出来的,方法体永远不会被调用。
|
||
// 之所以还要定义成真实方法,是因为 GRL 需要能编译通过。
|
||
//
|
||
// 未声明任何动作的规则默认为拦截(Deny),保证老规则行为不变。
|
||
|
||
// Deny 命中后拦截请求(默认动作,可不写)
|
||
// 使用示例: then RF.Deny();
|
||
func (rf *RuleFunc) Deny() {}
|
||
|
||
// Log 命中后仅记录,不拦截,继续执行后续检测
|
||
// 使用示例: then RF.Log();
|
||
func (rf *RuleFunc) Log() {}
|
||
|
||
// Allow 命中后放行(不被自定义规则拦截),可选跳过指定的后续检测模块
|
||
// modules: 要跳过的检测模块名,如 "CC"、"AI"、"SQLI";传 "ALL" 表示跳过全部
|
||
// 使用示例: then RF.Allow(); 仅本环节放行,后续检测照常
|
||
// 使用示例: then RF.Allow("CC", "AI"); 放行并跳过CC和AI检测
|
||
func (rf *RuleFunc) Allow(modules ...string) {}
|
||
|
||
// AllowAll 命中后放行并跳过后续所有检测,直通后端(等价于 RF.Allow("ALL"))
|
||
// 使用示例: then RF.AllowAll();
|
||
func (rf *RuleFunc) AllowAll() {}
|
||
|
||
// Ban 命中后拦截当前请求,并将客户端IP在该站点封禁指定分钟数
|
||
// minutes: 封禁时长(分钟),必须是正整数
|
||
// 使用示例: then RF.Ban(10);
|
||
func (rf *RuleFunc) Ban(minutes int64) {}
|