mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-23 03:31:53 +08:00
196 lines
4.9 KiB
Go
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
|
|
}
|