Skip to content

Latest commit

 

History

History

README.md

ruby - authorized CTF post-exploitation orchestrator

This demo runs a lab-only CTF campaign against deliberately vulnerable local targets. The runtime drives Metasploit through msfrpc, streams each decision and tool call, captures the flag when it finds one, and writes an after-action report.

Authorized lab only. The lease scopes net.fetch and tool.call to the bundled lab CIDR. Do not point this at systems you do not own. Seriously, don't make this weird.

Showcases: provisioned credentials, strict network leases, dual budgets (USD and credits), auditable tool_call events, artifact refs for loot, and graceful budget exhaustion.

Quickstart

cp .env.example .env
make up

Submit the first bundled challenge once the client is wired:

make submit CHALLENGE=DVWA-1

The default compose file keeps the lab on an internal Docker network. The client talks to the runtime; the lab does not get internet.

Configure

Variable Default Effect
ARCP_AGENT_NAME ctf.campaign Campaign agent.
LAB_CIDR 10.13.13.0/24 Only network range the lease allows.
LAB_NETWORK_NAME arcp_ruby_lab Internal Docker network name.
MSF_HOST msf-server msfrpc host.
MSF_PORT 55553 msfrpc port.
MSF_SECRET_FILE /run/secrets/msfrpc Runtime secret path for provisioned credentials.
CHALLENGES_DIR /work/challenges Bundled challenge manifests.
LOOT_DIR /work/loot Captured artifacts and reports.
CAMPAIGN_BUDGET_USD 0.50 LLM spend ceiling.
CAMPAIGN_BUDGET_CREDITS 300 Metasploit module invocation ceiling.
PHASE_TIMEOUT_S 120 Timeout for each campaign phase.
DEFAULT_CHALLENGE DVWA-1 Challenge submitted by make submit.
OLLAMA_MODEL qwen2.5:1.5b-instruct Local planning model.
ARCP_SDK_VERSION latest Ruby gem version.

How it is wired

The runtime registers ctf.campaign and wraps msfrpc as ARCP tools:

  • msf.search
  • msf.module.aux.*
  • msf.module.exploit.*
  • msf.module.post.*
  • msf.session.*
  • loot.upload

The agent never sees the raw msfrpc password. It opens creds.msfrpc, gets a runtime-issued handle, and loses it when the job lease expires.

The campaign phases are simple:

  1. Recon.
  2. Exploit selection.
  3. Exploit.
  4. Session open.
  5. Post-exploitation.
  6. Loot.
  7. Complete.

Each phase emits a status event. Every module run emits tool_call and tool_result, so the write-up is basically the event stream with nicer formatting.

Budget story

The lease uses cost.budget: ["USD:0.50", "credits:300"]. Dollars cap inference. Credits cap external actions. Each module invocation spends one credit, which is the thing you actually care about in offensive tooling. If the DEAD_END challenge eats all 300, the campaign stops and writes what it tried.

Bundled challenges

  • samples/challenges/DVWA-1/ - low-difficulty SQL injection lab.
  • samples/challenges/CVE-2017-5638-toy/ - local Struts-style reproducer placeholder.
  • samples/challenges/DEAD_END/ - intentionally unsolvable budget-exhaustion demo.

Each challenge includes a manifest with the intended path so trainees know what winning should look like.

Where to add code

  • lib/arcp_example/runtime/agents/ctf_campaign.rb - campaign agent.
  • lib/arcp_example/runtime/tools/ - msfrpc wrappers.
  • bin/arcp-example-client - TTY-Prompt CLI.
  • samples/challenges/ - lab challenge manifests and target scaffolds.

src/ remains a build-safe scaffold for install verification.

Verify install only

make verify