Add MSSQL zgrab2 module (#38)
This commit is contained in:
parent
a8b4461d29
commit
8eb958e22c
2
Makefile
2
Makefile
@ -30,7 +30,7 @@ integration-test-clean:
|
||||
# This is the target for re-building from source in the container
|
||||
container-clean:
|
||||
rm -f zgrab2
|
||||
cd cmd/zgrab2 && go build -v -a && cd ../..
|
||||
cd cmd/zgrab2 && go get -v ./... && go build -v -a && cd ../..
|
||||
ln -s cmd/zgrab2/zgrab2$(EXECUTABLE_EXTENSION) zgrab2
|
||||
|
||||
clean:
|
||||
|
60
conn.go
Normal file
60
conn.go
Normal file
@ -0,0 +1,60 @@
|
||||
package zgrab2
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TimeoutConnection wraps an existing net.Conn connection, overriding the Read/Write methods to use the configured timeouts
|
||||
type TimeoutConnection struct {
|
||||
net.Conn
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// TimeoutConnection.Read calls Read() on the underlying connection, using any configured deadlines
|
||||
func (c *TimeoutConnection) Read(b []byte) (n int, err error) {
|
||||
if c.Timeout > 0 {
|
||||
if err = c.Conn.SetReadDeadline(time.Now().Add(c.Timeout)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return c.Conn.Read(b)
|
||||
}
|
||||
|
||||
// TimeoutConnection.Write calls Write() on the underlying connection, using any configured deadlines
|
||||
func (c *TimeoutConnection) Write(b []byte) (n int, err error) {
|
||||
if c.Timeout > 0 {
|
||||
if err = c.Conn.SetWriteDeadline(time.Now().Add(c.Timeout)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
// GetTimeoutDialer returns a Dialer function that dials with the given timeout
|
||||
func GetTimeoutDialer(timeout time.Duration) func(string, string) (net.Conn, error) {
|
||||
return func(proto, target string) (net.Conn, error) {
|
||||
return DialTimeoutConnection(proto, target, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
// DialTimeoutConnection dials the target and returns a net.Conn that uses the configured timeouts for Read/Write operations.
|
||||
func DialTimeoutConnection(proto string, target string, timeout time.Duration) (net.Conn, error) {
|
||||
var conn net.Conn
|
||||
var err error
|
||||
if timeout > 0 {
|
||||
conn, err = net.DialTimeout(proto, target, timeout)
|
||||
} else {
|
||||
conn, err = net.Dial(proto, target)
|
||||
}
|
||||
if err != nil {
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &TimeoutConnection{
|
||||
Conn: conn,
|
||||
Timeout: timeout,
|
||||
}, nil
|
||||
}
|
@ -8,13 +8,13 @@ all: docker-runner.id
|
||||
|
||||
.PHONY: clean clean-all
|
||||
|
||||
service-base-image.id:
|
||||
service-base-image.id: service-base.Dockerfile
|
||||
docker build -t zgrab2_service_base:latest -f service-base.Dockerfile -q . > service-base-image.id || rm -f service-base-image.id
|
||||
|
||||
runner-base-image.id:
|
||||
runner-base-image.id: runner-base.Dockerfile
|
||||
docker build -t zgrab2_runner_base:latest -f runner-base.Dockerfile -q . > runner-base-image.id || rm -f runner-base-image.id
|
||||
|
||||
docker-runner.id: ../cmd/zgrab2/zgrab2$(EXECUTABLE_EXTENSION) runner-base-image.id service-base-image.id
|
||||
docker-runner.id: Dockerfile ../cmd/zgrab2/zgrab2$(EXECUTABLE_EXTENSION) runner-base-image.id service-base-image.id
|
||||
docker build -t zgrab2_runner:latest -f Dockerfile -q .. > docker-runner.id || rm -f docker-runner.id
|
||||
|
||||
clean:
|
||||
|
9
integration_tests/mssql/cleanup.sh
Executable file
9
integration_tests/mssql/cleanup.sh
Executable file
@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set +e
|
||||
|
||||
CONTAINER_NAME="zgrab_mssql-2017-linux"
|
||||
|
||||
echo "mssql/cleanup: Tests cleanup for mssql"
|
||||
|
||||
docker stop $CONTAINER_NAME
|
28
integration_tests/mssql/setup.sh
Executable file
28
integration_tests/mssql/setup.sh
Executable file
@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "mssql/setup: Tests setup for mssql"
|
||||
|
||||
CONTAINER_IMAGE="microsoft/mssql-server-linux"
|
||||
CONTAINER_VERSION="2017-CU3"
|
||||
CONTAINER_NAME="zgrab_mssql-2017-linux"
|
||||
|
||||
# Supported MSSQL_PRODUCT_ID values are Developer, Express, Standard, Enterprise, EnterpriseCore
|
||||
MSSQL_PRODUCT_ID="Enterprise"
|
||||
|
||||
if docker ps --filter "name=$CONTAINER_NAME" | grep $CONTAINER_NAME; then
|
||||
echo "mssql/setup: Container $CONTAINER_NAME already running -- nothing more to do."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
docker run -td --rm -e "MSSQL_PID=$MSSQL_PRODUCT_ID" -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=$(openssl rand -base64 12)" --name $CONTAINER_NAME $CONTAINER_IMAGE:$CONTAINER_VERSION
|
||||
|
||||
echo -n "mssql/setup: Waiting on $CONTAINER_NAME..."
|
||||
|
||||
while ! docker logs $CONTAINER_NAME --tail all | grep -q "Server is listening on"; do
|
||||
echo -n "."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
sleep 1
|
||||
|
||||
echo "...done."
|
20
integration_tests/mssql/test.sh
Executable file
20
integration_tests/mssql/test.sh
Executable file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
MODULE_DIR=$(dirname $0)
|
||||
TEST_ROOT=$MODULE_DIR/..
|
||||
ZGRAB_ROOT=$MODULE_DIR/../..
|
||||
ZGRAB_OUTPUT=$ZGRAB_ROOT/zgrab-output
|
||||
|
||||
CONTAINER_NAME="zgrab_mssql-2017-linux"
|
||||
|
||||
mkdir -p $ZGRAB_OUTPUT/mssql
|
||||
|
||||
OUTPUT_FILE="$ZGRAB_OUTPUT/mssql/2017-linux.json"
|
||||
|
||||
echo "mssql/test: Tests runner for mssql"
|
||||
CONTAINER_NAME=$CONTAINER_NAME $ZGRAB_ROOT/docker-runner/docker-run.sh mssql > $OUTPUT_FILE
|
||||
|
||||
echo "BEGIN DOCKER LOGS FROM $CONTAINER_NAME [{("
|
||||
docker logs --tail all $CONTAINER_NAME
|
||||
echo ")}] END DOCKER LOGS FROM $CONTAINER_NAME"
|
7
modules/mssql.go
Normal file
7
modules/mssql.go
Normal file
@ -0,0 +1,7 @@
|
||||
package modules
|
||||
|
||||
import "github.com/zmap/zgrab2/modules/mssql"
|
||||
|
||||
func init() {
|
||||
mssql.RegisterModule()
|
||||
}
|
829
modules/mssql/connection.go
Normal file
829
modules/mssql/connection.go
Normal file
@ -0,0 +1,829 @@
|
||||
package mssql
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
logrus "github.com/sirupsen/logrus"
|
||||
"github.com/zmap/zgrab2"
|
||||
)
|
||||
|
||||
var (
|
||||
errTooLarge = errors.New("data too large")
|
||||
errBufferTooSmall = errors.New("buffer too small")
|
||||
errInvalidData = errors.New("received invalid data")
|
||||
errNoServerEncryption = errors.New("server doesn't support encryption")
|
||||
errServerRequiresEncryption = errors.New("server requires encryption")
|
||||
errInvalidState = errors.New("operation cannot be performed in this state")
|
||||
)
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/dd358342.aspx
|
||||
const (
|
||||
tdsStatusNormal uint8 = 0x00
|
||||
tdsStatusEOM = 0x01
|
||||
tdsStatusIgnore = 0x02
|
||||
tdsStatusResetConnection = 0x08
|
||||
tdsStatusResetConnectionSkipTran = 0x10
|
||||
)
|
||||
|
||||
type tdsPacketType uint8
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/dd304214.aspx
|
||||
const (
|
||||
tdsPacketTypeSQLBatch tdsPacketType = 0x01
|
||||
tdsPacketTypePreTDS7Login = 0x02
|
||||
tdsPacketTypeRPC = 0x03
|
||||
tdsPacketTypeTabularResult = 0x04
|
||||
tdsPacketTypeAttentionSignal = 0x06
|
||||
tdsPacketTypeBulkLoadData = 0x07
|
||||
tdsPacketTypeFederatedAuthToken = 0x08
|
||||
tdsPacketTypeTransactionManagerRequest = 0x0E
|
||||
tdsPacketTypeTDS7Login = 0x10
|
||||
tdsPacketTypeSSPI = 0x11
|
||||
tdsPacketTypePrelogin = 0x12
|
||||
)
|
||||
|
||||
// From https://msdn.microsoft.com/en-us/library/dd357559.aspx
|
||||
// See PL_OPTION_TOKEN values
|
||||
type preloginOptionToken uint8
|
||||
|
||||
const (
|
||||
preloginVersion preloginOptionToken = 0x00
|
||||
preloginEncryption = 0x01
|
||||
preloginInstance = 0x02
|
||||
preloginThreadID = 0x03
|
||||
preloginMARS = 0x04
|
||||
preloginTraceID = 0x05
|
||||
preloginFedAuthRequired = 0x06
|
||||
preloginNonce = 0x07
|
||||
preloginTerminator = 0xFF
|
||||
)
|
||||
|
||||
// Mapping to documented names, also serves to identify unknown values for JSON
|
||||
// marshalling
|
||||
var knownPreloginOptionTokens = map[preloginOptionToken]string{
|
||||
preloginVersion: "VERSION",
|
||||
preloginEncryption: "ENCRYPTION",
|
||||
preloginInstance: "INSTOPT",
|
||||
preloginThreadID: "THREADID",
|
||||
preloginMARS: "MARS",
|
||||
preloginTraceID: "TRACEID",
|
||||
preloginFedAuthRequired: "FEDAUTHREQUIRED",
|
||||
preloginNonce: "NONCE",
|
||||
}
|
||||
|
||||
// preloginOption values are stored as byte arrays; actual types are specified
|
||||
// in the docs
|
||||
type preloginOption []byte
|
||||
|
||||
// preloginOptions maps the token to the value for that option
|
||||
type preloginOptions map[preloginOptionToken]preloginOption
|
||||
|
||||
// EncryptMode is defined at
|
||||
// https://msdn.microsoft.com/en-us/library/dd357559.aspx
|
||||
type EncryptMode byte
|
||||
|
||||
const (
|
||||
// encryptModeUnknown is not a valid ENCRYPTION value
|
||||
encryptModeUnknown EncryptMode = 0xff
|
||||
|
||||
// encryptModeOff means that encryption will only be used for login
|
||||
encryptModeOff = 0x00
|
||||
|
||||
// encryptModeOn means that encryption will be used for the entire session
|
||||
encryptModeOn = 0x01
|
||||
|
||||
// encryptModeNotSupported means that the client/server does not support
|
||||
// encryption
|
||||
encryptModeNotSupported = 0x02
|
||||
|
||||
// encryptModeRequired is sent by the server when the client sends
|
||||
// EncryptModNotSupported but the server requires it
|
||||
encryptModeRequired = 0x03
|
||||
)
|
||||
|
||||
// These are the macro values defined in the MSDN docs
|
||||
var stringToEncryptMode = map[string]EncryptMode{
|
||||
"UNKNOWN": 0xff,
|
||||
"ENCRYPT_OFF": 0x00,
|
||||
"ENCRYPT_ON": 0x01,
|
||||
"ENCRYPT_NOT_SUP": 0x02,
|
||||
"ENCRYPT_REQ": 0x03,
|
||||
}
|
||||
|
||||
var encryptModeToString = map[EncryptMode]string{
|
||||
encryptModeOff: "ENCRYPT_OFF",
|
||||
encryptModeOn: "ENCRYPT_ON",
|
||||
encryptModeNotSupported: "ENCRYPT_NOT_SUP",
|
||||
encryptModeRequired: "ENCRYPT_REQ",
|
||||
encryptModeUnknown: "UNKNOWN",
|
||||
}
|
||||
|
||||
// Direct representation of the VERSION PRELOGIN token value.
|
||||
type serverVersion struct {
|
||||
Major uint8 `json:"major"`
|
||||
Minor uint8 `json:"minor"`
|
||||
BuildNumber uint16 `json:"build_number"`
|
||||
}
|
||||
|
||||
// Decode a VERSION response and return the parsed serverVersion struct
|
||||
// As defined in the MSDN docs, these come from token 0:
|
||||
// VERSION -- UL_VERSION = ((US_BUILD<<16)|(VER_SQL_MINOR<<8)|( VER_SQL_MAJOR))
|
||||
func decodeServerVersion(buf []byte) *serverVersion {
|
||||
if len(buf) != 6 {
|
||||
return nil
|
||||
}
|
||||
return &serverVersion{
|
||||
Major: buf[0],
|
||||
Minor: buf[1],
|
||||
BuildNumber: binary.BigEndian.Uint16(buf[2:4]),
|
||||
}
|
||||
}
|
||||
|
||||
// tdsHeader: an 8-byte structure prepended to all TDS packets.
|
||||
// See https://msdn.microsoft.com/en-us/library/dd340948.aspx for details.
|
||||
type tdsHeader struct {
|
||||
Type uint8
|
||||
|
||||
// Status is a bit field indicating message state.
|
||||
Status uint8
|
||||
|
||||
// "Length is the size of the packet including the 8 bytes in the packet
|
||||
// header. It is the number of bytes from the start of this header to the
|
||||
// start of the next packet header. Length is a 2-byte, unsigned short int
|
||||
// and is represented in network byte order (big-endian). Starting with TDS
|
||||
// 7.3, the Length MUST be the negotiated packet size when sending a packet
|
||||
// from client to server, unless it is the last packet of a request (that
|
||||
// is, the EOM bit in Status is ON), or the client has not logged in."
|
||||
Length uint16
|
||||
|
||||
// SPID is the process ID on the server for the current connection.
|
||||
// Provided for debugging purposes (e.g. identify which server thread sent
|
||||
// the packet).
|
||||
SPID uint16
|
||||
|
||||
// Called PacketID in the docs. Incremented (modulo 256) each time a packet
|
||||
// is sent. Allegedly ignored by the server.
|
||||
SequenceNumber uint8
|
||||
|
||||
// "This 1 byte is currently not used. This byte SHOULD be set to 0x00 and
|
||||
// SHOULD be ignored by the receiver."
|
||||
Window uint8
|
||||
}
|
||||
|
||||
// decodeTDSHeader interprets the first 8 bytes of buf as a tdsHeader.
|
||||
func decodeTDSHeader(buf []byte) (*tdsHeader, error) {
|
||||
if len(buf) < 8 {
|
||||
return nil, errBufferTooSmall
|
||||
}
|
||||
return &tdsHeader{
|
||||
Type: buf[0],
|
||||
Status: buf[1],
|
||||
Length: binary.BigEndian.Uint16(buf[2:4]),
|
||||
SPID: binary.BigEndian.Uint16(buf[4:6]),
|
||||
SequenceNumber: buf[6],
|
||||
Window: buf[7],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// readTDSHeader attempts to read 8 bytes from conn using io.ReadFull, and
|
||||
// decodes the result as a tdsHeader.
|
||||
func readTDSHeader(conn io.Reader) (*tdsHeader, error) {
|
||||
buf := make([]byte, 8)
|
||||
_, err := io.ReadFull(conn, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decodeTDSHeader(buf)
|
||||
}
|
||||
|
||||
// tdsHeader.Encode() returns the encoding of the header as a byte slice.
|
||||
func (header *tdsHeader) Encode() []byte {
|
||||
ret := make([]byte, 8)
|
||||
ret[0] = header.Type
|
||||
ret[1] = header.Status
|
||||
binary.BigEndian.PutUint16(ret[2:4], header.Length)
|
||||
binary.BigEndian.PutUint16(ret[4:6], header.SPID)
|
||||
ret[6] = header.SequenceNumber
|
||||
ret[7] = header.Window
|
||||
return ret
|
||||
}
|
||||
|
||||
// preloginOptions.HeaderSize() calculates the length of the PRELOGIN_OPTIONs /
|
||||
// the number of bytes before the payload starts.
|
||||
// Each PRELOGIN_OPTION is a 1-byte token, a 2-byte length and a 2-byte offset,
|
||||
// and each is followed by a single-byte TERMINATOR, giving 5 * len(*self) + 1.
|
||||
func (options preloginOptions) HeaderSize() int {
|
||||
return 5*len(options) + 1
|
||||
}
|
||||
|
||||
// preloginOptions.Size() returns the total size of the PRELOGIN packet body (so
|
||||
// not including the tdsPacket header).
|
||||
// Specifically, the header size + the size of all of the values.
|
||||
func (options preloginOptions) Size() int {
|
||||
// 5 bytes per option for token/offset/length + 1 byte for terminator
|
||||
ret := options.HeaderSize()
|
||||
// + actual sizes of each option
|
||||
for _, option := range options {
|
||||
ret += len(option)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// preloginOptions.GetByteOption() returns a single-byte PRELOGIN option for the
|
||||
// given token. If there is no value for that token present, or if the value is
|
||||
// not exactly one byte long, returns an errInvalidData.
|
||||
func (options preloginOptions) GetByteOption(token preloginOptionToken) (byte, error) {
|
||||
ret, ok := options[token]
|
||||
if !ok || len(ret) != 1 {
|
||||
return 0, errInvalidData
|
||||
}
|
||||
return ret[0], nil
|
||||
}
|
||||
|
||||
// preloginOptions.GetByteOption() returns a big-endian uint16 PRELOGIN option
|
||||
// for the given token. If there is no value for that token present, or if the
|
||||
// value is not exactly two bytes long, returns an errInvalidData.
|
||||
func (options preloginOptions) GetUint16Option(token preloginOptionToken) (uint16, error) {
|
||||
ret, ok := options[token]
|
||||
if !ok || len(ret) != 2 {
|
||||
return 0, errInvalidData
|
||||
}
|
||||
return binary.BigEndian.Uint16(ret[0:2]), nil
|
||||
}
|
||||
|
||||
// preloginOptions.GetVersion() decodes the VERSION response value if present;
|
||||
// if not (or invalid), returns nil
|
||||
func (options preloginOptions) GetVersion() *serverVersion {
|
||||
version, hasVersion := options[preloginVersion]
|
||||
if !hasVersion {
|
||||
return nil
|
||||
}
|
||||
return decodeServerVersion(version)
|
||||
}
|
||||
|
||||
// preloginOptions.Encode() returns the encoding of the PRELOGIN body as
|
||||
// described in https://msdn.microsoft.com/en-us/library/dd357559.aspx
|
||||
func (options preloginOptions) Encode() ([]byte, error) {
|
||||
size := options.Size()
|
||||
if size > 0xffff {
|
||||
return nil, errTooLarge
|
||||
}
|
||||
ret := make([]byte, size)
|
||||
// cursor always points to the location for the next PL_OPTION header value
|
||||
cursor := ret[0:]
|
||||
// offset always points to the next-available location for values in body,
|
||||
// starting just after the TERMINATOR token
|
||||
offset := options.HeaderSize()
|
||||
// Ensure that the tokens are encoded in ascending order
|
||||
var sortedKeys []int
|
||||
for k := range options {
|
||||
sortedKeys = append(sortedKeys, int(k))
|
||||
}
|
||||
sort.Ints(sortedKeys)
|
||||
for _, ik := range sortedKeys {
|
||||
k := preloginOptionToken(ik)
|
||||
v := options[k]
|
||||
cursor[0] = byte(k)
|
||||
if offset > 0xffff {
|
||||
return nil, errTooLarge
|
||||
}
|
||||
binary.BigEndian.PutUint16(cursor[1:3], uint16(offset))
|
||||
binary.BigEndian.PutUint16(cursor[3:5], uint16(len(v)))
|
||||
copy(ret[offset:offset+len(v)], v)
|
||||
offset += len(v)
|
||||
cursor = cursor[5:]
|
||||
}
|
||||
// Write the terminator after the last PL_OPTION header
|
||||
// (and just before the first value)
|
||||
cursor[0] = 0xff
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Decode a preloginOptions object from the given body. Any extra bytes are
|
||||
// returned in rest.
|
||||
// If body can't be decoded as a PRELOGIN body, returns nil, nil, errInvalidData
|
||||
func decodePreloginOptions(body []byte) (result *preloginOptions, rest []byte, err error) {
|
||||
cursor := body[:]
|
||||
options := make(preloginOptions)
|
||||
max := 0
|
||||
for cursor[0] != 0xff {
|
||||
if len(cursor) < 6 {
|
||||
// if the cursor is not pointing to the terminator, and we do not
|
||||
// have 5 bytes + terminator remaining, it's a bad packet
|
||||
return nil, nil, errInvalidData
|
||||
}
|
||||
token := preloginOptionToken(cursor[0])
|
||||
offset := binary.BigEndian.Uint16(cursor[1:3])
|
||||
length := binary.BigEndian.Uint16(cursor[3:5])
|
||||
if len(body) < int(offset+length) {
|
||||
return nil, nil, errInvalidData
|
||||
}
|
||||
options[token] = body[offset : offset+length]
|
||||
|
||||
if int(offset+length) > max {
|
||||
// max points to the byte after the last byte consumed in body
|
||||
max = int(offset + length)
|
||||
}
|
||||
cursor = cursor[5:]
|
||||
}
|
||||
return &options, body[max:], nil
|
||||
}
|
||||
|
||||
// preloginOptionsJSON is an auxiliary struct that holds the output format of
|
||||
// the preloginOptions
|
||||
type preloginOptionsJSON struct {
|
||||
Version *serverVersion `json:"version,omitempty"`
|
||||
|
||||
Encryption *EncryptMode `json:"encrypt_mode,omitempty"`
|
||||
Instance string `json:"instance,omitempty"`
|
||||
ThreadID *uint32 `json:"thread_id,omitempty"`
|
||||
// Using a *uint8 to distinguish 0 from undefined
|
||||
MARS *uint8 `json:"mars,omitempty"`
|
||||
TraceID []byte `json:"trace_id,omitempty"`
|
||||
FedAuthRequired *uint8 `json:"fed_auth_required,omitempty"`
|
||||
Nonce []byte `json:"nonce,omitempty"`
|
||||
Unknown []unknownPreloginOptionJSON `json:"unknown,omitempty"`
|
||||
}
|
||||
|
||||
// unknownPreloginOptionJSON holds the raw PRELOGIN token and value for unknown
|
||||
// tokens.
|
||||
type unknownPreloginOptionJSON struct {
|
||||
Token uint8 `json:"token"`
|
||||
Value []byte `json:"value"`
|
||||
}
|
||||
|
||||
// MarshalJSON() puts the map[preloginOptionToken]preloginOption into a more
|
||||
// database-friendly format.
|
||||
func (options preloginOptions) MarshalJSON() ([]byte, error) {
|
||||
opts := options
|
||||
aux := preloginOptionsJSON{}
|
||||
aux.Version = options.GetVersion()
|
||||
|
||||
theEncryptMode, hasEncrypt := opts[preloginEncryption]
|
||||
if hasEncrypt && len(theEncryptMode) == 1 {
|
||||
temp := EncryptMode(theEncryptMode[0])
|
||||
aux.Encryption = &temp
|
||||
}
|
||||
|
||||
instance, hasInstance := opts[preloginInstance]
|
||||
if hasInstance {
|
||||
aux.Instance = strings.Trim(string(instance), "\x00")
|
||||
}
|
||||
|
||||
threadID, hasThreadID := opts[preloginThreadID]
|
||||
if hasThreadID && len(threadID) == 4 {
|
||||
temp := binary.BigEndian.Uint32(threadID[:])
|
||||
aux.ThreadID = &temp
|
||||
}
|
||||
|
||||
mars, hasMars := opts[preloginMARS]
|
||||
if hasMars && len(mars) == 1 {
|
||||
aux.MARS = &mars[0]
|
||||
}
|
||||
|
||||
traceID, hasTraceID := opts[preloginTraceID]
|
||||
if hasTraceID {
|
||||
aux.TraceID = traceID
|
||||
}
|
||||
|
||||
fedAuthRequired, hasFedAuthRequired := opts[preloginFedAuthRequired]
|
||||
if hasFedAuthRequired {
|
||||
aux.FedAuthRequired = &fedAuthRequired[0]
|
||||
}
|
||||
|
||||
nonce, hasNonce := opts[preloginNonce]
|
||||
if hasNonce {
|
||||
aux.Nonce = nonce
|
||||
}
|
||||
|
||||
for k, v := range opts {
|
||||
_, ok := knownPreloginOptionTokens[k]
|
||||
if !ok {
|
||||
aux.Unknown = append(aux.Unknown, unknownPreloginOptionJSON{
|
||||
Token: uint8(k),
|
||||
Value: v,
|
||||
})
|
||||
}
|
||||
}
|
||||
return json.Marshal(aux)
|
||||
}
|
||||
|
||||
// tdsPacket is a header followed by the body. Length is calculated from the
|
||||
// start of the packet, NOT the start of the body.
|
||||
type tdsPacket struct {
|
||||
tdsHeader
|
||||
Body []byte
|
||||
}
|
||||
|
||||
// decodeTDSPacket decodes a tdsPacket from the start of buf, returning the
|
||||
// packet and any remaining bytes following it.
|
||||
func decodeTDSPacket(buf []byte) (*tdsPacket, []byte, error) {
|
||||
header, err := decodeTDSHeader(buf)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(buf) < int(header.Length) {
|
||||
return nil, nil, errBufferTooSmall
|
||||
}
|
||||
body := buf[8:header.Length]
|
||||
return &tdsPacket{
|
||||
tdsHeader: *header,
|
||||
Body: body,
|
||||
}, buf[header.Length:], nil
|
||||
}
|
||||
|
||||
// tdsPacket.Encode() returns the encoded packet: header + body. Updates the
|
||||
// header's length to match the actual body length.
|
||||
func (packet *tdsPacket) Encode() ([]byte, error) {
|
||||
if len(packet.Body)+8 > 0xffff {
|
||||
return nil, errTooLarge
|
||||
}
|
||||
packet.tdsHeader.Length = uint16(len(packet.Body) + 8)
|
||||
header := packet.tdsHeader.Encode()
|
||||
ret := append(header, packet.Body...)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// String returns a string representation of the EncryptMode.
|
||||
func (mode EncryptMode) String() string {
|
||||
ret, ok := encryptModeToString[mode]
|
||||
if !ok {
|
||||
return encryptModeToString[encryptModeUnknown]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// MarshalJSON ensures that the EncryptMode is encoded in the string format.
|
||||
func (mode EncryptMode) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(mode.String())
|
||||
}
|
||||
|
||||
// getEncryptMode returns the EncryptMode value for the given string label.
|
||||
func getEncryptMode(enum string) EncryptMode {
|
||||
ret, ok := stringToEncryptMode[enum]
|
||||
if !ok {
|
||||
return encryptModeUnknown
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Connection wraps the state of a single MSSQL connection.
|
||||
// NOT thread safe, due e.g. to the state (e.g. messageType) in tdsConnection.
|
||||
type Connection struct {
|
||||
// rawConn is the raw network connection. Both tlsConn and tdsConn wrap this
|
||||
rawConn net.Conn
|
||||
|
||||
// tlsConn is the TLS client. During the handshake, it wraps an active
|
||||
// tdsConnection. Afterwards, the inner tdsConnection is deactivated.
|
||||
tlsConn *zgrab2.TLSConnection
|
||||
|
||||
// tdsConn allows sending / receiving TDS packets through the net.Conn
|
||||
// interface. Wraps either rawConn or tlsConn.
|
||||
// The genesis of this is the fact that MSSQL requires the TLS handshake
|
||||
// packets to be wrapped in TDS headers.
|
||||
tdsConn *tdsConnection
|
||||
|
||||
// sequenceNumber is the sequence number used for the last packet (though
|
||||
// they are sent to the server mod 256).
|
||||
sequenceNumber int
|
||||
|
||||
// preloginOptions contains the values returned by the server in the
|
||||
// PRELOGIN call, once it has happened.
|
||||
preloginOptions *preloginOptions
|
||||
}
|
||||
|
||||
// SendTDSPacket sends a TDS packet with the given type and body.
|
||||
// NOTE - sets tdsConn.messageType to packetType and leaves it there.
|
||||
func (connection *Connection) SendTDSPacket(packetType uint8, body []byte) error {
|
||||
connection.sequenceNumber++
|
||||
connection.tdsConn.messageType = packetType
|
||||
_, err := connection.tdsConn.Write(body)
|
||||
return err
|
||||
}
|
||||
|
||||
// readPreloginPacket reads and decodes an entire Prelogin packet from tdsConn
|
||||
func (connection *Connection) readPreloginPacket() (*tdsPacket, *preloginOptions, error) {
|
||||
packet, err := connection.tdsConn.ReadPacket()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if packet.Type != tdsPacketTypeTabularResult {
|
||||
return packet, nil, &zgrab2.ScanError{Status: zgrab2.SCAN_APPLICATION_ERROR, Err: err}
|
||||
}
|
||||
plOptions, rest, err := decodePreloginOptions(packet.Body)
|
||||
if err != nil {
|
||||
return packet, nil, err
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
return packet, nil, errInvalidData
|
||||
}
|
||||
return packet, plOptions, nil
|
||||
}
|
||||
|
||||
// Prelogin sends the Prelogin packet and reads the response from the server.
|
||||
// It populates the connection's preloginOptions field with the response, and
|
||||
// specifically returns the ENCRYPTION value (which is used to determine whether
|
||||
// a TLS handshake needs to be done).
|
||||
func (connection *Connection) prelogin(clientEncrypt EncryptMode) (EncryptMode, error) {
|
||||
if clientEncrypt < 0 || clientEncrypt > 0xff {
|
||||
return encryptModeUnknown, errInvalidData
|
||||
}
|
||||
preloginOptions := preloginOptions{
|
||||
preloginVersion: {0, 0, 0, 0, 0, 0},
|
||||
preloginEncryption: {byte(clientEncrypt)},
|
||||
preloginInstance: {0},
|
||||
preloginThreadID: {0, 0, 0, 0},
|
||||
preloginMARS: {0},
|
||||
}
|
||||
preloginBody, err := preloginOptions.Encode()
|
||||
if err != nil {
|
||||
return encryptModeUnknown, err
|
||||
}
|
||||
err = connection.SendTDSPacket(tdsPacketTypePrelogin, preloginBody)
|
||||
|
||||
if err != nil {
|
||||
return encryptModeUnknown, err
|
||||
}
|
||||
packet, response, err := connection.readPreloginPacket()
|
||||
if response != nil {
|
||||
connection.preloginOptions = response
|
||||
}
|
||||
if err != nil {
|
||||
if packet != nil {
|
||||
// FIXME: debug packet info?
|
||||
logrus.Warnf("Got bad packet? type=0x%02x", packet.Type)
|
||||
}
|
||||
return encryptModeUnknown, err
|
||||
}
|
||||
|
||||
serverEncrypt := connection.getEncryptMode()
|
||||
|
||||
if clientEncrypt == encryptModeOn && serverEncrypt == encryptModeNotSupported {
|
||||
return serverEncrypt, errNoServerEncryption
|
||||
}
|
||||
if clientEncrypt == encryptModeNotSupported && serverEncrypt == encryptModeRequired {
|
||||
return serverEncrypt, errServerRequiresEncryption
|
||||
}
|
||||
return serverEncrypt, nil
|
||||
}
|
||||
|
||||
// Close closes / resets any resources associated with the connection, and
|
||||
// returns the first error (if any) that it encounters.
|
||||
func (connection *Connection) Close() error {
|
||||
connection.sequenceNumber = 0
|
||||
connection.tdsConn = nil
|
||||
connection.tlsConn = nil
|
||||
temp := connection.rawConn
|
||||
connection.rawConn = nil
|
||||
return temp.Close()
|
||||
}
|
||||
|
||||
// tdsConnection is an implementation of net.Conn that adapts raw input (e.g.
|
||||
// from an external library like tls.Handshake()) by adding / removing TDS
|
||||
// headers for writes / reads.
|
||||
// For example, wrapped.Write("abc") will call
|
||||
// wrapped.conn.Write(tdsHeader + "abc"), while wrapped.Read() will read
|
||||
// tdsHeader + "def" from net.Conn, then return "def" to the caller.
|
||||
// For reads, this reads entire TDS packets at a time -- blocking until it
|
||||
// can -- and returns partial packets (or data from multiple packets) as needed.
|
||||
type tdsConnection struct {
|
||||
// The underlying conn. Traffic sent to this conn is sent as-is, but when
|
||||
// using the higher-level APIs, this sends and receives TDS-wrapped packets.
|
||||
conn net.Conn
|
||||
|
||||
// The connection this wrapper is attached to.
|
||||
session *Connection
|
||||
|
||||
// If enabled == false, reads and writes to the wrapped connection pass
|
||||
// directly through to conn.
|
||||
enabled bool
|
||||
|
||||
// messageType is the header type added to written packets.
|
||||
messageType byte
|
||||
|
||||
// remainder contains bytes read from net.conn that have not yet been
|
||||
// returned to Read() calls on this instance.
|
||||
remainder []byte
|
||||
}
|
||||
|
||||
// return the lesser of a, b
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Read a single packet from the connection and return the whole packet (this is
|
||||
// the only way to see the packet type, sequence number, etc).
|
||||
func (connection *tdsConnection) ReadPacket() (*tdsPacket, error) {
|
||||
if !connection.enabled {
|
||||
return nil, errInvalidState
|
||||
}
|
||||
header, err := readTDSHeader(connection.conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf := make([]byte, header.Length-8)
|
||||
_, err = io.ReadFull(connection.conn, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tdsPacket{
|
||||
tdsHeader: *header,
|
||||
Body: buf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// The wrapped Read() call. If not enabled, just passes through to conn.Read(b).
|
||||
// If it has sufficient data in remainder to satisfy the read, just return that.
|
||||
// Otherwise, attempt to read a header (FIXME: with a 1s timeout), then block
|
||||
// oreading the entire packet and add it to the remainder.
|
||||
// Then, consume and repeat. If there is an error reading, return the error back
|
||||
// to the user with the corresponding bytes read.
|
||||
func (connection *tdsConnection) Read(b []byte) (n int, err error) {
|
||||
if !connection.enabled {
|
||||
return connection.conn.Read(b)
|
||||
}
|
||||
output := b
|
||||
soFar := 0
|
||||
for len(output) > len(connection.remainder) {
|
||||
copy(output, connection.remainder)
|
||||
output = output[len(connection.remainder):]
|
||||
soFar = soFar + len(connection.remainder)
|
||||
connection.remainder = make([]byte, 0)
|
||||
// BEGIN FIXME
|
||||
connection.conn.SetReadDeadline(time.Now().Add(1e9))
|
||||
header, err := readTDSHeader(connection.conn)
|
||||
if err != nil {
|
||||
return soFar, err
|
||||
}
|
||||
// END FIXME
|
||||
connection.remainder = make([]byte, header.Length-8)
|
||||
n, err = io.ReadFull(connection.conn, connection.remainder)
|
||||
if err != nil {
|
||||
logrus.Warn("Error reading body", err)
|
||||
return soFar, err
|
||||
}
|
||||
toCopy := min(len(output), len(connection.remainder))
|
||||
copy(output, connection.remainder[0:toCopy])
|
||||
output = output[toCopy:]
|
||||
connection.remainder = connection.remainder[toCopy:]
|
||||
soFar = soFar + toCopy
|
||||
}
|
||||
// now len(output) <= len(remainder)
|
||||
copy(output, connection.remainder)
|
||||
connection.remainder = connection.remainder[len(output):]
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// The wrapped Write method. If not enabled, just pass through to conn.Write.
|
||||
// Otherise, wrap b in a tdsHeader with the next sequence number and packet type
|
||||
// given by messageType, and send it in a single conn.Write().
|
||||
func (connection *tdsConnection) Write(b []byte) (n int, err error) {
|
||||
if !connection.enabled {
|
||||
return connection.conn.Write(b)
|
||||
}
|
||||
if len(b)+8 > 0xffff {
|
||||
return 0, errTooLarge
|
||||
}
|
||||
connection.session.sequenceNumber++
|
||||
header := tdsHeader{
|
||||
Type: connection.messageType,
|
||||
Status: tdsStatusEOM,
|
||||
Length: uint16(len(b) + 8),
|
||||
SPID: 0,
|
||||
SequenceNumber: uint8(connection.session.sequenceNumber % 0x100),
|
||||
Window: 0,
|
||||
}
|
||||
buf := header.Encode()
|
||||
output := append(buf, b...)
|
||||
ret, err := connection.conn.Write(output)
|
||||
if ret > 0 {
|
||||
ret = ret - 8
|
||||
if ret < 0 {
|
||||
ret = 0
|
||||
}
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Passthrough to the underlying connection.
|
||||
func (connection *tdsConnection) Close() error {
|
||||
return connection.conn.Close()
|
||||
}
|
||||
|
||||
// Passthrough to the underlying connection.
|
||||
func (connection *tdsConnection) LocalAddr() net.Addr {
|
||||
return connection.conn.LocalAddr()
|
||||
}
|
||||
|
||||
// Passthrough to the underlying connection.
|
||||
func (connection *tdsConnection) RemoteAddr() net.Addr {
|
||||
return connection.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
// Passthrough to the underlying connection.
|
||||
func (connection *tdsConnection) SetDeadline(t time.Time) error {
|
||||
return connection.conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
// Passthrough to the underlying connection.
|
||||
func (connection *tdsConnection) SetReadDeadline(t time.Time) error {
|
||||
return connection.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
// Passthrough to the underlying connection.
|
||||
func (connection *tdsConnection) SetWriteDeadline(t time.Time) error {
|
||||
return connection.conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
// NewConnection creates a new MSSQL connection using the given raw socket
|
||||
// connection to the database.
|
||||
func NewConnection(conn net.Conn) *Connection {
|
||||
ret := &Connection{rawConn: conn}
|
||||
ret.tdsConn = &tdsConnection{conn: conn, session: ret, enabled: true}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Login sends the LOGIN packet. Called after Handshake(). If
|
||||
// self.getEncryptMode() == encryptModeOff, disables TLS afterwards.
|
||||
// NOTE: Not currently implemented.
|
||||
func (connection *Connection) Login() {
|
||||
panic("unimplemented")
|
||||
// TODO: send login
|
||||
if connection.getEncryptMode() != encryptModeOn {
|
||||
// Client was only using encryption for login, so switch back to rawConn
|
||||
connection.tdsConn = &tdsConnection{conn: connection.rawConn, enabled: true, session: connection}
|
||||
// tdsConnection.Write(rawData) -> net.Conn.Write(header + rawData)
|
||||
// conn.Read() -> header + rawData -> tdsConnection.Read() -> rawData
|
||||
}
|
||||
}
|
||||
|
||||
// getEncryptMode returns the EncryptMode enum returned by the server in the
|
||||
// PRELOGIN step. If PRELOGIN has not yet been called or if the ENCRYPTION token
|
||||
// was not included / was invalid, returns encryptModeUnknown.
|
||||
func (connection *Connection) getEncryptMode() EncryptMode {
|
||||
if connection.preloginOptions == nil {
|
||||
return encryptModeUnknown
|
||||
}
|
||||
|
||||
ret, err := connection.preloginOptions.GetByteOption(preloginEncryption)
|
||||
if err != nil {
|
||||
return encryptModeUnknown
|
||||
}
|
||||
return EncryptMode(ret)
|
||||
}
|
||||
|
||||
// Handshake performs the initial handshake with the MSSQL server.
|
||||
// First sends the PRELOGIN packet to the server and reads the response.
|
||||
// Then, if necessary, does a TLS handshake.
|
||||
// Returns the ENCRYPTION value from the response to PRELOGIN.
|
||||
func (connection *Connection) Handshake(flags *mssqlFlags) (EncryptMode, error) {
|
||||
encryptMode := getEncryptMode(flags.EncryptMode)
|
||||
mode, err := connection.prelogin(encryptMode)
|
||||
if err != nil {
|
||||
return mode, err
|
||||
}
|
||||
connection.tdsConn.messageType = 0x12
|
||||
if mode == encryptModeNotSupported {
|
||||
return mode, nil
|
||||
}
|
||||
tlsClient, err := flags.TLSFlags.GetTLSConnection(connection.tdsConn)
|
||||
if err != nil {
|
||||
return mode, err
|
||||
}
|
||||
// do handshake: the raw TLS frames are wrapped in a TDS packet:
|
||||
// tls.Conn.Handshake() ->
|
||||
// -> tdsConnection.Write(clientHello) ->
|
||||
// -> net.Conn.Write(header + clientHello)
|
||||
//
|
||||
// net.Conn.Read() => header + serverHello ->
|
||||
// -> tdsConnection.Read() => serverHello ->
|
||||
// -> tls.Conn.Handshake()
|
||||
err = tlsClient.Handshake()
|
||||
if err != nil {
|
||||
return mode, err
|
||||
}
|
||||
// After the SSL handshake has been established, wrap packets before they
|
||||
// are passed into TLS, not after.
|
||||
|
||||
// tdsConnection.Write(rawData) ->
|
||||
// -> tls.Conn.Write(header + rawData) ->
|
||||
// -> net.Conn.Write(protected[header + rawData])
|
||||
//
|
||||
// net.Conn.Read() => protected[header + rawData] ->
|
||||
// -> tls.Conn.Read() => header + rawData ->
|
||||
// -> TDSWrappedClient.Read() => rawData
|
||||
connection.tdsConn.enabled = false
|
||||
connection.tdsConn = &tdsConnection{conn: tlsClient, enabled: true, session: connection}
|
||||
connection.tlsConn = tlsClient
|
||||
return mode, nil
|
||||
}
|
118
modules/mssql/scanner.go
Normal file
118
modules/mssql/scanner.go
Normal file
@ -0,0 +1,118 @@
|
||||
package mssql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/zmap/zgrab2"
|
||||
)
|
||||
|
||||
// HandshakeLog contains detailed information about each step of the
|
||||
// MySQL handshake, and can be encoded to JSON.
|
||||
type mssqlScanResults struct {
|
||||
Version string `json:"version,omitempty"`
|
||||
InstanceName string `json:"instance_name,omitempty"`
|
||||
TLSLog *zgrab2.TLSLog `json:"tls,omitempty"`
|
||||
PreloginOptions *preloginOptions `json:"prelogin_options,omitempty" zgrab:"debug"`
|
||||
}
|
||||
|
||||
type mssqlFlags struct {
|
||||
zgrab2.BaseFlags
|
||||
zgrab2.TLSFlags
|
||||
EncryptMode string `long:"encrypt-mode" description:"The type of encryption to request in the pre-login step. One of ENCRYPT_ON, ENCRYPT_OFF, ENCRYPT_NOT_SUP."`
|
||||
Verbose bool `long:"verbose" description:"More verbose logging, include debug fields in the scan results"`
|
||||
}
|
||||
|
||||
type mssqlModule struct {
|
||||
}
|
||||
|
||||
type mssqlScanner struct {
|
||||
config *mssqlFlags
|
||||
}
|
||||
|
||||
func init() {
|
||||
var module mssqlModule
|
||||
_, err := zgrab2.AddCommand("mssql", "MSSQL", "Grab a MSSQL handshake", 1433, &module)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (module *mssqlModule) NewFlags() interface{} {
|
||||
return new(mssqlFlags)
|
||||
}
|
||||
|
||||
func (module *mssqlModule) NewScanner() zgrab2.Scanner {
|
||||
return new(mssqlScanner)
|
||||
}
|
||||
|
||||
func (flags *mssqlFlags) Validate(args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flags *mssqlFlags) Help() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (scanner *mssqlScanner) Init(flags zgrab2.ScanFlags) error {
|
||||
f, _ := flags.(*mssqlFlags)
|
||||
scanner.config = f
|
||||
return nil
|
||||
}
|
||||
|
||||
func (scanner *mssqlScanner) InitPerSender(senderID int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (scanner *mssqlScanner) GetName() string {
|
||||
return scanner.config.Name
|
||||
}
|
||||
|
||||
func (scanner *mssqlScanner) GetPort() uint {
|
||||
return scanner.config.Port
|
||||
}
|
||||
|
||||
func (scanner *mssqlScanner) Scan(target zgrab2.ScanTarget) (zgrab2.ScanStatus, interface{}, error) {
|
||||
conn, err := target.Open(&scanner.config.BaseFlags)
|
||||
if err != nil {
|
||||
return zgrab2.TryGetScanStatus(err), nil, err
|
||||
}
|
||||
sql := NewConnection(conn)
|
||||
defer sql.Close()
|
||||
result := &mssqlScanResults{}
|
||||
_, err = sql.Handshake(scanner.config)
|
||||
|
||||
if sql.tlsConn != nil {
|
||||
result.TLSLog = sql.tlsConn.GetLog()
|
||||
}
|
||||
|
||||
if sql.preloginOptions != nil {
|
||||
result.PreloginOptions = sql.preloginOptions
|
||||
version := sql.preloginOptions.GetVersion()
|
||||
if version != nil {
|
||||
result.Version = fmt.Sprintf("%d.%d.%d", version.Major, version.Minor, version.BuildNumber)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
switch err {
|
||||
case errNoServerEncryption:
|
||||
return zgrab2.SCAN_APPLICATION_ERROR, &result, err
|
||||
case errServerRequiresEncryption:
|
||||
return zgrab2.SCAN_APPLICATION_ERROR, &result, err
|
||||
default:
|
||||
return zgrab2.TryGetScanStatus(err), &result, err
|
||||
}
|
||||
}
|
||||
return zgrab2.SCAN_SUCCESS, result, nil
|
||||
}
|
||||
|
||||
// RegisterModule is called by modules/mssql.go's init()
|
||||
func RegisterModule() {
|
||||
var module mssqlModule
|
||||
_, err := zgrab2.AddCommand("mssql", "MSSQL", "Grab a mssql handshake", 1433, &module)
|
||||
log.SetLevel(log.DebugLevel)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
@ -66,23 +66,7 @@ func (c *scanTargetConnection) Write(b []byte) (n int, err error) {
|
||||
func (t *ScanTarget) Open(flags *BaseFlags) (net.Conn, error) {
|
||||
timeout := time.Second * time.Duration(flags.Timeout)
|
||||
target := fmt.Sprintf("%s:%d", t.IP.String(), flags.Port)
|
||||
var conn net.Conn
|
||||
var err error
|
||||
if timeout > 0 {
|
||||
conn, err = net.DialTimeout("tcp", target, timeout)
|
||||
} else {
|
||||
conn, err = net.Dial("tcp", target)
|
||||
}
|
||||
if err != nil {
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &scanTargetConnection{
|
||||
Conn: conn,
|
||||
Timeout: timeout,
|
||||
}, nil
|
||||
return DialTimeoutConnection("tcp", target, timeout)
|
||||
}
|
||||
|
||||
// grabTarget calls handler for each action
|
||||
|
@ -3,3 +3,4 @@ import schemas.mysql
|
||||
import schemas.ssh
|
||||
import schemas.postgres
|
||||
import schemas.ftp
|
||||
import schemas.mssql
|
||||
|
50
schemas/mssql.py
Normal file
50
schemas/mssql.py
Normal file
@ -0,0 +1,50 @@
|
||||
# zschema sub-schema for zgrab2's mssql module
|
||||
# Registers zgrab2-mssql globally, and mssql with the main zgrab2 schema.
|
||||
from zschema.leaves import *
|
||||
from zschema.compounds import *
|
||||
import zschema.registry
|
||||
|
||||
import schemas.zcrypto as zcrypto
|
||||
import schemas.zgrab2 as zgrab2
|
||||
|
||||
ENCRYPT_MODES = [
|
||||
"ENCRYPT_OFF",
|
||||
"ENCRYPT_ON",
|
||||
"ENCRYPT_NOT_SUP",
|
||||
"ENCRYPT_REQ",
|
||||
"UNKNOWN"
|
||||
]
|
||||
|
||||
unknown_prelogin_option = SubRecord({
|
||||
"token": Unsigned8BitInteger(),
|
||||
"value": Binary(),
|
||||
})
|
||||
|
||||
prelogin_options = SubRecord({
|
||||
"version": SubRecord({
|
||||
"major": Unsigned8BitInteger(),
|
||||
"minor": Unsigned8BitInteger(),
|
||||
"build_number": Unsigned16BitInteger(),
|
||||
}),
|
||||
"encrypt_mode": Enum(values=ENCRYPT_MODES),
|
||||
"instance": String(),
|
||||
"thread_id": Unsigned32BitInteger(),
|
||||
"mars": Unsigned8BitInteger(),
|
||||
"trace_id": Binary(),
|
||||
"fed_auth_required": Unsigned8BitInteger(),
|
||||
"nonce": Binary(),
|
||||
"unknown": ListOf(unknown_prelogin_option),
|
||||
})
|
||||
|
||||
mssql_scan_response = SubRecord({
|
||||
"result": SubRecord({
|
||||
"version": String(),
|
||||
"instance_name": String(),
|
||||
"prelogin_options": prelogin_options,
|
||||
"tls": zgrab2.tls_log,
|
||||
})
|
||||
}, extends=zgrab2.base_scan_response)
|
||||
|
||||
zschema.registry.register_schema("zgrab2-mssql", mssql_scan_response)
|
||||
|
||||
zgrab2.register_scan_response_type("mssql", mssql_scan_response)
|
Loading…
Reference in New Issue
Block a user