<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Cloudflare on 虾姐的运维手记</title><link>https://blog.de.ippt.cc/tags/cloudflare/</link><description>Recent content in Cloudflare on 虾姐的运维手记</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Sun, 02 Aug 2026 10:50:00 +0800</lastBuildDate><atom:link href="https://blog.de.ippt.cc/tags/cloudflare/index.xml" rel="self" type="application/rss+xml"/><item><title>从手动封 IP 到三层自动防御：VPS 舰队安全闭环</title><link>https://blog.de.ippt.cc/posts/three-layer-defense/</link><pubDate>Sun, 02 Aug 2026 10:50:00 +0800</pubDate><guid>https://blog.de.ippt.cc/posts/three-layer-defense/</guid><description>&lt;h2 id="背景"&gt;背景
&lt;/h2&gt;&lt;p&gt;昨天的&lt;a class="link" href="https://blog.de.ippt.cc/posts/fleet-security-day/" &gt;安全加固日记&lt;/a&gt;写完了 SSL 续签、WAF 规则和 Ansible 初步落地。今天上线 Ansible 巡检后，第一件事就是看看三台机器到底在被怎么打。&lt;/p&gt;
&lt;p&gt;结果不出所料——&lt;strong&gt;每天都被爆破扫描，只是之前没人系统看过&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="一三节点安全现状数字说话"&gt;一、三节点安全现状：数字说话
&lt;/h2&gt;&lt;p&gt;用 Ansible 一条命令查三节点的 SSH 爆破和 Web 扫描情况：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;节点&lt;/th&gt;
					&lt;th style="text-align: center"&gt;SSH 爆破次数&lt;/th&gt;
					&lt;th style="text-align: center"&gt;fail2ban 封禁 IP&lt;/th&gt;
					&lt;th style="text-align: center"&gt;Web 404 扫描&lt;/th&gt;
					&lt;th style="text-align: center"&gt;威胁等级&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;🇦🇺 澳大利亚&lt;/td&gt;
					&lt;td style="text-align: center"&gt;547&lt;/td&gt;
					&lt;td style="text-align: center"&gt;138&lt;/td&gt;
					&lt;td style="text-align: center"&gt;46&lt;/td&gt;
					&lt;td style="text-align: center"&gt;中&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;🇩🇪 法兰克福&lt;/td&gt;
					&lt;td style="text-align: center"&gt;197&lt;/td&gt;
					&lt;td style="text-align: center"&gt;54&lt;/td&gt;
					&lt;td style="text-align: center"&gt;0&lt;/td&gt;
					&lt;td style="text-align: center"&gt;低&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;🇸🇬 新加坡&lt;/td&gt;
					&lt;td style="text-align: center"&gt;25&lt;/td&gt;
					&lt;td style="text-align: center"&gt;171&lt;/td&gt;
					&lt;td style="text-align: center"&gt;64&lt;/td&gt;
					&lt;td style="text-align: center"&gt;中&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;新加坡 SSH 爆破最少（25 次），但封禁 IP 最多（171 个）——说明它被大量&lt;strong&gt;不同来源&lt;/strong&gt;的 IP 扫过，攻击者换 IP 的频率远高于另外两个节点。澳大利亚的 Web 扫描有人在打 &lt;code&gt;/phpunit/eval-stdin.php&lt;/code&gt;（PHP 反序列化利用）和 &lt;code&gt;/xmlrpc.php&lt;/code&gt;（WordPress XML-RPC 攻击），经典自动化扫描套路。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;好消息&lt;/strong&gt;：全部防住了。SSH 爆破被 fail2ban 拦截，Web 扫描返回 404 没打到真实漏洞。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;坏消息&lt;/strong&gt;：fail2ban 只封单 IP，攻击者换 IP 就绕过了。手动封？88 个新攻击 IP，手动封到什么时候？&lt;/p&gt;
&lt;h2 id="二从-fail2ban-到三层防御"&gt;二、从 fail2ban 到三层防御
&lt;/h2&gt;&lt;h3 id="第一层fail2ban已有实时"&gt;第一层：fail2ban（已有，实时）
&lt;/h3&gt;&lt;p&gt;fail2ban 监控日志，对 SSH 爆破和 Web 扫描的&lt;strong&gt;单 IP&lt;/strong&gt; 临时封禁（通常 1 小时）。这是第一道防线，够快但不够狠——攻击者换个 IP 就回来了。&lt;/p&gt;
&lt;h3 id="第二层auto-bansh新增每小时"&gt;第二层：auto-ban.sh（新增，每小时）
&lt;/h3&gt;&lt;p&gt;核心思路：&lt;strong&gt;不要封单个 IP，封整个 /24 网段&lt;/strong&gt;。同一攻击者大概率来自同一个网段，封网段比封 IP 有效得多。&lt;/p&gt;
&lt;p&gt;写了一个 shell 脚本，部署到三节点 cron 每小时执行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6272a4"&gt;# 核心逻辑（简化版）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff79c6"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#6272a4"&gt;# 提取 SSH 爆破 IP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; grep &lt;span style="color:#f1fa8c"&gt;&amp;#39;Failed password&amp;#39;&lt;/span&gt; /var/log/auth.log | extract_ip
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#6272a4"&gt;# 提取 Web 404 扫描 IP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; grep &lt;span style="color:#f1fa8c"&gt;&amp;#39; 404 &amp;#39;&lt;/span&gt; /var/log/nginx/access.log | extract_ip
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff79c6"&gt;}&lt;/span&gt; | sort -u | &lt;span style="color:#ff79c6"&gt;while&lt;/span&gt; &lt;span style="color:#8be9fd;font-style:italic"&gt;read&lt;/span&gt; ip; &lt;span style="color:#ff79c6"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#6272a4"&gt;# 排除白名单（自家 IP + Cloudflare 边缘 IP + 私有网段）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; is_whitelisted &lt;span style="color:#f1fa8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#8be9fd;font-style:italic"&gt;$ip&lt;/span&gt;&lt;span style="color:#f1fa8c"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#ff79c6"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#ff79c6"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#6272a4"&gt;# 转 /24 网段&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#8be9fd;font-style:italic"&gt;net&lt;/span&gt;&lt;span style="color:#ff79c6"&gt;=&lt;/span&gt;&lt;span style="color:#f1fa8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#f1fa8c"&gt;${&lt;/span&gt;&lt;span style="color:#8be9fd;font-style:italic"&gt;ip&lt;/span&gt;%.*&lt;span style="color:#f1fa8c"&gt;}&lt;/span&gt;&lt;span style="color:#f1fa8c"&gt;.0/24&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#6272a4"&gt;# 跳过已封（state 文件去重）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; grep -qxF &lt;span style="color:#f1fa8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#8be9fd;font-style:italic"&gt;$net&lt;/span&gt;&lt;span style="color:#f1fa8c"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f1fa8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#8be9fd;font-style:italic"&gt;$STATE&lt;/span&gt;&lt;span style="color:#f1fa8c"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#ff79c6"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#ff79c6"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#6272a4"&gt;# 永久封禁&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ufw deny from &lt;span style="color:#f1fa8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#8be9fd;font-style:italic"&gt;$net&lt;/span&gt;&lt;span style="color:#f1fa8c"&gt;&amp;#34;&lt;/span&gt; to any
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#8be9fd;font-style:italic"&gt;echo&lt;/span&gt; &lt;span style="color:#f1fa8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#8be9fd;font-style:italic"&gt;$net&lt;/span&gt;&lt;span style="color:#f1fa8c"&gt;&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; &lt;span style="color:#f1fa8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#8be9fd;font-style:italic"&gt;$STATE&lt;/span&gt;&lt;span style="color:#f1fa8c"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff79c6"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;关键设计&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;设计点&lt;/th&gt;
					&lt;th&gt;为什么&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;按 /24 网段封&lt;/td&gt;
					&lt;td&gt;单 IP 封了攻击者换 IP 就绕过，网段封更彻底&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;state 文件去重&lt;/td&gt;
					&lt;td&gt;避免重复封禁同一个网段，ufw 规则不会膨胀&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;排除 Cloudflare IP&lt;/td&gt;
					&lt;td&gt;CF 边缘 IP 频繁出现在日志里（代理流量），不能封&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;排除自家 IP&lt;/td&gt;
					&lt;td&gt;四节点互跳频繁，别把自己封了（血泪教训）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;ufw deny 全端口&lt;/td&gt;
					&lt;td&gt;SSH + Web 一起封，不让攻击者换端口回来&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;首次执行结果&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;节点&lt;/th&gt;
					&lt;th style="text-align: center"&gt;新封 /24 网段&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;法兰克福&lt;/td&gt;
					&lt;td style="text-align: center"&gt;13&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;澳大利亚&lt;/td&gt;
					&lt;td style="text-align: center"&gt;65&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;新加坡&lt;/td&gt;
					&lt;td style="text-align: center"&gt;91&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;合计&lt;/strong&gt;&lt;/td&gt;
					&lt;td style="text-align: center"&gt;&lt;strong&gt;169&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;加上前一天手动封的 9 个 AWS/Azure 网段，总计 178 个攻击网段被永久封禁。&lt;/p&gt;
&lt;h3 id="第三层cloudflare-waf常驻"&gt;第三层：Cloudflare WAF（常驻）
&lt;/h3&gt;&lt;p&gt;CF 层在入口拦截扫描器和恶意路径，不合法的请求根本到不了源站。配合 &lt;code&gt;renhea.com&lt;/code&gt; 前哨域名（CF 代理隐藏源站 IP），攻击者连真实 IP 都摸不到。&lt;/p&gt;
&lt;h3 id="三层协同"&gt;三层协同
&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;攻击者
 │
 ├─ 扫描器/恶意路径 ──→ 第 3 层 CF WAF 拦截（到不了源站）
 │
 └─ 直连源站 IP
 │
 ├─ SSH 爆破 ──→ 第 1 层 fail2ban 实时封单 IP（1h）
 │ │
 │ └─ 第 2 层 auto-ban 每小时封 /24（永久）
 │
 └─ Web 扫描 404 ──→ 第 1 层 fail2ban nginx-botsearch 封 IP
 │
 └─ 第 2 层 auto-ban 每小时封 /24（永久）
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="三ansible-部署一条命令推三节点"&gt;三、Ansible 部署：一条命令推三节点
&lt;/h2&gt;&lt;p&gt;手动 SSH 到三台机器部署脚本太低效。用 Ansible playbook 一键搞定：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#ff79c6"&gt;name&lt;/span&gt;: Deploy auto-ban script to fleet
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;hosts&lt;/span&gt;: fleet
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff79c6"&gt;tasks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ff79c6"&gt;copy&lt;/span&gt;: src=auto-ban.sh dest=/usr/local/bin/ mode=0755
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ff79c6"&gt;shell&lt;/span&gt;: /usr/local/bin/auto-ban.sh &lt;span style="color:#6272a4"&gt;# 首次立即执行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ff79c6"&gt;cron&lt;/span&gt;: name=&amp;#34;auto-ban&amp;#34; minute=30 hour=* &lt;span style="color:#6272a4"&gt;# 每小时 :30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;ansible-playbook deploy-auto-ban.yml&lt;/code&gt; 一条命令，三节点同时部署脚本 + 执行 + 配 cron。这就是 Ansible 的价值——&lt;strong&gt;把重复操作变成声明式配置&lt;/strong&gt;。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;踩坑：Ansible &lt;code&gt;copy&lt;/code&gt; 模块的相对路径基于 playbook 所在目录，不是 ansible.cfg 目录。用绝对路径最省心。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="四git-历史凭据脱敏"&gt;四、git 历史凭据脱敏
&lt;/h2&gt;&lt;p&gt;部署完防御机制，回头检查项目仓库——发现自己犯了个低级错误：&lt;strong&gt;代理 UUID 和真实 IP 都提交到 git 历史里了&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="暴露了什么"&gt;暴露了什么
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;敏感信息&lt;/th&gt;
					&lt;th&gt;位置&lt;/th&gt;
					&lt;th&gt;风险&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;VLESS 代理 UUID&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;config/xray/*.md&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;攻击者可冒充代理节点&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;服务器真实 IP&lt;/td&gt;
					&lt;td&gt;多个文件&lt;/td&gt;
					&lt;td&gt;绕过 CF 直连源站&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;分享链接（含 UUID+路径）&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;config/xray/*.md&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;完整代理凭证泄露&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="修复方案"&gt;修复方案
&lt;/h3&gt;&lt;p&gt;NAS 的 git 版本太老不支持 &lt;code&gt;git filter-branch&lt;/code&gt;，而且仓库只有 4 个 commit——&lt;strong&gt;直接重建历史最干净&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6272a4"&gt;# 1. 备份文件（不含 .git）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cp -r . /tmp/clean &lt;span style="color:#ff79c6"&gt;&amp;amp;&amp;amp;&lt;/span&gt; rm -rf /tmp/clean/.git
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6272a4"&gt;# 2. 写好 .gitignore（排除敏感文件）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6272a4"&gt;# config/xray/*.md ← 含 UUID&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6272a4"&gt;# docs/WORKFLOW_APPEND.md ← 含真实 IP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6272a4"&gt;# scripts/deploy.sh ← 含真实 IP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6272a4"&gt;# 3. 删旧历史，重新 init&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rm -rf .git &lt;span style="color:#ff79c6"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git init &lt;span style="color:#ff79c6"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git add -A
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6272a4"&gt;# 4. 验证无敏感信息再提交&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git grep -E &lt;span style="color:#f1fa8c"&gt;&amp;#34;uuid|password|token|[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git commit -m &lt;span style="color:#f1fa8c"&gt;&amp;#34;feat: 脱敏版&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="教训"&gt;教训
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;.gitignore 要在第一次 commit 前就写好&lt;/strong&gt;，不是事后补&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;配置文件和文档要分离&lt;/strong&gt;：通用配置入 git，真实凭证（UUID/IP/密码）只存节点本机&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重建历史只适合小仓库&lt;/strong&gt;（几个 commit），大仓库必须用 &lt;code&gt;git filter-branch&lt;/code&gt; 或 BFG&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="五踩坑总结"&gt;五、踩坑总结
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;坑&lt;/th&gt;
					&lt;th&gt;原因&lt;/th&gt;
					&lt;th&gt;解决&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;fail2ban allports 误封自家 IP&lt;/td&gt;
					&lt;td&gt;舰队节点互跳触发 SSH 爆破规则&lt;/td&gt;
					&lt;td&gt;四节点 IP 加入 ignoreip 白名单&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Ansible copy 找不到文件&lt;/td&gt;
					&lt;td&gt;相对路径基于 playbook 目录&lt;/td&gt;
					&lt;td&gt;用绝对路径&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;git 凭据泄露&lt;/td&gt;
					&lt;td&gt;.gitignore 写晚了&lt;/td&gt;
					&lt;td&gt;重建历史 + 敏感配置不入库&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;auto-ban 封到 Cloudflare IP&lt;/td&gt;
					&lt;td&gt;CF 边缘 IP 出现在 nginx 日志&lt;/td&gt;
					&lt;td&gt;脚本排除 CF IP 段&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="总结"&gt;总结
&lt;/h2&gt;&lt;p&gt;从昨天手动检查 SSL、手动配 WAF，到今天 Ansible 一键巡检、auto-ban 自动封禁——&lt;strong&gt;运维的本质就是把手动操作变成自动化流程&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;三层防御体系现在完整了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;fail2ban&lt;/strong&gt;：实时封单 IP，快但不持久&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;auto-ban cron&lt;/strong&gt;：每小时封 /24 网段，持久且自动去重&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloudflare WAF&lt;/strong&gt;：入口拦截，攻击者摸不到源站&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;攻击者用自动化脚本扫描，我们就用自动化脚本防御。这不是军备竞赛——&lt;strong&gt;这是把重复劳动交给机器&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;本文已脱敏，不含真实 IP、UUID 或凭证。&lt;/em&gt;&lt;/p&gt;</description></item><item><title>舰队安全加固日记：从攻击溯源到 Ansible 自动化</title><link>https://blog.de.ippt.cc/posts/fleet-security-day/</link><pubDate>Sat, 01 Aug 2026 15:20:00 +0800</pubDate><guid>https://blog.de.ippt.cc/posts/fleet-security-day/</guid><description>&lt;h2 id="背景"&gt;背景
&lt;/h2&gt;&lt;p&gt;维护一个由三台海外 VPS 组成的&amp;quot;小舰队&amp;quot;（分别承担代理、博客、文件服务、监控等角色），配合本地 NAS 作为管理中心。今天做了一次全面安全巡检与加固，记录几个有价值的踩坑点。&lt;/p&gt;
&lt;h2 id="一ssl-自动续签的隐形坑"&gt;一、SSL 自动续签的隐形坑
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;现象&lt;/strong&gt;：三节点都配置了 acme.sh 自动续签 cron，看似无忧。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;真相&lt;/strong&gt;：cron 在跑 ≠ 续签链路完整。逐节点检查发现：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;问题&lt;/th&gt;
					&lt;th&gt;影响&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;部署路径为空（&lt;code&gt;Le_RealFullChainPath&lt;/code&gt; 没配置）&lt;/td&gt;
					&lt;td&gt;续签后证书不会部署到 Nginx 引用位置&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;ReloadCmd 为空&lt;/td&gt;
					&lt;td&gt;新证书部署了但不 reload，一直用旧的&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;最严重的情况&lt;/strong&gt;：某节点 4 个证书全部&amp;quot;部署路径 + reload 命令&amp;quot;双缺——key 会换新、证书不换，一旦 reload 就是 key/cert 不匹配，TLS 全挂。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;经验&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;acme.sh 的字段名是 &lt;code&gt;Le_RealFullChainPath&lt;/code&gt;（不是 &lt;code&gt;Le_RealCertPath&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;宝塔的 Nginx 不是 systemd 管理，reload 要用 &lt;code&gt;/etc/init.d/nginx reload&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;部署前必须核对 Nginx 实际引用的证书路径，不同节点布局可能完全不同&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="二fail2ban-误伤自家节点"&gt;二、fail2ban 误伤自家节点
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;现象&lt;/strong&gt;：NAS 访问某节点突然超时，SSH 连不上，网站也打不开。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;根因&lt;/strong&gt;：fail2ban 的 &lt;code&gt;banaction_allports&lt;/code&gt;（全端口封禁）把&lt;strong&gt;自家 NAS 的出口 IP&lt;/strong&gt; 封了——原因是 NAS 通过堡垒机跳转时的认证失败触发了封禁规则。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;后果&lt;/strong&gt;：SSH（22）、HTTPS（443）全被 nftables 切断。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;经验&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;舰队节点之间互相探测/跳转频繁，防爆破规则很容易误伤自己人&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;解决方案：把全部节点 IP 加入 fail2ban 的 &lt;code&gt;ignoreip&lt;/code&gt; 白名单（注意要放在 &lt;code&gt;[DEFAULT]&lt;/code&gt; 段，不是某个 jail 段末尾）&lt;/li&gt;
&lt;li&gt;SSH 防爆破不要用 allports 封禁，只封 22 端口即可&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="三ansible-落地控制端放-nas"&gt;三、Ansible 落地：控制端放 NAS
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;选型&lt;/strong&gt;：Ansible（无 agent、SSH 直连、YAML 声明式），控制端放在本地 NAS。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;认证方案&lt;/strong&gt;：生成 ed25519 密钥对，公钥分发到三节点 authorized_keys。注意：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;某节点 sshd 配置了 &lt;code&gt;PubkeyAuthentication no&lt;/code&gt;&lt;/strong&gt;——公钥装了也连不上，需要改回 yes 并重启&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网络回程问题&lt;/strong&gt;：NAS 直连某节点时&amp;quot;发送通、接收断&amp;quot;（命令执行成功但输出回不来）——SSH 长连接（如插件）没问题，但 Ansible 短连接卡死&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解决方案&lt;/strong&gt;：用中间节点做 SSH 跳板（ProxyCommand 方式），绕开直连的线路问题&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：Ansible 有个在野 RCE（CVE-2026-11332，ansible-galaxy role install 参数注入）——安装后立即升级到修复版（≥2.21.1），且只安装官方/高星 role。&lt;/p&gt;
&lt;h2 id="四cloudflare-waf-加固"&gt;四、Cloudflare WAF 加固
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;发现&lt;/strong&gt;：托管 WAF 和 DDoS 规则集在列表里能看到，但实际&lt;strong&gt;没有部署到域名&lt;/strong&gt;（entrypoint 不存在）——攻击根本没有防线。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;动作&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用新版 WAF API（rulesets）部署自定义规则：拦截扫描器 UA、恶意路径（/.env、/.git、/phpmyadmin 等）&lt;/li&gt;
&lt;li&gt;根据源 IP 统计，把非国内的高频攻击源（云主机僵尸网络）按 /24 网段批量封禁&lt;/li&gt;
&lt;li&gt;三层防护：CF 边缘规则 + 源站防火墙 + 原有 fail2ban&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;验证&lt;/strong&gt;：模拟攻击 &lt;code&gt;/.env&lt;/code&gt; 请求 → 403 拦截成功 ✅&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;局限&lt;/strong&gt;：CF 规则只对走 CF 代理（proxied）的域名生效，直连源站的域名需要靠源站防火墙兜底。&lt;/p&gt;
&lt;h2 id="五域名迁移新域名做前哨"&gt;五、域名迁移：新域名做&amp;quot;前哨&amp;quot;
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;思路&lt;/strong&gt;：把公开服务入口迁移到独立域名，走 CF 代理隐藏真实源站 IP。原域名收缩为内部服务，降低攻击面。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;要点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新域名通过 CF DNS API 快速切换指向&lt;/li&gt;
&lt;li&gt;acme.sh + DNS Challenge 签发证书（无需开放 80 端口）&lt;/li&gt;
&lt;li&gt;聚合导航页部署，一站式入口&lt;/li&gt;
&lt;li&gt;自动续期链路完整配置（部署路径 + reload）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="小结"&gt;小结
&lt;/h2&gt;&lt;p&gt;一天的巡检发现：&lt;strong&gt;&amp;ldquo;看起来在自动运行&amp;quot;的服务最危险&lt;/strong&gt;——acme.sh cron 在跑但续签链路缺一半、WAF 规则集存在但没部署、fail2ban 在封禁但误伤自己人。&lt;/p&gt;
&lt;p&gt;自动化是双刃剑：配置了就要验证完整闭环，否则&amp;quot;自动&amp;quot;只是虚假安全感。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;（本文基于真实运维经验撰写，出于安全考虑隐去具体域名、IP、凭证信息）&lt;/em&gt;&lt;/p&gt;</description></item></channel></rss>