exploitfarm 0.1.9__py3-none-any.whl
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/__init__.py +99 -0
- exploitfarm/cmd/__init__.py +0 -0
- exploitfarm/cmd/config.py +264 -0
- exploitfarm/cmd/exploitinit.py +217 -0
- exploitfarm/cmd/login.py +163 -0
- exploitfarm/cmd/startxploit.py +331 -0
- exploitfarm/model.py +149 -0
- exploitfarm/utils/__init__.py +47 -0
- exploitfarm/utils/config.py +169 -0
- exploitfarm/utils/reqs.py +165 -0
- exploitfarm/xploit.py +527 -0
- exploitfarm-0.1.9.data/scripts/xfarm +352 -0
- exploitfarm-0.1.9.dist-info/METADATA +210 -0
- exploitfarm-0.1.9.dist-info/RECORD +16 -0
- exploitfarm-0.1.9.dist-info/WHEEL +5 -0
- exploitfarm-0.1.9.dist-info/top_level.txt +1 -0
exploitfarm/__init__.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
|
|
2
|
+
__version__ = "0.1.9"
|
|
3
|
+
|
|
4
|
+
from exploitfarm.utils import try_tcp_connection
|
|
5
|
+
from exploitfarm.model import ServiceDTO, AttackMode, SetupStatus
|
|
6
|
+
import requests, functools, random, os
|
|
7
|
+
from exploitfarm.utils.config import ClientConfig
|
|
8
|
+
from exploitfarm.cmd.config import inital_config_setup
|
|
9
|
+
from exploitfarm.cmd.login import login_required
|
|
10
|
+
|
|
11
|
+
def get_host():
|
|
12
|
+
result = os.getenv("XFARM_HOST", None)
|
|
13
|
+
if not result:
|
|
14
|
+
raise ValueError("this exploit has to be run with xfarm")
|
|
15
|
+
return result
|
|
16
|
+
|
|
17
|
+
def service_info() -> ServiceDTO|None:
|
|
18
|
+
result = os.getenv("XFARM_SERVICE", None)
|
|
19
|
+
if not result:
|
|
20
|
+
return None
|
|
21
|
+
try:
|
|
22
|
+
return ServiceDTO.model_validate_json(result)
|
|
23
|
+
except Exception:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
def runtime_info() -> float:
|
|
27
|
+
data = os.getenv("XFARM_RUNTIME", None)
|
|
28
|
+
if data is None:
|
|
29
|
+
raise ValueError("this exploit has to be run with xfarm")
|
|
30
|
+
return float(data)
|
|
31
|
+
|
|
32
|
+
def get_config():
|
|
33
|
+
config = ClientConfig.read()
|
|
34
|
+
if not inital_config_setup(config):
|
|
35
|
+
raise ValueError("It's required to setup the client first")
|
|
36
|
+
login_required(config)
|
|
37
|
+
return config
|
|
38
|
+
|
|
39
|
+
def random_str(
|
|
40
|
+
length:int|None = None,
|
|
41
|
+
length_range:int = (8,12),
|
|
42
|
+
numbers:bool = True,
|
|
43
|
+
lower:bool = True,
|
|
44
|
+
upper:bool = True,
|
|
45
|
+
specials:bool = False,
|
|
46
|
+
exclude:str = "",
|
|
47
|
+
include:str = ""
|
|
48
|
+
) -> str:
|
|
49
|
+
alphabet = ""
|
|
50
|
+
if numbers: alphabet += "0123456789"
|
|
51
|
+
if lower: alphabet += "abcdefghijklmnopqrstuvwxyz"
|
|
52
|
+
if upper: alphabet += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
53
|
+
if specials: alphabet += "!@#$%^&*()_+-=[]{}|;:,.<>?/~"
|
|
54
|
+
if exclude: alphabet = alphabet.translate(str.maketrans("", "", exclude))
|
|
55
|
+
if include: alphabet += include
|
|
56
|
+
if not alphabet:
|
|
57
|
+
raise ValueError("No alphabet selected")
|
|
58
|
+
alphabet = "".join(list(set([ele for ele in alphabet])))
|
|
59
|
+
if length is None:
|
|
60
|
+
length = random.randint(*length_range)
|
|
61
|
+
return "".join(random.choices(alphabet, k=length))
|
|
62
|
+
|
|
63
|
+
def session(
|
|
64
|
+
random_agent:bool = True,
|
|
65
|
+
additional_headers:list = [],
|
|
66
|
+
user_agent:str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.3",
|
|
67
|
+
) -> requests.Session:
|
|
68
|
+
headers = {
|
|
69
|
+
"User-Agent": random.choice(
|
|
70
|
+
[
|
|
71
|
+
"Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0",
|
|
72
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.3",
|
|
73
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.3",
|
|
74
|
+
]+additional_headers
|
|
75
|
+
) if random_agent else user_agent,
|
|
76
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
|
|
77
|
+
"Accept-Encoding": "gzip, deflate",
|
|
78
|
+
"Accept-Language": "en-GB,en;q=0.8,it;q=0.7,en-US;q=0.6",
|
|
79
|
+
}
|
|
80
|
+
s = requests.Session()
|
|
81
|
+
s.headers.update(headers)
|
|
82
|
+
return s
|
|
83
|
+
|
|
84
|
+
#Force flush print
|
|
85
|
+
print = functools.partial(print, flush = True)
|
|
86
|
+
|
|
87
|
+
#Exported functions
|
|
88
|
+
__all__ = [
|
|
89
|
+
"try_tcp_connection",
|
|
90
|
+
"AttackMode",
|
|
91
|
+
"SetupStatus",
|
|
92
|
+
"get_host",
|
|
93
|
+
"service_info",
|
|
94
|
+
"runtime_info",
|
|
95
|
+
"get_config",
|
|
96
|
+
"random_str",
|
|
97
|
+
"session",
|
|
98
|
+
"print",
|
|
99
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
from uuid import uuid4
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
import dateutil.parser
|
|
5
|
+
from textual.app import App, ComposeResult
|
|
6
|
+
from textual.widgets import Button, Header
|
|
7
|
+
from textual import on
|
|
8
|
+
from textual.app import App, ComposeResult
|
|
9
|
+
from textual.validation import Number
|
|
10
|
+
from textual.widgets import Input, Label, Pretty, Checkbox, Footer
|
|
11
|
+
from textual.containers import Horizontal
|
|
12
|
+
from textual.validation import Length
|
|
13
|
+
from rich import print
|
|
14
|
+
from exploitfarm.utils.config import ClientConfig
|
|
15
|
+
from exploitfarm.utils.reqs import get_url
|
|
16
|
+
import dateutil, traceback
|
|
17
|
+
from datetime import datetime as dt, timedelta
|
|
18
|
+
import datetime
|
|
19
|
+
|
|
20
|
+
TOLLERANCE_TIME = timedelta(seconds=5)
|
|
21
|
+
|
|
22
|
+
class InitialConfiguration(App):
|
|
23
|
+
|
|
24
|
+
def __init__(self, config: ClientConfig, initial_error: str|None = None):
|
|
25
|
+
super().__init__()
|
|
26
|
+
self.title = "xFarm - Initial Configuration"
|
|
27
|
+
self.config = config
|
|
28
|
+
self.initial_error = "[bold red]"+initial_error+"[/]" if initial_error else ""
|
|
29
|
+
self.ignore_connection_failed = False
|
|
30
|
+
|
|
31
|
+
BINDINGS = [
|
|
32
|
+
("ctrl+t", "test_connection()", "Test connection to server"),
|
|
33
|
+
("ctrl+s", "save()", "Save configuration"),
|
|
34
|
+
("ctrl+c", "cancel()", "Cancel configuration")
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
CSS = """
|
|
38
|
+
Input.-valid {
|
|
39
|
+
border: tall $success 60%;
|
|
40
|
+
}
|
|
41
|
+
Input.-valid:focus {
|
|
42
|
+
border: tall $success;
|
|
43
|
+
}
|
|
44
|
+
Input {
|
|
45
|
+
margin: 1 1;
|
|
46
|
+
}
|
|
47
|
+
Pretty {
|
|
48
|
+
margin-left: 2;
|
|
49
|
+
}
|
|
50
|
+
#error{
|
|
51
|
+
margin: 2 0 0 2;
|
|
52
|
+
}
|
|
53
|
+
.input-label {
|
|
54
|
+
margin: 1 2;
|
|
55
|
+
}
|
|
56
|
+
.error-box {
|
|
57
|
+
margin: 1 2;
|
|
58
|
+
height: 1.8;
|
|
59
|
+
}
|
|
60
|
+
Button {
|
|
61
|
+
margin: 1 2;
|
|
62
|
+
}
|
|
63
|
+
.float-right {
|
|
64
|
+
content-align: right middle;
|
|
65
|
+
}
|
|
66
|
+
.button-box {
|
|
67
|
+
height: 4;
|
|
68
|
+
margin: 2 0;
|
|
69
|
+
margin-top: 1;
|
|
70
|
+
}
|
|
71
|
+
.max-width {
|
|
72
|
+
width: 100%;
|
|
73
|
+
}
|
|
74
|
+
.hidden {
|
|
75
|
+
display: none;
|
|
76
|
+
}
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def compose(self) -> ComposeResult:
|
|
80
|
+
yield Header("xFarm - Initial Configuration")
|
|
81
|
+
yield Label("[bold]Welcome to xFarm, please fill the following fields to configure the client", classes="error-box")
|
|
82
|
+
yield Label("[bold]Server address:[/]", classes="input-label")
|
|
83
|
+
yield Input(
|
|
84
|
+
placeholder="127.0.0.1",
|
|
85
|
+
value=self.config.server.address,
|
|
86
|
+
validators=[Length(minimum=1)],
|
|
87
|
+
id="address",
|
|
88
|
+
classes="form-input"
|
|
89
|
+
)
|
|
90
|
+
yield Horizontal(
|
|
91
|
+
Label("[bold]Errors:[/]"),
|
|
92
|
+
Pretty([], id="address_errors"),
|
|
93
|
+
classes="error-box hidden",
|
|
94
|
+
id="address_errors_box"
|
|
95
|
+
)
|
|
96
|
+
yield Label("[bold]Server port:[/]", classes="input-label")
|
|
97
|
+
yield Input(
|
|
98
|
+
placeholder="5050",
|
|
99
|
+
value=str(self.config.server.port),
|
|
100
|
+
validators=[Number(minimum=1, maximum=65535)],
|
|
101
|
+
id="port",
|
|
102
|
+
classes="form-input"
|
|
103
|
+
)
|
|
104
|
+
yield Horizontal(
|
|
105
|
+
Label("[bold]Errors:[/]"),
|
|
106
|
+
Pretty([], id="port_errors"),
|
|
107
|
+
classes="error-box hidden",
|
|
108
|
+
id="port_errors_box"
|
|
109
|
+
)
|
|
110
|
+
yield Label("[bold]Nickname:[/]", classes="input-label")
|
|
111
|
+
yield Input(
|
|
112
|
+
placeholder="John Doe",
|
|
113
|
+
value=self.config.client_name,
|
|
114
|
+
validators=[Length(minimum=1)],
|
|
115
|
+
id="nickname",
|
|
116
|
+
classes="form-input"
|
|
117
|
+
)
|
|
118
|
+
yield Horizontal(
|
|
119
|
+
Label("[bold]Errors:[/]"),
|
|
120
|
+
Pretty([], id="nickname_errors"),
|
|
121
|
+
classes="error-box hidden",
|
|
122
|
+
id="nickname_errors_box"
|
|
123
|
+
)
|
|
124
|
+
yield Checkbox("Use HTTPS", id="https", value=self.config.server.https, classes="input-label")
|
|
125
|
+
yield Checkbox("Ignore connection failed", id="ign_conn_failed", value=self.ignore_connection_failed, classes="input-label")
|
|
126
|
+
yield Label(self.initial_error, classes=f"{'' if self.initial_error else 'hidden'}", id="error")
|
|
127
|
+
yield Horizontal(
|
|
128
|
+
Button("Save", id="save", variant="success"),
|
|
129
|
+
Button("Cancel", id="cancel", variant="error"),
|
|
130
|
+
Button("Test Connection", id="test", variant="primary", classes="float-right"),
|
|
131
|
+
classes="max-width button-box"
|
|
132
|
+
)
|
|
133
|
+
yield Footer()
|
|
134
|
+
|
|
135
|
+
def action_save(self):
|
|
136
|
+
self.save()
|
|
137
|
+
|
|
138
|
+
def action_cancel(self):
|
|
139
|
+
self.cancel()
|
|
140
|
+
|
|
141
|
+
def action_test_connection(self):
|
|
142
|
+
self.test()
|
|
143
|
+
|
|
144
|
+
@on(Button.Pressed, "#save")
|
|
145
|
+
def save(self):
|
|
146
|
+
input_forms: List[Input] = self.query(".form-input")
|
|
147
|
+
errors = []
|
|
148
|
+
for form in input_forms:
|
|
149
|
+
errors.extend([ele.failure_description for ele in form.validators if ele.failure_description])
|
|
150
|
+
if len(errors) == 0 and (self.ignore_connection_failed or self.test()):
|
|
151
|
+
self.config.write()
|
|
152
|
+
self.exit(99 if self.ignore_connection_failed else 0)
|
|
153
|
+
|
|
154
|
+
@on(Button.Pressed, "#cancel")
|
|
155
|
+
def cancel(self):
|
|
156
|
+
self.exit(1)
|
|
157
|
+
|
|
158
|
+
@on(Button.Pressed, "#test")
|
|
159
|
+
def test(self):
|
|
160
|
+
error_label = self.query_one("#error", Label)
|
|
161
|
+
error_label.remove_class("hidden")
|
|
162
|
+
try:
|
|
163
|
+
self.config.fetch_status()
|
|
164
|
+
error_label.update(f"[bold green]Connection done successfully to server[/]: {get_url('//', self.config)}")
|
|
165
|
+
return True
|
|
166
|
+
except Exception as e:
|
|
167
|
+
error_label.update(f"[bold red]Failed to connect to server at {get_url('//', self.config)}[/]: [orange]{e}[/]")
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
@on(Checkbox.Changed, "#https")
|
|
171
|
+
def https_change(self, event: Checkbox.Changed):
|
|
172
|
+
self.config.server.https = event.value
|
|
173
|
+
|
|
174
|
+
@on(Checkbox.Changed, "#ign_conn_failed")
|
|
175
|
+
def ignore_connection_failed_change(self, event: Checkbox.Changed):
|
|
176
|
+
self.ignore_connection_failed = event.value
|
|
177
|
+
|
|
178
|
+
@on(Input.Changed, "#address")
|
|
179
|
+
def address_check(self, event: Input.Changed) -> None:
|
|
180
|
+
# Updating the UI to show the reasons why validation failed
|
|
181
|
+
error_label = self.query_one("#address_errors", Pretty)
|
|
182
|
+
error_box = self.query_one("#address_errors_box", Horizontal)
|
|
183
|
+
if not event.validation_result.is_valid:
|
|
184
|
+
error_box.remove_class("hidden")
|
|
185
|
+
error_label.update(event.validation_result.failure_descriptions)
|
|
186
|
+
else:
|
|
187
|
+
error_box.add_class("hidden")
|
|
188
|
+
self.config.server.address = event.input.value
|
|
189
|
+
|
|
190
|
+
@on(Input.Changed, "#port")
|
|
191
|
+
def port_check(self, event: Input.Changed) -> None:
|
|
192
|
+
# Updating the UI to show the reasons why validation failed
|
|
193
|
+
error_label = self.query_one("#port_errors",Pretty)
|
|
194
|
+
error_box = self.query_one("#port_errors_box", Horizontal)
|
|
195
|
+
if not event.validation_result.is_valid:
|
|
196
|
+
error_box.remove_class("hidden")
|
|
197
|
+
error_label.update(event.validation_result.failure_descriptions)
|
|
198
|
+
else:
|
|
199
|
+
error_box.add_class("hidden")
|
|
200
|
+
self.config.server.port = int(event.input.value)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@on(Input.Changed, "#nickname")
|
|
204
|
+
def nickname_check(self, event: Input.Changed) -> None:
|
|
205
|
+
# Updating the UI to show the reasons why validation failed
|
|
206
|
+
error_label = self.query_one("#nickname_errors",Pretty)
|
|
207
|
+
error_box = self.query_one("#nickname_errors_box", Horizontal)
|
|
208
|
+
if not event.validation_result.is_valid:
|
|
209
|
+
error_box.remove_class("hidden")
|
|
210
|
+
error_label.update(event.validation_result.failure_descriptions)
|
|
211
|
+
else:
|
|
212
|
+
error_box.add_class("hidden")
|
|
213
|
+
self.config.client_name = event.input.value
|
|
214
|
+
|
|
215
|
+
def _run_setup_config(config: ClientConfig, initial_error: str|None = None):
|
|
216
|
+
init_conf = InitialConfiguration(config, initial_error)
|
|
217
|
+
status = init_conf.run()
|
|
218
|
+
if status == 99:
|
|
219
|
+
return False
|
|
220
|
+
elif status != 0:
|
|
221
|
+
print(f"[bold red]Failed to configure the client[/]")
|
|
222
|
+
exit(1)
|
|
223
|
+
return True
|
|
224
|
+
|
|
225
|
+
def inital_config_setup(config: ClientConfig, interactive: bool = True) -> bool:
|
|
226
|
+
|
|
227
|
+
if config.client_id is None:
|
|
228
|
+
config.client_id = uuid4()
|
|
229
|
+
config.write()
|
|
230
|
+
|
|
231
|
+
if (
|
|
232
|
+
config.server.address is None or
|
|
233
|
+
config.client_name is None or
|
|
234
|
+
config.client_name == ""
|
|
235
|
+
):
|
|
236
|
+
if not interactive:
|
|
237
|
+
print(f"[bold red]Missing configuration, set it with 'xfarm config' command[/]")
|
|
238
|
+
exit(1)
|
|
239
|
+
if not _run_setup_config(config):
|
|
240
|
+
return False
|
|
241
|
+
try:
|
|
242
|
+
delta_request = config.delta_fetch_status()
|
|
243
|
+
except Exception:
|
|
244
|
+
msg = f"Connection to {get_url('//', config)} failed, the url is wrong or the server is down"
|
|
245
|
+
if not interactive:
|
|
246
|
+
print(f"[bold red]{msg}[/]")
|
|
247
|
+
exit(1)
|
|
248
|
+
if not _run_setup_config(config, msg):
|
|
249
|
+
return False
|
|
250
|
+
try:
|
|
251
|
+
delta_request = config.delta_fetch_status()
|
|
252
|
+
except Exception:
|
|
253
|
+
print(f"[bold red]{msg}[/]")
|
|
254
|
+
exit(1)
|
|
255
|
+
|
|
256
|
+
if config.status:
|
|
257
|
+
server_time = dateutil.parser.parse(config.status["server_time"], tzinfos={"UTC": datetime.timezone.utc})
|
|
258
|
+
delta = server_time - dt.now(datetime.timezone.utc)
|
|
259
|
+
delta = abs(delta.total_seconds())
|
|
260
|
+
tollerance = TOLLERANCE_TIME+delta_request
|
|
261
|
+
tollerance = abs(tollerance.total_seconds())
|
|
262
|
+
if delta > tollerance:
|
|
263
|
+
print(f"[bold yellow]⚠️ WARNING! The server time is not synchronized with the client, please check the server/client time is correct (delta of {delta} > tol: {tollerance})[/]")
|
|
264
|
+
return True
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
from uuid import UUID
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from textual.app import App, ComposeResult
|
|
5
|
+
from textual.widgets import Button, Header
|
|
6
|
+
from textual import on
|
|
7
|
+
from textual.app import App, ComposeResult
|
|
8
|
+
from textual.widgets import Input, Label, Pretty, Footer, Select
|
|
9
|
+
from textual.containers import Horizontal
|
|
10
|
+
from textual.validation import Length, Regex
|
|
11
|
+
from exploitfarm.utils.config import ClientConfig
|
|
12
|
+
from exploitfarm.model import Language
|
|
13
|
+
from exploitfarm.utils.config import EXPLOIT_CONFIG_REGEX
|
|
14
|
+
from exploitfarm.utils.config import ExploitConfig
|
|
15
|
+
from exploitfarm.utils.config import check_exploit_config_exists
|
|
16
|
+
from rich import print
|
|
17
|
+
|
|
18
|
+
class ExploitConf(App):
|
|
19
|
+
|
|
20
|
+
def __init__(self,
|
|
21
|
+
config: ClientConfig,
|
|
22
|
+
edit:bool,
|
|
23
|
+
name:str|None = None,
|
|
24
|
+
lang:Language|None = None,
|
|
25
|
+
service:str|None = None
|
|
26
|
+
):
|
|
27
|
+
super().__init__()
|
|
28
|
+
self.config = config
|
|
29
|
+
self.services = [(srv["name"], srv["id"]) for srv in self.config.status["services"]]+[("[bold]+ Add a new service", None)]
|
|
30
|
+
self.langs = [(lang.value, lang.value) for lang in list(Language)]
|
|
31
|
+
|
|
32
|
+
search_service = list(filter(lambda x: x[1] == service, self.services))
|
|
33
|
+
self.service = search_service[0][1] if search_service else self.services[0][1]
|
|
34
|
+
|
|
35
|
+
search_lang = list(filter(lambda x: Language(x[1]) == lang, self.langs))
|
|
36
|
+
self.lang = search_lang[0][1] if search_lang else Language.python.value
|
|
37
|
+
|
|
38
|
+
self.edit = edit
|
|
39
|
+
self.title = f"xFarm - {'Editing' if self.edit else 'Creating'} Exploit"
|
|
40
|
+
self.service_name = ""
|
|
41
|
+
self.exploit_name = name if name else ""
|
|
42
|
+
|
|
43
|
+
BINDINGS = [
|
|
44
|
+
("ctrl+s", "save()", f"Save exploit"),
|
|
45
|
+
("ctrl+c", "cancel()", "Cancel operation")
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
CSS = """
|
|
49
|
+
Input.-valid {
|
|
50
|
+
border: tall $success 60%;
|
|
51
|
+
}
|
|
52
|
+
Input.-valid:focus {
|
|
53
|
+
border: tall $success;
|
|
54
|
+
}
|
|
55
|
+
Input {
|
|
56
|
+
margin: 1 1;
|
|
57
|
+
}
|
|
58
|
+
Pretty {
|
|
59
|
+
margin-left: 2;
|
|
60
|
+
}
|
|
61
|
+
#error{
|
|
62
|
+
margin: 2 0 0 2;
|
|
63
|
+
}
|
|
64
|
+
.input-label, .form-input {
|
|
65
|
+
margin: 1 2;
|
|
66
|
+
}
|
|
67
|
+
.error-box {
|
|
68
|
+
margin: 1 2;
|
|
69
|
+
height: 1.8;
|
|
70
|
+
}
|
|
71
|
+
Button {
|
|
72
|
+
margin: 1 2;
|
|
73
|
+
}
|
|
74
|
+
.button-box {
|
|
75
|
+
height: 4;
|
|
76
|
+
margin: 2 0;
|
|
77
|
+
margin-top: 1;
|
|
78
|
+
}
|
|
79
|
+
.max-width {
|
|
80
|
+
width: 100%;
|
|
81
|
+
}
|
|
82
|
+
.hidden {
|
|
83
|
+
display: none;
|
|
84
|
+
}
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def compose(self) -> ComposeResult:
|
|
88
|
+
yield Header(f"xFarm - {'Editing' if self.edit else 'Creating'} exploit")
|
|
89
|
+
yield Label("[bold]Exploit name:[/]", classes="input-label")
|
|
90
|
+
yield Input(
|
|
91
|
+
placeholder="funny_service_sqli",
|
|
92
|
+
value=self.exploit_name,
|
|
93
|
+
validators=[Regex(EXPLOIT_CONFIG_REGEX)],
|
|
94
|
+
id="exploit_name",
|
|
95
|
+
classes="form-input"
|
|
96
|
+
)
|
|
97
|
+
yield Horizontal(
|
|
98
|
+
Label("[bold]Errors:[/]"),
|
|
99
|
+
Pretty([], id="name_errors"),
|
|
100
|
+
classes="error-box hidden",
|
|
101
|
+
id="name_errors_box"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
yield Select(self.langs, id="lang", classes="input-label", value=self.lang, allow_blank=False)
|
|
105
|
+
yield Select(self.services, id="srv", classes="input-label", value=self.service, allow_blank=False)
|
|
106
|
+
|
|
107
|
+
yield Label("[bold]New service name:[/]", classes="input-label hidden add-srv")
|
|
108
|
+
yield Input(
|
|
109
|
+
placeholder="service-name",
|
|
110
|
+
value=self.service_name,
|
|
111
|
+
validators=[Length(minimum=1)],
|
|
112
|
+
id="srv_name",
|
|
113
|
+
classes="form-input hidden add-srv"
|
|
114
|
+
)
|
|
115
|
+
yield Horizontal(
|
|
116
|
+
Label("[bold]Errors:[/]"),
|
|
117
|
+
Pretty([], id="srv_name_errors"),
|
|
118
|
+
classes="error-box hidden",
|
|
119
|
+
id="srv_name_errors_box"
|
|
120
|
+
)
|
|
121
|
+
yield Label("", classes="hidden", id="error")
|
|
122
|
+
yield Horizontal(
|
|
123
|
+
Button("Edit" if self.edit else "Create", id="save", variant="success"),
|
|
124
|
+
Button("Cancel", id="cancel", variant="error"),
|
|
125
|
+
classes="max-width button-box"
|
|
126
|
+
)
|
|
127
|
+
yield Footer()
|
|
128
|
+
|
|
129
|
+
def show_error(self, s:str):
|
|
130
|
+
self.query_one("#error", Label).update(s)
|
|
131
|
+
self.query_one("#error", Label).remove_class("hidden")
|
|
132
|
+
|
|
133
|
+
@on(Select.Changed, "#lang")
|
|
134
|
+
def lang_changed(self, event: Select.Changed) -> None:
|
|
135
|
+
if event.value == Language.other.value:
|
|
136
|
+
self.show_error("[bold yellow]Remember to set teh correct main file and interpreter in the config file!")
|
|
137
|
+
else:
|
|
138
|
+
self.query_one("#error", Label).add_class("hidden")
|
|
139
|
+
self.lang = event.value
|
|
140
|
+
|
|
141
|
+
@on(Select.Changed, "#srv")
|
|
142
|
+
def srv_changed(self, event: Select.Changed) -> None:
|
|
143
|
+
if event.value is None:
|
|
144
|
+
self.query(".add-srv").remove_class("hidden")
|
|
145
|
+
else:
|
|
146
|
+
self.query(".add-srv").add_class("hidden")
|
|
147
|
+
self.query_one("#srv_name_errors_box", Horizontal).add_class("hidden")
|
|
148
|
+
self.service = event.value
|
|
149
|
+
|
|
150
|
+
def action_save(self):
|
|
151
|
+
self.save()
|
|
152
|
+
|
|
153
|
+
def action_cancel(self):
|
|
154
|
+
self.cancel()
|
|
155
|
+
|
|
156
|
+
@on(Button.Pressed, "#save")
|
|
157
|
+
def save(self):
|
|
158
|
+
input_forms: List[Input] = self.query(".form-input")
|
|
159
|
+
errors = []
|
|
160
|
+
for form in input_forms:
|
|
161
|
+
errors.extend([ele.failure_description for ele in form.validators if ele.failure_description])
|
|
162
|
+
|
|
163
|
+
if len(errors) == 0 and self.exploit_name:
|
|
164
|
+
if self.service is None:
|
|
165
|
+
try:
|
|
166
|
+
self.service = UUID(self.config.reqs.new_service({"name": self.service_name})["id"])
|
|
167
|
+
self.config.fetch_status()
|
|
168
|
+
except Exception as e:
|
|
169
|
+
print(f"[bold yellow] Service not subscribed!: {e}")
|
|
170
|
+
self.service = UUID("00000000-0000-0000-0000-000000000000")
|
|
171
|
+
if self.edit:
|
|
172
|
+
x_conf = ExploitConfig.read(".")
|
|
173
|
+
x_conf.name = self.exploit_name
|
|
174
|
+
x_conf.language = Language(self.lang)
|
|
175
|
+
x_conf.service = self.service
|
|
176
|
+
else:
|
|
177
|
+
if check_exploit_config_exists(self.exploit_name):
|
|
178
|
+
self.show_error(f"[bold red]The exploit named '{self.exploit_name}' already exists")
|
|
179
|
+
return
|
|
180
|
+
x_conf = ExploitConfig.new(self.exploit_name, Language(self.lang), self.service)
|
|
181
|
+
x_conf.write(x_conf.name if not self.edit else ".")
|
|
182
|
+
try:
|
|
183
|
+
x_conf.publish_exploit(self.config)
|
|
184
|
+
except Exception as e:
|
|
185
|
+
self.exit(99)
|
|
186
|
+
return
|
|
187
|
+
self.exit(0)
|
|
188
|
+
else:
|
|
189
|
+
self.show_error(f"[bold red]{', '.join(errors)}")
|
|
190
|
+
|
|
191
|
+
@on(Button.Pressed, "#cancel")
|
|
192
|
+
def cancel(self):
|
|
193
|
+
self.exit(1)
|
|
194
|
+
|
|
195
|
+
@on(Input.Changed, "#exploit_name")
|
|
196
|
+
def name_check(self, event: Input.Changed) -> None:
|
|
197
|
+
# Updating the UI to show the reasons why validation failed
|
|
198
|
+
error_label = self.query_one("#name_errors", Pretty)
|
|
199
|
+
error_box = self.query_one("#name_errors_box", Horizontal)
|
|
200
|
+
if not event.validation_result.is_valid:
|
|
201
|
+
error_box.remove_class("hidden")
|
|
202
|
+
error_label.update(event.validation_result.failure_descriptions)
|
|
203
|
+
else:
|
|
204
|
+
error_box.add_class("hidden")
|
|
205
|
+
self.exploit_name = event.input.value
|
|
206
|
+
|
|
207
|
+
@on(Input.Changed, "#srv_name")
|
|
208
|
+
def srv_name_check(self, event: Input.Changed) -> None:
|
|
209
|
+
error_label = self.query_one("#srv_name_errors",Pretty)
|
|
210
|
+
error_box = self.query_one("#srv_name_errors_box", Horizontal)
|
|
211
|
+
if not event.validation_result.is_valid:
|
|
212
|
+
error_box.remove_class("hidden")
|
|
213
|
+
error_label.update(event.validation_result.failure_descriptions)
|
|
214
|
+
else:
|
|
215
|
+
error_box.add_class("hidden")
|
|
216
|
+
self.service_name = event.input.value
|
|
217
|
+
|