Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature fail http to https #5

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions lib/smb/smb/encoder/unicode.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,21 @@ func ToUnicode(s string) []byte {
binary.Write(&b, binary.LittleEndian, &uints)
return b.Bytes()
}

func ToSmbString(s string) []byte {
res := ToUnicode(s)
res = append(res, 0x0, 0x0)
return res
}

func FromSmbString(d []byte) (string, error) {
res, err := FromUnicode(d)
if err != nil {
return "", err
}
if len(res) == 0 {
return "", nil
}
// Trim null terminator
return res[:len(res)-1], nil
}
52 changes: 49 additions & 3 deletions lib/smb/smb/smb.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ const (
ShareCapAsymmetric uint32 = 0x00000080
)

const (
SmbHeaderV1Length = 32
)

type HeaderV1 struct {
ProtocolID []byte `smb:"fixed:4"`
Command uint8
Expand Down Expand Up @@ -133,6 +137,24 @@ type NegotiateReqV1 struct {
Dialects []uint8 `smb:"fixed:12"`
}

type SessionSetupV1Req struct {
HeaderV1
WordCount uint8
AndCommand uint8
Reserved1 uint8
AndOffset uint16
MaxBuffer uint16
MaxMPXCount uint16
VCNumber uint16
SessionKey uint32
OEMPasswordLength uint16
UnicodePasswordLength uint16
Reserved2 uint32
Capabilities uint32
ByteCount uint16
VarData []byte
}

type NegotiateResV1 struct {
HeaderV1
WordCount uint8
Expand All @@ -147,8 +169,8 @@ type NegotiateResV1 struct {
SystemTime uint64
ServerTimezon uint16
ChallengeLength uint8
ByteCount uint16
// variable data afterwords that we don't care about
ByteCount uint16 `smb:"len:VarData"`
VarData []byte
}

type NegotiateReq struct {
Expand Down Expand Up @@ -260,6 +282,17 @@ type TreeDisconnectRes struct {
func newHeaderV1() HeaderV1 {
return HeaderV1{
ProtocolID: []byte(ProtocolSmb),
Status: 0,
Flags: 0x18,
Flags2: 0xc843,
PIDHigh: 0,
// These bytes must be explicit here
SecurityFeatures: []byte{0, 0, 0, 0, 0, 0, 0, 0},
Reserved: 0,
TID: 0xffff,
PIDLow: 0xfeff,
UID: 0,
MID: 0,
}
}

Expand Down Expand Up @@ -287,11 +320,24 @@ func (s *Session) NewNegotiateReqV1() NegotiateReqV1 {
return NegotiateReqV1{
HeaderV1: header,
WordCount: 0,
ByteCount: 14,
ByteCount: 12,
Dialects: []uint8(DialectSmb_1_0),
}
}

func (s *Session) NewSessionSetupV1Req() SessionSetupV1Req {
header := newHeaderV1()
header.Command = 0x73 // SMB1 Session Setup
return SessionSetupV1Req{
HeaderV1: header,
WordCount: 0xd,
AndCommand: 0xff,
MaxBuffer: 0x1111,
MaxMPXCount: 0xa,
VarData: []byte{},
}
}

func (s *Session) NewNegotiateReq() NegotiateReq {
header := newHeader()
header.Command = CommandNegotiate
Expand Down
69 changes: 68 additions & 1 deletion lib/smb/smb/zgrab.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net"
"strings"

"unicode/utf16"

Expand Down Expand Up @@ -130,6 +131,12 @@ type SMBLog struct {

Version *SMBVersions `json:"smb_version,omitempty"`

// If present, represent the NativeOS, NTLM, and GroupName fields of SMBv1 Session Setup Negotiation
// An empty string for these values indicate the data was not available
NativeOs string `json:"native_os"`
NTLM string `json:"ntlm"`
GroupName string `json:"group_name"`

// While the NegotiationLogs and SessionSetupLog each have their own
// Capabilties field, we are ignoring the SessionsSetupLog capability
// when decoding, and only representing the server capabilties based
Expand Down Expand Up @@ -208,7 +215,10 @@ func GetSMBLog(conn net.Conn, session bool, v1 bool, debug bool) (smbLog *SMBLog
}

if v1 {
err = s.LoggedNegotiateProtocolv1(session)
err := s.LoggedNegotiateProtocolv1(session)
if err == nil && session {
s.LoggedSessionSetupV1()
}
} else {
err = s.LoggedNegotiateProtocol(session)
}
Expand Down Expand Up @@ -269,6 +279,63 @@ func (ls *LoggedSession) LoggedNegotiateProtocolv1(setup bool) error {
return nil
}

func (ls *LoggedSession) LoggedSessionSetupV1() (err error) {
s := &ls.Session
var buf []byte

req := s.NewSessionSetupV1Req()
s.Debug("Sending LoggedSessionSetupV1 Request", nil)
buf, err = s.send(req)
if err != nil {
s.Debug("No response to SMBv1 cleartext SessionSetup", nil)
return nil
}

// Safely trim down everything except the payload
if len(buf) < SmbHeaderV1Length {
return nil
}
// When using unicode, a padding byte will exist after the header
paddingLength := int((buf[11] >> 7) & 1)
// Skip header
buf = buf[SmbHeaderV1Length:]
// The byte after the header holds the number of words remaining in uint16s
// words + 3 bytes for wordlength & bytecount + potential unicode padding
claimedRemainingSize := int(buf[0])*2 + 3 + paddingLength
if len(buf) < claimedRemainingSize {
return nil
}
buf = buf[claimedRemainingSize:]

var decoded string
if paddingLength == 1 {
// Unicode string
decoded, err = encoder.FromSmbString(buf)
if err != nil {
s.Debug("Error encountered while decoding SMB string", err)
return nil
}
} else {
// ASCII string
decoded = string(buf)
}

// We expect 3 null-terminated strings in this order;
// These fields are technically all optional, but guaranteed to be in this order
fields := strings.Split(decoded, "\000")
if len(fields) > 0 {
ls.Log.NativeOs = fields[0]
}
if len(fields) > 1 {
ls.Log.NTLM = fields[1]
}
if len(fields) > 2 {
ls.Log.GroupName = fields[2]
}

return nil
}

// LoggedNegotiateProtocol performs the same operations as
// Session.NegotiateProtocol() up to the point where user credentials would be
// required, and logs the server's responses.
Expand Down
5 changes: 5 additions & 0 deletions modules/fox/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package fox

import (
"errors"
log "github.com/sirupsen/logrus"
"github.com/zmap/zgrab2"
)
Expand Down Expand Up @@ -107,6 +108,10 @@ func (scanner *Scanner) Scan(target zgrab2.ScanTarget) (zgrab2.ScanStatus, inter
err = GetFoxBanner(result, conn)
if !result.IsFox {
result = nil
err = &zgrab2.ScanError{
Err: errors.New("host responds, but is not a fox service"),
Status: zgrab2.SCAN_PROTOCOL_ERROR,
}
}
return zgrab2.TryGetScanStatus(err), result, err
}
33 changes: 26 additions & 7 deletions modules/http/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"net"
"net/url"
"strconv"
"strings"
"time"

log "github.com/sirupsen/logrus"
Expand All @@ -44,12 +45,13 @@ var (
type Flags struct {
zgrab2.BaseFlags
zgrab2.TLSFlags
Method string `long:"method" default:"GET" description:"Set HTTP request method type"`
Endpoint string `long:"endpoint" default:"/" description:"Send an HTTP request to an endpoint"`
UserAgent string `long:"user-agent" default:"Mozilla/5.0 zgrab/0.x" description:"Set a custom user agent"`
RetryHTTPS bool `long:"retry-https" description:"If the initial request fails, reconnect and try with HTTPS."`
MaxSize int `long:"max-size" default:"256" description:"Max kilobytes to read in response to an HTTP request"`
MaxRedirects int `long:"max-redirects" default:"0" description:"Max number of redirects to follow"`
Method string `long:"method" default:"GET" description:"Set HTTP request method type"`
Endpoint string `long:"endpoint" default:"/" description:"Send an HTTP request to an endpoint"`
FailHTTPToHTTPS bool `long:"fail-http-to-https" description:"Trigger retry-https logic on known HTTP/400 protocol mismatch responses"`
UserAgent string `long:"user-agent" default:"Mozilla/5.0 zgrab/0.x" description:"Set a custom user agent"`
RetryHTTPS bool `long:"retry-https" description:"If the initial request fails, reconnect and try with HTTPS."`
MaxSize int `long:"max-size" default:"256" description:"Max kilobytes to read in response to an HTTP request"`
MaxRedirects int `long:"max-redirects" default:"0" description:"Max number of redirects to follow"`

// FollowLocalhostRedirects overrides the default behavior to return
// ErrRedirLocalhost whenever a redirect points to localhost.
Expand Down Expand Up @@ -431,7 +433,7 @@ func (scan *scan) Grab() *zgrab2.ScanError {
bodyText := ""
decodedSuccessfully := false
decoder := encoder.NewDecoder()

//"windows-1252" is the default value and will likely not decode correctly
if certain || encoding != "windows-1252" {
decoded, decErr := decoder.Bytes(buf.Bytes())
Expand All @@ -446,6 +448,23 @@ func (scan *scan) Grab() *zgrab2.ScanError {
bodyText = buf.String()
}

// Application-specific logic for retrying HTTP as HTTPS; if condition matches, return protocol error
if scan.scanner.config.FailHTTPToHTTPS && scan.results.Response.StatusCode == 400 && readLen < 1024 && readLen > 24 {
// Apache: "You're speaking plain HTTP to an SSL-enabled server port"
// NGINX: "The plain HTTP request was sent to HTTPS port"
var sliceLen int64 = 128
if readLen < sliceLen {
sliceLen = readLen
}
sliceBuf := bodyText[:sliceLen]
if strings.Contains(sliceBuf, "The plain HTTP request was sent to HTTPS port") ||
strings.Contains(sliceBuf, "You're speaking plain HTTP") ||
strings.Contains(sliceBuf, "combination of host and port requires TLS") ||
strings.Contains(sliceBuf, "Client sent an HTTP request to an HTTPS server") {
return zgrab2.NewScanError(zgrab2.SCAN_PROTOCOL_ERROR, errors.New("NGINX or Apache HTTP over HTTPS failure"))
}
}

// re-enforce readlen
if int64(len(bodyText)) > readLen {
scan.results.Response.BodyText = bodyText[:int(readLen)]
Expand Down
7 changes: 6 additions & 1 deletion modules/postgres/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const maxOutputSize = 1024
// Don't read an unlimited number of tag/value pairs from the server
const maxReadAllPackets = 64

const uint32Len = 4

// Connection wraps the state of a given connection to a server.
type Connection struct {
// Target is the requested scan target.
Expand Down Expand Up @@ -143,7 +145,10 @@ func (c *Connection) tryReadPacket(header byte) (*ServerPacket, *zgrab2.ScanErro
log.Debugf("postgres server %s reported packet size of %d bytes; only reading %d bytes.", c.Target.String(), bodyLen, maxPacketSize)
sizeToRead = maxPacketSize
}
body := make([]byte, sizeToRead - 4) // Length includes the length of the Length uint32
if sizeToRead < uint32Len {
sizeToRead = uint32Len
}
body := make([]byte, sizeToRead - uint32Len) // Length includes the length of the Length uint32
_, err = io.ReadFull(c.Connection, body)
if err != nil && err != io.EOF {
return nil, zgrab2.DetectScanError(err)
Expand Down
5 changes: 4 additions & 1 deletion zgrab2_schemas/zgrab2/smb.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ def extended(base, new):
"revision": Unsigned8BitInteger(doc="Protocol Revision"),
"version_string": String(doc="Full SMB Version String"),
}),
"native_os": String(doc="Operating system claimed by server"),
"ntlm": String(doc="Native LAN Manager"),
"group_name": String(doc="Group name"),
"smb_capabilities": SubRecord({
"smb_dfs_support": Boolean(doc="Server supports Distributed File System"),
"smb_leasing_support": Boolean(doc="Server supports Leasing"),
Expand All @@ -62,7 +65,7 @@ def extended(base, new):
"smb_encryption_support": Boolean(doc="Server supports encryption"),
}, doc="Capabilities flags for the connection. See [MS-SMB2] Sect. 2.2.4."),
'negotiation_log': negotiate_log,
'has_ntlm': Boolean(),
'has_ntlm': Boolean(doc="Server supports the NTLM authentication method"),
'session_setup_log': session_setup_log,
})
}, extends=zgrab2.base_scan_response)
Expand Down