ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

开源轻量级Web防火墙SamWaf:编译部署与规则配置避坑指南

开源轻量级Web防火墙SamWaf:编译部署与规则配置避坑指南

简介:SamWaf是一款开源轻量级网站防火墙,面向小公司、工作室及个人站长,主打私有化部署与轻量化防护,不依赖IIS、Nginx等外部服务,数据加密且仅保存在本地,支持Linux、Windows 64位与Arm64平台,开箱即用。该源码包共701个文件,其中Go源码占493个,是整体核心逻辑所在,另有conf配置文件用于防护规则与站点策略,md文档辅助理解项目结构,png/svg图标及示例页面完善了交互界面,压缩包整体约20MB,结构清晰便于二次开发。已有99人学习下载。包内完整提供CC频率访问限制、IP黑名单、URL白名单、自定义拦截界面等核心实现,并集成OWASP CRS规则集、自动申请与续签SSL证书、批量证书到期检测等功能;对希望快速搭建私有WAF或研究Web安全防护的开发者,是一份可直接编译运行、也可灵活改造的参考源码。

1. SamWaf是什么:多数网站用不上重WAF,先试这个轻量级开源方案

一家公司内部有十几二十个业务站点,有 PHP 有 Java,都挂在同一台服务器上,安全组已经挡住了一大半境外 IP,可每天的访问日志里还是能看到大量扫描痕迹。想上 WAF,云厂商的按 QPS 计费一个月算下来心疼,开源的 ModSecurity 又得改 Nginx 编译或者装 OpenResty,规则集一启用,误报比攻击还勤。这种场景里,SamWaf 这类开源轻量级网站防火墙就有了价值:单个 Go 编译出来的二进制文件,带 Web 管理界面,SQLite 存配置和日志,反向代理模式接入现有站点,不侵入业务代码。这篇文章就围绕“源码下载、编译部署、规则配置、避坑”这条线展开,目标是让读者照着走一遍就能把防护跑起来,顺便看清楚这套方案的边界在哪。

2. 原理先行:Go单二进制+SQLite的架构,为什么适合做轻量级WAF

2.1 同是WAF,SamWaf和ModSecurity、云WAF的差异到底在哪

先说定位。ModSecurity 是库级别的 WAF,通常作为 Nginx 或 Apache 的一个模块嵌入运行,规则跑在 Web 服务器进程内部。它的功能上限很高,但部署成本也高:要么重新编译 Nginx,要么维护一套 OpenResty 环境,规则集如 OWASP CRS 一旦打开,对业务请求的兼容性非常敏感,一个小参数误报就要花半天调规则。云 WAF 走的是 DNS 或 CNAME 接入,流量先到云端清洗,回到源站只放行干净请求,防护能力确实强,但对内网部署、测试环境、无公网域名的站点来说,接入链路太长,而且费用按 QPS 或域名数量计算,低频内网站点很不划算。

SamWaf 这类开源轻量级方案选了一条中间路线:独立进程,反向代理模式,收到请求后先做规则匹配,再决定放行、拦截还是验证,然后转发给上游的真实 Web 服务器。它不嵌入 Nginx,也不依赖容器编排,一个二进制加一个配置文件就能跑。Go 编译出的单文件部署方便,SQLite 免去数据库运维负担,管理后台直接在浏览器里操作,这三点加起来,正好命中中小团队“想低成本看到真实攻击日志”的需求。

从架构选型角度看,这个设计比想象中更贴合实际使用场景。Go 标准库的 net/http 反向代理能力足够稳定,处理常规业务站点每秒几百上千的请求没压力;SQLite 虽然不支持高并发写入,但 WAF 日志的量级每天几万到几十万行,SQLite 完全消化得了,而且备份就是拷贝一个文件,不用管 MySQL 或 PostgreSQL 的连接池和权限。轻量不是功能弱,而是把复杂度控制在了“一个人能维护”的范围内。

2.2 反向代理模式的完整流量路径,从建连到响应要看明白

SamWaf 接入后,访客请求的路径会变成这样:客户端先连接到 SamWaf 的监听端口,比如 80 或 443;SamWaf 完成 TLS 终结(如果启用 HTTPS),解析请求行、请求头、请求体;然后经过规则引擎逐条匹配,再检查 IP 黑白名单和 CC 频率计数器;以上都通过后,由反向代理模块把请求转发给 upstream 里配置的真实服务器地址。

响应路径是反向的:上游服务器返回的数据经过 SamWaf 回传给客户端,这个过程中 SamWaf 可以改写响应头的 Server 字段、注入或移除特定 Header。但需要明确一点:响应体内容是透传的,WAF 本身不对 HTML、JS 做语义分析,它只按规则匹配。所以凡是声称“自动学习业务流量”的 WAF,多半还需要额外日志和调参来支撑,纯靠开箱默认规则就能防住所有攻击是不现实的。

不过流量路径里有一个非常重要的细节:转发时要正确传递客户端真实 IP。如果 SamWaf 后端还套了一层 Nginx,或者客户端经过了 CDN,SamWaf 必须依赖 X-Forwarded-For 取真实来源 IP,否则所有 CC 防护计数都会落在 CDN 节点或转发层的内网 IP 上,导致频率限制完全失效。这个坑后面避坑章节还会专门展开。常规做法是,在管理后台把“信任代理层数”或者“真实来源 IP 获取方式”设为对应值,再确保上游链路不会伪造该头。

2.3 模块拆分:规则、IP名单、CC防护、日志都在哪一层处理

一个可维护的轻量级 WAF,功能模块一定得清楚。SamWaf 常规包含下面这几个模块,我按处理顺序排列:

第一层是连接层:负责 TLS、HTTP 协议解析、请求体大小限制。这儿的参数影响最大的是大文件上传场景,默认值如果太小,业务上传图片或附件会被拦。

第二层是 IP 模块:全局黑名单、白名单、地区封禁。黑名单优先直接拒绝,白名单则跳过后面所有规则,包括 CC 防护。

第三层是规则引擎:每条规则包含站点绑定范围、匹配位置、正表达式、动作(拦截/仅记录/放行)。这里做的是正则匹配,不区分攻击类型,SQL 注入、XSS、命令执行都靠正则特征来识别,所以规则质量直接决定误报率。

第四层是 CC 防护:基于 IP+URL 维度做滑动窗口计数,超过阈值就触发封禁或验证码。它依赖 IP 模块给到的真实客户端 IP,也依赖对白名单的放行优先级。

最后一层是日志模块:所有请求的结果、命中的规则、动作、耗时写入 SQLite。日志不仅是事后追查的依据,更是调规则的输入。

理解了这个模块顺序,就会发现一个问题:如果某条请求命中了 IP 白名单,它根本走不到规则引擎。很多人在调试时,改了规则以为生效了,但请求其实早就被白名单放走了。这类逻辑顺序问题,比正则写错更隐蔽。

3. 从源码到跑通:本地编译、初始化配置与接入第一个业务站点

3.1 源码编译:Go环境准备与常见编译报错

标题既然叫“源码下载”,那编译这一步就得真正走一遍。源码包通常是一个压缩包,解压后是标准的 Go 工程结构。先在本机准备好 Go 环境,版本不用最新,但建议 1.20 以上,然后进入工程根目录执行编译。

# 解压源码包,进入工程根目录 cd samwaf # 下载依赖模块并编译 go mod tidy go build -o samwaf-server .

编译完成后,目录里会出现一个samwaf-server可执行文件,这就是整个 WAF 的产物。依赖模块里如果有需要 CGO 编的库——比如 SQLite 驱动——那么系统里得装 gcc,否则编译期间会报“cgo: C compiler not found”之类。找不到 gcc 时,常见做法是:

# CentOS / RHEL 系 yum install -y gcc glibc-devel # Debian / Ubuntu 系 apt install -y build-essential

这里想强调一个判断编译产物是否可移植的技巧:ldd命令能查看二进制依赖了哪些动态库。如果输出里只有 libc 这类系统库,说明这文件可以复制到同架构的服务器上直接运行;如果带着一堆私有路径,说明编译环境的库没打干净。轻量级 WAF 的价值就在于部署简单,所以这一步值得花 30 秒确认一次。

3.2 初始化配置:监听端口、数据库路径、日志级别怎么设

编译完成后先跑一次,首次启动通常会在工作目录生成默认配置文件。结合我对这类 Go 项目的工程理解,常见配置文件长这样:

server: listen: ":80" management: ":8080" read_timeout: 30 write_timeout: 30 database: path: "./data/samwaf.db" log: level: "info" access_log: true admin: username: "admin" password: "admin123"

监听端口listen是 WAF 对外服务的入口,management是管理后台的独立端口,两者分开是惯例,避免管理后台直接暴露在业务链路上。database.path指定 SQLite 文件位置,编译运行前先建好data目录,否则启动可能报错。日志级别level建议调试期间用debug,正式上线改回info,后面日志量的控制跟这里直接相关。

首次登录管理后台后,第一件事就是改掉admin的初始密码。密码用强一点,不然攻击者扫描到 8080 端口,用默认账号登进去,相当于给 WAF 留了后门。每次修改配置里这些敏感参数,我一般习惯同步改掉 SQLite 里存的那份旧值,避免下次重启又从数据库里读回旧配置。

3.3 接入第一个站点:从管理后台添加网站到验证流量真的经过WAF

配置好基础项之后,进入管理后台,找到“网站管理”或“站点管理”页面,添加第一个被防护的站点。这里需要一个参数表来说明关键配置:

参数建议值说明
域名例如 www.example.com用于匹配 Host,多个域名用逗号分隔
上游地址例如 127.0.0.1:8081真实 Web 服务的地址,支持 IP:端口
协议http 或 httpsWAF 到上游之间的协议
启用状态开启只有开启的站点才会进入规则匹配流程
真实IP获取直连 / 一层代理 / 多层代理影响后续所有 IP 维度防护

填完保存后,还需要让流量真正经过 WAF。对外网站点,将 DNS 的 A 记录解析到 SamWaf 所在服务器的 IP,并把 Nginx 原监听端口改到别的端口,只允许 SamWaf 转发过来。对内网测试环境更简单:把调用方的请求地址直接改成http://WAF_IP:80,上游地址不变,就能立刻看到效果。

验证流量路径是否接管成功,最直接的办法是在管理后台看实时访问日志。如果没有入口,就查 SQLite 里的日志表:

sqlite3 ./data/samwaf.db "SELECT site_id, request_uri, action, rule_id FROM request_log ORDER BY id DESC LIMIT 10;"

如果这个表有记录但业务站点的访问日志里没有对应请求,说明流量还是直连源站,没走 WAF,先查 DNS 或本机 hosts,别急着调规则。

4. 规则与防护参数:SQLite表结构、规则语法、CC阈值调参思路

4.1 查看SQLite表结构:WAF配置和数据存在哪张表

SamWaf 用 SQLite 存配置和日志,这意味着可以直接用命令行钻进去看运行状态。连上数据库后,先看有哪些表和字段,这一步是排查所有配置问题的起点。

# 进入 SQLite 交互环境,列出所有表 sqlite3 ./data/samwaf.db ".tables" # 查看规则表的建表语句 sqlite3 ./data/samwaf.db ".schema rule" # 查看最近 10 条访问日志 sqlite3 ./data/samwaf.db "SELECT id, site_id, action, rule_id, url, create_time FROM request_log ORDER BY id DESC LIMIT 10;"

一个典型的规则表,字段会包含:规则名称、绑定站点、规则类型(SQL注入/XSS/命令执行等)、正则表达式、匹配位置、动作、启停状态、优先级。日志表则至少包含:时间、来源IP、站点ID、请求方法、URL、User-Agent、命中的规则ID、动作、响应码。每次排查询这类表,都能定位很多问题:规则是否真的存在、是否绑定了当前站点、动作是否被改成了 monitor。

直接操作 SQLite 的另一个好处是备份方便,把samwaf.db拷走就行。但要注意,不要在服务运行过程中直接用rm删库文件,SQLite 有页缓存,直接删文件可能让正在运行的进程崩掉或丢数据。要备份,用 SQLite 的.backup命令,或者停服后拷贝。

4.2 配置一条SQL注入拦截规则:正则语法、匹配位置、动作含义

规则引擎是这个 WAF 的核心,所有拦截动作都来自正则匹配。在管理后台找到“规则管理”页,新建一条规则,核心参数包含正则表达式、匹配位置(请求URI、请求头、请求体或全部)、动作。

# SQL注入特征:联合查询(常见误报源) (?i)(union\s+select|select\s+.*\bfrom\b|sleep\s*\(\s*\d+\s*\)) # 命令执行特征(含编码绕过场景) (?i)(id\s*;|cat\s+/etc/passwd|\$\(.*whoami.*\))

这里解释几个参数设置的必要性。(?i)表示不区分大小写,SQL 关键字大写小写都能匹配到,不加的话Select就绕过拦截了。\s*用来容忍空格、换行和 Tab,很多扫描器会在关键字之间插入大量空格来绕过简单匹配。sleep函数是时间盲注的常见特征,\d+限定秒数,避免匹配到业务里正常的心理学词语“sleep(休眠)”。

匹配位置的选择直接关系到误报率。请求URI 只匹配 URL 路径和查询参数,误报最低;请求体匹配 POST 内容,适合拦截表单注入和 API 请求体攻击;如果选“全部”,那么请求头里的 User-Agent、Cookie 也会参与匹配,命中几率大增。调这参数时牢记一句话:范围越大,误报越多;先用“仅记录”观察几天,确认命中内容确实是攻击,再切到“拦截”。这也是我踩过坑之后固化的流程。

动作建议: monitor # 仅记录日志,不拦截。上线新规则的第一周务必用这个 deny # 命中即返回403,用于确认无异常后 allow # 放行,一般用于为特殊请求开白 verify # 触发验证码,多用于CC防护而非规则命中

4.3 CC防护参数:阈值、封禁时长、滑动窗口与白名单优先级

CC 防护的逻辑是统计某个 IP 在时间窗口内访问某 URL 的频率,超过阈值就触发临时封禁。参数设置是否合理,直接决定业务正常用户会不会被误杀。下面是典型的参数表:

参数典型值调参方向
时间窗口60 秒窗口越小,越能捕捉短时突增
触发阈值120 次/窗口低于正常峰值,高于恶意突增
封禁时长300 秒封太久会误伤办公网出口IP
放行白名单内网段、回源IP白名单优先级最高,跳过CC和规则

CC 防护的阈值没有通用“最优解”。先看正常业务日志,取访问量最高的 URL 的每分钟请求数,乘以 1.5 到 2 作为阈值,这是我从实际项目里总结的相对可用的经验算法。另外一定要把公司办公网出口 IP 段加进白名单,否则内部员工频繁刷新后台,自己把自己的 IP 封了,属实的翻车现场。

CC 防护还需要注意一个联动点:它是按“IP+URL”计数还是按“IP+站点”计数。按站点维度会好一些,单个用户访问多个 URL 总和超过阈值被误封的概率低。选择时先看选的维度是否适合当前站点路径风格,再看日志里拦截的 URL 是否集中。

5. SamWaf接入避坑:5个我从现象到解决的血泪经验

5.1 现象:接入WAF后,网站登录接口一直返回302重定向

这是一个很典型的反向代理接入事故。现象是:直接访问源站 IP 时登录正常,一切换到 WAF 后,登录提交就 302 跳转回登录页,会话始终建立不起来。

原因是登录依赖 Cookie,而 WAF 转发请求或读取响应时没有处理好 Set-Cookie 头,或者代理层把 Cookie 的 Domain 属性改了;还有一种常见原因,是 WAF 到上游的请求丢失了原始 Host 头,源站做了基于域名的回跳。

解决方法是先查 WAF 转发时是否透传了 Host 和 Cookie 头。常见做法是在管理后台的站点配置里确认“转发host头”和“透传Cookie”开关已打开,或者在调试模式下在 WAF 后面的请求日志里打印完整请求头,对比直连源站的差异。命中的规则动作是 deny 或 redirect 也会导致 302,查看中间日志能立刻定位。

5.2 现象:WebSocket长连接频繁断开,消息推送时断时续

接入 WAF 后,业务方反馈实时消息偶发断开,重连后又能恢复,非常玄学。WebSocket 和普通 HTTP 的差别在于连接建立后要保持长时间双向通信,如果 WAF 的读写超时设得比心跳间隔短,空闲连接会被判定超时并主动关闭。

原因是反向代理默认按普通 HTTP 请求处理超时,没有针对 WebSocket 升级机制或长连接场景单独处理。解决思路有三步走。先在配置里把读写超时调大,比如从 30 秒调到 120 秒以上。再确认 WAF 转发时正确处理了Upgrade和Connection头,这两个头一旦被改写,源站不会把连接当成 WebSocket。最后,对 WebSocket 路径单独加一条放行规则,跳过规则引擎,因为部分正则会对长 URL 或二进制帧产生意外命中。

5.3 现象:管理后台打开很慢,但业务请求没感觉

现象很具体:WAF 管理页面加载要好几秒,查看规则列表或日志时尤其明显,但外部访问业务站点还算流畅。我在实际排查中,最先怀疑的就是 SQLite 文件过大,或者日志表缺少索引。

原因通常是 access_log 表行数持续膨胀,又没有及时清理或归档,页面读取日志做统计时全表扫描。解决方法是定时清理日志表。常见做法是写一个 cron,每天执行一次 DELETE 只保留最近 7 天数据,同时用VACUUM回收文件空间。

-- 只保留最近7天的日志 DELETE FROM request_log WHERE create_time < datetime('now', '-7 day'); -- 收缩数据库文件,顺序执行 VACUUM;

管理后台慢还有一种可能性:管理端口直接暴露在公网,被扫描器频繁请求导致并发变高。把管理端口在网络层面限制成只允许内网 IP 访问,既安全又省资源。

5.4 现象:规则已启用并设成拦截,但攻击测试请求还是200

这个问题在刚接触 WAF 时非常容易让人怀疑人生。现象是:规则在管理后台里已经开启,动作是 deny,但用攻击 Payload 打过去,上游依然返回 200。

原因从三个方向排查。第一,规则没绑定到当前站点,或状态虽然显示开启但站点维度未生效。第二,匹配位置选错了,规则只匹配 URI,而 Payload 放在了请求体。第三,正则本身有绕过空间,比如大小写变化、URL 编码、中间插入额外字符,sleep(1)、sLeep(1)、sleep%281%29都可能绕过不严谨的表达式。

解决方法:先用“仅记录”动作收日志,看 WAF 到底有没有把该请求识别成攻击;确认命中后再切 deny。如果 monitor 日志里根本没有这条请求,说明请求连规则引擎都没走到,问题出在站点绑定或白名单优先级上。白名单放行的 IP 会跳过所有规则,这一点常被忽略。

5.5 现象:源站Nginx日志里出现大量重复请求

接入 WAF 后,去看源站 Nginx 日志,发现同一个请求出现好多次,攻击者没来流量,源站却自己扛了多倍压力。

原因是反向代理在上游无响应或响应超时时自动重试。打过来的恶意请求命中规则后返回403不会转发上游,但如果是 CC 攻击的慢连接请求,WAF 转发部分可能在超时后重试,导致同一请求被发送多次。

解决办法是分开看待两类流量。第一类是 WAF 转发超时设置过短,先把读超时调大到合理值。第二类是游戏或接口服务本身响应慢,属于源站瓶颈,和 WAF 无直接关系,只是暴露了问题。排查时先在源站 Nginx 日志里看这些重复请求的请求头和来源 IP,能识别出是哪个地址段、哪个路径,再决定是否给该路径加更宽松的超时或直接限流。

6. 收尾:用日志反向迭代规则的一个具体方法,避免 WAF 变成误报制造机

SamWaf 这类轻量级 WAF 的规则质量,本质是靠日志“喂”出来的。每次上线新规则,先 monitor 一周成了我的固定习惯。具体操作是:把数据库中 action=monitor 的命中记录导出来,分析哪些规则命中量异常大、哪些命中来自同一 IP 段、哪些命中的是真实攻击特征。

-- 按规则统计命中次数,看哪些规则触发最频繁 SELECT rule_id, COUNT(*) AS hit_count, COUNT(DISTINCT client_ip) AS ip_count FROM request_log WHERE action = 'monitor' GROUP BY rule_id ORDER BY hit_count DESC LIMIT 10; -- 查看异常命中规则的具体样本,判断是误报还是真攻击 SELECT create_time, client_ip, request_uri, request_body FROM request_log WHERE rule_id = '某个规则ID' AND action = 'monitor' ORDER BY id DESC LIMIT 20;

第一条 SQL 能快速找出“命中量最大的规则”。如果某条规则 hit_count 很高但 ip_count 只有个位数,很可能来自扫描器或某个固定出口的爬虫;如果 ip_count 也很高,且 request_uri 五花八门,说明规则正则过宽,命中了大批正常请求,这种规则绝不能在 monitor 阶段就直接切 deny。第二条 SQL 是用来人工复核样本的。

我养成的习惯是:每次新规则至少经历“monitor 7 天 → 人工复核样本 → 切 deny → 观察 3 天”的完整流程。刚上手的时候,我为了图省事,把网上找的规则集一次性全开成 deny,结果第二天业务方就找上门说后台导出功能白屏了,一查原因,规则把包含 base64 的参数全当成命令执行拦了。自那以后,我坚决只用日志驱动来调规则。SamWaf 的价值在于“轻量”和“能看到真实攻击样本”,这两点用好了,比堆一堆不精准的规则要实用得多。希望这个日志反向迭代的方法能帮你在部署少走点弯路。

本文还有配套的精品资源,点击获取

返回列表