exploitfarm 1.6.8__tar.gz → 1.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/PKG-INFO +1 -1
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/__init__.py +1 -1
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/models/dbtypes.py +1 -0
- exploitfarm-1.7.0/exploitfarm/models/exploit_template.py +23 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/tui/exploitinit.py +77 -7
- exploitfarm-1.7.0/exploitfarm/tui/template.py +227 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/utils/config.py +47 -20
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/utils/reqs.py +128 -26
- exploitfarm-1.7.0/exploitfarm/utils/templates.py +124 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/xfarm.py +278 -34
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm.egg-info/PKG-INFO +1 -1
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm.egg-info/SOURCES.txt +3 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/setup.py +1 -1
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/MANIFEST.in +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/README.md +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/__main__.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/_init_exposed.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/models/__init__.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/models/client.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/models/enums.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/models/exploit.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/models/flags.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/models/groups.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/models/response.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/models/service.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/models/submitter.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/models/teams.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/tui/__init__.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/tui/config.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/tui/exploitdownload.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/tui/group_prejoin.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/tui/group_select.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/tui/login.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/tui/shared_attack_group.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/tui/startxploit.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/utils/__init__.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/utils/cmd.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/utils/exploit.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/utils/windows_close_fix.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm/xploit.py +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm.egg-info/dependency_links.txt +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm.egg-info/requires.txt +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/exploitfarm.egg-info/top_level.txt +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/requirements.txt +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/setup.cfg +0 -0
- {exploitfarm-1.6.8 → exploitfarm-1.7.0}/xfarm +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from pydantic import AwareDatetime, BaseModel, Field
|
|
2
|
+
|
|
3
|
+
from exploitfarm.models.dbtypes import ExploitTemplateID
|
|
4
|
+
from exploitfarm.models.enums import Language
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ExploitTemplateDTO(BaseModel):
|
|
8
|
+
id: ExploitTemplateID
|
|
9
|
+
name: str
|
|
10
|
+
description: str | None = None
|
|
11
|
+
language: Language
|
|
12
|
+
run: str
|
|
13
|
+
interpreter: str | None = None
|
|
14
|
+
hash: str
|
|
15
|
+
created_at: AwareDatetime
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ExploitTemplateEditForm(BaseModel):
|
|
19
|
+
name: str | None = Field(None, min_length=1)
|
|
20
|
+
description: str | None = None
|
|
21
|
+
language: Language | None = None
|
|
22
|
+
run: str | None = Field(None, min_length=1)
|
|
23
|
+
interpreter: str | None = None
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from uuid import UUID
|
|
2
3
|
from typing import List
|
|
3
4
|
|
|
@@ -11,7 +12,13 @@ from exploitfarm.utils.config import ClientConfig
|
|
|
11
12
|
from exploitfarm.models.enums import Language
|
|
12
13
|
from exploitfarm.utils.config import ExploitConfig
|
|
13
14
|
from exploitfarm.utils.config import check_exploit_config_exists
|
|
15
|
+
from exploitfarm.utils.templates import (
|
|
16
|
+
DEFAULT_TEMPLATE_ID,
|
|
17
|
+
extract_cached_template,
|
|
18
|
+
templates_for_language,
|
|
19
|
+
)
|
|
14
20
|
from rich import print
|
|
21
|
+
from rich.markup import escape
|
|
15
22
|
|
|
16
23
|
class ExploitConf(App[int]):
|
|
17
24
|
|
|
@@ -20,7 +27,8 @@ class ExploitConf(App[int]):
|
|
|
20
27
|
edit:bool,
|
|
21
28
|
name:str|None = None,
|
|
22
29
|
lang:Language|None = None,
|
|
23
|
-
service:str|None = None
|
|
30
|
+
service:str|None = None,
|
|
31
|
+
templates: list[dict] | None = None,
|
|
24
32
|
):
|
|
25
33
|
super().__init__()
|
|
26
34
|
self.config = config
|
|
@@ -37,6 +45,8 @@ class ExploitConf(App[int]):
|
|
|
37
45
|
self.title = f"xFarm - {'Editing' if self.edit else 'Creating'} Exploit"
|
|
38
46
|
self.service_name = ""
|
|
39
47
|
self.exploit_name = name if name else ""
|
|
48
|
+
self.templates = templates or []
|
|
49
|
+
self.template_id = DEFAULT_TEMPLATE_ID
|
|
40
50
|
|
|
41
51
|
BINDINGS = [
|
|
42
52
|
("ctrl+s", "save()", "Save exploit"),
|
|
@@ -101,6 +111,15 @@ class ExploitConf(App[int]):
|
|
|
101
111
|
|
|
102
112
|
yield Select(self.langs, id="lang", classes="input-label", value=self.lang, allow_blank=False)
|
|
103
113
|
yield Select(self.services, id="srv", classes="input-label", value=self.service, allow_blank=False)
|
|
114
|
+
if not self.edit:
|
|
115
|
+
yield Label("[bold]Template:[/]", classes="input-label")
|
|
116
|
+
yield Select(
|
|
117
|
+
self.template_options(),
|
|
118
|
+
id="template",
|
|
119
|
+
classes="input-label",
|
|
120
|
+
value=self.template_id,
|
|
121
|
+
allow_blank=False,
|
|
122
|
+
)
|
|
104
123
|
|
|
105
124
|
yield Label("[bold]New service name:[/]", classes="input-label hidden add-srv")
|
|
106
125
|
yield Input(
|
|
@@ -127,6 +146,33 @@ class ExploitConf(App[int]):
|
|
|
127
146
|
def show_error(self, s:str):
|
|
128
147
|
self.query_one("#error", Label).update(s)
|
|
129
148
|
self.query_one("#error", Label).remove_class("hidden")
|
|
149
|
+
|
|
150
|
+
def template_options(self):
|
|
151
|
+
custom_templates = templates_for_language(self.templates, self.lang)
|
|
152
|
+
return [("Default template", DEFAULT_TEMPLATE_ID)] + [
|
|
153
|
+
(f"{template['name']} ({template['run']})", str(template["id"]))
|
|
154
|
+
for template in custom_templates
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
def selected_template(self):
|
|
158
|
+
if self.template_id == DEFAULT_TEMPLATE_ID:
|
|
159
|
+
return None
|
|
160
|
+
return next(
|
|
161
|
+
(
|
|
162
|
+
template
|
|
163
|
+
for template in templates_for_language(self.templates, self.lang)
|
|
164
|
+
if str(template["id"]) == self.template_id
|
|
165
|
+
),
|
|
166
|
+
None,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def refresh_template_options(self) -> None:
|
|
170
|
+
if self.edit:
|
|
171
|
+
return
|
|
172
|
+
self.template_id = DEFAULT_TEMPLATE_ID
|
|
173
|
+
selector = self.query_one("#template", Select)
|
|
174
|
+
selector.set_options(self.template_options())
|
|
175
|
+
selector.value = self.template_id
|
|
130
176
|
|
|
131
177
|
@on(Select.Changed, "#lang")
|
|
132
178
|
def lang_changed(self, event: Select.Changed) -> None:
|
|
@@ -135,6 +181,7 @@ class ExploitConf(App[int]):
|
|
|
135
181
|
else:
|
|
136
182
|
self.query_one("#error", Label).add_class("hidden")
|
|
137
183
|
self.lang = event.value
|
|
184
|
+
self.refresh_template_options()
|
|
138
185
|
|
|
139
186
|
@on(Select.Changed, "#srv")
|
|
140
187
|
def srv_changed(self, event: Select.Changed) -> None:
|
|
@@ -144,6 +191,10 @@ class ExploitConf(App[int]):
|
|
|
144
191
|
self.query(".add-srv").add_class("hidden")
|
|
145
192
|
self.query_one("#srv_name_errors_box", Horizontal).add_class("hidden")
|
|
146
193
|
self.service = event.value
|
|
194
|
+
|
|
195
|
+
@on(Select.Changed, "#template")
|
|
196
|
+
def template_changed(self, event: Select.Changed) -> None:
|
|
197
|
+
self.template_id = str(event.value)
|
|
147
198
|
|
|
148
199
|
def action_save(self):
|
|
149
200
|
self.save()
|
|
@@ -156,16 +207,27 @@ class ExploitConf(App[int]):
|
|
|
156
207
|
input_forms: List[Input] = self.query(".form-input")
|
|
157
208
|
errors = []
|
|
158
209
|
for form in input_forms:
|
|
210
|
+
# Skip validating the service name input if we are using an existing service
|
|
211
|
+
if form.id == "srv_name" and self.service is not None:
|
|
212
|
+
continue
|
|
159
213
|
errors.extend([ele.failure_description for ele in form.validators if ele.failure_description and not ele.success()])
|
|
160
214
|
|
|
215
|
+
# Manually validate service name for empty/spaces if creating a new service
|
|
216
|
+
if self.service is None:
|
|
217
|
+
srv_name_val = self.query_one("#srv_name", Input).value
|
|
218
|
+
if not srv_name_val.strip():
|
|
219
|
+
errors.append("Service name cannot be empty")
|
|
220
|
+
|
|
161
221
|
if len(errors) == 0 and self.exploit_name:
|
|
222
|
+
selected_template = None
|
|
162
223
|
if self.service is None:
|
|
163
224
|
try:
|
|
164
|
-
|
|
225
|
+
srv_name_val = self.query_one("#srv_name", Input).value.strip()
|
|
226
|
+
self.service = UUID(self.config.reqs.new_service({"name": srv_name_val})["id"])
|
|
165
227
|
self.config.fetch_status()
|
|
166
228
|
except Exception as e:
|
|
167
|
-
|
|
168
|
-
|
|
229
|
+
self.show_error(f"[bold red]Failed to create service:[/] {escape(str(e))}")
|
|
230
|
+
return
|
|
169
231
|
if self.edit:
|
|
170
232
|
x_conf = ExploitConfig.read(".")
|
|
171
233
|
x_conf.name = self.exploit_name
|
|
@@ -176,7 +238,16 @@ class ExploitConf(App[int]):
|
|
|
176
238
|
self.show_error(f"[bold red]The exploit named '{self.exploit_name}' already exists")
|
|
177
239
|
return
|
|
178
240
|
x_conf = ExploitConfig.new(self.exploit_name, Language(self.lang), self.service, generate_folder=True)
|
|
179
|
-
|
|
241
|
+
selected_template = self.selected_template()
|
|
242
|
+
if selected_template:
|
|
243
|
+
x_conf.run = selected_template["run"]
|
|
244
|
+
x_conf.interpreter = selected_template.get("interpreter")
|
|
245
|
+
os.makedirs(x_conf.folder, exist_ok=True)
|
|
246
|
+
extract_cached_template(selected_template, x_conf.folder)
|
|
247
|
+
x_conf.write(
|
|
248
|
+
exists_ok=True,
|
|
249
|
+
create_default_source=not self.edit and selected_template is None,
|
|
250
|
+
)
|
|
180
251
|
try:
|
|
181
252
|
x_conf.publish_exploit(self.config)
|
|
182
253
|
except Exception:
|
|
@@ -184,7 +255,7 @@ class ExploitConf(App[int]):
|
|
|
184
255
|
return
|
|
185
256
|
self.exit(0)
|
|
186
257
|
else:
|
|
187
|
-
self.show_error(f"[bold red]{', '.join(errors)}")
|
|
258
|
+
self.show_error(f"[bold red]{escape(', '.join(errors))}")
|
|
188
259
|
|
|
189
260
|
@on(Button.Pressed, "#cancel")
|
|
190
261
|
def cancel(self):
|
|
@@ -212,4 +283,3 @@ class ExploitConf(App[int]):
|
|
|
212
283
|
else:
|
|
213
284
|
error_box.add_class("hidden")
|
|
214
285
|
self.service_name = event.input.value
|
|
215
|
-
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from rich.markup import escape
|
|
5
|
+
from textual import on
|
|
6
|
+
from textual.app import App, ComposeResult
|
|
7
|
+
from textual.containers import Horizontal
|
|
8
|
+
from textual.widgets import Button, Footer, Header, Input, Label, OptionList, Select
|
|
9
|
+
|
|
10
|
+
from exploitfarm.models.enums import Language
|
|
11
|
+
from exploitfarm.utils.exploit import get_filename, get_interpreter
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TemplateMetadataForm(App[dict[str, Any] | None]):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
path: str | None = None,
|
|
18
|
+
template: dict[str, Any] | None = None,
|
|
19
|
+
):
|
|
20
|
+
super().__init__()
|
|
21
|
+
self.path = path
|
|
22
|
+
self.template = template or {}
|
|
23
|
+
self.edit = template is not None
|
|
24
|
+
self.title = f"xFarm - {'Edit' if self.edit else 'Add'} Exploit Template"
|
|
25
|
+
self.langs = [(lang.value, lang.value) for lang in list(Language)]
|
|
26
|
+
self.template_name = self.template.get("name") or self.default_name(path)
|
|
27
|
+
self.description = self.template.get("description") or ""
|
|
28
|
+
self.language = self.template.get("language") or Language.python.value
|
|
29
|
+
self.run_file = self.template.get("run") or get_filename(Language(self.language))
|
|
30
|
+
self.interpreter = (
|
|
31
|
+
self.template.get("interpreter")
|
|
32
|
+
if self.edit
|
|
33
|
+
else get_interpreter(Language(self.language))
|
|
34
|
+
) or ""
|
|
35
|
+
|
|
36
|
+
BINDINGS = [
|
|
37
|
+
("ctrl+s", "save()", "Save template"),
|
|
38
|
+
("ctrl+c", "cancel()", "Cancel operation"),
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
CSS = """
|
|
42
|
+
Input, Select {
|
|
43
|
+
margin: 1 2;
|
|
44
|
+
}
|
|
45
|
+
.input-label {
|
|
46
|
+
margin: 1 2;
|
|
47
|
+
}
|
|
48
|
+
#error {
|
|
49
|
+
margin: 1 2;
|
|
50
|
+
}
|
|
51
|
+
.button-box {
|
|
52
|
+
height: 4;
|
|
53
|
+
margin: 2 0;
|
|
54
|
+
margin-top: 1;
|
|
55
|
+
}
|
|
56
|
+
Button {
|
|
57
|
+
margin: 1 2;
|
|
58
|
+
}
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def default_name(path: str | None) -> str:
|
|
63
|
+
if path is None:
|
|
64
|
+
return ""
|
|
65
|
+
abs_path = os.path.abspath(path)
|
|
66
|
+
if os.path.basename(abs_path):
|
|
67
|
+
return os.path.basename(abs_path)
|
|
68
|
+
return os.path.basename(os.path.dirname(abs_path))
|
|
69
|
+
|
|
70
|
+
def compose(self) -> ComposeResult:
|
|
71
|
+
yield Header(f"xFarm - {'Edit' if self.edit else 'Add'} Exploit Template")
|
|
72
|
+
if self.path:
|
|
73
|
+
yield Label(
|
|
74
|
+
f"[bold]Template source:[/] [gray62]{escape(os.path.abspath(self.path))}[/]",
|
|
75
|
+
classes="input-label",
|
|
76
|
+
)
|
|
77
|
+
yield Label("[bold]Template name:[/]", classes="input-label")
|
|
78
|
+
yield Input(
|
|
79
|
+
value=self.template_name,
|
|
80
|
+
placeholder="Python requests skeleton",
|
|
81
|
+
id="name",
|
|
82
|
+
)
|
|
83
|
+
yield Label("[bold]Description:[/]", classes="input-label")
|
|
84
|
+
yield Input(
|
|
85
|
+
value=self.description,
|
|
86
|
+
placeholder="Optional description",
|
|
87
|
+
id="description",
|
|
88
|
+
)
|
|
89
|
+
yield Label("[bold]Language:[/]", classes="input-label")
|
|
90
|
+
yield Select(self.langs, value=self.language, allow_blank=False, id="language")
|
|
91
|
+
yield Label("[bold]Run file:[/]", classes="input-label")
|
|
92
|
+
yield Input(value=self.run_file, placeholder="main.py", id="run")
|
|
93
|
+
yield Label("[bold]Interpreter:[/]", classes="input-label")
|
|
94
|
+
yield Input(value=self.interpreter, placeholder="python3", id="interpreter")
|
|
95
|
+
yield Label("", id="error")
|
|
96
|
+
yield Horizontal(
|
|
97
|
+
Button("Save", id="save", variant="success"),
|
|
98
|
+
Button("Cancel", id="cancel", variant="error"),
|
|
99
|
+
classes="button-box",
|
|
100
|
+
)
|
|
101
|
+
yield Footer()
|
|
102
|
+
|
|
103
|
+
def action_save(self):
|
|
104
|
+
self.save()
|
|
105
|
+
|
|
106
|
+
def action_cancel(self):
|
|
107
|
+
self.cancel()
|
|
108
|
+
|
|
109
|
+
def validate_inputs(self) -> str | None:
|
|
110
|
+
if not self.template_name.strip():
|
|
111
|
+
return "Template name is required"
|
|
112
|
+
if not self.run_file.strip():
|
|
113
|
+
return "Run file is required"
|
|
114
|
+
normalized_run = self.run_file.replace("\\", "/").strip()
|
|
115
|
+
if normalized_run.startswith("/") or normalized_run in ["", "."]:
|
|
116
|
+
return "Run file must be a relative file path"
|
|
117
|
+
if ".." in normalized_run.split("/"):
|
|
118
|
+
return "Run file cannot contain '..'"
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
@on(Button.Pressed, "#save")
|
|
122
|
+
def save(self):
|
|
123
|
+
error = self.validate_inputs()
|
|
124
|
+
if error:
|
|
125
|
+
self.query_one("#error", Label).update(f"[bold red]{escape(error)}[/]")
|
|
126
|
+
return
|
|
127
|
+
self.exit(
|
|
128
|
+
{
|
|
129
|
+
"name": self.template_name.strip(),
|
|
130
|
+
"description": self.description.strip() or None,
|
|
131
|
+
"language": self.language,
|
|
132
|
+
"run": self.run_file.replace("\\", "/").strip(),
|
|
133
|
+
"interpreter": self.interpreter.strip() or None,
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
@on(Button.Pressed, "#cancel")
|
|
138
|
+
def cancel(self):
|
|
139
|
+
self.exit(None)
|
|
140
|
+
|
|
141
|
+
@on(Input.Changed, "#name")
|
|
142
|
+
def name_changed(self, event: Input.Changed) -> None:
|
|
143
|
+
self.template_name = event.value
|
|
144
|
+
|
|
145
|
+
@on(Input.Changed, "#description")
|
|
146
|
+
def description_changed(self, event: Input.Changed) -> None:
|
|
147
|
+
self.description = event.value
|
|
148
|
+
|
|
149
|
+
@on(Select.Changed, "#language")
|
|
150
|
+
def language_changed(self, event: Select.Changed) -> None:
|
|
151
|
+
self.language = str(event.value)
|
|
152
|
+
if not self.edit:
|
|
153
|
+
lang = Language(self.language)
|
|
154
|
+
self.run_file = get_filename(lang)
|
|
155
|
+
self.interpreter = get_interpreter(lang) or ""
|
|
156
|
+
self.query_one("#run", Input).value = self.run_file
|
|
157
|
+
self.query_one("#interpreter", Input).value = self.interpreter
|
|
158
|
+
|
|
159
|
+
@on(Input.Changed, "#run")
|
|
160
|
+
def run_changed(self, event: Input.Changed) -> None:
|
|
161
|
+
self.run_file = event.value
|
|
162
|
+
|
|
163
|
+
@on(Input.Changed, "#interpreter")
|
|
164
|
+
def interpreter_changed(self, event: Input.Changed) -> None:
|
|
165
|
+
self.interpreter = event.value
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class TemplateSelect(App[dict[str, Any] | None]):
|
|
169
|
+
def __init__(self, templates: list[dict[str, Any]]):
|
|
170
|
+
super().__init__()
|
|
171
|
+
self.title = "xFarm - Select Exploit Template"
|
|
172
|
+
self.templates = templates
|
|
173
|
+
|
|
174
|
+
BINDINGS = [
|
|
175
|
+
("ctrl+c", "cancel()", "Cancel operation"),
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
CSS = """
|
|
179
|
+
#selector {
|
|
180
|
+
margin: 2 5 0 5;
|
|
181
|
+
height: 80%;
|
|
182
|
+
}
|
|
183
|
+
.button-box {
|
|
184
|
+
height: 4;
|
|
185
|
+
margin: 2 0;
|
|
186
|
+
margin-top: 1;
|
|
187
|
+
}
|
|
188
|
+
Button {
|
|
189
|
+
margin: 1 2;
|
|
190
|
+
}
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
def compose(self) -> ComposeResult:
|
|
194
|
+
yield Header("xFarm - Select Exploit Template")
|
|
195
|
+
yield OptionList(id="selector")
|
|
196
|
+
yield Horizontal(
|
|
197
|
+
Button("Edit", id="select", variant="success"),
|
|
198
|
+
Button("Cancel", id="cancel", variant="error"),
|
|
199
|
+
classes="button-box",
|
|
200
|
+
)
|
|
201
|
+
yield Footer()
|
|
202
|
+
|
|
203
|
+
def on_mount(self) -> None:
|
|
204
|
+
self.query_one("#selector", OptionList).add_options(
|
|
205
|
+
[
|
|
206
|
+
f"\n [bold blue]{escape(template['name'])}[/] "
|
|
207
|
+
f"([gray62]{escape(template['id'])}[/])\n"
|
|
208
|
+
f" [bold]language:[/] [gray62]{escape(template['language'])}[/] "
|
|
209
|
+
f"[bold]run:[/] [gray62]{escape(template['run'])}[/]\n"
|
|
210
|
+
for template in self.templates
|
|
211
|
+
]
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def action_cancel(self):
|
|
215
|
+
self.cancel()
|
|
216
|
+
|
|
217
|
+
@on(Button.Pressed, "#select")
|
|
218
|
+
def select(self):
|
|
219
|
+
self.query_one("#selector", OptionList).action_select()
|
|
220
|
+
|
|
221
|
+
@on(OptionList.OptionSelected, "#selector")
|
|
222
|
+
def template_selected(self, event: OptionList.OptionSelected) -> None:
|
|
223
|
+
self.exit(self.templates[event.option_index])
|
|
224
|
+
|
|
225
|
+
@on(Button.Pressed, "#cancel")
|
|
226
|
+
def cancel(self):
|
|
227
|
+
self.exit(None)
|
|
@@ -157,11 +157,11 @@ EXPLOIT_CONFIG_FILE_NAME = "config.toml"
|
|
|
157
157
|
|
|
158
158
|
class ExploitConfig(BaseModel):
|
|
159
159
|
uuid: UUID
|
|
160
|
-
name: str
|
|
160
|
+
name: str | None = None
|
|
161
161
|
interpreter: str | None = None
|
|
162
162
|
run: str
|
|
163
|
-
language: Language
|
|
164
|
-
service: UUID
|
|
163
|
+
language: Language | None = None
|
|
164
|
+
service: UUID | None = None
|
|
165
165
|
|
|
166
166
|
__exploit_lock = None
|
|
167
167
|
__folder = "."
|
|
@@ -178,13 +178,16 @@ class ExploitConfig(BaseModel):
|
|
|
178
178
|
) -> Self:
|
|
179
179
|
if isinstance(lang, str):
|
|
180
180
|
lang = get_lang(lang)
|
|
181
|
+
if not lang:
|
|
182
|
+
raise ValueError(f"Unknown language: {lang}")
|
|
183
|
+
|
|
181
184
|
obj = cls(
|
|
182
185
|
uuid=uuid4(),
|
|
183
186
|
name=name,
|
|
184
187
|
language=lang,
|
|
185
188
|
service=srv,
|
|
186
|
-
interpreter=get_interpreter(lang),
|
|
187
189
|
run=get_filename(lang),
|
|
190
|
+
interpreter=get_interpreter(lang),
|
|
188
191
|
)
|
|
189
192
|
if generate_folder:
|
|
190
193
|
obj.__folder = generate_exploit_folder_name(name, obj.uuid)
|
|
@@ -193,13 +196,17 @@ class ExploitConfig(BaseModel):
|
|
|
193
196
|
return obj
|
|
194
197
|
|
|
195
198
|
def model_dump_toml(self) -> str:
|
|
196
|
-
return toml.dumps(self.model_dump(mode="json"))
|
|
199
|
+
return toml.dumps(self.model_dump(mode="json", exclude_none=True))
|
|
197
200
|
|
|
198
201
|
@classmethod
|
|
199
202
|
def model_validate_toml(cls, data: str) -> Self:
|
|
200
203
|
return cls(**toml.loads(data))
|
|
201
204
|
|
|
202
|
-
|
|
205
|
+
@property
|
|
206
|
+
def folder(self) -> str:
|
|
207
|
+
return self.__folder
|
|
208
|
+
|
|
209
|
+
def write(self, exists_ok: bool = False, create_default_source: bool = True):
|
|
203
210
|
# Autogenerated path
|
|
204
211
|
pathexists = os.path.exists(self.__folder)
|
|
205
212
|
if not pathexists and not exists_ok:
|
|
@@ -210,25 +217,45 @@ class ExploitConfig(BaseModel):
|
|
|
210
217
|
|
|
211
218
|
if not pathexists:
|
|
212
219
|
os.makedirs(self.__folder)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
f
|
|
220
|
+
if create_default_source:
|
|
221
|
+
with open(
|
|
222
|
+
os.path.join(self.__folder, get_filename(self.language)), "wt"
|
|
223
|
+
) as f:
|
|
224
|
+
f.write(get_default_file(self.language))
|
|
217
225
|
|
|
218
226
|
with open(os.path.join(self.__folder, EXPLOIT_CONFIG_FILE_NAME), "w") as f:
|
|
219
227
|
f.write(self.model_dump_toml())
|
|
220
228
|
|
|
221
229
|
def publish_exploit(self, config: ClientConfig):
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
data_to_edit = {}
|
|
231
|
+
if self.name is not None:
|
|
232
|
+
data_to_edit["name"] = self.name
|
|
233
|
+
if self.service is not None:
|
|
234
|
+
data_to_edit["service"] = self.service
|
|
235
|
+
if self.language is not None:
|
|
236
|
+
data_to_edit["language"] = self.language.value
|
|
237
|
+
|
|
238
|
+
if not data_to_edit:
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
if self.name is not None and self.service is not None:
|
|
242
|
+
config.reqs.new_exploit(
|
|
243
|
+
{
|
|
244
|
+
"id": self.uuid,
|
|
245
|
+
"name": self.name,
|
|
246
|
+
"language": self.language.value if self.language else Language.other.value,
|
|
247
|
+
"status": ExploitStatus.disabled.value,
|
|
248
|
+
"service": self.service,
|
|
249
|
+
"created_by": config.client_id,
|
|
250
|
+
}
|
|
251
|
+
)
|
|
252
|
+
else:
|
|
253
|
+
config.reqs.edit_exploit(self.uuid, data_to_edit)
|
|
254
|
+
|
|
255
|
+
self.name = None
|
|
256
|
+
self.service = None
|
|
257
|
+
self.language = None
|
|
258
|
+
self.write(exists_ok=True, create_default_source=False)
|
|
232
259
|
|
|
233
260
|
@property
|
|
234
261
|
def lock(self):
|