2026-08-10 17:17:02
其实我一直有在关注Stalwart Mail这个项目,早在2年多以前我就写过一篇部署的文章,当时的Stalwart Mail还没有Web UI,所有操作都只能通过CLI完成。我还有一台服务器一直在使用Stalwart Mail,只不过这台服务器运行的版本比较旧了:
0.14还是升级过的,如果我没记错的话,我应该是从0.11升级上来的。时过境迁,这个项目也从最初的几百个star变成了如今的破万star,我觉得我有资格谈一谈这个程序目前面临的一些问题。
用了这么多年这个邮件服务器,让我觉得最操蛋的地方就是每次大版本更新简直就是灾难,隔个版本就来一大堆破坏性的更改,每次更新都得跟着官方release里面的步骤小心翼翼一步步来,没有哪一次大版本更新是能够轻松完成的。在我的印象里,像这种打包成一个二进制文件或者用一个docker容器就能跑起来的程序,更新不就是pull个新的image然后up就完事了么,但是Stalwart Mail很任性,你要想这么简单的升个级那等着你的绝对是boom,这也就是为什么我之前这台服务器一直运行的是0.14版本,我已经不打算把它升级到0.15然后再从0.15升级到目前最新的0.16了。
现在好就好在作者意识到这个问题了,且之前有说过0.16是个很关键的版本,在0.16后应该不会再引入大规模的破坏性更改,且在今年晚些时候会发布具有里程碑意义的1.0版本:《立即升级到0.16,或者等待1.0版本》
然后就是这个项目的定位我觉得有点问题,作者似乎在开发这块有点割裂,想让Stalwart Mail成为一个新手小白都能快速上手的All-in-One邮件服务器,但同时又想兼顾具备专业知识和有能力的人去折腾,这就导致了两个问题:
1.官方文档写的摸棱两可,不清不楚,明明给个示例配置就完事儿的事情,非要在那里车轱辘话说一大堆,到头来新手看了半天文档还是不知道怎么操作,老鸟又没有看的必要。。我认为一篇优秀的文档应该是理论与实践并行,缺一不可,如果只能2选1,那我宁愿选择实践。
2.Web UI对每个功能的可配置性我觉得有点过于细致了,作者似乎想把有关邮件服务器内所有能配置的东西都给你在Web UI上列出来,让你可以看到每一个细节,修改每一个参数。这看上去很美好,但是新手一看到这个Web UI头皮都是麻的,完全看不懂这些设置项有什么用,该怎么配置。别说新手了,就算是具备一定专业知识的人可能都不能完全了解这些设置的作用,再加上文档极其抽象,第一次接触到这个Web UI的人就只会感觉到复杂与繁琐。
当然这些只是我的猜测,作者真实的开发意愿我无从得知,我也无意干预作者对项目开发的路线,我只是想说如果能在这之间找到一个平衡点那当然是最好的,如果找不到平衡点那就专攻一个点去做就行了。
好就好在现在的0.16版本似乎在朝着这个“平衡”的方向发展,在用户首次登录的时候会有一个“配置向导”,只要你按照这个向导去完成配置,那么剩下的那些“让人看不懂”的配置基本就可以不用管了,你的邮件服务器也能正常工作。对于喜欢折腾或者有更多需求的人可以在向导完成后去修改那些更高级的配置。
除了上述我说的这些问题外,还有一个我有点担心的问题是,作者能否保持初心不去恰烂钱,目前来看似乎有点这方面的趋势,小声bb:Masked Emails功能。但愿作者后续不会把关键且核心的功能加到企业版本付费才能使用。
让我们正式开始部署,本文尽量用大白话把一些容易出问题的地方给讲清楚,同时为方便理解,本文不对敏感内容脱敏。准备工作,一个域名(本文示例:ohsb.cc)接入到Cloudflare并做好如下DNS解析:
| 名称 | 类型 | 内容 |
|---|---|---|
| A | 152.53.89.75 | |
| bulwark | A | 152.53.89.75 |
我们不在这里配置MX、SPF、DKIM、DMARC等DNS记录,这些记录稍后统一交给Stalwart自动管理,由Stalwart自行添加。除了这些正向DNS记录外,你的服务器最好支持设置PTR/rDNS,这里我以Netcup的VPS为例,值与正向DNS中的A记录匹配:
验证PTR/rDNS是否生效:
dig -x 152.53.89.75 +short
安装NGINX、CertBot、Docker:
apt update
apt install curl nginx python3-certbot-nginx
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
创建目录和compose文件:
mkdir -p /opt/stalwart-mail && cd /opt/stalwart-mail && nano docker-compose.yml
写入如下内容:
services:
stalwart:
image: stalwartlabs/stalwart:v0.16
container_name: stalwart
restart: unless-stopped
ports:
- "8443:443"
- "8080:8080"
- "25:25"
# - "587:587"
- "465:465"
# - "143:143"
- "993:993"
# - "110:110"
# - "995:995"
# - "4190:4190"
volumes:
- ./stalwart-etc:/etc/stalwart
- ./stalwart-data:/var/lib/stalwart
bulwark:
image: ghcr.io/bulwarkmail/webmail:latest
container_name: bulwark
restart: unless-stopped
environment:
BULWARK_TELEMETRY: off
ports:
- "3008:3000"
depends_on:
- stalwart
volumes:
- bulwark-settings:/app/data/settings
- bulwark-config:/app/data/admin
- bulwark-state:/app/data/admin-state
healthcheck:
test:
[
"CMD",
"wget",
"--no-verbose",
"--tries=1",
"--spider",
"http://127.0.0.1:3000/api/health",
]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
volumes:
bulwark-settings:
bulwark-config:
bulwark-state:
1.由于stalwart容器使用的是bind-mounts,且该镜像以非特权用户(UID2000)身份运行,所以主机目录的所有者必须是UID2000,我们就自行创建目录并修改所有者:
mkdir stalwart-data stalwart-etc && chown 2000:2000 stalwart-data/ stalwart-etc/
2.根据官方的安全实践,禁用了587 / 143 / 110 / 995 / 4190这些非必要且可能有安全问题的端口,如果你有需要可以取消相应的注释。
3.考虑到stalwart后续的大版本更新可能会比较麻烦,官方也不建议将镜像的tag改成latest,所以这里固定使用v0.16,避免导致后续升级时的混乱,例如当前使用的latest指向的是0.16,但这期间官方连发了2个大版本,最新的latest已经指向0.18了,那现在本地的这个0.16去拉最新的0.18升级肯定是要出问题的。
4.官方文档关于NGINX反向代理的示例配置是使用TCP直通,既NGINX不终止TLS,直接将TLS会话原封不动地传递给stalwart。我不打算使用这种方式,我觉得这种方式有点脱裤子放屁的意思,谁没事会在一台服务器上面运行两个甚至多个邮件服务器?完全没有必要通过NGINX去转发25 / 465 / 993的流量嘛,且一旦NGINX使用stream模块监听443端口后,443端口会被stream模块独占,那么http模块就不能再监听443端口了,如果你的服务器上还运行着其它网站,那这些网站都将无法访问。除了这些以外,你还得去配置Proxy Protocol,不然stalwart拿不到客户端真实IP一大堆的功能都会出问题。这简直是一个亏损最大化的反代方式。。。如果要我用这种方式,那我不如单独拿一台服务器出来只跑stalwart得了。。。
考虑再三,我决定使用一个折中的方案,既我们只使用NGINX反向代理stalwart的HTTP端口(8080),其它的邮件服务端口,如25 / 465 / 993这些全部由stalwart自身处理。这种方案唯一的缺点是你需要维护两套TLS证书,既NGINX需要一套证书,stalwart自身也需要一套证书,但好在证书申请和配置都比较简单,NGINX有certbot,stalwart则可以通过ACME DNS 01全自动完成。这种方案也不会导致stalwart的功能有异常或者缺失,因为stalwart 8080端口提供的服务(OAuth、OIDC、JMAP、Autoconfig等)与stalwart 443端口提供的服务完全一致。
启动:
docker compose up -d
查看临时的管理员账号和密码:
docker logs stalwart 2>&1 | grep -A8 'bootstrap mode'
注意这个管理员账号和密码仅用于引导程序运行设置向导,等向导完成后会重新配置一个永久的管理员帐户:
配置NGINX反向代理,先配置stalwart的反代:
nano /etc/nginx/sites-available/stalwart
写入如下内容:
server {
listen 80;
server_name mail.ohsb.cc autoconfig.ohsb.cc autodiscover.ohsb.cc ua-auto-config.ohsb.cc mta-sts.ohsb.cc;
client_max_body_size 0;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 支持 JMAP Push (WebSocket)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
1.注意server_name不仅仅要有mail.ohsb.cc,自动配置、自动发现以及mta-sts这些功能也需要配置单独的主机名。
2.JMAP依赖WebSocket,务必启用。
启用站点:
ln -s /etc/nginx/sites-available/stalwart /etc/nginx/sites-enabled/stalwart
继续配置bulwark webmail的反代:
nano /etc/nginx/sites-available/bulwark
写入如下内容:
server {
listen 80;
server_name bulwark.ohsb.cc;
client_max_body_size 0;
location / {
proxy_pass http://127.0.0.1:3008;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
启用站点:
ln -s /etc/nginx/sites-available/bulwark /etc/nginx/sites-enabled/bulwark
签发证书:
certbot --nginx
访问https://mail.ohsb.cc/admin打开Web UI,如果你发现访问https://mail.ohsb.cc/admin报错502,那么暂时先使用http://152.53.89.75/admin,这大概率是因为stalwart没有获取到客户端的真实IP,将所有传入的请求都识别为Docker桥接网络的网关IP,然后因为有公网扫描器执行的一些探测扫描被stalwart识别到了,stalwart就把Docker桥接网络的网关IP给ban了导致的,这个问题稍后等安装向导完成后再来解决。
使用临时管理员账号登录开始安装向导,配置主机名和邮箱域名:
配置存储,直接用默认的RocksDB就好了,单机部署最佳选择:
账户目录,直接用默认的内部目录即可:
日志目标,这里默认的是文件形式,且保存路径在/var/log/stalwart:
由于我们之前没有挂载这个目录,建议将这里的日志输出模式改为console,后续可以通过如下命令来查看日志:
docker compose logs
如果硬要以文件形式来保存日志,那么应该在compose文件内增加如下内容:
volumes:
- ./stalwart-etc:/etc/stalwart
- ./stalwart-data:/var/lib/stalwart
- ./stalwart-log:/var/log/stalwart
并主动创建目录及修改所有者:
mkdir stalwart-log && chown 2000:2000 stalwart-log
然后就到了非常关键的一步了:自动DNS管理。将Cloudflare的API Token输入到这里,即可让stalwart自动为你创建DNS记录,例如MX、SPF、DKIM、DMARC等。注意MX记录默认是没有为你创建的,需要后续手动配置,我稍后会详细说明:
向导完成后,会重新打印一个管理员账号和密码,这就是永久的管理员账号了,请妥善保管:
重启容器以使新配置生效:
docker compose down
docker compose up -d
再次登录到Web UI,让我们继续完善设置。向导虽然为我们配置了大多数设置,但还有以下步骤缺一不可,请务必全部按照操作完成。
1.找到HTTP Server页面,启用Obtain remote IP from Forwarded header,这将确保stalwart能够获取到客户端的真实IP:
2.找到HTTP Security页面,启用Permissive CORS policy,这将解决bulwark webmail无法正常登录,报错CORS禁止的问题:
3.如果你之前访问https://mail.ohsb.cc/admin报错502,那么请找到Blocked IP addresses页面,不出意外的话,这里会列出你的Docker桥接网络的网关IP:
把这个IP从封禁列表删除,然后找到Actions页面,点击Blocked IPs list按钮重载配置,使其生效:
之前已经启用了Obtain remote IP from Forwarded header,所以stalwart不会再将所有通过NGINX代理的IP都识别为Docker桥接网络的网关IP,这个问题也就解决了,且自动封禁的功能也将正常工作。
4.我们使用stalwart自动管理DNS记录,所以找到Domains页面,点击你的域名进入详情页:
找到DNS Management内的Record Types,可以看到这里默认没有MX记录:
勾选MX记录,保存设置:
找到Actions,重载服务器配置使其生效,请注意,大多数配置修改后都需要这一步才能使新的配置生效:
最后我们还需要找到Tasks页面,创建一个新的任务:
Task type选择Perform DNS management for a domain,Record Types选择MX records,Due设置为你当前时间的后1分钟:
5.在你的个人账户设置页面,创建一个名为Archive的文件夹,以适配bulwark webmail及其它邮件客户端的归档功能:
到这里stalwart就配置好了,接下来配置bulwark webmail,在你首次访问bulwark webmail的时候会让你输入一个安装token:
使用如下命令查看bulwark容器日志以获取安装token:
docker compose logs -f bulwark
bulwark webmail是一个基于JMAP协议的客户端,这里需要配置你的JMAP server URL:
测试一下邮件发送,能发保底10分:
能收:
文章篇幅有限,更多高级功能如PGP加密,反垃圾邮件配置,这些内容另外单独用一篇文章说明,未完待续。。。
2026-08-04 10:53:44
Wordfence CLI是一款开源、高性能的安全扫描器,使用Python编写,能够快速扫描文件系统,检测PHP及其他恶意软件和WordPress漏洞。CLI支持并行运行、定时执行,可以通过管道接收输入,也可以将输出通过管道传递给其他命令。
官方提供了多种安装方式,包括pip包、deb包、二进制文件等等,我这里选择二进制文件安装,因为Debian现在不允许直接用pip全局安装pip包了,你要装一个包还得先建个venv,特别麻烦。deb软件包在Debian 13有依赖问题,这坑我已经踩过了:
apt install ./wordfence.deb
Note, selecting 'wordfence' instead of './wordfence.deb'
Solving dependencies... Error!
Some packages could not be installed. This may mean that you have
requested an impossible situation or if you are using the unstable
distribution that some required packages have not yet been created
or been moved out of Incoming.
The following information may help to resolve the situation:
Unsatisfied dependencies:
wordfence : Depends: libpcre3 but it is not installable
Error: Unable to correct problems, you have held broken packages.
Error: The following information from --solver 3.0 may provide additional context:
Unable to satisfy dependencies. Reached two conflicting decisions:
1. wordfence:amd64=5.0.5rc1 is selected for install
2. wordfence:amd64 Depends libpcre3
but none of the choices are installable:
[no choices]
所以二进制是最舒服的,下载解压就能用了:
wget https://github.com/wordfence/wordfence-cli/releases/download/v5.0.4/wordfence_amd64.tar.gz
tar -xvf wordfence_amd64.tar.gz
试试看能不能运行,首次运行应该会提示让你注册一个免费的许可证,以及生成默认的配置文件:
./wordfence version
同时看一下扫描引擎的支持情况,现在PCRE和Vectorscan应该都显示的是No:
Wordfence CLI 5.0.4
PCRE Supported: No
Vectorscan Supported: No
我找了半天硬是找不到Debian 13的这个PCRE3的依赖包名叫啥,应该和之前安装deb包的那个依赖问题一样,索性我干脆放弃PCRE了,直接使用Vectorscan。
实际上也是Vectorscan更好用,Vectorscan的扫描速度比PCRE快几十倍,所以支不支持PCRE已经不重要了。在Debian 13安装这个包即可:
apt install libvectorscan5
我还有一台Debian 11的机器也需要装,发现Debian 11根本没有这个libvectorscan5,取而代之可以安装libhyperscan5,因为Vectorscan是Hyperscan的一个分支,并保持着兼容的API:
apt install libhyperscan5
Wordfence CLI目前同时支持这两种技术,但如果Vectorscan的API与Hyperscan的API出现差异,这种情况可能会随时间而改变,到时候就看具体情况怎么处理了。再次检查一下扫描引擎的支持情况,如果正常应该显示:
Wordfence CLI 5.0.4
PCRE Supported: No
Vectorscan Supported: Yes - Version: 5.4.2 2024-12-30 (API Version: 5.4.2)
默认情况下,CLI将使用PCRE进行扫描。要将CLI配置为使用Vectorscan,可以使用以下命令行参数:
./wordfence malware-scan --match-engine=vectorscan /var/www/wordpress
但每次都加上--match-engine=vectorscan使用起来不太方便,可以编辑配置文件:
nano ~/.config/wordfence/wordfence-cli.ini
在[MALWARE_SCAN]下面写入:
[MALWARE_SCAN]
match_engine=vectorscan
开始扫描:
./wordfence malware-scan --output-format csv --output-path scan_report.csv /var/www/wordpress
默认情况下Wordfence CLI不会扫描全部文件,例如图片之类的文件会跳过,如果你想扫描全部文件请使用:
./wordfence malware-scan --include-all-files --output-format csv --output-path scan_report.csv /var/www/wordpress
扫描结果会保存至scan_report.csv:
cat scan_report.csv
测试了一下,可以扫到后门:
/tmp/wf_test/test_webshell.php,11121,Backdoor:PHP/short.assert.11121,Short RCE,
如果没有检测到任何恶意程序,则scan_report.csv内容为空,你看到文件内没有内容应该感到高兴而不应该认为是Wordfence CLI没有正常工作。也可能是人家的后门太牛逼,Wordfence CLI扫不出来。
Wordfence CLI虽然是专为WordPress打造的,但请注意Wordfence CLI也可以扫描其它网站程序的PHP后门和恶意程序,并不是说你的网站程序不是WordPress就不能用Wordfence CLI。只是Wordfence CLI针对WordPress有更多的功能,比如扫描CVE漏洞,自动修复被篡改的文件等。
Wordfence CLI其实是一款收费软件,只是官方同时提供了免费版本,免费版本与收费版本的区别在于免费版本的数据库比收费版本慢了30天。如果你使用Wordfence CLI扫描后还不太放心,可以再试试YARA。
YARA是一款旨在(但不限于)帮助恶意软件研究人员识别和分类恶意软件样本的工具。YARA被誉为“恶意软件研究人员的瑞士军刀”,由VirusTotal的安全团队开发和维护。这么说吧,在网络安全行业里,几乎所有主流的杀毒软件和安全大厂(如卡巴斯基、赛门铁克等等)都在广泛使用YARA。
安装YARA:
apt install yara
YARA只是一个检测引擎,规则还需要自己写或者用别人现成的,Github上有很多,这里我使用signature-base。我们把规则下载放到目录内:
mkdir yara-rule && cd yara-rule/
wget https://raw.githubusercontent.com/Neo23x0/signature-base/master/yara/gen_webshells.yar
然后就可以使用这个规则来扫描了:
yara -r yara-rule/gen_webshells.yar /tmp/wf_test/
如果目录内有多个规则,可以用通配符:
yara -r yara-rule/*.yar /tmp/wf_test/
测试了一下,也可以检测到:
EXT_WEBSHELL_PHP_Generic /tmp/wf_test//test_webshell.php
WEBSHELL_PHP_Base64_Encoded_Payloads /tmp/wf_test//test_webshell.php
WEBSHELL_PHP_Gzinflated /tmp/wf_test//test_webshell.php
WEBSHELL_PHP_OBFUSC_3 /tmp/wf_test//test_webshell.php
WEBSHELL_PHP_Dynamic_Big /tmp/wf_test//test_webshell.php
补充点内容,如果你的站点使用WordPress,可以使用WP-CLI校验Core文件的官方哈希值:
sudo -u www-data wp core verify-checksums --path=/var/www/wordpress
一旦发现某个文件的指纹对不上就说明:文件被黑客修改、注入了恶意代码。请注意WP-CLI默认只拿英文版本做比对,如果你的WordPress是简中版本,可能会有个别文件误报,为了让它精准比对简中版本,请加上locale参数:
sudo -u www-data wp core verify-checksums --path=/var/www/wordpress --locale=zh_CN
WP-CLI还可以校验主题、插件的哈希值,但是仅支持WordPress官方市场安装的主题、插件。考虑到大部分站点都会从其它地方安装主题和插件,所以校验的价值就不太高了。
南无阿弥陀佛,佛祖保佑,希望自己永远也不会用到这些工具!!
2026-08-03 21:55:09
我之前写过一篇文章《记一次流媒体MPEG-DASH DRM解密过程(Widevine)》后来有很多人跟我说WVGuesserExtension这个浏览器扩展获取不到MGSTAGE和DMMTV的Key了,但是我实际测试下来都是没问题的啊,至少MGSTAGE我能肯定目前是没问题的。
另外我在DMM购买的视频也能获取到Key,而且DMM的那些假素人视频根本都没有用Widevine,用的是ClearKey,这个扩展能自动算出来ClearKey的KID:KEY,但是由于我只买这些素人视频,DMM还有其它的服务,比如包月的流媒体服务,这个我就没测试。反正我也不知道他们是怎么操作的,按道理来说都是能用的。
但是u1s1这个扩展确实很久没更新了,而且已经不兼容现在的新版Chrome浏览器了,现在安装的话直接就提示不支持了:
这是由于很多老的扩展都基于Manifest V2,谷歌为了限制扩展对浏览器底层权限的滥用采用了新标准:Manifest V3。但如果你硬要用的话也不是不行,换个火狐或者其它套壳旧版本的浏览器还是能用的,而且这个扩展确实很方便。。
然后还有就是总有人想要我分享WVD(L3 CDM)文件,讲道理这个我是肯定不可能分享的,这是我自己手机提取出来的,如果分享出去给其他人用,到时候很可能被谷歌拉黑,一旦被谷歌拉黑,就属于全球性、永久性封杀,这个WVD文件在所有使用Widevine技术的平台都将彻底作废,到时候我自己都没法用了。所以这个我肯定不会分享,但是我在这里可以给一点建议或者说提示,如果你的安卓手机确实没办法ROOT,可以尝试这几种方法(最近爆出了很多高危漏洞,ROOT应该比之前容易一些了吧):
1.使用Android Studio导出你自己的L3 CDM
2.总有好心人会分享一些L3 CDM,比如VideoHelp论坛的这个帖子:Ready to use CDMs available here!
3.有一些专门提供CDM的网站可用,这些网站一般会有免费试用,但最终目的是为了恰米。我这里不会说具体的名字,免得有人说我给他们打广告,我也不想给他们打广告,如果你需要请善用Google搜索。
我这篇文章主要想介绍一款工具:vsd,全称:Video Stream Downloader,这是一个用Rust写的CLI工具,支持DRM解密。可以从Widevine或者PlayReady的License服务器获取Key解密受保护的内容。这也就意味着你可以用它代替WVGuesserExtension扩展以及N_m3u8DL-RE。
这篇文章实战演示一下如何使用vsd下载并解密MGSTAGE的视频,请注意WVD(L3 CDM)文件是必须要的,如果你没有就没有必要往下看了。
安装vsd,你可以使用PowerShell执行如下命令将vsd一键安装至当前目录:
irm https://github.com/clitic/vsd/releases/download/vsd-0.5.0/vsd-0.5.0-x86_64-pc-windows-msvc.zip -OutFile vsd.zip; Expand-Archive vsd.zip -DestinationPath . -Force; rm vsd.zip
也可以使用Scoop包管理器安装:
scoop install vsd
其它平台的安装见:https://clitic.github.io/vsd/installation/
除了vsd以外,在开始前还应该准备好其它依赖:
1.MGSTAGE的视频文件是分离的,你需要安装FFMPEG以将分离的音频流、视频流合并成一个单一视频文件,vsd会自动完成这些操作,但需要你将FFMPEG的二进制文件放在与vsd相同的目录内。
2.将device.wvd(L3 CDM)文件放在vsd同级的目录内,方便后续vsd命令的执行与调用。
3.浏览器安装猫抓扩展,用于嗅探MGSTAGE的MPD媒体链接地址
让我们正式开始:
1.打开MGSTAGE播放一个视频,同时打开猫抓,获取MPD的URL:
2.按F12打开浏览器控制台,找到License服务器的目标URL:
3.在PowerShell执行如下命令获取KID:KEY:
.\vsd.exe license "https://dash-streaming.mgstage.com/streaming/bibid/522dht/1338/522dht-1338_20260701T143002.mpd..." `
--widevine-url "https://cenc.webstream.ne.jp/drmapi/wv/mgstage?custom_data=..." `
--widevine-device device.wvd `
--skip-playready `
-H "Origin: https://www.mgstage.com" `
-H "Referer: https://www.mgstage.com" `
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36"
请务必使用-H带上必要的标头(header),否则License服务器会返回400报错。
如果正常,会回显类似下图的内容,其中DrmKey就是我们需要的KID:KEY:
4.使用上一步获取到的KID:KEY下载并解密视频:
.\vsd.exe save "https://dash-streaming.mgstage.com/streaming/bibid/522dht/1338/522dht-1338_20260701T143002.mpd?..." `
--keys "62bd99e8c11047809157e0a8058a6444:21639f31dcac3d8555919f055ba0cd77" `
-f best `
-o 522DHT-1338.mp4
测试视频能播放,声音也正常:
补充一点vsd的其它用法,其实vsd也可以代替猫抓,但是我说实话不如直接用猫抓方便。vsd有一个capture功能,当你执行capture后,vsd会自己启一个Chrome浏览器,然后vsd会把抓到的媒体地址以curl可调用的形式回显到终端。
具体用法:
.\vsd.exe capture "https://www.mgstage.com/mgsplayer/?PID=...&PVFID=...&TPID=...&MgsvrURL=http://www.mgstage.com/mgsvr/Mgsvr.php&html5=1"
然后你在vsd启动的Chrome浏览器登录MGSTAGE账号,播放这个视频,稍等片刻MPD媒体URL就嗅探出来了:
2026-07-29 17:09:34
这篇文章记录下将本地部署的CrowdSec接入到CrowdSec Web UI以获取更多实用的功能:
由于CrowdSec LAPI的限制,此Web UI能做的事情(实现的功能)其实不多,更多的是一个数据可视化的作用,除此之外,还可以管理一下决策和警报,就不用每次都去使用cscli这个CLI工具了,但其它的操作还是得用cscli,所以并不是装上这个Web UI就完全不用了解CrowdSec了,至少得入个门,入门看看我这篇文章就可以了。
安装NGINX、CertBot、Docker:
apt update
apt install curl nginx python3-certbot-nginx
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
生成一个足够复杂的密码:
openssl rand -hex 32
使用刚生成的密码创建LAPI的登录凭据:
cscli machines add crowdsec-web-ui --password 'replace-with-generated-password' -f /dev/null
请注意一定要加上-f /dev/null,否则它将替换掉/etc/crowdsec/local_api_credentials.yaml文件内的默认凭据。
新建compose文件:
mkdir /opt/crowdsec-web-ui && cd /opt/crowdsec-web-ui && nano docker-compose.yml
由于我的CrowdSec是部署在服务器本地的(没有使用Docker),所以与CrowdSec Web UI对接的方案有多种,这里我会全部写下来,至于你选择用哪一种那就不是我能决定的事情了。
1.直接让crowdsec-web-ui容器使用主机网络(Host Network)这种是最简单的,因为不需要修改CrowdSec的配置:
services:
crowdsec-web-ui:
image: ghcr.io/theduffman85/crowdsec-web-ui:latest
container_name: crowdsec_web_ui
network_mode: "host"
environment:
CONFIG_SERVER_PORT: 3002
CONFIG_INSTANCE_METRICS_URL: http://127.0.0.1:6060/metrics
CONFIG_INSTANCE_LAPI_URL: http://127.0.0.1:8088
CONFIG_INSTANCE_LAPI_AUTH_USERNAME: crowdsec-web-ui
CONFIG_INSTANCE_LAPI_AUTH_PASSWORD:
volumes:
- ./data:/app/data
restart: unless-stopped
1.crowdsec-web-ui默认使用3000端口,但3000端口是一个常用端口,已经被我服务器上的其它程序占用了,所以这里我改成了3002端口。因使用的是主机网络,所以不存在端口映射,只能修改程序自身运行的端口,依靠程序自带的环境变量CONFIG_SERVER_PORT实现。
2.之所以说这种配置是最简单的原因是,CrowdSec的LAPI默认就是监听在127.0.0.1的,且两者都在同一个网络中了,所以无论是CONFIG_INSTANCE_LAPI_URL还是CONFIG_INSTANCE_METRICS_URL都直接配置成127.0.0.1就行了。还有CrowdSec的信任IP配置默认就是信任127.0.0.1的。
启动:
docker compose up -d
2.使用host.docker.internal:host-gateway:
services:
crowdsec-web-ui:
image: ghcr.io/theduffman85/crowdsec-web-ui:latest
container_name: crowdsec_web_ui
ports:
- "3002:3002"
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
CONFIG_SERVER_PORT: 3002
CONFIG_INSTANCE_METRICS_URL: http://host.docker.internal:6060/metrics
CONFIG_INSTANCE_LAPI_URL: http://host.docker.internal:8088
CONFIG_INSTANCE_LAPI_AUTH_USERNAME: crowdsec-web-ui
CONFIG_INSTANCE_LAPI_AUTH_PASSWORD:
volumes:
- ./data:/app/data
restart: unless-stopped
这种方案必须修改CrowdSec的主配置文件:
nano /etc/crowdsec/config.yaml
将LAPI监听改为0.0.0.0并配置信任IP:
api:
server:
log_level: info
listen_uri: 0.0.0.0:8088
trusted_ips: # IP ranges, or IPs which can have admin API access
- 127.0.0.1
- ::1
- 172.16.0.0/12 # 信任Docker默认的桥接网络
1.host.docker.internal:host-gateway本质是往容器的操作系统里面写了一个Hosts,指向的是宿主机Docker默认的桥接网络的网关IP,CrowdSec如果只监听127.0.0.1,host.docker.internal也无法访问。
2.必须将crowdsec-web-ui容器的源IP地址(最好是其Docker网络的CIDR)添加到CrowdSec的信任IP配置中。这不是你浏览器的IP地址或Docker主机的公网IP地址。如果没有此配置,决策操作仍可正常工作,但警报删除操作会失败并报错:403 Forbidden。
3.这方案有一点好处就是,在你配置反向代理后,可以将端口映射改为:
ports:
- "127.0.0.1:3002:3002"
这可以保证crowdsec-web-ui只能通过NGINX反向代理访问,且在你部署了crowdsec-nginx-bouncer时能够为crowdsec-web-ui提供保护。如果不这样配置,你的crowdsec-web-ui相当于裸奔在公网,既没有TLS也没有受到crowdsec-nginx-bouncer保护。这方案也有缺点,那就是将CrowdSec LAPI暴露在公网了,虽然LAPI是必须要鉴权的,但原本只监听在127.0.0.1肯定是最安全的,假设哪一天CrowdSec自身曝了个洞这些也说不准。
重启CrowdSec使新的配置生效:
systemctl restart crowdsec
启动:
docker compose up -d
新建NGINX站点配置文件:
nano /etc/nginx/sites-available/crowdsec-webui
写入如下内容:
server {
listen 80;
server_name crowdsec-webui.example.com;
client_max_body_size 0;
location / {
proxy_pass http://127.0.0.1:3002/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
启用站点:
ln -s /etc/nginx/sites-available/crowdsec-webui /etc/nginx/sites-enabled/crowdsec-webui
访问crowdsec-webui.example.com创建一个管理员账户就可以登录了,看下效果。
仪表板:
告警:
决策:
指标:
2026-07-25 20:32:07
CrowdSec的一大亮点就是有一个CAPI(官方的中央API服务器)专门收集和共享各类威胁情报,其中包括攻击者的IP,这些恶意IP会被记录到“社区黑名单”,CrowdSec会定期从CAPI拉取这份黑名单来保护你的服务器。同时如果你的服务器刚被攻击了,那么CrowdSec也会把刚刚发现的恶意IP提交给CAPI,全球其它数万个部署了CrowdSec的服务器也在实时上报,这样就提供了一套联动的防御体系。平子有人类命运共同体,CrowdSec有网络安全共同体
除此之外,CrowdSec的整体设计架构也挺有意思,有人说它和Fail2Ban很像,都是读取、分析系统日志然后作出决策,但现如今的CrowdSec已经远不止这些能力了,CrowdSec不久前还推出了AppSec组件,它可以将你的CrowdSec安装变成一个功能齐全的WAF(虽然肯定不如长亭的雷池)
本文将详细介绍CrowdSec的安装与使用方法。我的服务器系统是Debian,Web服务器是NGINX。
安装需要用到的软件包:
apt update
apt install curl gnupg apt-transport-https debian-archive-keyring
导入GPG密钥:
mkdir -p /etc/apt/keyrings/
curl -fsSL https://packagecloud.io/crowdsec/crowdsec/gpgkey | gpg --dearmor > /etc/apt/keyrings/crowdsec_crowdsec-archive-keyring.gpg
创建存储库配置文件:
nano /etc/apt/sources.list.d/crowdsec_crowdsec.list
写入如下内容:
deb [signed-by=/etc/apt/keyrings/crowdsec_crowdsec-archive-keyring.gpg] https://packagecloud.io/crowdsec/crowdsec/any any main
deb-src [signed-by=/etc/apt/keyrings/crowdsec_crowdsec-archive-keyring.gpg] https://packagecloud.io/crowdsec/crowdsec/any any main
现在就可以安装CrowdSec了:
apt update
apt install crowdsec
CrowdSec LAPI(Local API)默认监听的端口是8080(127.0.0.1:8080)如果服务器已经有程序占用了8080端口,毕竟8080算是一个比较热门的端口,很多程序都喜欢用这个端口,此时为了避免端口冲突CrowdSec是不会自己启动的,你需要将LAPI的端口改为其它的才能让CrowdSec正常运行,为此我们得修改两个配置文件:
nano /etc/crowdsec/config.yaml
这里我将其改为8088:
api:
server:
log_level: info
listen_uri: 127.0.0.1:8088
还有这个配置文件:
nano /etc/crowdsec/local_api_credentials.yaml
将url改为如下所示内容:
url: http://127.0.0.1:8088
启动并设置开机自启:
systemctl enable --now crowdsec
默认情况下,CrowdSec会尝试检测服务器正在运行的服务(CrowdSec >= 1.7.0),然后安装相应的日志源和集合。例如我的服务器在安装CrowdSec前就有SSH服务、NGINX服务在运行,那么CrowdSec就会自动将这些日志源和集合安装好,可以执行如下命令查询当前已经安装的日志源:
cscli metrics show acquisition
这里就列出了我的SSH和NGINX日志源,这里我还有一个Appsec的日志源,这个我稍后说明:
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Acquisition Metrics │
├─────────────────────────────────────────────────┬────────────┬──────────────┬────────────────┬────────────────────────┬───────────────────┤
│ Source │ Lines read │ Lines parsed │ Lines unparsed │ Lines poured to bucket │ Lines whitelisted │
├─────────────────────────────────────────────────┼────────────┼──────────────┼────────────────┼────────────────────────┼───────────────────┤
│ appsec:appsec │ 221 │ 221 │ - │ 210 │ - │
│ file:/var/log/nginx/access.log │ 2.41k │ 2.40k │ 1 │ 1.09k │ - │
│ file:/var/log/nginx/error.log │ 422 │ 259 │ 163 │ 235 │ - │
│ journalctl:journalctl-_SYSTEMD_UNIT=ssh.service │ 13.90k │ 9.14k │ 4.76k │ 35.87k │ - │
╰─────────────────────────────────────────────────┴────────────┴──────────────┴────────────────┴────────────────────────┴───────────────────╯
此时假设你不打算将NGINX作为Web服务器了,想用Caddy替换NGINX,那么你就需要手动配置一个新的日志源。先去CrowdSec的Hub找到你需要的集合:
找到Caddy的集合URL:
根据集合页面提供的命令安装Caddy集合:
cscli collections install crowdsecurity/caddy
CrowdSec Hub上的每个集合都包含一个采集示例,Caddy也不例外,所以你只需要在acquis.d目录下创建一个日志采集的配置文件:
nano /etc/crowdsec/acquis.d/caddy.yaml
对于Caddy而言,你应该写入如下内容:
filenames:
- /var/log/caddy/*.log
labels:
type: caddy
同时你的Caddyfile配置文件内应该指定日志文件的路径。重启CrowdSec使新的配置生效:
systemctl restart crowdsec
请注意CrowdSec或者说CrowdSec的安全引擎,本身是一个检测引擎(IDS),它只做行为检测,不负责阻止攻击行为。
我们刚才配置的这些内容如果拆分的更细一点,其实就是配置了:log-parsers(日志解析器) + scenarios(攻击场景) + Acquisition(日志采集)
其中日志解析器和攻击场景都是由collections(集合)提供的,集合就是自动打包好了这些内容,方便你一次性部署,你就不需要单独去一个个部署了,如果你没有特殊的需求,我们后续在使用过程中都是直接安装集合,很少单独去安装日志解析器和攻击场景。
CrowdSec在首次部署时会根据当时系统内运行的服务自动安装集合、配置日志采集,但如果后续你的系统内安装了新的服务,你想用CrowdSec保护它,那就需要你手动配置了,刚才我们已经使用Caddy实战演示过一遍完整的流程了。
在较新版本的CrowdSec中还有交互式自动探测与添加模式,如果你不想手动配置,可以尝试使用这个命令:
cscli setup interactive
静默全自动添加(适用于自动化脚本):
cscli setup unattended
使用自动化的方式往往没有手动配置可靠,如果可以请一定手动检查添加的配置是否正确。
CrowdSec基本的工作流程是:先采集某个服务的日志,然后将采集到的日志结构化解析再去和攻击场景匹配,比如一个攻击场景描述的是:只要一个IP在10秒内发起了5次SSH登录验证,且全部登录失败,那就说明这个IP在暴力破解SSH。当一个IP触发了此攻击场景后,CrowdSec的安全引擎会作出一个决策:把这个IP封禁4小时。
请注意这里的决策:“把这个IP封禁4小时”,此时的IP根本没有被真正的封禁,它只是CrowdSec安全引擎给出的一个建议或者说决定,还没有真正的去执行。
谁去执行CrowdSec安全引擎的决策?此时就需要你安装对应的Remediation Components(修复组件)了,官方以前又称为Bouncers(拦截器),这才是真正去执行决策的工具。在你安装并配置好修复组件后,CrowdSec就会从IDS变成一个IPS(入侵防御系统)。
下面我们就来实战配置一个防火墙Bouncer,防火墙Bouncer非常适合保护SSH基础架构服务。先查看当前系统使用的是iptables还是nftables:
iptables -V
如果回显中有显示nf_tables,那么就说明你的系统正在使用nftables:
iptables v1.8.11 (nf_tables)
此时你应该安装的软件包是:
apt install crowdsec-firewall-bouncer-nftables
否则安装:
apt install crowdsec-firewall-bouncer-iptables
查看防火墙bouncers运行状态,确保正常:
systemctl status crowdsec-firewall-bouncer.service
防火墙bouncers几乎是开箱即用的,你在安装好后无须修改任何配置应该就能很好的工作。
让我们更进一步,现在来保护Web服务(NGINX)。我的服务器之前已经安装好NGINX了,所以这里我不需要重新安装NGINX,并且我在安装CrowdSec前就已经安装了NGINX,那么CrowdSec会自动帮我配置好NGINX的集合和日志采集,所以这里我只需要安装NGINX bouncers需要用到的依赖即可:
apt install lua5.1 libnginx-mod-http-lua luarocks gettext-base lua-cjson
如果你没有安装NGINX,则这里需要先安装,仅支持Debian官方存储库的NGINX软件包,如果你是NGINX官方存储库安装的NGINX或者自己编译的NGINX则这里的步骤不适合你:
apt install nginx
然后安装对应的集合:
cscli collections install crowdsecurity/nginx
配置日志采集这里就不重复说明了,这是CrowdSec自动为我配置的,仅供参考:
filenames:
- /var/log/nginx/*.log
labels:
type: nginx
source: file
接下来安装crowdsec-nginx-bouncer:
apt install crowdsec-nginx-bouncer
这里有个坑,可能会遇到这个报错:
lua-cjson 2.1.0.10-1 depends on lua >= 5.1 (5.1-1 provided by VM)
gcc -O2 -fPIC -I/usr/include/lua5.1 -c lua_cjson.c -o lua_cjson.o
sh: 1: gcc: not found
Error: Build error: Failed compiling object lua_cjson.o
原因是系统内缺少编译用的gcc,也可能还缺少其它编译用的依赖包,所以这里安装一下这个全家桶:
apt install build-essential
将之前的crowdsec-nginx-bouncer软件包卸载重装以触发重新编译:
apt purge crowdsec-nginx-bouncer
apt install crowdsec-nginx-bouncer
由于安装了两次crowdsec-nginx-bouncer软件包,在CrowdSec那边可能会出现多个相同的Bouncer,可以通过这个命令查看:
cscli bouncers list
我不知道哪个是不再需要的Bouncer,所以我使用curl往NGINX发送一个请求:
curl 127.0.0.1
稍等片刻再次查看bouncers list应该就能发现Last API pull的时间更新了,没更新的那个就是没用的,可以执行如下命令删除:
cscli bouncers delete crowdsec-nginx-bouncer-1784122720
我还发现一个问题,crowdsec-nginx-bouncer软件包不支持Debian 11,我在另外一台Debian 11服务器内安装会报错。排查了一下发现是该软件包对NGINX版本有硬性限制:
crowdsec-nginx-bouncer
Depends: nginx
nginx-core
nginx-extras
nginx-full
nginx-light
Depends: lua5.1
Depends: libnginx-mod-http-lua
Depends: luarocks
Depends: gettext-base
Breaks: libnginx-mod-http-lua (
此修改见Pull。这意味着如果NGINX版本低于1.20将无法安装,而Debian 11官方存储库内的NGINX版本为1.18。如果你也遇到这个问题,可以改为手动安装:
wget https://github.com/crowdsecurity/cs-nginx-bouncer/releases/download/v1.2.0/crowdsec-nginx-bouncer.tgz
tar xvzf crowdsec-nginx-bouncer.tgz
cd crowdsec-nginx-bouncer-v1.2.0/
./install.sh
经过测试,手动安装的crowdsec-nginx-bouncer可以完美工作在NGINX 1.18,至少目前我没有发现有什么问题,搞不懂官方为什么要放弃对NGINX 1.20之前的版本支持。
请注意手动安装crowdsec-nginx-bouncer也需要先安装之前提到的那些依赖包,尤其是build-essential,如果你在执行install.sh安装脚本后也遇到了编译lua_cjson.o报错,请补全依赖后执行如下命令重新编译lua_cjson.o:
luarocks install lua-cjson 2.1.0.10-1
在默认情况下,CrowdSec安全引擎的决策是封禁(ban),当crowdsec-nginx-bouncer执行这个决策后,用户将无法访问你的网站,这在很多时候太过于武断了,可能会误杀正常访问的真实用户,此时你可以将HTTP场景的决策改为更灵活的验证码挑战(captcha)。配置验证码挑战,主要分为两个部分,既CrowdSec安全引擎要能够支持验证码挑战的决策,同时Bouncer还需要拥有“展示验证码”的能力。
我们先来配置crowdsec-nginx-bouncer,目前支持的验证码有:recaptcha / hcaptcha / turnstile,这里我选择使用turnstile。编辑crowdsec-nginx-bouncer的配置文件:
nano /etc/crowdsec/bouncers/crowdsec-nginx-bouncer.conf
修改如下内容:
CAPTCHA_PROVIDER=turnstile
# Captcha Secret Key
SECRET_KEY=
# Captcha Site key
SITE_KEY=
CAPTCHA_TEMPLATE_PATH=/var/lib/crowdsec/lua/templates/captcha.html
CAPTCHA_EXPIRATION=3600
为了让验证码挑战功能能够正常工作,你还需要编辑这个NGINX配置文件添加一个DNS resolver:
nano /etc/nginx/conf.d/crowdsec_nginx.conf
写入如下内容:
resolver 1.1.1.1 ipv6=off;
同时请确保该配置文件内有这一行内容,如果没有请自己加上:
lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
重启NGINX服务使新配置生效:
systemctl restart nginx
接下来配置CrowdSec安全引擎,编辑profiles.yaml决策配置文件:
nano /etc/crowdsec/profiles.yaml
在文件的顶部加入如下内容:
name: captcha_remediation
filters:
- Alert.Remediation == true && Alert.GetScope() == "Ip" && Alert.GetScenario() contains "http"
## Any scenario with http in its name will trigger a captcha challenge
decisions:
- type: captcha
duration: 4h
on_success: break
---
修改后的文件内容应该长这样:
name: captcha_remediation
filters:
- Alert.Remediation == true && Alert.GetScope() == "Ip" && Alert.GetScenario() contains "http"
## Any scenario with http in its name will trigger a captcha challenge
decisions:
- type: captcha
duration: 4h
on_success: break
---
name: default_ip_remediation
#debug: true
filters:
- Alert.Remediation == true && Alert.GetScope() == "Ip"
decisions:
- type: ban
duration: 4h
#duration_expr: Sprintf('%dh', (GetDecisionsCount(Alert.GetValue()) + 1) * 4)
# notifications:
# - slack_default # Set the webhook in /etc/crowdsec/notifications/slack.yaml before enabling this.
# - splunk_default # Set the splunk url and token in /etc/crowdsec/notifications/splunk.yaml before enabling this.
# - http_default # Set the required http parameters in /etc/crowdsec/notifications/http.yaml before enabling this.
# - email_default # Set the required email parameters in /etc/crowdsec/notifications/email.yaml before enabling this.
on_success: break
---
name: default_range_remediation
#debug: true
filters:
- Alert.Remediation == true && Alert.GetScope() == "Range"
decisions:
- type: ban
duration: 4h
#duration_expr: Sprintf('%dh', (GetDecisionsCount(Alert.GetValue()) + 1) * 4)
# notifications:
# - slack_default # Set the webhook in /etc/crowdsec/notifications/slack.yaml before enabling this.
# - splunk_default # Set the splunk url and token in /etc/crowdsec/notifications/splunk.yaml before enabling this.
# - http_default # Set the required http parameters in /etc/crowdsec/notifications/http.yaml before enabling this.
# - email_default # Set the required email parameters in /etc/crowdsec/notifications/email.yaml before enabling this.
on_success: break
重启CrowdSec使新的决策配置生效:
systemctl restart crowdsec
然后我们模拟真实攻击场景进行测试,看看验证码挑战是否能够正常工作,往NGINX日志里面写一些假的访问日志:
for i in {1..15}; do
echo "1.2.3.4 - - [22/Jul/2026:12:00:00 +0000] \"GET /random-test-$i.html HTTP/1.1\" 404 150 \"-\" \"Mozilla/5.0\"" | tee -a /var/log/nginx/access.log
done
将1.2.3.4换成你的代理IP或者蜂窝移动网络IP,然后查看决策列表:
cscli decisions list
正常的话应该有一条如图所示的记录:
此时你通过代理IP访问网站也应该弹出验证码挑战页面。如果你只是想单纯测试验证码挑战页面能否正常加载,可以使用此命令进行快速测试:
cscli decisions add -i 1.2.3.4 -t captcha
请注意此命令会完全绕过整个检测管线,所以无法用来测试profiles.yaml的逻辑。要真正验证profiles.yaml的配置是否正常,关键在于:必须触发一次真实的“告警(Alert)”
如果要测试完整的逻辑,建议使用往NGINX写假日志的方式。测试完成后别忘了删除决策:
cscli decisions delete -i 1.2.3.4
还记得最开始日志源里面有一个appsec:appsec吗?现在让我们来深入了解这个AppSec是什么。
AppSec全称:CrowdSec WAF - AppSec Component,你可以简单理解为配置好AppSec后,你安装的CrowdSec将变成一个功能齐全的WAF。
这和之前基于日志分析的模式有本质区别,在没有WAF的时候CrowdSec的防御其实叫“事后惩罚”,假设一个攻击者向你的WordPress网站发送一个SQL注入请求:
1.恶意的SQL注入请求直接成功到达了你的WordPress网站并执行了
2.NGINX把这次请求记录到了日志文件access.log
3.后台的CrowdSec进程读取日志,发现日志里包含SQL注入的攻击的请求
4.场景触发(Scenario):“哦!这个IP刚才对我们进行了SQL注入”
5.CrowdSec做出决策:ban掉这个IP
6.等到这个攻击者下次再来请求时,crowdsec-nginx-bouncer才会去封禁它
很明显这种模式防不住“一发入魂”的攻击:如果攻击者通过第一个SQL注入请求就把你的数据库拖走了,或者通过第一个XSS请求就盗取了管理员的Cookie。虽然CrowdSec在1秒后读取日志发现了异样并拉黑了它,但伤害已经造成了。而当你启用WAF后,请求还没到达网页时就被拦截了,直接返回403。
安装AppSec规则集(集合):
cscli collections install \
crowdsecurity/appsec-virtual-patching \
crowdsecurity/appsec-generic-rules
创建日志采集配置文件:
nano /etc/crowdsec/acquis.d/appsec.yaml
写入如下内容:
appsec_configs:
- crowdsecurity/appsec-default
labels:
type: appsec
listen_addr: 127.0.0.1:7422
source: appsec
name: myAppSecComponent
重启CrowdSec使新的配置生效:
systemctl restart crowdsec
编辑crowdsec-nginx-bouncer的配置文件:
nano /etc/crowdsec/bouncers/crowdsec-nginx-bouncer.conf
将crowdsec-nginx-bouncer接入AppSec:
APPSEC_URL=http://127.0.0.1:7422
APPSEC_FAILURE_ACTION=passthrough
APPSEC_CONNECT_TIMEOUT=
APPSEC_SEND_TIMEOUT=
APPSEC_PROCESS_TIMEOUT=
ALWAYS_SEND_TO_APPSEC=true
APPSEC_DROP_UNREADABLE_BODY=false
SSL_VERIFY=true
1.修改APPSEC_URL指向127.0.0.1:7422
2.启用ALWAYS_SEND_TO_APPSEC始终将所有请求都发送给AppSec检查。
重启NGINX使配置生效:
systemctl restart nginx
现在你已经拥有了基本的防护,为什么说是基本?因为刚才安装的两个集合内的规则仅能防护特定的已知CVE漏洞和一些常见的网络攻击,对于其它未知的攻击和漏洞基本无能为力。官方这么做的目的其实更多的是为了减少误报和方便运维(快速上手)。
如果你需要更高级别的防护,就需要启用CRS规则了。启用CRS规则有利有弊,利就是防护范围更广,全面覆盖各种通用且未知的SQL注入、XSS、RCE远程代码执行、路径穿越等Web攻击,也有一定的0Day防护能力,即便漏洞今天刚曝光,如果这个漏洞涉及SQL注入/XSS,CRS规则依然能直接阻断。但弊也很明显,CRS规则非常严格,正常用户发含特殊字符的文章/代码段时,可能会被误判,需要后期不断调优设置白名单。
CRS在CrowdSec WAF中又分为带外CRS和带内CRS,只有带内CRS才是真正的WAF模式,它的处理动作是:直接阻止请求,如果某个IP地址被阻止3次请求后还会被封禁。而带外CRS是:可疑请求不会立即被阻止,只有屡犯者才会被封禁。我个人是建议直接用带内CRS。
[不推荐]如果你使用带外CRS,则安装:
cscli collections install crowdsecurity/appsec-crs
如果你使用带内CRS,则安装:
cscli collections install crowdsecurity/appsec-crs-inband
然后编辑AppSec日志采集配置文件:
nano /etc/crowdsec/acquis.d/appsec.yaml
带外CRS配置:
appsec_configs:
- crowdsecurity/appsec-default
- crowdsecurity/crs
labels:
type: appsec
listen_addr: 127.0.0.1:7422
source: appsec
name: myAppSecComponent
带内CRS配置:
appsec_configs:
- crowdsecurity/appsec-default
- crowdsecurity/crs-inband
labels:
type: appsec
listen_addr: 127.0.0.1:7422
source: appsec
name: myAppSecComponent
我使用带内CRS来保护我的WordPress,但遇到了非常多的误报,此时可以安装官方提供的插件,来一定程度缓解:
cscli collections install crowdsecurity/appsec-crs-exclusion-plugin-wordpress
很多时候还是会出现误报甚至把自己给拦在门外,导致无法登录服务器的尴尬场面,为避免这种情况发生,我们可以配置白名单。CrowdSec中有三种白名单类型:
咱这里先介绍一下最简单最暴力的方法:AllowLists,这是一种IP / CIDR级别的白名单。
创建AllowList列表:
cscli allowlists create my_trusted_ip -d "我的代理节点IP"
添加IP或网段:
cscli allowlists add my_trusted_ip 8.9.6.4
cscli allowlists add my_trusted_ip 10.0.0.0/24 -d "内网网段"
查看列表内的IP:
cscli allowlist inspect my_trusted_ip
由于文章篇幅过长,还有很多内容没写,这篇文章先暂时写到这里,未完待续。
2026-07-18 17:15:11
Fluxer目前还处于Beta测试阶段,但已经是我见过的开源软件当中仿Discord完成度最高的了。现已支持如下功能:
虽然这个项目目前来看非常值得期待,但是如果你打算自托管的话,这里我可能要给你泼一盆冷水了,目前这个项目的架构极其复杂,当你使用Docker部署的时候,它需要启动足足24个容器:
不过好在官方的部署文档足够详细,且我认为官方对自托管的步骤做了一定的优化,如果你不介意让Caddy容器独占服务器的80 / 443端口,那么其实整个项目部署起来不算麻烦。鉴于此本文不对官方的部署步骤做修改,全部照搬官方文档的步骤,只在一些容易出问题的地方重点说明一下。
部署前的准备:
启动时是最耗内存的阶段,因为所有服务的镜像都会同时初始化,4GB内存是最低运行要求,对于小型活跃社区建议使用8GB内存或更多。
安装Docker:
apt update
apt install curl
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
创建工作目录并下载需要用到的文件:
cd /opt
mkdir fluxer
cd fluxer
base=https://raw.githubusercontent.com/fluxerapp/fluxer/main/deploy/self-hosting
curl -fsSLO "$base/docker-compose.yml"
curl -fsSLO "$base/Caddyfile"
curl -fsSLO "$base/livekit.yaml"
curl -fsSL "$base/.env.example" -o .env
你现在应该拥有这些文件:
docker-compose.yml
Caddyfile
livekit.yaml
.env
编辑.env环境变量配置文件:
nano .env
我们使用Caddy容器直接获取证书,所以必须正确配置如下内容:
FLUXER_DOMAIN=chat.example.com
FLUXER_PUBLIC_SCHEME=https
FLUXER_PUBLIC_PORT=443
FLUXER_CADDY_SITE_ADDRESS=chat.example.com
[email protected]
[可选]配置SMTP:
FLUXER_EMAIL_ENABLED=false
FLUXER_EMAIL_PROVIDER=none
[email protected]
FLUXER_EMAIL_FROM_NAME=Fluxer
FLUXER_EMAIL_SMTP_HOST=
FLUXER_EMAIL_SMTP_PORT=587
FLUXER_EMAIL_SMTP_USERNAME=
FLUXER_EMAIL_SMTP_PASSWORD=
FLUXER_EMAIL_SMTP_SECURE=true
.env文件里的其它内容不用手动配置,全部使用如下命令,一次性生成所需的全部密钥:
for key in POSTGRES_PASSWORD MEILI_MASTER_KEY FLUXER_S3_SECRET_KEY \
FLUXER_SUDO_MODE_SECRET FLUXER_CONNECTION_INITIATION_SECRET \
FLUXER_GATEWAY_RPC_AUTH_TOKEN FLUXER_MEDIA_PROXY_SECRET_KEY \
FLUXER_ADMIN_SECRET_KEY_BASE FLUXER_ADMIN_OAUTH_CLIENT_SECRET \
LIVEKIT_API_SECRET; do
sed -i "s|^$key=.*|$key=$(openssl rand -hex 32)|" .env
done
sed -i "s|^FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64=.*|FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64=$(openssl rand -base64 32)|" .env
VAPID=$(docker run --rm node:24-alpine npx --yes web-push generate-vapid-keys --json)
pub=$(printf '%s' "$VAPID" | grep -o '"publicKey":"[^"]*"' | cut -d'"' -f4)
priv=$(printf '%s' "$VAPID" | grep -o '"privateKey":"[^"]*"' | cut -d'"' -f4)
sed -i "s|^FLUXER_VAPID_PUBLIC_KEY=.*|FLUXER_VAPID_PUBLIC_KEY=$pub|" .env
sed -i "s|^FLUXER_VAPID_PRIVATE_KEY=.*|FLUXER_VAPID_PRIVATE_KEY=$priv|" .env
接下来为域名创建DNS记录,Caddy将使用你之前配置的FLUXER_CADDY_SITE_ADDRESS=chat.example.com自动申请和续订证书:
A chat.example.com ---> 服务器的公网IPv4
AAAA chat.example.com ---> 服务器的公网IPv6(可选)
按正常情况来说,接下来就可以启动了,但是这里有个坑,我提前替你们踩了。
因为它一次性要启动24个容器,对于一般的服务器来说可能有点顶不住(性能不太够),再加上它使用的是seaweedfs作为s3对象存储服务,seaweedfs本来启动时加载各个组件就慢,再加上其它的容器一怼,大概率会导致seaweedfs-init容器的初始化失败。
一旦这个容器初始化失败了,就会导致S3存储桶无法成功创建,你后续在Fluxer上传头像、表情包、贴纸都会报错500,见此issue:#1164。这个issue里面有解决方法,但是我仔细看了下并没有完全解决问题,而且他那个解决方法也不是最优解,这里记录下我的解决思路。
既然是容器内服务启动优先级问题,那我加一个健康检查不就行了,编辑compose文件:
nano docker-compose.yml
将seaweedfs / seaweedfs-init服务的配置修改为如下内容:
services:
seaweedfs:
image: chrislusf/seaweedfs:4.34
restart: unless-stopped
networks: [fluxer]
command: ["server", "-s3", "-dir=/data"]
volumes:
- seaweedfs-data:/data
healthcheck:
test: ["CMD-SHELL", "test -S /tmp/seaweedfs-filer-grpc-18888.sock || exit 1"]
interval: 2s
timeout: 2s
retries: 10
start_period: 10s
seaweedfs-init:
image: chrislusf/seaweedfs:4.34
networks: [fluxer]
depends_on:
seaweedfs:
condition: service_healthy
restart: "no"
entrypoint:
- /bin/sh
- -c
- >
for i in $$(seq 1 60); do
echo "s3.bucket.create -name fluxer" | weed shell -master=seaweedfs:9333 >/dev/null 2>&1 && break || sleep 2;
done;
for b in fluxer fluxer-uploads fluxer-downloads fluxer-reports fluxer-harvests; do
echo "s3.bucket.create -name $$b" | weed shell -master=seaweedfs:9333 || true;
done;
echo "buckets ready";
seaweedfs容器启动后,里面的服务启动还得很长一段时间,尤其是S3服务,所以这里探测/tmp/seaweedfs-filer-grpc-18888.sock套接字是否创建,只要这个GRPC套接字创建了,那seaweedfs的S3服务就肯定是起来了的,那么seaweedfs-init服务用于创建S3存储桶的脚本肯定就能执行成功了。
现在我们就可以启动整个堆栈了:
docker compose up -d
有关实例的备份和升级,请参阅官方的文档:
https://docs.fluxer.app/operator/get-started/#backups
https://docs.fluxer.app/operator/get-started/#upgrading
群聊、私聊、URL预览、文件上传、表情包、贴纸和管理员面板,都可以正常工作:
此项目的开发团队目前正大力开发中,基本上是一天发一个版,BUG肯定还是有点多的,再加上这24个容器的架构,说真的,我有点不敢用,我觉得可以再观望一下。。。