Files
fscan/plugins/services/ms17010_test.go
T

196 lines
4.9 KiB
Go

//go:build plugin_ms17010 || !plugin_selective
package services
import (
"bytes"
"context"
"net"
"testing"
"time"
"github.com/shadow1ng/fscan/common"
)
func TestMS17010LegacyRequestsDecodeToSMB1Packets(t *testing.T) {
requests := map[string][]byte{
"negotiate": negotiateProtocolRequest,
"sessionSetup": sessionSetupRequest,
"treeConnect": treeConnectRequest,
"transNamedPipe": transNamedPipeRequest,
"trans2SessionSetup": trans2SessionSetupRequest,
}
for name, request := range requests {
t.Run(name, func(t *testing.T) {
if len(request) < 36 {
t.Fatalf("request length = %d, want at least 36", len(request))
}
if request[0] != 0x00 {
t.Fatalf("NetBIOS message type = 0x%02x, want 0x00", request[0])
}
payloadLen := int(request[1])<<16 | int(request[2])<<8 | int(request[3])
if payloadLen != len(request)-4 {
t.Fatalf("NetBIOS payload length = %d, want %d", payloadLen, len(request)-4)
}
if !bytes.Equal(request[4:8], []byte{0xff, 0x53, 0x4d, 0x42}) {
t.Fatalf("SMB signature = % x, want ff 53 4d 42", request[4:8])
}
})
}
}
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
}