From cb11784a267bb264adfb04fe66a99ad8c0ed0621 Mon Sep 17 00:00:00 2001 From: SOC-SE Date: Mon, 26 Jan 2026 22:12:39 -0600 Subject: [PATCH 1/3] Added an example configuration to the README --- README.md | 169 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 168 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 883b670c..ac74f099 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,174 @@ Custom checks can be added to the `./custom-checks/` directory. It is very commo For a detailed walkthrough of writing custom checks, see [docs/custom-checks.md](docs/custom-checks.md). +### Full Example Configuration + +Below is a complete example configuration directly taken from a non-WRCCDC invitational. This was the exact configuration used, minus ommitted passwords: + +```toml +[RequiredSettings] + EventName = "2026 SEMO Invitational" + EventType = "rvb" + DBConnectURL = "postgres://engineuser:[postgress_password]@quotient_database:5432/engine" + BindAddress = "0.0.0.0" +[MiscSettings] + EasyPCR = true + ShowDebugToBlueTeam = false + LogoImage = "/static/assets/quotient.svg" + StartPaused = true + + Delay = 60 + Jitter = 10 + + Points = 5 + Timeout = 30 + SlaThreshold = 5 + SlaPenalty = 25 +[CredlistSettings] + [[CredlistSettings.Credlist]] + CredlistName = "AD" + CredlistPath = "AD.credlist" + CredlistExplainText = "username,password" +# =========================================== +# ADMIN ACCOUNTS +# =========================================== +[[Admin]] + Name = "admin" + Pw = "Changeme_example" +# =========================================== +# TEAM ACCOUNTS +# =========================================== +[[Team]] + Name = "redteam" + Pw = "Changeme_example" +[[Team]] + Name = "guest" + PW = "Changeme_example" +[[Team]] + Name = "team1" + Pw = "Changeme_example" +[[Team]] + Name = "team2" + Pw = "Changeme_example" +[[Team]] + Name = "team3" + Pw = "Changeme_example" +[[Team]] + Name = "team4" + Pw = "Changeme_example" +[[Team]] + Name = "team5" + Pw = "Changeme_example" + +[[Team]] + Name = "team6" + Pw = "Changeme_example" + +[[Team]] + Name = "team7" + Pw = "Changeme_example" +# =========================================== +# BOX DEFINITIONS +# =========================================== +# ---- Ubuntu-Ecomm (TTP Service) ---- +[[Box]] + Name = "Ubuntu-Ecomm" + IP = "172.16._.38" + [[Box.Web]] + Display = "http" + Scheme = "http" + Port = 80 + Points = 5 + [[Box.Web.Url]] + Path = "/" + Status = 200 + + [[Box.Ssh]] + Display = "ssh" + CredLists = ["AD"] + Points = 5 + +# ---- Fedora-Webmail (SMTP, POP3) ---- +[[Box]] + Name = "Fedora-Webmail" + IP = "172.16._.17" + [[Box.Smtp]] + Display = "smtp" + Port = 25 + CredLists = ["AD"] + Domain = "@comp.local" + RequireAuth = true + Points = 5 + [[Box.Pop3]] + Display = "pop3" + Port = 110 + CredLists = ["AD"] + Domain = "@comp.local" + Points = 5 +# ---- Devuan-Web (HTTP) ---- +[[Box]] + Name = "Devuan-Web" + IP = "172.16._.21" + [[Box.Web]] + Display = "http" + Scheme = "http" + Port = 80 + Points = 5 + [[Box.Web.Url]] + Path = "/" + Status = 200 +# ---- Win-AD (DNS, SSH) ---- +[[Box]] + Name = "Win-AD" + IP = "172.16._.1" + [[Box.Dns]] + Display = "dns" + Port = 53 + Points = 5 + [[Box.Dns.Record]] + Kind = "A" + Domain = "splunk.comp.local" + Answer = ["172.16.1.20"] + + [[Box.Ssh]] + Display = "ssh" + CredLists = ["AD"] + Points = 5 +# ---- Win-FTP (FTP) ---- +[[Box]] + Name = "Win-FTP" + IP = "172.16._.50" + [[Box.Ftp]] + Display = "ftp" + Port = 21 + CredLists = ["AD"] + Points = 5 +# ---- Win-Web (HTTP) ---- +[[Box]] + Name = "Win-Web" + IP = "172.16._.150" + [[Box.Web]] + Display = "http" + Scheme = "http" + Port = 80 + Points = 5 + [[Box.Web.Url]] + Path = "/Default.aspx" + Status = 200 +# ---- Oracle-Splunk (HTTP) ---- +[[Box]] + Name = "Oracle-Splunk" + IP = "172.16._.20" + [[Box.Web]] + Display = "http" + Scheme = "http" + Port = 8000 + Points = 5 + [[Box.Web.Url]] + Path = "/en-US/account/login?return_to_=%2Fen-US%2F" + Status = 200 +``` + ## Contributing Please fork the repository and submit a pull request. For major changes, please open an issue first to discuss what you would like to change. @@ -238,4 +406,3 @@ This project is licensed under the GNU General Public License v3.0 - see the LIC ## Contact For support or questions, please open a GitHub issue. - From d5ba54a30be57871eb5595976da4f9ae75475b43 Mon Sep 17 00:00:00 2001 From: SOC-SE Date: Mon, 26 Jan 2026 22:18:25 -0600 Subject: [PATCH 2/3] Fixed spelling mistake --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ac74f099..38a009bb 100644 --- a/README.md +++ b/README.md @@ -229,7 +229,7 @@ For a detailed walkthrough of writing custom checks, see [docs/custom-checks.md] ### Full Example Configuration -Below is a complete example configuration directly taken from a non-WRCCDC invitational. This was the exact configuration used, minus ommitted passwords: +Below is a complete example configuration directly taken from a non-WRCCDC invitational. This was the exact configuration used, minus omitted passwords: ```toml [RequiredSettings] From 0e8cf768df63a3cf31d7055c4e15fc4ee143170f Mon Sep 17 00:00:00 2001 From: SOC-SE Date: Mon, 26 Jan 2026 22:22:20 -0600 Subject: [PATCH 3/3] Added AI generated documentation --- divisor.md | 377 +++++++++++++++++++++ scorechecks.md | 892 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1269 insertions(+) create mode 100644 divisor.md create mode 100644 scorechecks.md diff --git a/divisor.md b/divisor.md new file mode 100644 index 00000000..856c63c0 --- /dev/null +++ b/divisor.md @@ -0,0 +1,377 @@ +# Divisor Configuration Guide + +Divisor is an optional component that provides IP rotation for Quotient runner containers. It creates a network interface with multiple rotating IP addresses and manages NAT rules to distribute outbound traffic across those addresses. + +## Table of Contents + +- [Overview](#overview) +- [When to Use Divisor](#when-to-use-divisor) +- [Prerequisites](#prerequisites) +- [Configuration](#configuration) +- [Environment Variables](#environment-variables) +- [How It Works](#how-it-works) +- [Deployment](#deployment) +- [Troubleshooting](#troubleshooting) + +--- + +## Overview + +Divisor solves a common problem in cybersecurity competitions: preventing blue teams from identifying and blocking the scoring engine based on its source IP address. By rotating the source IP addresses used by runner containers, Divisor makes it significantly harder to distinguish scoring traffic from other network activity. + +**Key Features:** +- Creates a dedicated network interface with multiple IP addresses +- Automatically discovers Quotient runner containers +- Configures SNAT (Source NAT) rules to rotate outbound IPs +- Triggers reconfiguration at the end of each scoring round +- Randomizes source ports for additional obfuscation + +--- + +## When to Use Divisor + +**Use Divisor when:** +- Blue teams might block scoring engine IPs +- You want to simulate more realistic network traffic patterns +- Competition rules require IP rotation +- You need to distribute load across multiple source IPs + +**Skip Divisor when:** +- Running a simple practice environment +- Network isolation already prevents teams from seeing scoring traffic +- Blue teams are not expected to implement network-level blocks + +--- + +## Prerequisites + +Before configuring Divisor, ensure: + +1. **Host Network Access**: Divisor requires `network_mode: host` in Docker +2. **Privileged Mode**: Container must run with `privileged: true` to manage iptables +3. **Docker Socket**: Access to `/var/run/docker.sock` to discover runner containers +4. **Available Subnet**: A subnet range that doesn't conflict with existing networks +5. **Git Submodule**: The divisor submodule must be initialized: + +```bash +git submodule update --init --recursive +``` + +--- + +## Configuration + +Divisor is configured entirely through environment variables. These can be set in the `.env` file in your Quotient root directory. + +### Minimal Configuration + +Add these variables to your `.env` file: + +```bash +# Divisor Configuration +REDIS_ADDR=quotient_redis:6379 +REDIS_PASSWORD=your_redis_password +NUM_IPS=5 +DESIRED_SUBNET=10.192.0.0/10 +TARGET_SUBNETS=10.100.0.0/16 +INTERFACE_NAME=divisor +``` + +### Configuration with Multiple Target Subnets + +If your competition infrastructure spans multiple subnets: + +```bash +REDIS_ADDR=quotient_redis:6379 +REDIS_PASSWORD=your_redis_password +NUM_IPS=10 +DESIRED_SUBNET=192.168.200.0/24 +TARGET_SUBNETS=10.100.0.0/16,10.200.0.0/16,172.16.0.0/12 +INTERFACE_NAME=divisor +``` + +--- + +## Environment Variables + +| Variable | Required | Default | Description | +|----------|----------|---------|-------------| +| `REDIS_ADDR` | Yes | `localhost:6379` | Address of the Quotient Redis server | +| `REDIS_PASSWORD` | Yes | - | Password for Redis authentication | +| `NUM_IPS` | Yes | - | Number of IP addresses to create on the interface | +| `DESIRED_SUBNET` | Yes | - | CIDR subnet from which to allocate IPs | +| `TARGET_SUBNETS` | Yes | - | Comma-separated list of destination subnets | +| `INTERFACE_NAME` | No | `divisor` | Name for the network interface | + +### Variable Details + +#### REDIS_ADDR + +The address where Divisor can reach the Redis server. Since Divisor uses host networking, use `localhost:6379` if Redis port is exposed to the host, or the actual host IP. + +```bash +# If Redis is exposed on localhost +REDIS_ADDR=localhost:6379 + +# If using host IP +REDIS_ADDR=192.168.1.100:6379 +``` + +#### REDIS_PASSWORD + +Must match the `REDIS_PASSWORD` configured for the Quotient Redis container. + +#### NUM_IPS + +The number of IP addresses to configure on the divisor interface. This should typically match or exceed the number of runner replicas configured in `docker-compose.yml`. + +```bash +# For 5 runner replicas +NUM_IPS=5 + +# For high-availability with extra IPs +NUM_IPS=10 +``` + +#### DESIRED_SUBNET + +The CIDR subnet from which Divisor will randomly select IP addresses. Choose a subnet that: +- Does not conflict with your existing network infrastructure +- Does not overlap with team networks +- Has enough addresses for `NUM_IPS` + +```bash +# Large private subnet +DESIRED_SUBNET=10.192.0.0/10 + +# Smaller dedicated subnet +DESIRED_SUBNET=192.168.200.0/24 +``` + +#### TARGET_SUBNETS + +Comma-separated list of destination subnets that should use the divisor interface. This typically includes all team network ranges. + +```bash +# Single team subnet +TARGET_SUBNETS=10.100.0.0/16 + +# Multiple team subnets +TARGET_SUBNETS=10.100.0.0/16,10.200.0.0/16 + +# Broad range covering all teams +TARGET_SUBNETS=10.0.0.0/8 +``` + +#### INTERFACE_NAME + +The name assigned to the network interface created by Divisor. The default `divisor` works for most deployments. + +--- + +## How It Works + +### Initialization + +1. Divisor connects to Redis and subscribes to the `events` channel +2. Creates (or recreates) the network interface specified by `INTERFACE_NAME` +3. Assigns `NUM_IPS` random addresses from `DESIRED_SUBNET` to the interface +4. Each IP is validated via ICMP ping to ensure it's not already in use + +### Round-Based Rotation + +1. Quotient publishes a `round_finish` event to Redis at the end of each scoring round +2. Divisor receives the event and triggers reconfiguration +3. New random IPs are selected and assigned to the interface +4. NAT rules are updated for all runner containers + +### NAT Rule Configuration + +For each Quotient runner container: +1. Divisor discovers the container's IP address via Docker API +2. Creates SNAT rules mapping the container IP to a divisor interface IP +3. Rules target traffic destined for `TARGET_SUBNETS` +4. Source ports are randomized for additional obfuscation + +### Container Discovery + +Divisor automatically finds runner containers by matching the naming pattern `quotient-runner-*`. This aligns with how Docker Compose names replicated service containers. + +--- + +## Deployment + +### Docker Compose Configuration + +The default `docker-compose.yml` includes Divisor configuration: + +```yaml +divisor: + build: + context: divisor/ + dockerfile: Dockerfile + restart: always + env_file: + - .env + network_mode: host + depends_on: + redis: + condition: service_healthy + privileged: true + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro +``` + +### Enabling Divisor + +1. Initialize the submodule: + ```bash + git submodule update --init --recursive + ``` + +2. Add configuration to `.env`: + ```bash + # Add to existing .env file + NUM_IPS=5 + DESIRED_SUBNET=10.192.0.0/10 + TARGET_SUBNETS=10.100.0.0/16 + INTERFACE_NAME=divisor + ``` + +3. Start the stack: + ```bash + docker-compose up --build -d + ``` + +### Disabling Divisor + +To run Quotient without IP rotation, comment out or remove the divisor service from `docker-compose.yml`: + +```yaml +# divisor: +# build: +# context: divisor/ +# dockerfile: Dockerfile +# ... +``` + +--- + +## Troubleshooting + +### Divisor Container Won't Start + +**Check submodule initialization:** +```bash +ls -la divisor/ +# Should contain Dockerfile, main.go, etc. + +# If empty, initialize: +git submodule update --init --recursive +``` + +**Check environment variables:** +```bash +docker-compose config | grep -A 20 divisor +``` + +### IP Addresses Not Rotating + +**Verify Redis connectivity:** +```bash +docker logs quotient-divisor-1 2>&1 | grep -i redis +``` + +**Check for round_finish events:** +```bash +docker exec quotient_redis redis-cli -a $REDIS_PASSWORD SUBSCRIBE events +# Start a scoring round and verify events are published +``` + +### NAT Rules Not Applied + +**Check iptables on host:** +```bash +sudo iptables -t nat -L -n -v | grep -i snat +``` + +**Verify container discovery:** +```bash +docker ps --filter "name=quotient-runner" --format "{{.Names}}" +``` + +### Network Conflicts + +**Symptoms:** Connectivity issues, duplicate IP errors + +**Resolution:** +1. Choose a different `DESIRED_SUBNET` that doesn't overlap with: + - Host network interfaces + - Docker bridge networks + - Team infrastructure subnets + +2. Verify no conflicts: + ```bash + ip addr show + ip route show + ``` + +### Checking Divisor Logs + +```bash +# View recent logs +docker logs quotient-divisor-1 + +# Follow logs in real-time +docker logs -f quotient-divisor-1 + +# Check for errors +docker logs quotient-divisor-1 2>&1 | grep -i error +``` + +--- + +## Example Configurations + +### Small Competition (5 teams, single subnet) + +```bash +REDIS_ADDR=localhost:6379 +REDIS_PASSWORD=secretpassword +NUM_IPS=5 +DESIRED_SUBNET=192.168.250.0/24 +TARGET_SUBNETS=10.100.0.0/16 +INTERFACE_NAME=divisor +``` + +### Large Competition (20 teams, multiple subnets) + +```bash +REDIS_ADDR=localhost:6379 +REDIS_PASSWORD=secretpassword +NUM_IPS=20 +DESIRED_SUBNET=10.192.0.0/10 +TARGET_SUBNETS=10.100.0.0/16,10.200.0.0/16,172.16.0.0/12 +INTERFACE_NAME=divisor +``` + +### High-Availability Setup (extra IPs for failover) + +```bash +REDIS_ADDR=localhost:6379 +REDIS_PASSWORD=secretpassword +NUM_IPS=15 # 3x the number of runners +DESIRED_SUBNET=10.192.0.0/10 +TARGET_SUBNETS=10.0.0.0/8 +INTERFACE_NAME=divisor +``` + +--- + +## Security Considerations + +- Divisor requires privileged access and host networking +- The Docker socket is mounted read-only to limit exposure +- Ensure `REDIS_PASSWORD` is strong and matches across all services +- The `DESIRED_SUBNET` should not be routable from team networks +- Monitor iptables rules for unexpected modifications diff --git a/scorechecks.md b/scorechecks.md new file mode 100644 index 00000000..24a59c06 --- /dev/null +++ b/scorechecks.md @@ -0,0 +1,892 @@ +# Score Checks Configuration Guide + +This document provides detailed configuration instructions for all service checks available in Quotient. + +## Table of Contents + +- [Common Properties](#common-properties) +- [Check Types](#check-types) + - [Custom](#custom) + - [DNS](#dns) + - [FTP](#ftp) + - [IMAP](#imap) + - [LDAP](#ldap) + - [Ping](#ping) + - [POP3](#pop3) + - [RDP](#rdp) + - [SMB](#smb) + - [SMTP](#smtp) + - [SQL](#sql) + - [SSH](#ssh) + - [TCP](#tcp) + - [VNC](#vnc) + - [Web](#web) + - [WinRM](#winrm) + +--- + +## Common Properties + +All service checks inherit these common properties from the base `Service` type: + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `Display` | string | No | Check type name (lowercase) | Display name shown in the UI | +| `CredLists` | string[] | No | - | List of credential list names to use for authentication | +| `Port` | int | No | Varies by check type | Port number to connect to | +| `Points` | int | No | Global default | Points awarded for successful check | +| `Timeout` | int | No | Global default | Timeout in seconds | +| `SlaPenalty` | int | No | Global default | Points deducted for SLA violation | +| `SlaThreshold` | int | No | Global default | Number of consecutive failures before SLA penalty | +| `LaunchTime` | datetime | No | - | Time when the check should start running | +| `StopTime` | datetime | No | - | Time when the check should stop running | +| `Disabled` | bool | No | `false` | Whether the check is disabled | +| `Target` | string | No | Box IP | Override target IP/hostname (supports `_` placeholder) | +| `Attempts` | int | No | `1` | Number of check attempts per round | + +### IP Address Templating + +The `_` character in IP addresses and hostnames is replaced with the team identifier. For example: +- `172.19.12_.22` becomes `172.19.1201.22` for team01 +- `team_.example.com` becomes `team01.example.com` for team01 + +### Credential Lists + +Credential lists are CSV files stored in `config/credlists/` with the format: +``` +username,password +user1,pass1 +user2,pass2 +``` + +Reference them in checks using the `CredlistName` defined in `[CredlistSettings]`. + +--- + +## Check Types + +### Custom + +Executes a custom shell command or script. Use this for services not covered by built-in checks. + +**Default Port:** None (not applicable) + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `Command` | string | **Yes** | - | Shell command to execute | +| `Regex` | string | No | - | Regex pattern that must match command output for success | + +#### Placeholder Variables + +The following placeholders are replaced in the `Command` string: + +| Placeholder | Description | +|-------------|-------------| +| `ROUND` | Current round number | +| `TARGET` | Target IP address (with team identifier substituted) | +| `TEAMIDENTIFIER` | Team identifier (e.g., "01", "02") | +| `USERNAME` | Random username from credential list (shell-escaped) | +| `PASSWORD` | Corresponding password (shell-escaped) | + +#### Success Criteria + +- If `Regex` is specified: Command must exit 0 AND output must match regex +- If no `Regex`: Command must exit 0 + +#### Example + +```toml +[[Box]] + Name = "webserver" + IP = "10.10.1_.5" + + [[Box.Custom]] + Display = "api-health" + CredLists = ["api-users"] + Command = "curl -s -u USERNAME:PASSWORD http://TARGET:8080/health" + Regex = "\"status\":\\s*\"ok\"" + Timeout = 10 +``` + +#### Custom Check Scripts + +Place scripts in `custom-checks/` directory. Example script: + +```bash +#!/bin/sh +# Usage: ./check.sh + +ROUND="$1" +TARGET="$2" +TEAM_ID="$3" +USERNAME="$4" +PASSWORD="$5" + +# Your check logic here +curl -s "http://$TARGET/api" | grep -q "success" +exit $? +``` + +--- + +### DNS + +Queries a DNS server and validates the response. + +**Default Port:** `53` + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `Record` | DnsRecord[] | **Yes** | - | List of DNS records to check | + +#### DnsRecord Structure + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `Kind` | string | **Yes** | Record type: `"A"` or `"MX"` | +| `Domain` | string | **Yes** | Domain name to query (supports `_` placeholder) | +| `Answer` | string[] | **Yes** | Expected answers (supports `_` placeholder) | + +#### Success Criteria + +A random record is selected each round. The check passes if any expected answer matches the DNS response. + +#### Example + +```toml +[[Box]] + Name = "dns-server" + IP = "10.10.1_.2" + + [[Box.Dns]] + Display = "dns" + Port = 53 + + [[Box.Dns.Record]] + Kind = "A" + Domain = "www.team_.local" + Answer = ["10.10.1_.10"] + + [[Box.Dns.Record]] + Kind = "A" + Domain = "mail.team_.local" + Answer = ["10.10.1_.11", "10.10.1_.12"] + + [[Box.Dns.Record]] + Kind = "MX" + Domain = "team_.local" + Answer = ["mail.team_.local"] +``` + +--- + +### FTP + +Connects to an FTP server, optionally authenticates, and optionally verifies file contents. + +**Default Port:** `21` + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `File` | FtpFile[] | No | - | Files to retrieve and verify | + +#### FtpFile Structure + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `Name` | string | **Yes** | Path to the file on the FTP server | +| `Hash` | string | No | Expected SHA256 hash of file contents | +| `Regex` | string | No | Regex pattern that must match file contents | + +**Note:** Cannot specify both `Hash` and `Regex` for the same file. + +#### Success Criteria + +1. Connection and authentication succeed +2. If `File` specified: Random file is retrieved successfully +3. If `Hash` specified: File hash must match +4. If `Regex` specified: File contents must match pattern + +#### Example + +```toml +[[Box]] + Name = "ftp-server" + IP = "10.10.1_.20" + + [[Box.Ftp]] + Display = "ftp" + CredLists = ["ftp-users"] + + [[Box.Ftp.File]] + Name = "pub/readme.txt" + Regex = "Welcome to" + + [[Box.Ftp.File]] + Name = "pub/config.dat" + Hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" +``` + +#### Anonymous FTP + +If no `CredLists` specified, anonymous login is attempted with `anonymous:anonymous`. + +--- + +### IMAP + +Connects to an IMAP mail server and lists mailboxes. + +**Default Port:** `143` + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `Domain` | string | No | - | Domain suffix appended to username (e.g., `"@example.com"`) | +| `Encrypted` | bool | No | `false` | Use TLS/SSL connection | + +#### Success Criteria + +1. Connection succeeds +2. If credentials provided: Login succeeds and mailboxes can be listed + +#### Example + +```toml +[[Box]] + Name = "mail-server" + IP = "10.10.1_.30" + + [[Box.Imap]] + Display = "imap" + CredLists = ["mail-users"] + Domain = "@team_.local" + Port = 143 + + [[Box.Imap]] + Display = "imaps" + CredLists = ["mail-users"] + Domain = "@team_.local" + Port = 993 + Encrypted = true +``` + +--- + +### LDAP + +Authenticates against an LDAP/Active Directory server. + +**Default Port:** `636` + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `Domain` | string | **Yes** | - | Domain for authentication (format: `domain.tld`) | +| `Encrypted` | bool | No | `false` | Use LDAPS (TLS) connection | + +#### Authentication Format + +Credentials are bound using: `username@domain` + +#### Success Criteria + +LDAP bind operation succeeds with provided credentials. + +#### Example + +```toml +[[Box]] + Name = "domain-controller" + IP = "10.10.1_.5" + + [[Box.Ldap]] + Display = "ldap" + CredLists = ["domain-users"] + Domain = "team_.local" + Port = 389 + + [[Box.Ldap]] + Display = "ldaps" + CredLists = ["domain-users"] + Domain = "team_.local" + Port = 636 + Encrypted = true +``` + +--- + +### Ping + +Sends ICMP ping requests to verify host availability. + +**Default Port:** None (not applicable) + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `Count` | int | No | `1` | Number of ping packets to send | +| `AllowPacketLoss` | bool | No | `false` | Allow partial packet loss | +| `Percent` | int | No | `0` | Maximum allowed packet loss percentage (when `AllowPacketLoss` is true) | + +#### Success Criteria + +- If `AllowPacketLoss` is false: All packets must be received +- If `AllowPacketLoss` is true: Packet loss must be less than `Percent` + +#### Example + +```toml +[[Box]] + Name = "router" + IP = "10.10.1_.1" + + [[Box.Ping]] + Display = "ping" + Count = 3 + + [[Box.Ping]] + Display = "ping-tolerant" + Count = 5 + AllowPacketLoss = true + Percent = 40 +``` + +--- + +### POP3 + +Connects to a POP3 mail server and retrieves mailbox statistics. + +**Default Port:** `110` + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `Domain` | string | No | - | Domain suffix appended to username | +| `Encrypted` | bool | No | `false` | Use TLS/SSL connection | + +#### Success Criteria + +1. Connection succeeds +2. If credentials provided: Login succeeds and STAT command returns successfully + +#### Example + +```toml +[[Box]] + Name = "mail-server" + IP = "10.10.1_.30" + + [[Box.Pop3]] + Display = "pop3" + CredLists = ["mail-users"] + Domain = "@team_.local" + Port = 110 + + [[Box.Pop3]] + Display = "pop3s" + CredLists = ["mail-users"] + Domain = "@team_.local" + Port = 995 + Encrypted = true +``` + +--- + +### RDP + +Verifies that an RDP service is accepting connections (TCP connectivity check only). + +**Default Port:** `3389` + +No additional properties. + +#### Success Criteria + +TCP connection to the RDP port succeeds. + +#### Example + +```toml +[[Box]] + Name = "windows-server" + IP = "10.10.1_.50" + + [[Box.Rdp]] + Display = "rdp" + Port = 3389 +``` + +--- + +### SMB + +Connects to an SMB file share and optionally verifies file contents. + +**Default Port:** `445` + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `Domain` | string | No | - | Domain for NTLM authentication | +| `Share` | string | No | - | Share name to mount (required if `File` specified) | +| `File` | smbFile[] | No | - | Files to retrieve and verify | + +#### smbFile Structure + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `Name` | string | **Yes** | Path to file within the share | +| `Hash` | string | No | Expected SHA256 hash of file contents | +| `Regex` | string | No | Regex pattern that must match file contents | + +#### Success Criteria + +1. SMB connection and authentication succeed +2. If `File` specified: Share mounts and file retrieval succeeds +3. If `Hash`/`Regex` specified: File contents must match + +#### Example + +```toml +[[Box]] + Name = "file-server" + IP = "10.10.1_.40" + + [[Box.Smb]] + Display = "smb" + CredLists = ["domain-users"] + Share = "shared" + + [[Box.Smb.File]] + Name = "documents/policy.txt" + Regex = "Acceptable Use Policy" + + [[Box.Smb.File]] + Name = "data/config.ini" + Hash = "abc123..." +``` + +#### Guest Access + +If no `CredLists` specified, guest login is attempted with `guest:` (empty password). + +--- + +### SMTP + +Connects to an SMTP server and sends a test email. + +**Default Port:** `25` + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `Encrypted` | bool | No | `false` | Use TLS/SSL connection | +| `Domain` | string | No | - | Domain suffix appended to usernames | +| `RequireAuth` | bool | No | `false` | Require authentication even if server doesn't advertise AUTH | + +#### Success Criteria + +1. Connection succeeds +2. If credentials and AUTH: Authentication succeeds +3. Email is accepted by the server (MAIL FROM, RCPT TO, DATA) + +#### Example + +```toml +[[Box]] + Name = "mail-server" + IP = "10.10.1_.30" + + [[Box.Smtp]] + Display = "smtp" + CredLists = ["mail-users"] + Domain = "@team_.local" + Port = 25 + + [[Box.Smtp]] + Display = "smtps" + CredLists = ["mail-users"] + Domain = "@team_.local" + Port = 465 + Encrypted = true + RequireAuth = true +``` + +--- + +### SQL + +Connects to a SQL database server and optionally executes queries. + +**Default Port:** `3306` + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `Kind` | string | No | `"mysql"` | Database driver: `"mysql"` | +| `Query` | queryData[] | No | - | Queries to execute | + +#### queryData Structure + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `Database` | string | No | Database name to connect to | +| `Command` | string | No | SQL query to execute | +| `Output` | string | No | Expected output (first column of first matching row) | +| `UseRegex` | bool | No | Treat `Output` as regex pattern | + +#### Success Criteria + +1. Database connection and authentication succeed +2. If `Query` specified: Random query executes successfully +3. If `Output` specified: First column of result matches (exact or regex) + +#### Example + +```toml +[[Box]] + Name = "database-server" + IP = "10.10.1_.60" + + [[Box.Sql]] + Display = "mysql" + CredLists = ["db-users"] + Kind = "mysql" + Port = 3306 + + [[Box.Sql.Query]] + Database = "webapp" + Command = "SELECT COUNT(*) FROM users" + Output = "5" + + [[Box.Sql.Query]] + Database = "webapp" + Command = "SELECT version()" + Output = "^[0-9]+\\.[0-9]+" + UseRegex = true +``` + +--- + +### SSH + +Connects to an SSH server and optionally executes commands. + +**Default Port:** `22` + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `PrivKey` | string | No | - | Private key filename (in `config/scoredfiles/`) | +| `BadAttempts` | int | No | `0` | Number of failed login attempts before real attempt | +| `Command` | commandData[] | No | - | Commands to execute | + +**Note:** Cannot use both `PrivKey` and `BadAttempts`. + +#### commandData Structure + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `Command` | string | **Yes** | Shell command to execute | +| `Output` | string | No | Expected output | +| `UseRegex` | bool | No | Treat `Output` as regex pattern | +| `Contains` | bool | No | Check if output contains `Output` string | + +#### Success Criteria + +1. SSH connection and authentication succeed +2. Shell session starts successfully +3. If `Command` specified: Random command executes +4. If `Contains`: Output must contain the string +5. If `UseRegex`: Output must match regex +6. Otherwise: stderr must be empty + +#### Example + +```toml +[[Box]] + Name = "linux-server" + IP = "10.10.1_.10" + + [[Box.Ssh]] + Display = "ssh" + CredLists = ["linux-users"] + Port = 22 + + [[Box.Ssh.Command]] + Command = "whoami" + # No output check - just verifies command runs without error + + [[Box.Ssh.Command]] + Command = "cat /etc/hostname" + Output = "server" + Contains = true + + [[Box.Ssh.Command]] + Command = "uptime" + Output = "load average" + Contains = true + + [[Box.Ssh]] + Display = "ssh-key" + PrivKey = "id_rsa" + Port = 22 +``` + +--- + +### TCP + +Simple TCP connectivity check to verify a port is open. + +**Default Port:** None (required) + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `Port` | int | **Yes** | - | Port number to connect to | + +#### Success Criteria + +TCP connection succeeds within timeout. + +#### Example + +```toml +[[Box]] + Name = "app-server" + IP = "10.10.1_.70" + + [[Box.Tcp]] + Display = "app-port" + Port = 8080 + + [[Box.Tcp]] + Display = "metrics" + Port = 9090 +``` + +--- + +### VNC + +Connects to a VNC server and authenticates. + +**Default Port:** `5900` + +No additional properties. Requires `CredLists` for password authentication. + +#### Success Criteria + +VNC connection and password authentication succeed. + +#### Example + +```toml +[[Box]] + Name = "workstation" + IP = "10.10.1_.80" + + [[Box.Vnc]] + Display = "vnc" + CredLists = ["vnc-passwords"] + Port = 5900 +``` + +**Note:** VNC typically uses password-only authentication. The credential list should have the password in the second column; the username column is logged but not used for authentication. + +--- + +### Web + +Makes HTTP/HTTPS requests and validates responses. + +**Default Port:** `80` (HTTP) or `443` (HTTPS) + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `Scheme` | string | No | `"http"` | URL scheme: `"http"` or `"https"` | +| `Url` | urlData[] | **Yes** | - | URLs to check | + +#### urlData Structure + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `Path` | string | No | URL path (default: `"/"`) | +| `Status` | int | No | Expected HTTP status code | +| `Regex` | string | No | Regex pattern that must match response body | +| `Diff` | int | No | Maximum allowed difference percentage from `CompareFile` | +| `CompareFile` | string | No | File to compare response against (required if `Diff` set) | + +#### Success Criteria + +1. HTTP request succeeds +2. If `Status` specified: Response status code must match +3. If `Regex` specified: Response body must match pattern + +#### Example + +```toml +[[Box]] + Name = "web-server" + IP = "10.10.1_.100" + + [[Box.Web]] + Display = "http" + Scheme = "http" + Port = 80 + + [[Box.Web.Url]] + Path = "/" + Status = 200 + Regex = "Welcome" + + [[Box.Web.Url]] + Path = "/api/health" + Status = 200 + Regex = "\"status\":\\s*\"healthy\"" + + [[Box.Web.Url]] + Path = "/login" + Status = 200 + + [[Box.Web]] + Display = "https" + Scheme = "https" + Port = 443 + + [[Box.Web.Url]] + Path = "/" + Status = 200 +``` + +--- + +### WinRM + +Connects to Windows Remote Management and executes PowerShell commands. + +**Default Port:** `80` (HTTP) or `443` (HTTPS) + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `Encrypted` | bool | No | `false` | Use HTTPS connection | +| `BadAttempts` | int | No | `0` | Number of failed login attempts before real attempt | +| `Command` | winCommandData[] | No | - | PowerShell commands to execute | + +#### winCommandData Structure + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `Command` | string | **Yes** | PowerShell command to execute | +| `Output` | string | No | Expected output | +| `UseRegex` | bool | No | Treat `Output` as regex pattern | + +#### Success Criteria + +1. WinRM connection succeeds +2. If no commands: `hostname` test command succeeds +3. If commands: Random command executes without errors +4. If `Output` specified: Output must match (exact or regex) + +#### Example + +```toml +[[Box]] + Name = "windows-server" + IP = "10.10.1_.50" + + [[Box.Winrm]] + Display = "winrm" + CredLists = ["windows-admins"] + Port = 5985 + + [[Box.Winrm.Command]] + Command = "Get-Service W32Time | Select-Object -ExpandProperty Status" + Output = "Running" + + [[Box.Winrm.Command]] + Command = "Get-Process | Measure-Object | Select-Object -ExpandProperty Count" + Output = "^[0-9]+$" + UseRegex = true + + [[Box.Winrm]] + Display = "winrm-ssl" + CredLists = ["windows-admins"] + Port = 5986 + Encrypted = true +``` + +--- + +## Complete Configuration Example + +```toml +[RequiredSettings] + EventName = "Cyber Defense Competition" + EventType = "rvb" + DBConnectURL = "postgres://user:pass@localhost:5432/quotient" + BindAddress = "0.0.0.0" + +[MiscSettings] + Delay = 60 + Jitter = 10 + Points = 1 + Timeout = 30 + SlaThreshold = 5 + SlaPenalty = 5 + +[CredlistSettings] + [[CredlistSettings.Credlist]] + CredlistName = "linux-users" + CredlistPath = "linux.csv" + CredlistExplainText = "username,password" + + [[CredlistSettings.Credlist]] + CredlistName = "windows-users" + CredlistPath = "windows.csv" + CredlistExplainText = "username,password" + +[[Admin]] + Name = "admin" + Pw = "changeme" + +[[Team]] + Name = "team01" + Pw = "team01pass" + +[[Team]] + Name = "team02" + Pw = "team02pass" + +[[Box]] + Name = "linux-web" + IP = "10.10.1_.10" + + [[Box.Ping]] + Display = "ping" + Count = 2 + + [[Box.Ssh]] + Display = "ssh" + CredLists = ["linux-users"] + + [[Box.Web]] + Display = "http" + [[Box.Web.Url]] + Path = "/" + Status = 200 + +[[Box]] + Name = "windows-dc" + IP = "10.10.1_.20" + + [[Box.Ping]] + Display = "ping" + + [[Box.Ldap]] + Display = "ldap" + CredLists = ["windows-users"] + Domain = "team_.local" + + [[Box.Dns]] + Display = "dns" + [[Box.Dns.Record]] + Kind = "A" + Domain = "dc.team_.local" + Answer = ["10.10.1_.20"] +```