Skip to content
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
15 changes: 15 additions & 0 deletions integration_tests/amqp091/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,19 @@ for version in $VERSIONS; do
doTest $version "--auth-user guest --auth-pass guest"
done

# TLS downgrade test: AMQP containers don't serve TLS on default port, so downgrade should fall back to plaintext
for version in $VERSIONS; do
CONTAINER_NAME="zgrab_amqp091-${version}"
OUTPUT_FILE="$ZGRAB_OUTPUT/amqp091/${version}-tls-downgrade.json"
echo "amqp091/test: Testing TLS downgrade on RabbitMQ ${version}..."
CONTAINER_NAME=$CONTAINER_NAME $ZGRAB_ROOT/docker-runner/docker-run.sh amqp091 --use-tls --allow-tls-downgrade --target-timeout 10s >$OUTPUT_FILE
SERVER_VERSION=$(jp -u data.amqp091.result.server_properties.version <$OUTPUT_FILE)
if [[ "$SERVER_VERSION" == "$version" ]]; then
echo "amqp091/test: TLS downgrade test passed: server version $SERVER_VERSION"
else
echo "amqp091/test: TLS downgrade test failed: Got $SERVER_VERSION, expected $version"
status=1
fi
done

exit $status
4 changes: 4 additions & 0 deletions integration_tests/mqtt/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ CONTAINER_NAME=$CONTAINER_NAME $ZGRAB_ROOT/docker-runner/docker-run.sh mqtt --v5
CONTAINER_NAME=$CONTAINER_NAME $ZGRAB_ROOT/docker-runner/docker-run.sh mqtt --tls -p 8883 > $ZGRAB_OUTPUT/mqtt/mqtt_tls.json
CONTAINER_NAME=$CONTAINER_NAME $ZGRAB_ROOT/docker-runner/docker-run.sh mqtt --tls --v5 -p 8883 > $ZGRAB_OUTPUT/mqtt/mqtt_tls_v5.json

# TLS downgrade tests
CONTAINER_NAME=$CONTAINER_NAME $ZGRAB_ROOT/docker-runner/docker-run.sh mqtt --tls --allow-tls-downgrade -p 1883 > $ZGRAB_OUTPUT/mqtt/tls_downgrade_plain_port.json
CONTAINER_NAME=$CONTAINER_NAME $ZGRAB_ROOT/docker-runner/docker-run.sh mqtt --tls --allow-tls-downgrade -p 8883 > $ZGRAB_OUTPUT/mqtt/tls_downgrade_tls_port.json

# Dump the docker logs
echo "mqtt/test: BEGIN docker logs from $CONTAINER_NAME [{("
docker logs --tail all $CONTAINER_NAME
Expand Down
2 changes: 2 additions & 0 deletions integration_tests/redis/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ for cfg in $configs; do
echo "redis/test: Testing $CONTAINER_NAME"
CONTAINER_NAME=$CONTAINER_NAME "$ZGRAB_ROOT/docker-runner/docker-run.sh" redis > "$ZGRAB_OUTPUT/redis/${cfg}-normal.json"
CONTAINER_NAME=$CONTAINER_NAME "$ZGRAB_ROOT/docker-runner/docker-run.sh" redis --inline > "$ZGRAB_OUTPUT/redis/${cfg}-inline.json"
# TLS downgrade: Redis containers don't support TLS, so --allow-tls-downgrade should fall back to plaintext
CONTAINER_NAME=$CONTAINER_NAME "$ZGRAB_ROOT/docker-runner/docker-run.sh" redis --use-tls --allow-tls-downgrade > "$ZGRAB_OUTPUT/redis/${cfg}-tls-downgrade.json"
# CONTAINER_NAME=$CONTAINER_NAME EXTRA_DOCKER_ARGS=$EXTRA_DOCKER_ARGS "$ZGRAB_ROOT/docker-runner/docker-run.sh" redis --mappings "$MOUNT_CONTAINER/mappings.json" > "$ZGRAB_OUTPUT/redis/${cfg}-normal-mappings.json"
# CONTAINER_NAME=$CONTAINER_NAME EXTRA_DOCKER_ARGS=$EXTRA_DOCKER_ARGS "$ZGRAB_ROOT/docker-runner/docker-run.sh" redis --inline --mappings "$MOUNT_CONTAINER/mappings.yaml" > "$ZGRAB_OUTPUT/redis/${cfg}-inline-mappings.json"
# CONTAINER_NAME=$CONTAINER_NAME EXTRA_DOCKER_ARGS=$EXTRA_DOCKER_ARGS "$ZGRAB_ROOT/docker-runner/docker-run.sh" redis --custom-commands "$MOUNT_CONTAINER/extra-commands.json" > "$ZGRAB_OUTPUT/redis/${cfg}-normal-extra.json"
Expand Down
39 changes: 39 additions & 0 deletions module.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,45 @@ func (d *DialerGroup) GetTLSDialer(ctx context.Context, t *ScanTarget) func(netw
}
}

// DialTLSDowngrade dials a TCP connection and attempts a TLS handshake. If the
// handshake fails and allowDowngrade is true, the poisoned connection is closed
// and a fresh plaintext TCP connection is returned instead. Returns the
// connection, whether TLS was successfully negotiated, and any error.
func (d *DialerGroup) DialTLSDowngrade(ctx context.Context, target *ScanTarget, allowDowngrade bool) (net.Conn, bool, error) {
if d.L4Dialer == nil {
return nil, false, errors.New("no L4 dialer set")
}
if d.TLSWrapper == nil {
return nil, false, errors.New("no TLS wrapper set")
}

addr := net.JoinHostPort(target.Host(), strconv.Itoa(int(target.Port)))

conn, err := d.L4Dialer(target)(ctx, "tcp", addr)
if err != nil {
return nil, false, fmt.Errorf("failed to connect to %v: %w", target.String(), err)
}

tlsConn, tlsErr := d.TLSWrapper(ctx, target, conn)
if tlsErr == nil {
return tlsConn, true, nil
}

// TLS handshake failed
if !allowDowngrade {
CloseConnAndHandleError(conn)
return nil, false, fmt.Errorf("TLS handshake failed for %v: %w", target.String(), tlsErr)
}

// Handshake poisoned the connection; reconnect plaintext
CloseConnAndHandleError(conn)
conn, err = d.L4Dialer(target)(ctx, "tcp", addr)
if err != nil {
return nil, false, fmt.Errorf("failed to reconnect to %v after TLS downgrade: %w", target.String(), err)
}
return conn, false, nil
}

// ScanResponse is the result of a scan on a single host
type ScanResponse struct {
// Status is required for all responses.
Expand Down
21 changes: 18 additions & 3 deletions modules/amqp091/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net"

amqpLib "github.com/rabbitmq/amqp091-go"
log "github.com/sirupsen/logrus"
Expand All @@ -20,8 +21,9 @@ type Flags struct {
AuthUser string `long:"auth-user" description:"Username to use for authentication. Must be used with --auth-pass. No auth is attempted if not provided."`
AuthPass string `long:"auth-pass" description:"Password to use for authentication. Must be used with --auth-user. No auth is attempted if not provided."`

UseTLS bool `long:"use-tls" description:"Use TLS to connect to the server. Note that AMQPS uses a different default port (5671) than AMQP (5672) and you will need to specify that port manually with -p."`
zgrab2.TLSFlags `group:"TLS Options"`
UseTLS bool `long:"use-tls" description:"Use TLS to connect to the server. Note that AMQPS uses a different default port (5671) than AMQP (5672) and you will need to specify that port manually with -p."`
AllowTLSDowngrade bool `long:"allow-tls-downgrade" description:"If --use-tls is enabled and the TLS handshake fails, fall back to plaintext instead of aborting. Requires --use-tls."`
zgrab2.TLSFlags `group:"TLS Options"`
}

// Module implements the zgrab2.Module interface.
Expand Down Expand Up @@ -128,6 +130,9 @@ func (flags *Flags) Validate(_ []string) error {
if flags.AuthPass != "" && flags.AuthUser == "" {
return errors.New("must provide --auth-user if --auth-pass is set")
}
if flags.AllowTLSDowngrade && !flags.UseTLS {
return errors.New("--allow-tls-downgrade requires --use-tls")
}
return nil
}

Expand All @@ -149,6 +154,7 @@ func (scanner *Scanner) Init(flags zgrab2.ScanFlags) error {
BaseFlags: &f.BaseFlags,
TLSEnabled: f.UseTLS,
TLSFlags: &f.TLSFlags,
NeedSeparateL4Dialer: f.AllowTLSDowngrade,
}
return nil
}
Expand Down Expand Up @@ -183,7 +189,16 @@ func (scanner *Scanner) GetScanMetadata() any {
}

func (scanner *Scanner) Scan(ctx context.Context, dialGroup *zgrab2.DialerGroup, target *zgrab2.ScanTarget) (zgrab2.ScanStatus, any, error) {
conn, err := dialGroup.Dial(ctx, target)
var (
conn net.Conn
err error
)

if scanner.config.AllowTLSDowngrade {
conn, _, err = dialGroup.DialTLSDowngrade(ctx, target, true)
} else {
conn, err = dialGroup.Dial(ctx, target)
}
if err != nil {
return zgrab2.TryGetScanStatus(err), nil, fmt.Errorf("unable to dial target (%v): %w", target.String(), err)
}
Expand Down
54 changes: 34 additions & 20 deletions modules/banner/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,20 @@ type Flags struct {
zgrab2.BaseFlags `group:"Basic Options"`
zgrab2.TLSFlags `group:"TLS Options"`

ReadTimeout int `long:"read-timeout" default:"10" description:"Read timeout in milliseconds"`
BufferSize int `long:"buffer-size" default:"8209" description:"Read buffer size in bytes"`
MaxReadSize int `long:"max-read-size" default:"512" description:"Maximum amount of data to read in KiB (1024 bytes)"`
Probe string `long:"probe" default:"\\n" description:"Probe to send to the server. Use triple slashes to escape, for example \\\\\\n is literal \\n. Mutually exclusive with --probe-file."`
ProbeFile string `long:"probe-file" description:"Read probe from file as byte array (hex). Mutually exclusive with --probe."`
Pattern string `long:"pattern" description:"Pattern to match, must be valid regexp."`
UseTLS bool `long:"tls" description:"Sends probe with TLS connection. Loads TLS module command options."`
MaxTries int `long:"max-tries" default:"1" description:"Number of tries for timeouts and connection errors before giving up. Includes making TLS connection if enabled."`
Hex bool `long:"hex" description:"Store banner value in hex. Mutually exclusive with --base64."`
Base64 bool `long:"base64" description:"Store banner value in base64. Mutually exclusive with --hex."`
MD5 bool `long:"md5" description:"Calculate MD5 hash of banner value."`
SHA1 bool `long:"sha1" description:"Calculate SHA1 hash of banner value."`
SHA256 bool `long:"sha256" description:"Calculate SHA256 hash of banner value."`
ReadTimeout int `long:"read-timeout" default:"10" description:"Read timeout in milliseconds"`
BufferSize int `long:"buffer-size" default:"8209" description:"Read buffer size in bytes"`
MaxReadSize int `long:"max-read-size" default:"512" description:"Maximum amount of data to read in KiB (1024 bytes)"`
Probe string `long:"probe" default:"\\n" description:"Probe to send to the server. Use triple slashes to escape, for example \\\\\\n is literal \\n. Mutually exclusive with --probe-file."`
ProbeFile string `long:"probe-file" description:"Read probe from file as byte array (hex). Mutually exclusive with --probe."`
Pattern string `long:"pattern" description:"Pattern to match, must be valid regexp."`
UseTLS bool `long:"tls" description:"Sends probe with TLS connection. Loads TLS module command options."`
AllowTLSDowngrade bool `long:"allow-tls-downgrade" description:"If --tls is enabled and the TLS handshake fails, fall back to plaintext instead of aborting. Requires --tls."`
MaxTries int `long:"max-tries" default:"1" description:"Number of tries for timeouts and connection errors before giving up. Includes making TLS connection if enabled."`
Hex bool `long:"hex" description:"Store banner value in hex. Mutually exclusive with --base64."`
Base64 bool `long:"base64" description:"Store banner value in base64. Mutually exclusive with --hex."`
MD5 bool `long:"md5" description:"Calculate MD5 hash of banner value."`
SHA1 bool `long:"sha1" description:"Calculate SHA1 hash of banner value."`
SHA256 bool `long:"sha256" description:"Calculate SHA256 hash of banner value."`
}

// Module is the implementation of the zgrab2.Module interface.
Expand Down Expand Up @@ -117,6 +118,10 @@ func (f *Flags) Validate(_ []string) error {
log.Fatal("Cannot set both --probe and --probe-file")
return zgrab2.ErrInvalidArguments
}
if f.AllowTLSDowngrade && !f.UseTLS {
log.Fatal("--allow-tls-downgrade requires --tls")
return zgrab2.ErrInvalidArguments
}
return nil
}

Expand Down Expand Up @@ -160,6 +165,7 @@ func (scanner *Scanner) Init(flags zgrab2.ScanFlags) error {
TransportAgnosticDialerProtocol: zgrab2.TransportTCP,
BaseFlags: &f.BaseFlags,
TLSEnabled: f.UseTLS,
NeedSeparateL4Dialer: f.AllowTLSDowngrade,
}
if f.UseTLS {
scanner.dialerGroupConfig.TLSFlags = &f.TLSFlags
Expand All @@ -175,16 +181,24 @@ func (scanner *Scanner) Scan(ctx context.Context, dialGroup *zgrab2.DialerGroup,
results Results
)

for try := 0; try < scanner.config.MaxTries; try++ {
conn, err = dialGroup.Dial(ctx, target)
if scanner.config.AllowTLSDowngrade {
conn, _, err = dialGroup.DialTLSDowngrade(ctx, target, true)
if err != nil {
continue // try again
return zgrab2.TryGetScanStatus(err), nil, err
}
} else {
for try := 0; try < scanner.config.MaxTries; try++ {
conn, err = dialGroup.Dial(ctx, target)
if err != nil {
continue
}
break
}
if err != nil {
return zgrab2.TryGetScanStatus(err), nil, fmt.Errorf("failed to connect to %v: %w", target.String(), err)
}
break
}
if err != nil {
return zgrab2.TryGetScanStatus(err), nil, fmt.Errorf("failed to connect to %v: %w", target.String(), err)
}

defer func() {
// attempt to collect TLS Log
if tlsConn, ok := conn.(*zgrab2.TLSConnection); ok {
Expand Down
22 changes: 18 additions & 4 deletions modules/fox/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ import (
// Flags holds the command-line configuration for the fox scan module.
// Populated by the framework.
type Flags struct {
zgrab2.BaseFlags `group:"Basic Options"`
zgrab2.TLSFlags `group:"TLS Options"`
UseTLS bool `long:"use-tls" description:"Sends probe with a TLS connection. Loads TLS module command options."`
zgrab2.BaseFlags `group:"Basic Options"`
zgrab2.TLSFlags `group:"TLS Options"`
UseTLS bool `long:"use-tls" description:"Sends probe with a TLS connection. Loads TLS module command options."`
AllowTLSDowngrade bool `long:"allow-tls-downgrade" description:"If --use-tls is enabled and the TLS handshake fails, fall back to plaintext instead of aborting. Requires --use-tls."`
}

// Module implements the zgrab2.Module interface.
Expand Down Expand Up @@ -62,6 +63,9 @@ func (module *Module) Description() string {
// On success, returns nil.
// On failure, returns an error instance describing the error.
func (flags *Flags) Validate(_ []string) error {
if flags.AllowTLSDowngrade && !flags.UseTLS {
return errors.New("--allow-tls-downgrade requires --use-tls")
}
return nil
}

Expand All @@ -79,6 +83,7 @@ func (scanner *Scanner) Init(flags zgrab2.ScanFlags) error {
BaseFlags: &f.BaseFlags,
TLSEnabled: scanner.config.UseTLS,
TLSFlags: &f.TLSFlags,
NeedSeparateL4Dialer: f.AllowTLSDowngrade,
}
return nil
}
Expand Down Expand Up @@ -119,7 +124,16 @@ func (scanner *Scanner) GetScanMetadata() any {
// 4. If the response has the Fox response prefix, mark the scan as having detected the service.
// 5. Attempt to read any / all of the data fields from the Log struct
func (scanner *Scanner) Scan(ctx context.Context, dialGroup *zgrab2.DialerGroup, target *zgrab2.ScanTarget) (zgrab2.ScanStatus, any, error) {
conn, err := dialGroup.Dial(ctx, target)
var (
conn net.Conn
err error
)

if scanner.config.AllowTLSDowngrade {
conn, _, err = dialGroup.DialTLSDowngrade(ctx, target, true)
} else {
conn, err = dialGroup.Dial(ctx, target)
}
if err != nil {
return zgrab2.TryGetScanStatus(err), nil, fmt.Errorf("unable to dial target (%s): %w", target.String(), err)
}
Expand Down
20 changes: 17 additions & 3 deletions modules/mqtt/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ type Flags struct {
zgrab2.BaseFlags
zgrab2.TLSFlags

V5 bool `long:"v5" description:"Scanning MQTT v5.0. Otherwise scanning MQTT v3.1.1"`
UseTLS bool `long:"tls" description:"Use TLS for the MQTT connection"`
V5 bool `long:"v5" description:"Scanning MQTT v5.0. Otherwise scanning MQTT v3.1.1"`
UseTLS bool `long:"tls" description:"Use TLS for the MQTT connection"`
AllowTLSDowngrade bool `long:"allow-tls-downgrade" description:"If --tls is enabled and the TLS handshake fails, fall back to plaintext instead of aborting. Requires --tls."`
}

// Module implements the zgrab2.Module interface.
Expand Down Expand Up @@ -75,6 +76,9 @@ func (m *Module) Description() string {

// Validate flags
func (f *Flags) Validate(_ []string) error {
if f.AllowTLSDowngrade && !f.UseTLS {
return errors.New("--allow-tls-downgrade requires --tls")
}
return nil
}

Expand Down Expand Up @@ -106,6 +110,7 @@ func (scanner *Scanner) Init(flags zgrab2.ScanFlags) error {
BaseFlags: &f.BaseFlags,
TLSEnabled: f.UseTLS,
TLSFlags: &f.TLSFlags,
NeedSeparateL4Dialer: f.AllowTLSDowngrade,
}
return nil
}
Expand Down Expand Up @@ -299,7 +304,16 @@ func readVariableByteInteger(r io.Reader) ([]byte, error) {

// Scan performs the configured scan on the MQTT server.
func (scanner *Scanner) Scan(ctx context.Context, dialGroup *zgrab2.DialerGroup, target *zgrab2.ScanTarget) (zgrab2.ScanStatus, any, error) {
conn, err := dialGroup.Dial(ctx, target)
var (
conn net.Conn
err error
)

if scanner.config.AllowTLSDowngrade {
conn, _, err = dialGroup.DialTLSDowngrade(ctx, target, true)
} else {
conn, err = dialGroup.Dial(ctx, target)
}
if err != nil {
return zgrab2.TryGetScanStatus(err), nil, fmt.Errorf("error opening connection to target %s: %w", target.String(), err)
}
Expand Down
Loading
Loading