Skip to content
Merged
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
151 changes: 151 additions & 0 deletions packs/techvault-participant-study/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# TechVault participant study

TechVault Participant Study is a first-party RAES environment pack for the complete enterprise
intrusion scenario authored in `sdl/techvault-participant-study.sdl.yaml`. It includes the
scenario's vulnerable customer portal, enterprise services, attacker host,
defensive SOC, seeded data, and exact content artifacts. It declares the
in-world state a conforming realization must provide without selecting how a
backend constructs or exposes that state.

The pack is named `techvault-participant-study`. It derives from TechVault and
adds a portable participant experiment. A consumer validates the pack and its associated-artifact
manifest, then resolves each SDL `content.source` by opaque artifact id. No
consumer is expected to recover content from this repository's checkout paths
or from the former APTL source tree.

Historical source attribution is recorded in `docs/lineage.md` and
`docs/provenance-ledger.yaml`; no originating backend is part of TechVault's
portable identity.

## Maturity

The pack is `built`: the complete scenario definition and byte-bound content
are present, but this repository does not yet claim golden-range proof. Golden
build, rehearsal, and participant walkthrough work is tracked separately in
[issue #237](https://github.com/OpenRAE/env-packs/issues/237).

## Validation

From the repository root:

```sh
raes-pack-validate --packs-root packs
raes-pack-release check --packs-root packs
python -m unittest tests.test_techvault_study_pack
```

The pack-local satisfaction profile in
`profiles/exact-artifact-copy-v1.json` defines the digest-bound copy route used
by every exact content requirement. Tar assets are deterministic POSIX tar
carriers whose members are materialized at the declared directory destination.

## Portable realization boundary

TechVault uses an open realization designation and carries no substrate
constraints, machine images, build recipes, host-port publications, launch
commands, environment-variable delivery, capability grants, backend mounts, or
restart policy. Product and protocol versions are declared through typed RAES
runtime state when they are known. A backend remains free to choose its own
implementation while preserving those in-world facts and exact content bytes.
That state includes service credential posture, certificate trust, and the
final ownership and modes of scenario-significant files; it does not prescribe
how a backend supplies a secret, installs trust, or reaches that final state.

TechVault likewise does not select where realization evidence is collected.
The backend must authoritatively corroborate the declared state at the RAES
verification scope, but may use native control-plane readback, daemon
observation, guest observation, or another admitted method. It selects the
least intrusive complete method allowed by realization scope; omission of a
collection-method floor does not make evidence optional.

## Flag values

Each of `victim`, `workstation`, `webapp`, `fileshare`, and `ad` declares a user
flag and a root flag in the SDL. The backend supplies fresh values for all ten
required string variables (`flag_<host>_user` and `flag_<host>_root`) when it
instantiates a run. These variables have no defaults. Sensitive `content` files
place the values; each host's `filesystem_inventory` declares their ownership
and permissions. User flags retain mode `0644`, with `labadmin` ownership on
`victim`, `dev-user` on `workstation`, and `root` on the other hosts. Root flags
are owned by `root:root` with mode `0600` at `/root/root.txt`.

Generation, signing, and verification belong to the backend. The pack supplies
no flag generator, signing keys, token format, or flag-generation service.
Required variables express the backend-supplied values while RAES's native
per-run generated-value declaration is tracked in
[rae#1276](https://github.com/OpenRAE/rae/issues/1276). The SDL owns these file
declarations; TechVault does not duplicate them in a flag placement map.

## Participant MCP sources

TechVault ships immutable source bundles for the participant tools based on APTL
commit `7c673a19f9fb6a3eb1d17305104196b600bd59cc`. The Kali workstation receives
`aptl-mcp-common` and `mcp-red`; the separate SOC workstation receives the
common package plus the seven defensive MCP packages. Each host declares a
Node.js 22 runtime. The archives contain only source, package/build manifests,
the upstream MIT license, and source metadata—no dependencies, generated build
output, tests, backend configuration, or launch wrapper. The shared telemetry
wrapper is adapted to emit only tool identity and status metadata; it never
exports request, response, or error content. The SDL selects no installation or
process-management method.

## Cortex enrichment contract

TechVault ships one exact, dependency-free offline analyzer for scenario IP
context. Typed application state declares Cortex's analysis capability, the
least-privilege TheHive service identity, and TheHive's Cortex connector. The
backend realizes that state without an initializer node or credential-delivery
recipe. Cortex owns its internal index schema; the portable scenario does not
reproduce vendor-internal state.

## Suricata content contract

The Suricata configuration and 16-rule TechVault local corpus are exact pack
artifacts. Product-provided built-in rules remain a separate source. The four
empty MISP files are clean-start seeds in an ephemeral shared volume; the
declared MISP forwarding agent may replace them and reload the engine through
the private Unix socket. A consumer must not source or copy replacement files
from an APTL/LilRAE checkout.

Static validation joins the artifact identities, content placements, selected
rule files, variables, engine inventory, generated-output path, SID namespace,
shared volumes, reload target, and evidence requirements. Live readiness still
requires realized evidence showing a successful Suricata configuration and the
selected sources and 16 active local SIDs. The declared behavioral probe sends
a participant-equivalent SQL-injection request to `/login` and requires
Suricata SID `1000010` plus the existing Wazuh correlation rule `303020`.
Passing static validation does not by itself establish that live result.

## Red-team session evidence

TechVault requires a transcript of every interactive session on the red-team
workstation, covering the commands issued and responses returned. The SDL's
`redteam-session-transcript` evidence requirement defines its scope, lifetime,
redaction, integrity, and loss-disclosure posture without selecting a capture
mechanism. A realizing backend decides how to satisfy that requirement and must
report evidence loss rather than silently treating an uncaptured session as
captured.

## Participant study sequence

This pack preserves TechVault's environment and adds two Claude Code
participants, a study controller, and four participant-directed inject
deliveries. The SDL supplies the exact sequence and instruction text:

1. red start at logical tick 1;
2. red stop at logical tick 3;
3. blue start at logical tick 5, after red has been directed to stop; and
4. blue stop at logical tick 7.

Each delivery binds its inject, event, script, story, exact logical-time window,
mixed-control transition, observation boundary, and evidence requirement. The
red and blue participants each retain one provider session across their start
and stop instructions. Their realization profile is
`participant-implementation-manifest:claude-code`; the realizing backend maps
that profile to an installed CLI and the role's admitted tool surface. The pack
contains no provider credential and no backend command line.

With this acquired pack selected, a normal backend lab start executes the
authored sequence. Delivery evidence proves dispatch and records the participant
response. It does not by itself prove that the participant observed or complied
with an instruction; those remain separate evidence claims.
94 changes: 94 additions & 0 deletions packs/techvault-participant-study/assets/content/ad_rules.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<!-- Active Directory Detection Rules for APTL Lab -->
<group name="aptl,ad,authentication,">

<!-- AD Authentication Events -->
<rule id="301000" level="3">
<decoded_as>samba</decoded_as>
<description>Samba AD authentication event</description>
<group>ad,authentication,</group>
</rule>

<!-- Failed AD login -->
<rule id="301001" level="5">
<if_sid>301000</if_sid>
<match>Authentication for user</match>
<match>FAILED</match>
<description>AD login failure: $(srcuser)</description>
<group>ad,authentication_failed,</group>
</rule>

<!-- Brute force against AD (5+ failures in 2 min) -->
<rule id="301002" level="10" frequency="5" timeframe="120">
<if_matched_sid>301001</if_matched_sid>
<same_source_ip/>
<description>AD brute force attack detected from $(srcip)</description>
<group>ad,authentication_failed,brute_force,</group>
</rule>

<!-- Kerberos TGS request (potential Kerberoasting) -->
<rule id="301010" level="3">
<decoded_as>samba</decoded_as>
<match>TGS-REQ</match>
<description>Kerberos TGS request</description>
<group>ad,kerberos,</group>
</rule>

<!-- Multiple TGS requests (Kerberoasting indicator) -->
<rule id="301011" level="10" frequency="5" timeframe="60">
<if_matched_sid>301010</if_matched_sid>
<same_source_ip/>
<description>Potential Kerberoasting: multiple TGS requests from $(srcip)</description>
<group>ad,kerberos,kerberoasting,</group>
</rule>

<!-- LDAP enumeration -->
<rule id="301020" level="3">
<decoded_as>samba</decoded_as>
<match>LDAP search</match>
<description>LDAP search query</description>
<group>ad,ldap,</group>
</rule>

<!-- Excessive LDAP queries (enumeration) -->
<rule id="301021" level="8" frequency="20" timeframe="60">
<if_matched_sid>301020</if_matched_sid>
<same_source_ip/>
<description>LDAP enumeration detected from $(srcip)</description>
<group>ad,ldap,enumeration,</group>
</rule>

<!-- Domain Admin group modification -->
<rule id="301030" level="12">
<decoded_as>samba</decoded_as>
<match>Domain Admins</match>
<match>added</match>
<description>User added to Domain Admins group</description>
<group>ad,privilege_escalation,</group>
</rule>

<!-- New user created -->
<rule id="301031" level="5">
<decoded_as>samba</decoded_as>
<match>user create</match>
<description>New AD user account created</description>
<group>ad,account_change,</group>
</rule>

<!-- Service account login (unusual) -->
<rule id="301040" level="6">
<decoded_as>samba</decoded_as>
<match>Authentication for user svc-</match>
<match>succeeded</match>
<description>Service account interactive login: $(srcuser)</description>
<group>ad,authentication,service_account,</group>
</rule>

<!-- Password change event -->
<rule id="301050" level="4">
<decoded_as>samba</decoded_as>
<match>password changed</match>
<description>AD password changed for $(srcuser)</description>
<group>ad,account_change,password,</group>
</rule>

</group>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "TechVaultScenarioContext",
"version": "1.0",
"author": "OpenRAE",
"url": "https://github.com/OpenRAE/env-packs",
"license": "Apache-2.0",
"description": "Deterministic offline context for TechVault scenario IP observables.",
"dataTypeList": [
"ip"
],
"baseConfig": "TechVaultScenarioContext",
"command": "/opt/techvault/cortex-analyzers/TechVaultScenarioContext/techvault_scenario_context.py",
"configurationItems": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/python3
"""Offline Cortex analyzer for the fixed TechVault scenario context."""

from __future__ import annotations

import json
import pathlib
import sys


ATTACKER_IP = "172.20.1.30"
ANALYZER_ID = "TechVaultScenarioContext_1_0"


def analyze(observable: str) -> dict[str, object]:
matched = observable == ATTACKER_IP
verdict = "malicious" if matched else "unknown"
level = "malicious" if matched else "info"
value = "1" if matched else "0"
return {
"success": True,
"summary": {
"taxonomies": [
{
"namespace": "TechVault",
"predicate": "ScenarioAttacker",
"value": value,
"level": level,
}
]
},
"artifacts": [],
"full": {
"analyzer": ANALYZER_ID,
"offline": True,
"observable": observable,
"scenario_role": "attacker" if matched else "unclassified",
"verdict": verdict,
},
}


def main(argv: list[str]) -> int:
if len(argv) != 2:
return 64
job_directory = pathlib.Path(argv[1])
input_path = job_directory / "input" / "input.json"
output_path = job_directory / "output" / "output.json"
try:
request = json.loads(input_path.read_text(encoding="utf-8"))
observable = request["data"]
if not isinstance(observable, str) or not observable:
raise ValueError("observable must be a non-empty string")
report = analyze(observable)
except (OSError, ValueError, KeyError, json.JSONDecodeError):
report = {"success": False, "errorMessage": "invalid analyzer input"}
output_path.write_text(
json.dumps(report, sort_keys=True, separators=(",", ":")),
encoding="utf-8",
)
return 0 if report["success"] else 65


if __name__ == "__main__":
raise SystemExit(main(sys.argv))
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!-- Database Detection Rules for APTL Lab -->
<group name="aptl,database,postgresql,">

<!-- PostgreSQL authentication failure -->
<rule id="304000" level="5">
<decoded_as>postgresql_log</decoded_as>
<match>authentication failed</match>
<description>PostgreSQL authentication failure for user $(srcuser)</description>
<group>database,authentication_failed,</group>
</rule>

<!-- PostgreSQL brute force -->
<rule id="304001" level="10" frequency="5" timeframe="120">
<if_matched_sid>304000</if_matched_sid>
<same_source_ip/>
<description>PostgreSQL brute force from $(srcip)</description>
<group>database,brute_force,</group>
</rule>

<!-- PostgreSQL connection from known attacker IPs -->
<rule id="304010" level="8">
<decoded_as>postgresql_log</decoded_as>
<match>connection received</match>
<srcip>172.20.4.30</srcip>
<description>PostgreSQL connection from red team IP: $(srcip)</description>
<group>database,unauthorized_access,</group>
</rule>

<!-- PostgreSQL error indicating SQL injection -->
<rule id="304020" level="10">
<decoded_as>postgresql_log</decoded_as>
<match>syntax error|unterminated quoted string|ERROR: invalid input syntax</match>
<description>PostgreSQL SQL error (potential injection): $(data)</description>
<group>database,sqli,</group>
</rule>

<!-- Large data export (potential exfiltration) -->
<rule id="304030" level="8">
<decoded_as>postgresql_log</decoded_as>
<match>COPY|pg_dump|SELECT.*FROM.*customers|SELECT.*FROM.*backup_config</match>
<description>Large data operation on sensitive table</description>
<group>database,data_access,exfiltration,</group>
</rule>

<!-- Schema modification -->
<rule id="304040" level="10">
<decoded_as>postgresql_log</decoded_as>
<match>DROP TABLE|ALTER TABLE|CREATE TABLE|TRUNCATE</match>
<description>Database schema modification detected</description>
<group>database,schema_change,</group>
</rule>

<!-- Privilege escalation in database -->
<rule id="304050" level="12">
<decoded_as>postgresql_log</decoded_as>
<match>GRANT|ALTER ROLE|CREATE ROLE|ALTER USER.*SUPERUSER</match>
<description>Database privilege modification detected</description>
<group>database,privilege_escalation,</group>
</rule>

</group>
Loading
Loading