exploitfarm 1.6.9__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.
Files changed (46) hide show
  1. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/PKG-INFO +1 -1
  2. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/__init__.py +1 -1
  3. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/models/dbtypes.py +1 -0
  4. exploitfarm-1.7.0/exploitfarm/models/exploit_template.py +23 -0
  5. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/tui/exploitinit.py +77 -7
  6. exploitfarm-1.7.0/exploitfarm/tui/template.py +227 -0
  7. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/utils/config.py +47 -20
  8. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/utils/reqs.py +128 -26
  9. exploitfarm-1.7.0/exploitfarm/utils/templates.py +124 -0
  10. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/xfarm.py +278 -34
  11. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm.egg-info/PKG-INFO +1 -1
  12. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm.egg-info/SOURCES.txt +3 -0
  13. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/setup.py +1 -1
  14. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/MANIFEST.in +0 -0
  15. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/README.md +0 -0
  16. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/__main__.py +0 -0
  17. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/_init_exposed.py +0 -0
  18. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/models/__init__.py +0 -0
  19. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/models/client.py +0 -0
  20. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/models/enums.py +0 -0
  21. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/models/exploit.py +0 -0
  22. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/models/flags.py +0 -0
  23. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/models/groups.py +0 -0
  24. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/models/response.py +0 -0
  25. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/models/service.py +0 -0
  26. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/models/submitter.py +0 -0
  27. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/models/teams.py +0 -0
  28. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/tui/__init__.py +0 -0
  29. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/tui/config.py +0 -0
  30. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/tui/exploitdownload.py +0 -0
  31. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/tui/group_prejoin.py +0 -0
  32. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/tui/group_select.py +0 -0
  33. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/tui/login.py +0 -0
  34. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/tui/shared_attack_group.py +0 -0
  35. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/tui/startxploit.py +0 -0
  36. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/utils/__init__.py +0 -0
  37. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/utils/cmd.py +0 -0
  38. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/utils/exploit.py +0 -0
  39. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/utils/windows_close_fix.py +0 -0
  40. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm/xploit.py +0 -0
  41. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm.egg-info/dependency_links.txt +0 -0
  42. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm.egg-info/requires.txt +0 -0
  43. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/exploitfarm.egg-info/top_level.txt +0 -0
  44. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/requirements.txt +0 -0
  45. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/setup.cfg +0 -0
  46. {exploitfarm-1.6.9 → exploitfarm-1.7.0}/xfarm +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: exploitfarm
3
- Version: 1.6.9
3
+ Version: 1.7.0
4
4
  Summary: Exploit Farm client
5
5
  Home-page: https://github.com/pwnzer0tt1/exploitfarm
6
6
  Author: Pwnzer0tt1
@@ -1,5 +1,5 @@
1
1
  __version__ = (
2
- "1.6.9" if "{" not in "1.6.9" else "0.0.0"
2
+ "1.7.0" if "{" not in "1.7.0" else "0.0.0"
3
3
  )
4
4
 
5
5
  from exploitfarm.utils import try_tcp_connection
@@ -7,6 +7,7 @@ from pydantic import BaseModel, BeforeValidator
7
7
  EnvKey = str
8
8
  ClientID = str
9
9
  ExploitID = UUID
10
+ ExploitTemplateID = UUID
10
11
  ServiceID = UUID
11
12
  TeamID = int
12
13
  AttackGroupID = UUID
@@ -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
- self.service = UUID(self.config.reqs.new_service({"name": self.service_name})["id"])
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
- print(f"[bold yellow] Service not subscribed!: {e}")
168
- self.service = UUID("00000000-0000-0000-0000-000000000000")
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
- x_conf.write(exists_ok=True)
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
- def write(self, exists_ok: bool = False):
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
- with open(
214
- os.path.join(self.__folder, get_filename(self.language)), "wt"
215
- ) as f:
216
- f.write(get_default_file(self.language))
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
- config.reqs.new_exploit(
223
- {
224
- "id": self.uuid,
225
- "name": self.name,
226
- "language": self.language.value,
227
- "status": ExploitStatus.disabled.value,
228
- "service": self.service,
229
- "created_by": config.client_id,
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):