diff --git a/integration_tests/pptp/chap-secrets b/integration_tests/pptp/chap-secrets new file mode 100644 index 00000000..6bb970db --- /dev/null +++ b/integration_tests/pptp/chap-secrets @@ -0,0 +1,3 @@ +# Secrets for authentication using PAP +# client server secret acceptable local IP addresses +username * password * \ No newline at end of file diff --git a/integration_tests/pptp/cleanup.sh b/integration_tests/pptp/cleanup.sh new file mode 100755 index 00000000..23837101 --- /dev/null +++ b/integration_tests/pptp/cleanup.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set +e + +echo "pptp/cleanup: Tests cleanup for pptp" + +CONTAINER_NAME=zgrab_pptp + +docker stop $CONTAINER_NAME diff --git a/integration_tests/pptp/setup.sh b/integration_tests/pptp/setup.sh new file mode 100755 index 00000000..a587ee90 --- /dev/null +++ b/integration_tests/pptp/setup.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +echo "pptp/setup: Tests setup for pptp" + +CONTAINER_TAG="mobtitude/vpn-pptp" +CONTAINER_NAME="zgrab_pptp" + +# If the container is already running, use it. +if docker ps --filter "name=$CONTAINER_NAME" | grep -q $CONTAINER_NAME; then + echo "pptp/setup: Container $CONTAINER_NAME already running -- nothing to setup" + exit 0 +fi + +DOCKER_RUN_FLAGS="--rm --privileged --name $CONTAINER_NAME -td -v ./chap-secrets:/etc/ppp/chap-secrets" + +# If it is not running, try launching it -- on success, use that. +echo "pptp/setup: Trying to launch $CONTAINER_NAME..." +if ! docker run $DOCKER_RUN_FLAGS $CONTAINER_TAG; then + echo "failed" + # echo "pptp/setup: Building docker image $CONTAINER_TAG..." + # # If it fails, build it from ./container/Dockerfile + # docker build -t $CONTAINER_TAG ./container + # # Try again + # echo "pptp/setup: Launching $CONTAINER_NAME..." + # docker run $DOCKER_RUN_FLAGS $CONTAINER_TAG +fi diff --git a/integration_tests/pptp/test.sh b/integration_tests/pptp/test.sh new file mode 100755 index 00000000..83b152f8 --- /dev/null +++ b/integration_tests/pptp/test.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +set -e +MODULE_DIR=$(dirname $0) +ZGRAB_ROOT=$(git rev-parse --show-toplevel) +ZGRAB_OUTPUT=$ZGRAB_ROOT/zgrab-output + +mkdir -p $ZGRAB_OUTPUT/pptp + +CONTAINER_NAME=zgrab_pptp + +OUTPUT_FILE=$ZGRAB_OUTPUT/pptp/pptp.json + +echo "pptp/test: Tests runner for pptp" +# TODO FIXME: Add any necessary flags or additional tests +CONTAINER_NAME=$CONTAINER_NAME $ZGRAB_ROOT/docker-runner/docker-run.sh pptp > $OUTPUT_FILE + +# Dump the docker logs +echo "pptp/test: BEGIN docker logs from $CONTAINER_NAME [{(" +docker logs --tail all $CONTAINER_NAME +echo ")}] END docker logs from $CONTAINER_NAME" + +# TODO: If there are any other relevant log files, dump those to stdout here. diff --git a/modules/http/scanner.go b/modules/http/scanner.go index 0e32d320..f244dd3d 100644 --- a/modules/http/scanner.go +++ b/modules/http/scanner.go @@ -570,7 +570,8 @@ func (scan *scan) Grab() *zgrab2.ScanError { } // 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 { + bodyTextLen := int64(len(bodyText)) + if scan.scanner.config.FailHTTPToHTTPS && scan.results.Response.StatusCode == 400 && bodyTextLen < 1024 && bodyTextLen > 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 @@ -578,7 +579,6 @@ func (scan *scan) Grab() *zgrab2.ScanError { sliceLen = readLen } - bodyTextLen := int64(len(bodyText)) if bodyTextLen < sliceLen { sliceLen = bodyTextLen } diff --git a/modules/pptp.go b/modules/pptp.go new file mode 100644 index 00000000..305ce0ae --- /dev/null +++ b/modules/pptp.go @@ -0,0 +1,7 @@ +package modules + +import "github.com/zmap/zgrab2/modules/pptp" + +func init() { + pptp.RegisterModule() +} diff --git a/modules/pptp/scanner.go b/modules/pptp/scanner.go new file mode 100644 index 00000000..d7bdc91b --- /dev/null +++ b/modules/pptp/scanner.go @@ -0,0 +1,179 @@ +// Package pptp contains the zgrab2 Module implementation for PPTP. +package pptp + +import ( + "encoding/binary" + "fmt" + "net" + "time" + + log "github.com/sirupsen/logrus" + "github.com/zmap/zgrab2" +) + +// ScanResults is the output of the scan. +type ScanResults struct { + // Banner is the initial data banner sent by the server. + Banner string `json:"banner,omitempty"` + + // ControlMessage is the received PPTP control message. + ControlMessage string `json:"control_message,omitempty"` +} + +// Flags are the PPTP-specific command-line flags. +type Flags struct { + zgrab2.BaseFlags + Verbose bool `long:"verbose" description:"More verbose logging, include debug fields in the scan results"` +} + +// Module implements the zgrab2.Module interface. +type Module struct { +} + +// Scanner implements the zgrab2.Scanner interface, and holds the state +// for a single scan. +type Scanner struct { + config *Flags +} + +// RegisterModule registers the pptp zgrab2 module. +func RegisterModule() { + var module Module + _, err := zgrab2.AddCommand("pptp", "PPTP", module.Description(), 1723, &module) + if err != nil { + log.Fatal(err) + } +} + +// NewFlags returns the default flags object to be filled in with the +// command-line arguments. +func (m *Module) NewFlags() interface{} { + return new(Flags) +} + +// NewScanner returns a new Scanner instance. +func (m *Module) NewScanner() zgrab2.Scanner { + return new(Scanner) +} + +// Description returns an overview of this module. +func (m *Module) Description() string { + return "Scan for PPTP" +} + +// Validate flags +func (f *Flags) Validate(args []string) (err error) { + return +} + +// Help returns this module's help string. +func (f *Flags) Help() string { + return "" +} + +// Protocol returns the protocol identifier for the scanner. +func (s *Scanner) Protocol() string { + return "pptp" +} + +// Init initializes the Scanner instance with the flags from the command line. +func (s *Scanner) Init(flags zgrab2.ScanFlags) error { + f, _ := flags.(*Flags) + s.config = f + return nil +} + +// InitPerSender does nothing in this module. +func (s *Scanner) InitPerSender(senderID int) error { + return nil +} + +// GetName returns the configured name for the Scanner. +func (s *Scanner) GetName() string { + return s.config.Name +} + +// GetTrigger returns the Trigger defined in the Flags. +func (scanner *Scanner) GetTrigger() string { + return scanner.config.Trigger +} + +// PPTP Start-Control-Connection-Request message constants +const ( + PPTP_MAGIC_COOKIE = 0x1A2B3C4D + PPTP_CONTROL_MESSAGE = 1 + PPTP_START_CONN_REQUEST = 1 + PPTP_PROTOCOL_VERSION = 0x0100 // Split into two 16-bit values for binary.BigEndian.PutUint16 +) + +// Connection holds the state for a single connection to the PPTP server. +type Connection struct { + config *Flags + results ScanResults + conn net.Conn +} + +// Create the Start-Control-Connection-Request message +func createSCCRMessage() []byte { + message := make([]byte, 156) + binary.BigEndian.PutUint16(message[0:2], 156) // Length + binary.BigEndian.PutUint16(message[2:4], PPTP_CONTROL_MESSAGE) // PPTP Message Type + binary.BigEndian.PutUint32(message[4:8], PPTP_MAGIC_COOKIE) // Magic Cookie + binary.BigEndian.PutUint16(message[8:10], PPTP_START_CONN_REQUEST) // Control Message Type + binary.BigEndian.PutUint16(message[10:12], uint16(PPTP_PROTOCOL_VERSION>>16)) // Protocol Version (high 16 bits) + binary.BigEndian.PutUint16(message[12:14], uint16(PPTP_PROTOCOL_VERSION&0xFFFF)) // Protocol Version (low 16 bits) + binary.BigEndian.PutUint32(message[14:18], 0) // Framing Capabilities + binary.BigEndian.PutUint32(message[18:22], 0) // Bearer Capabilities + binary.BigEndian.PutUint16(message[22:24], 0) // Maximum Channels + binary.BigEndian.PutUint16(message[24:26], 0) // Firmware Revision + copy(message[26:90], "ZGRAB2-SCANNER") // Host Name + copy(message[90:], "ZGRAB2") // Vendor Name + return message +} + +// Read response from the PPTP server +func (pptp *Connection) readResponse() (string, error) { + buffer := make([]byte, 1024) + pptp.conn.SetReadDeadline(time.Now().Add(5 * time.Second)) + n, err := pptp.conn.Read(buffer) + if err != nil { + return "", err + } + return string(buffer[:n]), nil +} + +// Scan performs the configured scan on the PPTP server +func (s *Scanner) Scan(t zgrab2.ScanTarget) (status zgrab2.ScanStatus, result interface{}, thrown error) { + var err error + conn, err := t.Open(&s.config.BaseFlags) + if err != nil { + return zgrab2.TryGetScanStatus(err), nil, fmt.Errorf("error opening connection: %w", err) + } + cn := conn + defer func() { + cn.Close() + }() + + results := ScanResults{} + + pptp := Connection{conn: cn, config: s.config, results: results} + + // Send Start-Control-Connection-Request message + request := createSCCRMessage() + _, err = pptp.conn.Write(request) + if err != nil { + return zgrab2.TryGetScanStatus(err), &pptp.results, fmt.Errorf("error sending PPTP SCCR message: %w", err) + } + + // Read the response + response, err := pptp.readResponse() + if err != nil { + return zgrab2.TryGetScanStatus(err), &pptp.results, fmt.Errorf("error reading PPTP response: %w", err) + } + + // Store the banner and control message + pptp.results.Banner = string(request) + pptp.results.ControlMessage = response + + return zgrab2.SCAN_SUCCESS, &pptp.results, nil +} diff --git a/zgrab2_schemas/zgrab2/__init__.py b/zgrab2_schemas/zgrab2/__init__.py index c7a0e205..4a261f08 100644 --- a/zgrab2_schemas/zgrab2/__init__.py +++ b/zgrab2_schemas/zgrab2/__init__.py @@ -23,3 +23,4 @@ from . import banner from . import amqp091 from . import mqtt +from . import pptp diff --git a/zgrab2_schemas/zgrab2/pptp.py b/zgrab2_schemas/zgrab2/pptp.py new file mode 100644 index 00000000..f1aef60e --- /dev/null +++ b/zgrab2_schemas/zgrab2/pptp.py @@ -0,0 +1,25 @@ +# zschema sub-schema for zgrab2's PPTP module +# Registers zgrab2-pptp globally, and pptp with the main zgrab2 schema. +from zschema.leaves import * +from zschema.compounds import * +import zschema.registry + +from . import zgrab2 + +# Schema for ScanResults struct +pptp_scan_response = SubRecord( + { + "banner": String(), + "control_message": String(), + } +) + +pptp_scan = SubRecord( + { + "result": pptp_scan_response, + }, + extends=zgrab2.base_scan_response, +) + +zschema.registry.register_schema("zgrab2-pptp", pptp_scan) +zgrab2.register_scan_response_type("pptp", pptp_scan)