diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 8c136508e..3ac6269bb 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -37,7 +37,7 @@ jobs: integration-test: name: Integration Test - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Check out source uses: actions/checkout@v4 @@ -58,17 +58,16 @@ jobs: - name: Install dependencies run: | set -e - sudo wget https://github.com/jmespath/jp/releases/download/0.2.1/jp-linux-amd64 -O /usr/local/bin/jp - sudo chmod +x /usr/local/bin/jp - # Install Python 2.7 sudo apt update - sudo apt install -y python2 - curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py - sudo python2 get-pip.py + # Install latest Python + sudo apt install -y python3 jp python3-pip + python3 -m venv venv + source venv/bin/activate # Install Python dependencies - pip2 install --user zschema - pip2 install --user -r requirements.txt + pip install zschema + pip install -r requirements.txt - name: Run tests run: | + source venv/bin/activate make integration-test diff --git a/integration_tests/mssql/cleanup.sh b/integration_tests/mssql/cleanup.sh index 95071ea2a..68a65244a 100755 --- a/integration_tests/mssql/cleanup.sh +++ b/integration_tests/mssql/cleanup.sh @@ -2,7 +2,7 @@ set +e -CONTAINER_NAME="zgrab_mssql-2017-linux" +CONTAINER_NAME="zgrab_mssql-2022-linux" echo "mssql/cleanup: Tests cleanup for mssql" diff --git a/integration_tests/mssql/setup.sh b/integration_tests/mssql/setup.sh index a4b7a0c82..754ad3971 100755 --- a/integration_tests/mssql/setup.sh +++ b/integration_tests/mssql/setup.sh @@ -3,8 +3,8 @@ echo "mssql/setup: Tests setup for mssql" CONTAINER_IMAGE="mcr.microsoft.com/mssql/server" -CONTAINER_VERSION="2017-latest" -CONTAINER_NAME="zgrab_mssql-2017-linux" +CONTAINER_VERSION="2022-latest" +CONTAINER_NAME="zgrab_mssql-2022-linux" # Supported MSSQL_PRODUCT_ID values are Developer, Express, Standard, Enterprise, EnterpriseCore MSSQL_PRODUCT_ID="Enterprise" @@ -14,7 +14,7 @@ if docker ps --filter "name=$CONTAINER_NAME" | grep $CONTAINER_NAME; then 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 +docker run -td --rm -e "MSSQL_PID=$MSSQL_PRODUCT_ID" -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=$(openssl rand -base64 12)" --platform "linux/amd64" --name $CONTAINER_NAME $CONTAINER_IMAGE:$CONTAINER_VERSION echo -n "mssql/setup: Waiting on $CONTAINER_NAME..." diff --git a/integration_tests/mssql/test.sh b/integration_tests/mssql/test.sh index 8571dfaf4..005607017 100755 --- a/integration_tests/mssql/test.sh +++ b/integration_tests/mssql/test.sh @@ -6,11 +6,11 @@ TEST_ROOT=$MODULE_DIR/.. ZGRAB_ROOT=$(git rev-parse --show-toplevel) ZGRAB_OUTPUT=$ZGRAB_ROOT/zgrab-output -CONTAINER_NAME="zgrab_mssql-2017-linux" +CONTAINER_NAME="zgrab_mssql-2022-linux" mkdir -p $ZGRAB_OUTPUT/mssql -OUTPUT_FILE="$ZGRAB_OUTPUT/mssql/2017-linux.json" +OUTPUT_FILE="$ZGRAB_OUTPUT/mssql/2022-linux.json" echo "mssql/test: Tests runner for mssql" CONTAINER_NAME=$CONTAINER_NAME $ZGRAB_ROOT/docker-runner/docker-run.sh mssql > $OUTPUT_FILE diff --git a/integration_tests/pptp/chap-secrets b/integration_tests/pptp/chap-secrets new file mode 100644 index 000000000..6bb970db4 --- /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 000000000..23837101e --- /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 000000000..a587ee90d --- /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 000000000..83b152f81 --- /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/integration_tests/test.sh b/integration_tests/test.sh index 855910233..3f9ff2c04 100755 --- a/integration_tests/test.sh +++ b/integration_tests/test.sh @@ -78,7 +78,7 @@ for protocol in $(ls $ZGRAB_OUTPUT); do echo "Validating $target [{(" cat $target echo ")}]:" - if ! python2 -m zschema validate zgrab2 $target --path . --module zgrab2_schemas.zgrab2 ; then + if ! python3 -m zschema validate zgrab2 $target --path . --module zgrab2_schemas.zgrab2 ; then echo "Schema validation failed for $protocol/$outfile" err="schema failure@$protocol/$outfile" if [[ $status -eq 0 ]]; then diff --git a/modules/http/scanner.go b/modules/http/scanner.go index 0e32d3208..f244dd3d6 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 000000000..305ce0aee --- /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 000000000..d7bdc91b1 --- /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/README.md b/zgrab2_schemas/README.md index e047b2fa1..e3e803d94 100644 --- a/zgrab2_schemas/README.md +++ b/zgrab2_schemas/README.md @@ -15,7 +15,7 @@ you can follow these steps: 4. Pass in the zgrab2 JSON file to validate * ``` echo 127.0.0.1 | ./cmd/zgrab2/zgrab2 mysql > output.json - PYTHONPATH=/path/to/zschema python2 -m zschema validate zgrab2 output.json --path . --module zgrab2_schemas.zgrab2 + PYTHONPATH=/path/to/zschema python3 -m zschema validate zgrab2 output.json --path . --module zgrab2_schemas.zgrab2 ``` ## Adding new module schemas diff --git a/zgrab2_schemas/zgrab2/__init__.py b/zgrab2_schemas/zgrab2/__init__.py index 5ca9ed17b..e0806003c 100644 --- a/zgrab2_schemas/zgrab2/__init__.py +++ b/zgrab2_schemas/zgrab2/__init__.py @@ -22,3 +22,4 @@ from . import ipp from . import banner from . import amqp091 +from . import pptp diff --git a/zgrab2_schemas/zgrab2/pptp.py b/zgrab2_schemas/zgrab2/pptp.py new file mode 100644 index 000000000..f1aef60e9 --- /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)