Skip to content

Commit 7ad37d3

Browse files
committed
added a feature to allow for manual exploitation of targets if a
metasploit expoloit is not provided. This allows for exploitation of systems that might not have know msf exploits or need to be tested
1 parent 4f075e6 commit 7ad37d3

6 files changed

Lines changed: 80 additions & 12 deletions

File tree

README.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ Each config is a JSON array of exploit entries. SETC supports two target modes:
8888
|-------|----------|-------------|
8989
| `name` | Yes | CVE or exploit identifier |
9090
| `description` | Yes | Human-readable description |
91-
| `exploit` | Yes | Metasploit module path |
91+
| `exploit` | No | Metasploit module path (omit for manual exploit mode) |
9292
| `target_image` | One of these | Docker image for single-container targets |
9393
| `yml_file` + `target_name` | One of these | Docker Compose file + target container name |
9494
| `exploit_options` | No | Additional MSF console commands (semicolon-separated) |
@@ -165,6 +165,30 @@ usage: setc [-h] [-v] [-p PASSWORD] [--volume VOLUME] [--network NETWORK]
165165
| `--cleanup_postgres` | Remove PostgreSQL container after completion |
166166
| `--cleanup_elk` | Remove Elasticsearch and Kibana containers after completion |
167167

168+
### Manual exploit mode
169+
170+
When `exploit` is omitted (or set to `""`), SETC starts the target and tcpdump capture, then pauses and waits for you to manually exploit the target from a separate terminal. Press Enter when done and SETC proceeds with cleanup, PCAP parsing, and log conversion as usual.
171+
172+
This is useful for exploits not in Metasploit, custom attack chains, or interactive research.
173+
174+
```json
175+
[
176+
{
177+
"name": "CVE-2024-XXXXX",
178+
"settings": {
179+
"description": "Manual exploitation of custom vuln",
180+
"target_image": "vuln-app:latest"
181+
}
182+
}
183+
]
184+
```
185+
186+
```bash
187+
# SETC will start the target, then prompt you:
188+
# "Press Enter when finished exploiting to continue..."
189+
python3 setc/setc.py manual_config.json --cleanup_volume --cleanup_network
190+
```
191+
168192
### Example runs
169193

170194
```bash
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[
2+
{"name":"CVE-2018-11776",
3+
"settings": {
4+
"description":"Struts2 OGNL injection RCE",
5+
"target_image":"vulhub/struts2:2.5.25"
6+
}
7+
}
8+
]

setc/runners/base.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ def __init__(self, docker_client: docker.DockerClient, network_name: str = "set_
3838
self.target_name=target_name
3939
self.exploit_success_pattern="4444"
4040
self.target_logs=None
41+
self.attack=None
42+
self.manual=False
4143

4244
def _prefixed(self, name: str) -> str:
4345
"""Return the session-prefixed version of a container name."""
@@ -111,12 +113,14 @@ def setup_all(self) -> None:
111113
self.volume_setup()
112114
self.target_setup()
113115
self.tcpdump_setup()
114-
self.attack_setup()
116+
if not self.manual:
117+
self.attack_setup()
115118

116119
def cleanup_all(self) -> None:
117120
"""Tear down target and attack containers."""
118121
self.target_cleanup()
119-
self.attack_cleanup()
122+
if self.attack is not None:
123+
self.attack_cleanup()
120124

121125
def exploit(self) -> None:
122126
"""Execute the configured Metasploit exploit against the target."""

setc/setc.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,16 @@ def validate_config(config: Any) -> list[str]:
5656
s = entry["settings"]
5757

5858
# Required string fields
59-
for field in ("description", "exploit"):
59+
for field in ("description",):
6060
if field not in s:
6161
errors.append(f"{prefix}: missing required setting '{field}'.")
6262
elif not isinstance(s[field], str):
6363
errors.append(f"{prefix}: setting '{field}' must be a string.")
6464

65+
# exploit is optional — if present it must be a non-empty string
66+
if "exploit" in s and not isinstance(s["exploit"], str):
67+
errors.append(f"{prefix}: setting 'exploit' must be a string.")
68+
6569
# Exactly one of target_image or yml_file
6670
has_image = "target_image" in s
6771
has_yml = "yml_file" in s
@@ -336,14 +340,19 @@ def main() -> None:
336340
exploit_check_count=int(system_config["settings"].get("exploit_check_count", 7))
337341
ready_delay=int(system_config["settings"].get("ready_delay", 5))
338342
ready_retries=int(system_config["settings"].get("ready_retries", 5))
343+
344+
# Detect manual mode: no exploit field or empty string
345+
manual = not system_config["settings"].get("exploit")
346+
msf_exploit = system_config["settings"].get("exploit", "")
347+
339348
setc = None
340349
setc_type = None
341350
if "yml_file" in system_config["settings"]:
342351
setc = DockerComposeMsfCli(client,
343352
vuln_name=system_config["name"],
344353
target_name=system_config["settings"]["target_name"],
345354
target_yml=system_config["settings"]["yml_file"],
346-
msf_exploit=system_config["settings"]["exploit"],
355+
msf_exploit=msf_exploit,
347356
msf_options=msf_options,
348357
msf_image=args.msf,
349358
prefix=prefix)
@@ -352,7 +361,7 @@ def main() -> None:
352361
setc = DockerMsfCli(client,
353362
name = system_config["name"],
354363
target_image=system_config["settings"]["target_image"],
355-
msf_exploit=system_config["settings"]["exploit"],
364+
msf_exploit=msf_exploit,
356365
msf_options=msf_options,
357366
delay=delay,
358367
msf_image=args.msf,
@@ -362,6 +371,9 @@ def main() -> None:
362371
if "exploit_success_pattern" in system_config["settings"]:
363372
setc.exploit_success_pattern=system_config["settings"]["exploit_success_pattern"]
364373

374+
if manual:
375+
setc.manual = True
376+
365377
########################################
366378
### Pre UP Runners ###
367379
########################################
@@ -393,9 +405,16 @@ def main() -> None:
393405
break
394406
tries += 1
395407
logger.info("Target %s is ready for exploit", system_config["name"])
396-
setc.exploit_until_success(status_delay=exploit_check_delay,
397-
status_checks=exploit_check_count,
398-
tries=exploit_retries)
408+
409+
if manual:
410+
logger.info("Manual mode: target %s is up and tcpdump is capturing traffic", system_config["name"])
411+
logger.info("Exploit the target manually, then press Enter to continue")
412+
input("Press Enter when finished exploiting to continue...")
413+
logger.info("Resuming SETC pipeline for %s", system_config["name"])
414+
else:
415+
setc.exploit_until_success(status_delay=exploit_check_delay,
416+
status_checks=exploit_check_count,
417+
tries=exploit_retries)
399418

400419
########################################
401420
### Pre Down Runners ###

setc_config_schema.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"type": "object",
2222
"title": "Exploit Settings",
2323
"description": "Configuration for the target environment and Metasploit exploit.",
24-
"required": ["description", "exploit"],
24+
"required": ["description"],
2525
"additionalProperties": true,
2626
"properties": {
2727
"description": {
@@ -32,7 +32,7 @@
3232
"exploit": {
3333
"type": "string",
3434
"title": "Metasploit Exploit Module",
35-
"description": "Metasploit module path (e.g. multi/http/struts2_multi_eval_ognl)."
35+
"description": "Metasploit module path (e.g. multi/http/struts2_multi_eval_ognl). Omit or leave empty for manual exploit mode."
3636
},
3737
"target_image": {
3838
"type": "string",

tests/test_unit.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,22 @@ def test_missing_description(self):
152152
errors = validate_config(cfg)
153153
assert any("'description'" in e for e in errors)
154154

155-
def test_missing_exploit(self):
155+
def test_valid_config_without_exploit(self):
156+
"""Config without exploit field = manual mode, should be valid."""
156157
cfg = self._minimal_docker()
157158
del cfg[0]["settings"]["exploit"]
159+
assert validate_config(cfg) == []
160+
161+
def test_valid_config_with_empty_exploit(self):
162+
"""Config with empty exploit string = manual mode, should be valid."""
163+
cfg = self._minimal_docker()
164+
cfg[0]["settings"]["exploit"] = ""
165+
assert validate_config(cfg) == []
166+
167+
def test_exploit_wrong_type(self):
168+
"""exploit field must be a string if present."""
169+
cfg = self._minimal_docker()
170+
cfg[0]["settings"]["exploit"] = 123
158171
errors = validate_config(cfg)
159172
assert any("'exploit'" in e for e in errors)
160173

0 commit comments

Comments
 (0)