Files

321 lines
11 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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) {}