<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Incident on Wilson Wu</title><link>https://wilsonwu.me/zh/tags/incident/</link><description>Recent content in Incident on Wilson Wu</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Fri, 14 Aug 2026 00:00:00 -0400</lastBuildDate><atom:link href="https://wilsonwu.me/zh/tags/incident/index.xml" rel="self" type="application/rss+xml"/><item><title>使用 Azure 托管 PostgreSQL 降低自建数据库的安全风险</title><link>https://wilsonwu.me/zh/blog/2026/azure-managed-postgresql-vs-self-hosted-security/</link><pubDate>Fri, 14 Aug 2026 00:00:00 -0400</pubDate><guid>https://wilsonwu.me/zh/blog/2026/azure-managed-postgresql-vs-self-hosted-security/</guid><description>&lt;h2 id="问题的背景"&gt;问题的背景&lt;/h2&gt;
&lt;p&gt;&lt;img alt="PostgreSQL 安全问题" loading="lazy" src="https://wilsonwu.me/zh/blog/2026/azure-managed-postgresql-vs-self-hosted-security/1-overview.png"&gt;&lt;/p&gt;
&lt;p&gt;最近，一台部署在 Azure 上的 Linux 服务器突然出现 CPU 持续 100% 的情况。排查后发现，服务器上存在一个以 &lt;code&gt;postgres&lt;/code&gt; 用户身份运行的恶意程序，并通过 &lt;code&gt;cron&lt;/code&gt; 定时任务维持运行。&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="问题的背景">问题的背景</h2>
<p><img alt="PostgreSQL 安全问题" loading="lazy" src="/zh/blog/2026/azure-managed-postgresql-vs-self-hosted-security/1-overview.png"></p>
<p>最近，一台部署在 Azure 上的 Linux 服务器突然出现 CPU 持续 100% 的情况。排查后发现，服务器上存在一个以 <code>postgres</code> 用户身份运行的恶意程序，并通过 <code>cron</code> 定时任务维持运行。</p>
<p>这台服务器上的 PostgreSQL：</p>
<ul>
<li>没有通过 Azure NSG 向公网开放 <code>5432</code>；</li>
<li>没有被当前业务使用；</li>
<li>业务实际使用的是本机 MySQL；</li>
<li>但 PostgreSQL 服务仍然安装并运行；</li>
<li>系统中保留了 <code>postgres</code> 用户、主目录、cron 和历史脚本。</li>
</ul>
<p>这次事件说明：</p>

<blockquote>
  <p>PostgreSQL 没有向公网开放，并不代表服务器上与 PostgreSQL 相关的组件完全没有风险。</p>

</blockquote>
<p>需要特别说明的是，目前的证据只能证明恶意程序利用了 <code>postgres</code> 操作系统用户及其目录进行持久化，<strong>不能证明攻击者一定是通过 PostgreSQL 的 5432 端口进入服务器的</strong>。初始入口也可能是历史配置、SSH、Web 应用、容器、旧镜像或其他漏洞。</p>
<p>如果生产环境确实需要 PostgreSQL，但没有必须自建数据库服务器的特殊要求，建议优先使用：</p>

<blockquote>
  <p><strong>Azure Database for PostgreSQL，并通过私有网络访问，关闭公共网络入口。</strong></p>

</blockquote>
<p>这样可以减少自建数据库所带来的操作系统用户、SSH、cron、systemd、补丁、备份和主机入侵等风险。</p>
<h2 id="发现问题服务器-cpu-持续占满">发现问题：服务器 CPU 持续占满</h2>
<p>最初是在服务器上执行 <code>htop</code> 时发现异常。</p>
<p><img alt="服务器 htop 中 CPU 占用异常" loading="lazy" src="/zh/blog/2026/azure-managed-postgresql-vs-self-hosted-security/2-htop.png"></p>
<p>从截图中可以看到：</p>
<ul>
<li>服务器共有 4 个 CPU 核心；</li>
<li>4 个核心全部达到 <code>100%</code>；</li>
<li>Load Average 长期维持在 4 左右；</li>
<li>多个进程以 <code>postgres</code> 用户运行；</li>
<li>进程名称为随机字符串 <code>dzZ7yVrxv7</code>；</li>
<li>单个进程的 CPU 使用率超过 <code>200%</code>；</li>
<li>进程占用了大量内存。</li>
</ul>
<p>进一步执行：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo ps -eo user,pid,ppid,lstart,etime,%cpu,%mem,stat,comm,args <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --sort<span style="color:#f92672">=</span>-%cpu | head -20
</span></span></code></pre></div><p>其中一个主要进程如下：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>USER       PID   PPID  %CPU  %MEM  STAT  COMMAND
</span></span><span style="display:flex;"><span>postgres   9918     1   377  29.8  Ssl   dzZ7yVrxv7
</span></span></code></pre></div><p>这不是正常的 PostgreSQL 进程。</p>
<p>正常情况下，PostgreSQL 进程名称通常类似：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>postgres
</span></span><span style="display:flex;"><span>postgres: checkpointer
</span></span><span style="display:flex;"><span>postgres: background writer
</span></span><span style="display:flex;"><span>postgres: walwriter
</span></span><span style="display:flex;"><span>postgres: autovacuum launcher
</span></span></code></pre></div><p>而本次进程具有以下异常特征：</p>
<ul>
<li>名称为随机字符串；</li>
<li>以 <code>postgres</code> 用户运行；</li>
<li>CPU 使用率接近 4 个核心；</li>
<li>父进程已经变成 PID 1；</li>
<li>内存占用异常高。</li>
</ul>
<p>因此，初步判断这不是正常数据库负载，而是疑似挖矿程序或其他恶意程序。</p>
<h2 id="分析问题确认恶意程序来源">分析问题：确认恶意程序来源</h2>
<h3 id="1-检查进程真实文件">1. 检查进程真实文件</h3>
<p>Linux 进程名称可以被修改或伪装，所以不能只看 <code>COMMAND</code>，需要检查 <code>/proc/&lt;PID&gt;/exe</code>。</p>
<p>执行：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo sh -c <span style="color:#e6db74">&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">echo &#34;EXE:&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">ls -l /proc/9918/exe
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">echo &#34;CWD:&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">ls -l /proc/9918/cwd
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">echo &#34;CMDLINE:&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">tr &#34;\0&#34; &#34; &#34; &lt; /proc/9918/cmdline
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">echo
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">echo &#34;CGROUP:&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">cat /proc/9918/cgroup
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&#39;</span>
</span></span></code></pre></div><p>输出如下：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>EXE:
</span></span><span style="display:flex;"><span>/proc/9918/exe -&gt; /dev/shm/gJppJl6TbG (deleted)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>CWD:
</span></span><span style="display:flex;"><span>/proc/9918/cwd -&gt; /var/lib/postgresql
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>CMDLINE:
</span></span><span style="display:flex;"><span>dzZ7yVrxv7
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>CGROUP:
</span></span><span style="display:flex;"><span>0::/system.slice/cron.service
</span></span></code></pre></div><p>这几项信息非常关键。</p>
<h3 id="可执行文件位于-devshm">可执行文件位于 <code>/dev/shm</code></h3>
<p>真实程序路径为：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>/dev/shm/gJppJl6TbG
</span></span></code></pre></div><p><code>/dev/shm</code> 是基于内存的临时文件系统。恶意程序经常将文件下载到这里，再从内存文件系统中执行，以减少在磁盘上的痕迹。</p>
<h3 id="原文件已经被删除">原文件已经被删除</h3>
<p>路径末尾显示：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>(deleted)
</span></span></code></pre></div><p>说明程序启动后，磁盘上的原文件已被删除。但由于进程仍然持有该文件，程序依然可以继续运行。</p>
<p>这种“启动后删除自身”的行为是常见的恶意程序隐藏手段。</p>
<h3 id="工作目录属于-postgresql-用户">工作目录属于 PostgreSQL 用户</h3>
<p>进程工作目录是：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>/var/lib/postgresql
</span></span></code></pre></div><p>这说明恶意程序与 <code>postgres</code> 用户的主目录有关。</p>
<h3 id="进程由-cron-启动">进程由 cron 启动</h3>
<p>cgroup 显示：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>/system.slice/cron.service
</span></span></code></pre></div><p>这说明程序是通过定时任务启动的。</p>
<h3 id="2-从运行中的进程保存样本">2. 从运行中的进程保存样本</h3>
<p>因为原始文件已经被删除，如果立即结束进程，可能无法再提取样本。因此先从 <code>/proc</code> 保存程序：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mkdir -p /root/incident
</span></span><span style="display:flex;"><span>sudo chmod <span style="color:#ae81ff">700</span> /root/incident
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>sudo cp -L /proc/9918/exe <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /root/incident/suspect-9918.bin
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>sudo chmod <span style="color:#ae81ff">600</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /root/incident/suspect-9918.bin
</span></span></code></pre></div><p>检查文件类型：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo file /root/incident/suspect-9918.bin
</span></span></code></pre></div><p>结果如下：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>ELF 64-bit LSB shared object, x86-64,
</span></span><span style="display:flex;"><span>statically linked, no section header
</span></span></code></pre></div><p>计算 SHA-256：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo sha256sum /root/incident/suspect-9918.bin
</span></span></code></pre></div><p>得到：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>d32f9eef63c496a146a6a811677dd27e337db993d6f1a4b1af145c3135d391b3
</span></span></code></pre></div><p>该文件具有以下特征：</p>
<ul>
<li>随机文件名；</li>
<li>位于 <code>/dev/shm</code>；</li>
<li>静态链接；</li>
<li>没有节区表；</li>
<li>启动后删除原文件；</li>
<li>持续占用全部 CPU。</li>
</ul>
<p>这些行为进一步证明它不是正常的 PostgreSQL 组件。</p>
<h3 id="3-检查-postgresql-用户的定时任务">3. 检查 PostgreSQL 用户的定时任务</h3>
<p>由于进程是从 <code>cron.service</code> 启动的，继续检查 <code>postgres</code> 用户的 crontab：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo crontab -u postgres -l
</span></span></code></pre></div><p>发现如下任务：</p>
<pre tabindex="0"><code class="language-cron" data-lang="cron">24 * * * * /var/lib/postgresql/.dbus-XGodM5jOyL
</code></pre><p>该任务会在每小时第 24 分钟执行一次隐藏脚本：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>/var/lib/postgresql/.dbus-XGodM5jOyL
</span></span></code></pre></div><p>文件名使用了以点开头的隐藏形式，并模仿了 D-Bus 的命名方式，很容易被误认为系统文件。</p>
<p>检查 cron 日志：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo journalctl -u cron.service <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --since <span style="color:#e6db74">&#34;2026-08-15 07:15:00&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --until <span style="color:#e6db74">&#34;2026-08-15 09:15:00&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --no-pager
</span></span></code></pre></div><p>日志中可以看到：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Aug 15 07:24:01 CRON: (postgres) CMD (/var/lib/postgresql/.dbus-XGodM5jOyL)
</span></span><span style="display:flex;"><span>Aug 15 08:24:01 CRON: (postgres) CMD (/var/lib/postgresql/.dbus-XGodM5jOyL)
</span></span></code></pre></div><p>这证明恶意脚本至少在 <code>07:24</code> 和 <code>08:24</code> 被执行过。</p>
<h3 id="4-分析恶意启动脚本">4. 分析恶意启动脚本</h3>
<p>检查文件：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo file /var/lib/postgresql/.dbus-XGodM5jOyL
</span></span><span style="display:flex;"><span>sudo stat /var/lib/postgresql/.dbus-XGodM5jOyL
</span></span><span style="display:flex;"><span>sudo sha256sum /var/lib/postgresql/.dbus-XGodM5jOyL
</span></span></code></pre></div><p>结果显示它是一个 Bash 脚本：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Bourne-Again shell script, ASCII text executable,
</span></span><span style="display:flex;"><span>with very long lines
</span></span></code></pre></div><p>对应 SHA-256：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>36b1dcf43876f55aae2b8be7dada848b41f60ec61cd4ca7ab2a1278193f286f0
</span></span></code></pre></div><p>这份脚本负责下载、启动或恢复恶意程序，也是恶意进程能够周期性出现的主要持久化机制。</p>
<p>如果只结束高 CPU 进程，而不清理这条 cron，恶意程序会在下一个小时的第 24 分钟再次出现。</p>
<h3 id="5-在用户目录中发现第二个恶意下载器">5. 在用户目录中发现第二个恶意下载器</h3>
<p>停止并卸载 PostgreSQL 后，我继续检查 <code>/var/lib/postgresql</code> 的残留文件：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo find /var/lib/postgresql <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  -xdev -maxdepth <span style="color:#ae81ff">10</span> -ls
</span></span></code></pre></div><p>发现了另一个可疑文件：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>/var/lib/postgresql/.config/systemd/user/
</span></span><span style="display:flex;"><span>└── systemd-tmpfiles-cleanup/
</span></span><span style="display:flex;"><span>    └── systemd-tmpfiles-cleanup-TNWow7.sh
</span></span></code></pre></div><p>这个文件使用了：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>systemd-tmpfiles-cleanup
</span></span></code></pre></div><p>作为目录名，试图伪装成正常的 systemd 临时文件清理组件。</p>
<p>检查文件类型和哈希：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo file <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /var/lib/postgresql/.config/systemd/user/systemd-tmpfiles-cleanup/systemd-tmpfiles-cleanup-TNWow7.sh
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>sudo sha256sum <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /var/lib/postgresql/.config/systemd/user/systemd-tmpfiles-cleanup/systemd-tmpfiles-cleanup-TNWow7.sh
</span></span></code></pre></div><p>结果为：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Bourne-Again shell script, ASCII text executable,
</span></span><span style="display:flex;"><span>with very long lines
</span></span></code></pre></div><p>SHA-256：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>9962de5e0c68ea6e85879c5f62f2d0806360dbe186dc15e1d59abf8e4209de11
</span></span></code></pre></div><p>脚本内容经过 Base64 编码，末尾直接执行：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>base64 -d | bash
</span></span></code></pre></div><p>解码后的逻辑显示，它具备以下行为：</p>
<ul>
<li>通过 Tor、SOCKS5 或 Tor2web 连接外部地址；</li>
<li>获取服务器公网 IP；</li>
<li>收集当前用户名、主机名和内核版本；</li>
<li>读取机器 ID；</li>
<li>从外部下载其他程序；</li>
<li>在 <code>/dev/shm</code>、<code>/tmp</code>、<code>/var/tmp</code> 等目录测试和执行文件；</li>
<li>启动程序后删除临时文件。</li>
</ul>
<p>这已经不只是一个单纯的高 CPU 进程，而是一套包含下载、执行、隐藏和持久化能力的恶意程序。</p>
<h2 id="为什么-postgresql-没有开放公网端口仍然存在风险">为什么 PostgreSQL 没有开放公网端口，仍然存在风险？</h2>
<p>Azure 网络安全组当时只允许公网访问：</p>
<ul>
<li>TCP 22；</li>
<li>TCP 80；</li>
<li>TCP 443。</li>
</ul>
<p>并没有允许公网直接访问 PostgreSQL 的 <code>5432</code> 端口。</p>
<p>但检查服务器本机监听状态时发现：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo ss -lntp | grep <span style="color:#e6db74">&#39;:5432&#39;</span>
</span></span></code></pre></div><p>输出为：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>LISTEN 0 244 0.0.0.0:5432 0.0.0.0:*
</span></span><span style="display:flex;"><span>LISTEN 0 244 [::]:5432    [::]:*
</span></span></code></pre></div><p>这说明 PostgreSQL 在操作系统层面监听了所有 IPv4 和 IPv6 网卡。</p>
<p>虽然 Azure NSG 没有向公网放行 <code>5432</code>，但以下风险依然存在。</p>
<h3 id="1-同一虚拟网络中的主机仍可能访问">1. 同一虚拟网络中的主机仍可能访问</h3>
<p>Azure NSG 默认存在 <code>AllowVnetInBound</code> 规则，同一虚拟网络中的其他服务器可能访问该端口。</p>
<p>如果同一 VNet 内的其他服务器被入侵，攻击者就可能通过内网继续攻击数据库服务器。</p>
<h3 id="2-本机应用或容器仍可能访问">2. 本机应用或容器仍可能访问</h3>
<p>即使公网无法连接，服务器本机运行的以下组件仍然可能访问 PostgreSQL：</p>
<ul>
<li>Web 应用；</li>
<li>Docker 容器；</li>
<li>CI/CD Agent；</li>
<li>其他被攻陷的服务；</li>
<li>本机恶意程序。</li>
</ul>
<p>网络安全组无法阻止服务器本机进程访问本机数据库。</p>
<h3 id="3-风险不只来自数据库端口">3. 风险不只来自数据库端口</h3>
<p>本次恶意程序的实际持久化位置是：</p>
<ul>
<li><code>postgres</code> 用户的 crontab；</li>
<li><code>/var/lib/postgresql</code> 用户目录；</li>
<li>用户级 systemd 配置目录；</li>
<li><code>/dev/shm</code> 临时执行目录。</li>
</ul>
<p>即使 PostgreSQL 服务停止，只要这些用户、目录和定时任务仍然存在，恶意程序就可能继续运行。</p>
<p>所以，数据库的安全边界不只是 <code>5432</code> 端口，还包括：</p>
<ul>
<li>操作系统账户；</li>
<li>数据目录；</li>
<li>用户主目录；</li>
<li>cron；</li>
<li>systemd；</li>
<li>SSH；</li>
<li>软件包；</li>
<li>配置文件；</li>
<li>历史镜像和备份。</li>
</ul>
<h3 id="4-未使用的软件往往更容易被忽略">4. 未使用的软件往往更容易被忽略</h3>
<p>这台服务器的业务实际使用 MySQL，PostgreSQL 并没有业务用途。</p>
<p>由于“不使用”，它反而容易长期处于以下状态：</p>
<ul>
<li>没有人检查配置；</li>
<li>没有人检查系统用户；</li>
<li>没有人检查数据库日志；</li>
<li>没有人检查用户的 cron；</li>
<li>没有人检查相关目录；</li>
<li>没有人确认是否仍然需要；</li>
<li>没有人关注其补丁和版本。</li>
</ul>
<p>因此：</p>

<blockquote>
  <p>未使用但仍然安装和运行的软件，并不是备用能力，而是未被管理的攻击面。</p>

</blockquote>
<h2 id="解决问题清除恶意程序并卸载-postgresql">解决问题：清除恶意程序并卸载 PostgreSQL</h2>
<h3 id="1-暂停恶意进程">1. 暂停恶意进程</h3>
<p>取证完成后，先暂停进程：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo kill -STOP <span style="color:#ae81ff">9918</span>
</span></span></code></pre></div><p>检查进程状态：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ps -p <span style="color:#ae81ff">9918</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  -o user,pid,ppid,%cpu,%mem,stat,comm,args
</span></span></code></pre></div><p>状态中出现 <code>T</code>，说明进程已暂停。</p>
<p>暂停的好处是：</p>
<ul>
<li>立即停止继续占用 CPU；</li>
<li>暂时停止潜在网络行为；</li>
<li>保留进程，便于继续调查；</li>
<li>避免在完成取证前丢失内存中的样本。</li>
</ul>
<h3 id="2-备份并删除恶意-cron">2. 备份并删除恶意 cron</h3>
<p>先保存 crontab：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo crontab -u postgres -l <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  &gt; /root/incident/postgres-crontab.txt
</span></span></code></pre></div><p>然后删除：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo crontab -u postgres -r
</span></span></code></pre></div><p>确认已删除：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo crontab -u postgres -l
</span></span></code></pre></div><p>预期输出：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>no crontab for postgres
</span></span></code></pre></div><h3 id="3-隔离恶意启动脚本">3. 隔离恶意启动脚本</h3>
<p>先备份脚本：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo cp -a <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /var/lib/postgresql/.dbus-XGodM5jOyL <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /root/incident/cron-payload
</span></span></code></pre></div><p>再从原位置移走：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mv <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /var/lib/postgresql/.dbus-XGodM5jOyL <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /root/incident/original-dbus-XGodM5jOyL
</span></span></code></pre></div><p>限制证据文件权限：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo chown root:root <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /root/incident/original-dbus-XGodM5jOyL <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /root/incident/cron-payload
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>sudo chmod <span style="color:#ae81ff">600</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /root/incident/original-dbus-XGodM5jOyL <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /root/incident/cron-payload
</span></span></code></pre></div><h3 id="4-结束恶意进程">4. 结束恶意进程</h3>
<p>确认恶意 cron 和启动脚本已隔离后，结束进程：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo kill -KILL <span style="color:#ae81ff">9918</span>
</span></span></code></pre></div><p>检查进程是否消失：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ps -p <span style="color:#ae81ff">9918</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  -o user,pid,ppid,%cpu,%mem,stat,comm,args
</span></span></code></pre></div><p>随后查看系统负载：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>uptime
</span></span></code></pre></div><p>清理后的负载变为：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>load average: 0.36, 2.71, 3.68
</span></span></code></pre></div><p>1 分钟负载已经从 4 以上下降到 <code>0.36</code>。</p>
<p>5 分钟和 15 分钟负载仍然较高是正常的，因为它们反映的是一段时间内的平均值，会逐渐下降。</p>
<h3 id="5-停止并禁用-postgresql">5. 停止并禁用 PostgreSQL</h3>
<p>因为业务并不使用 PostgreSQL，所以先停止并禁止开机启动：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl disable --now postgresql
</span></span></code></pre></div><p>确认服务状态：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>systemctl status postgresql --no-pager
</span></span></code></pre></div><p>确认端口不再监听：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo ss -lntp | grep <span style="color:#e6db74">&#39;:5432&#39;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  <span style="color:#f92672">||</span> echo <span style="color:#e6db74">&#34;5432 端口未监听&#34;</span>
</span></span></code></pre></div><p>最终结果为：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>5432 端口未监听
</span></span></code></pre></div><h3 id="6-卸载-postgresql">6. 卸载 PostgreSQL</h3>
<p>确认业务没有受到影响后，卸载 PostgreSQL 相关软件包：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt-get purge -y <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  postgresql <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  postgresql-14 <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  postgresql-client-14 <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  postgresql-client-common <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  postgresql-common <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  postgresql-contrib
</span></span></code></pre></div><p>需要注意：</p>

<blockquote>
  <p>卸载软件包不等于完成恶意程序清理。</p>

</blockquote>
<p>还应继续检查以下位置：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>/var/lib/postgresql
</span></span><span style="display:flex;"><span>/etc/postgresql
</span></span><span style="display:flex;"><span>/etc/postgresql-common
</span></span><span style="display:flex;"><span>/var/spool/cron
</span></span><span style="display:flex;"><span>/tmp
</span></span><span style="display:flex;"><span>/var/tmp
</span></span><span style="display:flex;"><span>/dev/shm
</span></span><span style="display:flex;"><span>/home/*/.config/systemd
</span></span></code></pre></div><p>本次发现的第二个恶意下载器，就是在卸载 PostgreSQL 后检查残留目录时找到的。</p>
<h3 id="7-隔离第二个恶意下载器">7. 隔离第二个恶意下载器</h3>
<p>将伪装成 systemd 清理任务的脚本移入证据目录：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mv <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /var/lib/postgresql/.config/systemd/user/systemd-tmpfiles-cleanup/systemd-tmpfiles-cleanup-TNWow7.sh <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /root/incident/systemd-tmpfiles-cleanup-TNWow7.sh
</span></span></code></pre></div><p>限制权限：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo chown root:root <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /root/incident/systemd-tmpfiles-cleanup-TNWow7.sh
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>sudo chmod <span style="color:#ae81ff">600</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /root/incident/systemd-tmpfiles-cleanup-TNWow7.sh
</span></span></code></pre></div><p>然后搜索是否存在同类文件：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo grep -RIlE <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#39;TNWow7|XGodM5jOyL|tor2socks|tor2web|relay\.tor2socks&#39;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /etc <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /var/spool/cron <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /var/lib/postgresql <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /tmp <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /var/tmp <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /dev/shm <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  /home <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  2&gt;/dev/null
</span></span></code></pre></div><h2 id="azure-网络安全配置整改">Azure 网络安全配置整改</h2>
<h3 id="1-不需要删除不存在的-5432-入站规则">1. 不需要删除不存在的 5432 入站规则</h3>
<p>Azure NSG 中原本没有允许公网访问 <code>5432</code> 的规则，所以不需要专门删除 PostgreSQL 入站规则。</p>
<p>停止并卸载 PostgreSQL 后，本机也已经不再监听 <code>5432</code>。</p>
<p>但是，不能因为 NSG 没有开放 <code>5432</code> 就认为服务器已经安全。</p>
<h3 id="2-收紧-ssh-入站范围">2. 收紧 SSH 入站范围</h3>
<p>排查过程中，SSH 日志中出现了多个公网 IP 对 <code>postgres</code> 用户进行密码尝试：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Failed password for postgres from 172.83.83.194
</span></span><span style="display:flex;"><span>Failed password for postgres from 101.36.110.41
</span></span></code></pre></div><p>这些记录显示的是失败登录，不能说明对方已经通过 SSH 登录成功。但它们说明服务器的 SSH 正在被公网持续扫描。</p>
<p>原 Azure SSH 规则为：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Source: Any
</span></span><span style="display:flex;"><span>Destination port: 22
</span></span><span style="display:flex;"><span>Action: Allow
</span></span></code></pre></div><p>建议改为：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Source: 管理员固定公网 IP/32
</span></span><span style="display:flex;"><span>Destination port: 22
</span></span><span style="display:flex;"><span>Protocol: TCP
</span></span><span style="display:flex;"><span>Action: Allow
</span></span></code></pre></div><p>更安全的方式包括：</p>
<ul>
<li>Azure Bastion；</li>
<li>VPN；</li>
<li>Just-In-Time VM Access；</li>
<li>禁止 SSH 密码登录；</li>
<li>只使用 SSH Key；</li>
<li>禁止 root 远程登录；</li>
<li>配置失败登录告警。</li>
</ul>
<h3 id="3-限制服务器出站访问">3. 限制服务器出站访问</h3>
<p>恶意脚本需要连接外部地址下载程序，而当时 Azure NSG 允许任意出站访问。</p>
<p>如果业务条件允许，应限制服务器的出站范围：</p>
<ul>
<li>只允许必要的 DNS；</li>
<li>只允许必要的软件源；</li>
<li>只允许业务需要访问的 API；</li>
<li>使用 Azure Firewall 或集中式代理；</li>
<li>记录 DNS 和出站网络日志；</li>
<li>对 Tor、代理、矿池和异常域名建立告警。</li>
</ul>
<p>仅限制入站流量是不够的。恶意程序一旦进入服务器，通常还需要通过出站网络下载载荷、连接控制服务器或矿池。</p>
<h2 id="为什么建议使用-azure-托管-postgresql">为什么建议使用 Azure 托管 PostgreSQL？</h2>
<p>如果业务确实需要 PostgreSQL，建议优先使用：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Azure Database for PostgreSQL
</span></span></code></pre></div><p>而不是直接在 Azure VM 上安装 PostgreSQL。</p>
<h3 id="1-减少操作系统层面的攻击面">1. 减少操作系统层面的攻击面</h3>
<p>在 VM 上自建 PostgreSQL，需要自行维护：</p>
<ul>
<li>Linux 操作系统；</li>
<li>PostgreSQL 软件包；</li>
<li><code>postgres</code> 系统用户；</li>
<li>SSH；</li>
<li>cron；</li>
<li>systemd；</li>
<li>数据目录权限；</li>
<li>安全补丁；</li>
<li>版本升级；</li>
<li>网络防火墙；</li>
<li>日志；</li>
<li>备份；</li>
<li>高可用；</li>
<li>恶意进程检测。</li>
</ul>
<p>使用 Azure 托管 PostgreSQL 后，用户不会直接管理数据库所在主机的 root 权限，也不会在数据库主机上创建 cron、systemd 服务或运行 <code>/dev/shm</code> 中的程序。</p>
<p>这可以显著减少本次事件中出现的主机级风险。</p>
<h3 id="2-使用私有网络关闭公网访问">2. 使用私有网络，关闭公网访问</h3>
<p>建议采用以下网络架构：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Azure VM / App Service / AKS
</span></span><span style="display:flex;"><span>          |
</span></span><span style="display:flex;"><span>      Private Network
</span></span><span style="display:flex;"><span>          |
</span></span><span style="display:flex;"><span>Azure Database for PostgreSQL
</span></span></code></pre></div><p>数据库应配置为：</p>
<ul>
<li>禁止公共网络访问；</li>
<li>使用 Private Endpoint 或私有网络集成；</li>
<li>只允许应用所在子网访问；</li>
<li>不向公网开放数据库端口；</li>
<li>数据库管理也通过 VPN、Bastion 或受控网络完成。</li>
</ul>
<h3 id="3-自动备份和维护">3. 自动备份和维护</h3>
<p>Azure 托管 PostgreSQL 可以提供：</p>
<ul>
<li>自动备份；</li>
<li>时间点恢复；</li>
<li>高可用选项；</li>
<li>数据加密；</li>
<li>监控指标；</li>
<li>日志与告警；</li>
<li>平台维护；</li>
<li>安全更新；</li>
<li>版本支持。</li>
</ul>
<p>这些能力可以降低因长期无人维护而产生的风险。</p>
<h3 id="4-责任边界更清晰">4. 责任边界更清晰</h3>
<p>自建 PostgreSQL 经常出现这种情况：</p>

<blockquote>
  <p>为了测试临时安装，后来业务不用了，但服务、用户和目录一直留在服务器上。</p>

</blockquote>
<p>托管数据库的资源边界更加清晰：</p>
<ul>
<li>需要时创建；</li>
<li>通过私有网络连接；</li>
<li>不再使用时导出数据；</li>
<li>删除数据库实例；</li>
<li>删除访问权限和私有连接；</li>
<li>不会在业务 VM 上留下数据库系统用户和历史脚本。</li>
</ul>
<h3 id="5-托管服务并不等于绝对安全">5. 托管服务并不等于绝对安全</h3>
<p>Azure 托管 PostgreSQL 可以减少操作系统和基础设施维护风险，但仍需要正确管理：</p>
<ul>
<li>数据库账号；</li>
<li>强密码或身份认证；</li>
<li>最小权限；</li>
<li>网络访问范围；</li>
<li>SQL 注入；</li>
<li>连接字符串；</li>
<li>Azure Key Vault；</li>
<li>日志审计；</li>
<li>数据加密；</li>
<li>应用依赖；</li>
<li>凭据轮换。</li>
</ul>
<p>正确的理解是：</p>

<blockquote>
  <p>Azure 托管 PostgreSQL 缩小了需要自己负责的安全范围，但不会消除应用和账号层面的安全责任。</p>

</blockquote>
<h2 id="本次事件的-ioc">本次事件的 IOC</h2>
<p>以下指标可用于内部服务器排查。</p>
<h3 id="可疑进程及文件名">可疑进程及文件名</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>dzZ7yVrxv7
</span></span><span style="display:flex;"><span>gJppJl6TbG
</span></span><span style="display:flex;"><span>.dbus-XGodM5jOyL
</span></span><span style="display:flex;"><span>systemd-tmpfiles-cleanup-TNWow7.sh
</span></span></code></pre></div><h3 id="sha-256">SHA-256</h3>
<p>恶意 ELF 程序：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>d32f9eef63c496a146a6a811677dd27e337db993d6f1a4b1af145c3135d391b3
</span></span></code></pre></div><p>恶意 cron 启动脚本：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>36b1dcf43876f55aae2b8be7dada848b41f60ec61cd4ca7ab2a1278193f286f0
</span></span></code></pre></div><p>恶意下载器：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>9962de5e0c68ea6e85879c5f62f2d0806360dbe186dc15e1d59abf8e4209de11
</span></span></code></pre></div><h3 id="行为特征">行为特征</h3>
<ul>
<li>以 <code>postgres</code> 用户运行；</li>
<li>从 <code>/dev/shm</code> 执行随机名称文件；</li>
<li>启动后删除原文件；</li>
<li>利用用户 crontab 定时启动；</li>
<li>Base64 解码后直接交给 Bash 执行；</li>
<li>使用 Tor、SOCKS5 或 Tor2web；</li>
<li>收集公网 IP、用户名、主机名和机器 ID；</li>
<li>从外部下载其他程序；</li>
<li>在 <code>/tmp</code>、<code>/var/tmp</code> 和 <code>/dev/shm</code> 中执行程序；</li>
<li>持续占用全部 CPU；</li>
<li>伪装成 D-Bus 或 systemd 组件。</li>
</ul>
<h2 id="最终总结">最终总结</h2>
<p>本次事件从表面上看，是一次服务器 CPU 占用过高的问题。</p>
<p>但深入排查后发现，真正的问题包括：</p>
<ul>
<li>以 <code>postgres</code> 用户运行的恶意程序；</li>
<li>位于 <code>/dev/shm</code> 的随机 ELF 文件；</li>
<li><code>postgres</code> 用户的恶意 cron；</li>
<li><code>/var/lib/postgresql</code> 下的隐藏启动脚本；</li>
<li>伪装成 systemd 组件的恶意下载器；</li>
<li>可连接 Tor 和外部下载地址的恶意逻辑。</li>
</ul>
<p>最值得关注的是：</p>

<blockquote>
  <p>这台服务器没有通过 Azure NSG 向公网开放 PostgreSQL 的 5432 端口，而且业务也没有使用 PostgreSQL，但与 PostgreSQL 有关的系统用户、主目录、cron 和残留文件仍然构成了安全风险。</p>

</blockquote>
<p>当然，现有证据并不能证明初始入侵入口就是 PostgreSQL。恶意程序使用 <code>postgres</code> 用户运行，不等于攻击者一定通过 PostgreSQL 端口进入服务器。</p>
<p>但这次事件依然说明了几个重要原则。</p>
<h3 id="第一不使用的服务应立即停止和卸载">第一，不使用的服务应立即停止和卸载</h3>
<p>不要因为“没有向公网开放”就长期保留闲置服务。</p>
<p>不使用的软件应当：</p>
<ul>
<li>停止服务；</li>
<li>禁止开机启动；</li>
<li>卸载软件包；</li>
<li>检查系统用户；</li>
<li>检查 cron；</li>
<li>检查 systemd；</li>
<li>检查残留目录和脚本。</li>
</ul>
<h3 id="第二云安全组不是唯一安全边界">第二，云安全组不是唯一安全边界</h3>
<p>NSG 没有开放 <code>5432</code>，只能说明公网无法通过该规则直接连接数据库，不能防止：</p>
<ul>
<li>内网访问；</li>
<li>本机访问；</li>
<li>容器访问；</li>
<li>历史持久化；</li>
<li>用户级 cron；</li>
<li>本机恶意程序；</li>
<li>SSH 和应用漏洞。</li>
</ul>
<h3 id="第三生产-postgresql-优先使用-azure-托管服务">第三，生产 PostgreSQL 优先使用 Azure 托管服务</h3>
<p>如果没有必须自建数据库服务器的明确需求，建议使用：</p>

<blockquote>
  <p><strong>Azure Database for PostgreSQL + 私有网络 + 禁止公共访问。</strong></p>

</blockquote>
<p>这样可以减少数据库主机操作系统、SSH、系统用户、cron、systemd、补丁、备份和高可用等方面的维护风险。</p>
<h3 id="第四杀掉恶意进程不等于完成安全恢复">第四，杀掉恶意进程不等于完成安全恢复</h3>
<p>结束高 CPU 进程只解决了当前资源占用问题，还应继续：</p>
<ul>
<li>查找持久化；</li>
<li>调查入侵入口；</li>
<li>检查 SSH 成功登录记录；</li>
<li>检查 Web 应用和容器；</li>
<li>轮换服务器上的全部凭据；</li>
<li>检查同一 VNet 中的其他主机；</li>
<li>保存日志和磁盘快照；</li>
<li>必要时从可信镜像重建服务器。</li>
</ul>
<p>最终可以将这次事件概括为一句话：</p>

<blockquote>
  <p><strong>不使用的数据库不要留在服务器上；需要 PostgreSQL 时，优先使用 Azure 托管 PostgreSQL，并通过私有网络访问。</strong></p>

</blockquote>
]]></content:encoded></item></channel></rss>