exploitfarm 1.7.6__tar.gz → 1.7.8__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.7.6 → exploitfarm-1.7.8}/PKG-INFO +23 -10
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/README.md +22 -8
- exploitfarm-1.7.8/exploitfarm/__init__.py +91 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/flags.py +1 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/startxploit.py +9 -2
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/utils/reqs.py +14 -3
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/xfarm.py +20 -19
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/xploit.py +32 -16
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm.egg-info/PKG-INFO +23 -10
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm.egg-info/requires.txt +0 -1
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/requirements.txt +0 -1
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/setup.py +1 -1
- exploitfarm-1.7.6/exploitfarm/__init__.py +0 -29
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/MANIFEST.in +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/__main__.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/_init_exposed.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/__init__.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/client.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/dbtypes.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/enums.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/exploit.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/exploit_template.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/groups.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/response.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/service.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/submitter.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/teams.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/__init__.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/config.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/exploitdownload.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/exploitinit.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/group_prejoin.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/group_select.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/login.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/shared_attack_group.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/template.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/utils/__init__.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/utils/cmd.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/utils/config.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/utils/exploit.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/utils/templates.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/utils/windows_close_fix.py +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm.egg-info/SOURCES.txt +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm.egg-info/dependency_links.txt +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm.egg-info/top_level.txt +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/setup.cfg +0 -0
- {exploitfarm-1.7.6 → exploitfarm-1.7.8}/xfarm +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: exploitfarm
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.8
|
|
4
4
|
Summary: Exploit Farm client
|
|
5
5
|
Home-page: https://github.com/pwnzer0tt1/exploitfarm
|
|
6
6
|
Author: Pwnzer0tt1
|
|
@@ -24,7 +24,6 @@ Requires-Dist: python-socketio[client]>=5.14.1
|
|
|
24
24
|
Requires-Dist: textual>=3.0.0
|
|
25
25
|
Requires-Dist: typer>=0.16
|
|
26
26
|
Requires-Dist: xfarm
|
|
27
|
-
Requires-Dist: orjson
|
|
28
27
|
Dynamic: author
|
|
29
28
|
Dynamic: author-email
|
|
30
29
|
Dynamic: classifier
|
|
@@ -98,15 +97,18 @@ submit_flags(flags)
|
|
|
98
97
|
#### **Store Class**
|
|
99
98
|
|
|
100
99
|
The `Store` class provides a key-value storage system for exploits, allowing you to save and retrieve centralized data during execution.
|
|
101
|
-
The key value store is
|
|
100
|
+
The key value store is by default isolated per exploit id.
|
|
101
|
+
If you need data shared between exploits you should use a custom context_id that allows to access the same data from different exploits. The context_id can be set also in the constructor of the Store class, and will be used as default context_id for all the calls with that store instance.
|
|
102
102
|
|
|
103
103
|
```python
|
|
104
104
|
from exploitfarm import Store
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
# Initialize with an optional default context
|
|
107
|
+
store = Store(default_context_id="my_global_context_id")
|
|
107
108
|
|
|
108
|
-
# Set a value
|
|
109
|
+
# Set a value (context_id can be overridden per call)
|
|
109
110
|
store.set("example_key", {"example": "data"})
|
|
111
|
+
store.set("other_key", "value", context_id="explicit_ctx")
|
|
110
112
|
|
|
111
113
|
# Get a value (if not exists returns None)
|
|
112
114
|
data = store.get("example_key")
|
|
@@ -115,17 +117,28 @@ print(data)
|
|
|
115
117
|
# Delete a key
|
|
116
118
|
store.delete("example_key")
|
|
117
119
|
|
|
118
|
-
# List all keys
|
|
120
|
+
# List all keys in the current context
|
|
119
121
|
keys = store.keys()
|
|
120
122
|
print(keys)
|
|
123
|
+
|
|
124
|
+
# List all available contexts
|
|
125
|
+
contexts = store.contexts()
|
|
126
|
+
print(contexts)
|
|
127
|
+
|
|
128
|
+
# Delete an entire context
|
|
129
|
+
store.delete_context(context_id="explicit_ctx")
|
|
121
130
|
```
|
|
122
131
|
|
|
123
132
|
**Methods**:
|
|
124
133
|
|
|
125
|
-
- `get(key: str, timeout: int = HTTP_TIMEOUT) ->
|
|
126
|
-
- `
|
|
127
|
-
- `
|
|
128
|
-
- `
|
|
134
|
+
- `get(key: str, default=None, context_id: str | None = None, timeout: int = HTTP_TIMEOUT) -> any`: Retrieve and deserialize a value by key.
|
|
135
|
+
- `get_bytes(key: str, default=None, context_id: str | None = None, timeout: int = HTTP_TIMEOUT) -> bytes`: Retrieve a raw byte value.
|
|
136
|
+
- `set(key: str, value: any, context_id: str | None = None, timeout: int = HTTP_TIMEOUT)`: Serialize and store a value by key.
|
|
137
|
+
- `set_bytes(key: str, value: bytes, context_id: str | None = None, timeout: int = HTTP_TIMEOUT)`: Store a raw byte value.
|
|
138
|
+
- `delete(key: str, context_id: str | None = None, timeout: int = HTTP_TIMEOUT)`: Delete a key.
|
|
139
|
+
- `keys(timeout: int = HTTP_TIMEOUT, context_id: str | None = None) -> list[str]`: List all stored keys in a context.
|
|
140
|
+
- `contexts(timeout: int = HTTP_TIMEOUT) -> list[str]`: List all available contexts.
|
|
141
|
+
- `delete_context(context_id: str | None = None, timeout: int = HTTP_TIMEOUT)`: Delete an entire context and its keys.
|
|
129
142
|
|
|
130
143
|
**Environment Variables Required**:
|
|
131
144
|
|
|
@@ -61,15 +61,18 @@ submit_flags(flags)
|
|
|
61
61
|
#### **Store Class**
|
|
62
62
|
|
|
63
63
|
The `Store` class provides a key-value storage system for exploits, allowing you to save and retrieve centralized data during execution.
|
|
64
|
-
The key value store is
|
|
64
|
+
The key value store is by default isolated per exploit id.
|
|
65
|
+
If you need data shared between exploits you should use a custom context_id that allows to access the same data from different exploits. The context_id can be set also in the constructor of the Store class, and will be used as default context_id for all the calls with that store instance.
|
|
65
66
|
|
|
66
67
|
```python
|
|
67
68
|
from exploitfarm import Store
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
# Initialize with an optional default context
|
|
71
|
+
store = Store(default_context_id="my_global_context_id")
|
|
70
72
|
|
|
71
|
-
# Set a value
|
|
73
|
+
# Set a value (context_id can be overridden per call)
|
|
72
74
|
store.set("example_key", {"example": "data"})
|
|
75
|
+
store.set("other_key", "value", context_id="explicit_ctx")
|
|
73
76
|
|
|
74
77
|
# Get a value (if not exists returns None)
|
|
75
78
|
data = store.get("example_key")
|
|
@@ -78,17 +81,28 @@ print(data)
|
|
|
78
81
|
# Delete a key
|
|
79
82
|
store.delete("example_key")
|
|
80
83
|
|
|
81
|
-
# List all keys
|
|
84
|
+
# List all keys in the current context
|
|
82
85
|
keys = store.keys()
|
|
83
86
|
print(keys)
|
|
87
|
+
|
|
88
|
+
# List all available contexts
|
|
89
|
+
contexts = store.contexts()
|
|
90
|
+
print(contexts)
|
|
91
|
+
|
|
92
|
+
# Delete an entire context
|
|
93
|
+
store.delete_context(context_id="explicit_ctx")
|
|
84
94
|
```
|
|
85
95
|
|
|
86
96
|
**Methods**:
|
|
87
97
|
|
|
88
|
-
- `get(key: str, timeout: int = HTTP_TIMEOUT) ->
|
|
89
|
-
- `
|
|
90
|
-
- `
|
|
91
|
-
- `
|
|
98
|
+
- `get(key: str, default=None, context_id: str | None = None, timeout: int = HTTP_TIMEOUT) -> any`: Retrieve and deserialize a value by key.
|
|
99
|
+
- `get_bytes(key: str, default=None, context_id: str | None = None, timeout: int = HTTP_TIMEOUT) -> bytes`: Retrieve a raw byte value.
|
|
100
|
+
- `set(key: str, value: any, context_id: str | None = None, timeout: int = HTTP_TIMEOUT)`: Serialize and store a value by key.
|
|
101
|
+
- `set_bytes(key: str, value: bytes, context_id: str | None = None, timeout: int = HTTP_TIMEOUT)`: Store a raw byte value.
|
|
102
|
+
- `delete(key: str, context_id: str | None = None, timeout: int = HTTP_TIMEOUT)`: Delete a key.
|
|
103
|
+
- `keys(timeout: int = HTTP_TIMEOUT, context_id: str | None = None) -> list[str]`: List all stored keys in a context.
|
|
104
|
+
- `contexts(timeout: int = HTTP_TIMEOUT) -> list[str]`: List all available contexts.
|
|
105
|
+
- `delete_context(context_id: str | None = None, timeout: int = HTTP_TIMEOUT)`: Delete an entire context and its keys.
|
|
92
106
|
|
|
93
107
|
**Environment Variables Required**:
|
|
94
108
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
def _get_version():
|
|
2
|
+
v = "1.7.8" if "{" not in "1.7.8" else "0.0.0"
|
|
3
|
+
if v == "0.0.0":
|
|
4
|
+
import os
|
|
5
|
+
env_v = os.getenv("EXPLOITFARM_VERSION")
|
|
6
|
+
if env_v:
|
|
7
|
+
return env_v
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
def parse_version(ver):
|
|
11
|
+
ver = ver.lstrip("v")
|
|
12
|
+
parts = re.split(r'[^0-9]+', ver)
|
|
13
|
+
return tuple(int(p) if p.isdigit() else 0 for p in parts if p)
|
|
14
|
+
|
|
15
|
+
def find_git_dir(start_path):
|
|
16
|
+
current = start_path
|
|
17
|
+
while current and current != "/" and current != "":
|
|
18
|
+
gd = os.path.join(current, ".git")
|
|
19
|
+
if os.path.isdir(gd):
|
|
20
|
+
return gd
|
|
21
|
+
parent = os.path.dirname(current)
|
|
22
|
+
if parent == current:
|
|
23
|
+
break
|
|
24
|
+
current = parent
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
git_dir = find_git_dir(os.path.abspath(os.path.dirname(__file__)))
|
|
29
|
+
if not git_dir:
|
|
30
|
+
return v
|
|
31
|
+
|
|
32
|
+
branch = None
|
|
33
|
+
with open(os.path.join(git_dir, "HEAD"), "r") as f:
|
|
34
|
+
head = f.read().strip()
|
|
35
|
+
if head.startswith("ref: refs/heads/"):
|
|
36
|
+
branch = head.split("ref: refs/heads/")[1]
|
|
37
|
+
|
|
38
|
+
if branch != "main":
|
|
39
|
+
return v
|
|
40
|
+
|
|
41
|
+
tags = []
|
|
42
|
+
try:
|
|
43
|
+
with open(os.path.join(git_dir, "packed-refs"), "r") as f:
|
|
44
|
+
for line in f:
|
|
45
|
+
if " refs/tags/" in line:
|
|
46
|
+
tags.append(line.strip().split(" refs/tags/")[1])
|
|
47
|
+
except Exception:
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
tags_dir = os.path.join(git_dir, "refs", "tags")
|
|
51
|
+
try:
|
|
52
|
+
for root, _, files in os.walk(tags_dir):
|
|
53
|
+
for file in files:
|
|
54
|
+
tags.append(os.path.relpath(os.path.join(root, file), tags_dir).replace(os.path.sep, "/"))
|
|
55
|
+
except Exception:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
version_tags = [t for t in set(tags) if any(c.isdigit() for c in t)]
|
|
59
|
+
if version_tags:
|
|
60
|
+
return max(version_tags, key=parse_version)
|
|
61
|
+
except Exception:
|
|
62
|
+
pass
|
|
63
|
+
return v
|
|
64
|
+
|
|
65
|
+
__version__ = _get_version()
|
|
66
|
+
|
|
67
|
+
from exploitfarm.utils import try_tcp_connection
|
|
68
|
+
from exploitfarm.models.enums import AttackMode, SetupStatus, FlagStatus
|
|
69
|
+
from exploitfarm.utils import DEV_MODE
|
|
70
|
+
from exploitfarm._init_exposed import get_host, Prio
|
|
71
|
+
from exploitfarm._init_exposed import nicenessify, get_config, random_str, print, Store
|
|
72
|
+
from exploitfarm._init_exposed import os_unix, os_windows, session
|
|
73
|
+
|
|
74
|
+
# Exported functions
|
|
75
|
+
__all__ = [
|
|
76
|
+
"try_tcp_connection",
|
|
77
|
+
"AttackMode",
|
|
78
|
+
"SetupStatus",
|
|
79
|
+
"get_host",
|
|
80
|
+
"get_config",
|
|
81
|
+
"random_str",
|
|
82
|
+
"session",
|
|
83
|
+
"print",
|
|
84
|
+
"nicenessify",
|
|
85
|
+
"Prio",
|
|
86
|
+
"FlagStatus",
|
|
87
|
+
"Store",
|
|
88
|
+
"DEV_MODE",
|
|
89
|
+
"os_unix",
|
|
90
|
+
"os_windows",
|
|
91
|
+
]
|
|
@@ -21,6 +21,7 @@ class AttackExecutionDTO(BaseModel):
|
|
|
21
21
|
executed_by: ClientID|None = Field(None, validation_alias=AliasChoices('executed_by_id'))
|
|
22
22
|
executed_by_group: AttackGroupID|None = Field(None, validation_alias=AliasChoices('executed_by_group_id'))
|
|
23
23
|
flags: list[FlagDTOSmall]
|
|
24
|
+
duplicate_flags: list[str] = []
|
|
24
25
|
exploit_source: ExploitSourceID|None = Field(None, validation_alias=AliasChoices('exploit_source_id'))
|
|
25
26
|
|
|
26
27
|
class FlagDTOAttackDetails(BaseModel):
|
|
@@ -155,8 +155,15 @@ class XploitRun(App[int]):
|
|
|
155
155
|
time_delta = last_execution_time = last_attack_status = None
|
|
156
156
|
executing = team_info.executing
|
|
157
157
|
if team_info.last:
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
if team_info.last.end_time:
|
|
159
|
+
time_delta = str(dt.now(datetime.timezone.utc) - team_info.last.end_time).split(".")[0]
|
|
160
|
+
last_execution_time = str(team_info.last.end_time - team_info.last.start_time).split(".")[0]
|
|
161
|
+
else:
|
|
162
|
+
time_delta = "N/A"
|
|
163
|
+
if team_info.last.start_time:
|
|
164
|
+
last_execution_time = str(dt.now(datetime.timezone.utc) - team_info.last.start_time).split(".")[0]
|
|
165
|
+
else:
|
|
166
|
+
last_execution_time = "N/A"
|
|
160
167
|
last_attack_status = team_info.last.status
|
|
161
168
|
|
|
162
169
|
match last_attack_status:
|
|
@@ -8,7 +8,7 @@ from uuid import UUID
|
|
|
8
8
|
from typing import Dict, Any
|
|
9
9
|
from exploitfarm.utils.config import ClientConfig, ExploitConfig
|
|
10
10
|
from exploitfarm.utils import ExploitFarmClientError
|
|
11
|
-
import
|
|
11
|
+
import json
|
|
12
12
|
from datetime import datetime as dt
|
|
13
13
|
from exploitfarm.models.enums import AttackMode, SetupStatus
|
|
14
14
|
from posixpath import join as urljoin
|
|
@@ -30,8 +30,19 @@ def get_url(path: str, config: ClientConfig | None = None) -> str:
|
|
|
30
30
|
return result
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
from enum import Enum
|
|
34
|
+
|
|
35
|
+
def _custom_serializer(obj):
|
|
36
|
+
if isinstance(obj, UUID):
|
|
37
|
+
return str(obj)
|
|
38
|
+
if isinstance(obj, dt):
|
|
39
|
+
return obj.isoformat()
|
|
40
|
+
if isinstance(obj, Enum):
|
|
41
|
+
return obj.value
|
|
42
|
+
raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable")
|
|
43
|
+
|
|
44
|
+
def jsonify(data: Any) -> Any:
|
|
45
|
+
return json.loads(json.dumps(data, default=_custom_serializer))
|
|
35
46
|
|
|
36
47
|
|
|
37
48
|
def get_session(auth: str | None | ClientConfig = None) -> requests.Session:
|
|
@@ -24,7 +24,7 @@ from exploitfarm.utils.config import (
|
|
|
24
24
|
)
|
|
25
25
|
import getpass
|
|
26
26
|
import re
|
|
27
|
-
import
|
|
27
|
+
import json
|
|
28
28
|
import sys
|
|
29
29
|
from pydantic import PositiveInt
|
|
30
30
|
from typing import Optional
|
|
@@ -123,28 +123,29 @@ def fast_check_exploit_conf_service(exploit_config: ExploitConfig) -> ExploitCon
|
|
|
123
123
|
|
|
124
124
|
|
|
125
125
|
def auto_push_is_safe(exploit_config: ExploitConfig):
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
== 0
|
|
135
|
-
)
|
|
136
|
-
if no_latest_groups:
|
|
126
|
+
# Since groups always use the latest version, check if there are ANY groups using this exploit
|
|
127
|
+
active_groups = [
|
|
128
|
+
gr
|
|
129
|
+
for gr in g.config.reqs.groups()
|
|
130
|
+
if gr.get("exploit") == str(exploit_config.uuid)
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
if len(active_groups) == 0:
|
|
137
134
|
return True
|
|
138
135
|
else:
|
|
139
136
|
current_hash = exploit_config.hash()
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
137
|
+
try:
|
|
138
|
+
# If the exact source version is already pushed, it's safe
|
|
139
|
+
pushed_hashes = [ele["hash"] for ele in g.config.reqs.exploit_source_log(exploit_config.uuid)]
|
|
140
|
+
if current_hash in pushed_hashes:
|
|
141
|
+
return True
|
|
142
|
+
except Exception:
|
|
143
|
+
pass
|
|
144
|
+
|
|
144
145
|
if g.interactive:
|
|
145
146
|
return typer.confirm(
|
|
146
|
-
"There are attack groups
|
|
147
|
-
"
|
|
147
|
+
"There are active attack groups using this exploit. "
|
|
148
|
+
"Pushing a new version will update them to the new code. "
|
|
148
149
|
"Do you want to continue?",
|
|
149
150
|
default=False,
|
|
150
151
|
)
|
|
@@ -639,7 +640,7 @@ def submitter_test(
|
|
|
639
640
|
):
|
|
640
641
|
initial_setup()
|
|
641
642
|
try:
|
|
642
|
-
kwargs =
|
|
643
|
+
kwargs = json.loads(kwargs)
|
|
643
644
|
except Exception as e:
|
|
644
645
|
print(f"[bold red]Invalid kwargs json: {e}")
|
|
645
646
|
close_cli()
|
|
@@ -8,7 +8,8 @@ And that's one of the reasons why this attacker/submitter is called "exploitfarm
|
|
|
8
8
|
import dateutil.parser
|
|
9
9
|
import os
|
|
10
10
|
import re
|
|
11
|
-
import
|
|
11
|
+
import json
|
|
12
|
+
import shlex
|
|
12
13
|
import math
|
|
13
14
|
import subprocess
|
|
14
15
|
import time
|
|
@@ -55,7 +56,6 @@ class g:
|
|
|
55
56
|
server_id = None
|
|
56
57
|
thread_pool: ThreadPoolExecutor = None
|
|
57
58
|
tot_teams: int = 0
|
|
58
|
-
flag_cache: set = set()
|
|
59
59
|
attack_round_deadline = dt_now()
|
|
60
60
|
exploit_hash: str | None = None
|
|
61
61
|
virtual_time_available = 0
|
|
@@ -198,8 +198,6 @@ class AttackStorage:
|
|
|
198
198
|
stats: bool = True,
|
|
199
199
|
):
|
|
200
200
|
with self._lock:
|
|
201
|
-
flags = [flag for flag in flags if flag not in g.flag_cache]
|
|
202
|
-
g.flag_cache.update(flags)
|
|
203
201
|
execution_submit = {
|
|
204
202
|
"start_time": start_time,
|
|
205
203
|
"end_time": end_time,
|
|
@@ -288,7 +286,7 @@ def once_in_a_period(period):
|
|
|
288
286
|
def repush_flags():
|
|
289
287
|
try:
|
|
290
288
|
with open(".flag_queue.json", "rt") as f:
|
|
291
|
-
old_queue_data =
|
|
289
|
+
old_queue_data = json.loads(f.read())
|
|
292
290
|
if old_queue_data["server_id"] != g.server_id:
|
|
293
291
|
raise Exception("Server ID mismatch")
|
|
294
292
|
for ele in old_queue_data["queue"]:
|
|
@@ -500,11 +498,16 @@ def read_and_print(stdout: io.BytesIO, buffer: io.BytesIO):
|
|
|
500
498
|
stdout.close()
|
|
501
499
|
|
|
502
500
|
|
|
501
|
+
_exploit_config_cache = {}
|
|
502
|
+
|
|
503
503
|
def get_or_download_exploit(exploit_id: str, commit_id: str) -> ExploitConfig:
|
|
504
504
|
# Construct the absolute path for the exploit cache
|
|
505
505
|
base_dir = os.path.expanduser(f"~/.exploitfarm/cache/{exploit_id}/{commit_id}")
|
|
506
506
|
|
|
507
507
|
with g.exploit_cache_lock:
|
|
508
|
+
if base_dir in _exploit_config_cache:
|
|
509
|
+
return _exploit_config_cache[base_dir]
|
|
510
|
+
|
|
508
511
|
# Check if the exploit configuration file exists in the cache
|
|
509
512
|
if not check_exploit_config_exists(base_dir):
|
|
510
513
|
# If the directory exists but is incomplete/corrupted, we might need to clear it,
|
|
@@ -517,7 +520,9 @@ def get_or_download_exploit(exploit_id: str, commit_id: str) -> ExploitConfig:
|
|
|
517
520
|
tar.extractall(base_dir)
|
|
518
521
|
tar.close()
|
|
519
522
|
|
|
520
|
-
|
|
523
|
+
config = ExploitConfig.read(base_dir)
|
|
524
|
+
_exploit_config_cache[base_dir] = config
|
|
525
|
+
return config
|
|
521
526
|
|
|
522
527
|
|
|
523
528
|
def launch_sploit(
|
|
@@ -535,7 +540,8 @@ def launch_sploit(
|
|
|
535
540
|
env = os.environ.copy()
|
|
536
541
|
env["PYTHONUNBUFFERED"] = "1"
|
|
537
542
|
env["XFARM_HOST"] = team["host"]
|
|
538
|
-
|
|
543
|
+
team_json = json.dumps(team)
|
|
544
|
+
env["XFARM_TEAM"] = team_json.decode() if isinstance(team_json, bytes) else team_json
|
|
539
545
|
env["XFARM_EXPLOIT_ID"] = str(config.uuid)
|
|
540
546
|
env["XFARM_REMOTE_URL"] = get_url_from_config("", g.exec.config)
|
|
541
547
|
if g.exec.config.server.auth_key:
|
|
@@ -543,32 +549,33 @@ def launch_sploit(
|
|
|
543
549
|
|
|
544
550
|
# Command to run the exploit
|
|
545
551
|
command = f"{config.interpreter} {config.run}".strip()
|
|
552
|
+
cmd_args = shlex.split(command)
|
|
546
553
|
need_close_fds = not os_windows
|
|
547
554
|
|
|
548
555
|
proc = None
|
|
549
556
|
if os_windows:
|
|
550
557
|
with g.ctrl_c_win.skip_ctrl_c_handling():
|
|
551
558
|
proc = subprocess.Popen(
|
|
552
|
-
|
|
559
|
+
cmd_args,
|
|
553
560
|
stdout=subprocess.PIPE,
|
|
554
561
|
stderr=subprocess.STDOUT,
|
|
555
562
|
close_fds=need_close_fds,
|
|
556
563
|
env=env,
|
|
557
564
|
cwd=config._ExploitConfig__folder,
|
|
558
|
-
shell=
|
|
565
|
+
shell=False,
|
|
559
566
|
)
|
|
560
567
|
nicenessify(pid=proc.pid)
|
|
561
568
|
else:
|
|
562
569
|
proc = subprocess.Popen(
|
|
563
|
-
|
|
570
|
+
cmd_args,
|
|
564
571
|
stdout=subprocess.PIPE,
|
|
565
572
|
stderr=subprocess.STDOUT,
|
|
566
573
|
close_fds=need_close_fds,
|
|
567
574
|
env=env,
|
|
568
575
|
cwd=config._ExploitConfig__folder,
|
|
569
|
-
shell=
|
|
570
|
-
preexec_fn=nicenessify,
|
|
576
|
+
shell=False,
|
|
571
577
|
)
|
|
578
|
+
nicenessify(pid=proc.pid)
|
|
572
579
|
|
|
573
580
|
read_function = None
|
|
574
581
|
if stream_output:
|
|
@@ -585,12 +592,21 @@ def launch_sploit(
|
|
|
585
592
|
return value
|
|
586
593
|
|
|
587
594
|
read_function = func
|
|
588
|
-
|
|
589
|
-
|
|
595
|
+
else:
|
|
596
|
+
pipe = proc.stdout
|
|
597
|
+
buffer = io.BytesIO()
|
|
598
|
+
|
|
599
|
+
def silent_read():
|
|
600
|
+
buffer.write(pipe.read())
|
|
601
|
+
|
|
602
|
+
thr = threading.Thread(target=silent_read)
|
|
603
|
+
thr.start()
|
|
590
604
|
|
|
591
605
|
def func():
|
|
592
|
-
|
|
606
|
+
thr.join()
|
|
593
607
|
proc.stdout.close()
|
|
608
|
+
value = buffer.getvalue()
|
|
609
|
+
buffer.close()
|
|
594
610
|
return value
|
|
595
611
|
|
|
596
612
|
read_function = func
|
|
@@ -1049,7 +1065,7 @@ def shutdown(restart: bool = False):
|
|
|
1049
1065
|
attacks = g.attack_storage.pick_attacks()
|
|
1050
1066
|
if attacks:
|
|
1051
1067
|
with open(".flag_queue.json", "wb") as f:
|
|
1052
|
-
f.write(
|
|
1068
|
+
f.write(json.dumps({"server_id": g.server_id, "queue": attacks}))
|
|
1053
1069
|
elif os.path.exists(".flag_queue.json"):
|
|
1054
1070
|
os.remove(".flag_queue.json")
|
|
1055
1071
|
except Exception:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: exploitfarm
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.8
|
|
4
4
|
Summary: Exploit Farm client
|
|
5
5
|
Home-page: https://github.com/pwnzer0tt1/exploitfarm
|
|
6
6
|
Author: Pwnzer0tt1
|
|
@@ -24,7 +24,6 @@ Requires-Dist: python-socketio[client]>=5.14.1
|
|
|
24
24
|
Requires-Dist: textual>=3.0.0
|
|
25
25
|
Requires-Dist: typer>=0.16
|
|
26
26
|
Requires-Dist: xfarm
|
|
27
|
-
Requires-Dist: orjson
|
|
28
27
|
Dynamic: author
|
|
29
28
|
Dynamic: author-email
|
|
30
29
|
Dynamic: classifier
|
|
@@ -98,15 +97,18 @@ submit_flags(flags)
|
|
|
98
97
|
#### **Store Class**
|
|
99
98
|
|
|
100
99
|
The `Store` class provides a key-value storage system for exploits, allowing you to save and retrieve centralized data during execution.
|
|
101
|
-
The key value store is
|
|
100
|
+
The key value store is by default isolated per exploit id.
|
|
101
|
+
If you need data shared between exploits you should use a custom context_id that allows to access the same data from different exploits. The context_id can be set also in the constructor of the Store class, and will be used as default context_id for all the calls with that store instance.
|
|
102
102
|
|
|
103
103
|
```python
|
|
104
104
|
from exploitfarm import Store
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
# Initialize with an optional default context
|
|
107
|
+
store = Store(default_context_id="my_global_context_id")
|
|
107
108
|
|
|
108
|
-
# Set a value
|
|
109
|
+
# Set a value (context_id can be overridden per call)
|
|
109
110
|
store.set("example_key", {"example": "data"})
|
|
111
|
+
store.set("other_key", "value", context_id="explicit_ctx")
|
|
110
112
|
|
|
111
113
|
# Get a value (if not exists returns None)
|
|
112
114
|
data = store.get("example_key")
|
|
@@ -115,17 +117,28 @@ print(data)
|
|
|
115
117
|
# Delete a key
|
|
116
118
|
store.delete("example_key")
|
|
117
119
|
|
|
118
|
-
# List all keys
|
|
120
|
+
# List all keys in the current context
|
|
119
121
|
keys = store.keys()
|
|
120
122
|
print(keys)
|
|
123
|
+
|
|
124
|
+
# List all available contexts
|
|
125
|
+
contexts = store.contexts()
|
|
126
|
+
print(contexts)
|
|
127
|
+
|
|
128
|
+
# Delete an entire context
|
|
129
|
+
store.delete_context(context_id="explicit_ctx")
|
|
121
130
|
```
|
|
122
131
|
|
|
123
132
|
**Methods**:
|
|
124
133
|
|
|
125
|
-
- `get(key: str, timeout: int = HTTP_TIMEOUT) ->
|
|
126
|
-
- `
|
|
127
|
-
- `
|
|
128
|
-
- `
|
|
134
|
+
- `get(key: str, default=None, context_id: str | None = None, timeout: int = HTTP_TIMEOUT) -> any`: Retrieve and deserialize a value by key.
|
|
135
|
+
- `get_bytes(key: str, default=None, context_id: str | None = None, timeout: int = HTTP_TIMEOUT) -> bytes`: Retrieve a raw byte value.
|
|
136
|
+
- `set(key: str, value: any, context_id: str | None = None, timeout: int = HTTP_TIMEOUT)`: Serialize and store a value by key.
|
|
137
|
+
- `set_bytes(key: str, value: bytes, context_id: str | None = None, timeout: int = HTTP_TIMEOUT)`: Store a raw byte value.
|
|
138
|
+
- `delete(key: str, context_id: str | None = None, timeout: int = HTTP_TIMEOUT)`: Delete a key.
|
|
139
|
+
- `keys(timeout: int = HTTP_TIMEOUT, context_id: str | None = None) -> list[str]`: List all stored keys in a context.
|
|
140
|
+
- `contexts(timeout: int = HTTP_TIMEOUT) -> list[str]`: List all available contexts.
|
|
141
|
+
- `delete_context(context_id: str | None = None, timeout: int = HTTP_TIMEOUT)`: Delete an entire context and its keys.
|
|
129
142
|
|
|
130
143
|
**Environment Variables Required**:
|
|
131
144
|
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
__version__ = (
|
|
2
|
-
"1.7.6" if "{" not in "1.7.6" else "0.0.0"
|
|
3
|
-
)
|
|
4
|
-
|
|
5
|
-
from exploitfarm.utils import try_tcp_connection
|
|
6
|
-
from exploitfarm.models.enums import AttackMode, SetupStatus, FlagStatus
|
|
7
|
-
from exploitfarm.utils import DEV_MODE
|
|
8
|
-
from exploitfarm._init_exposed import get_host, Prio
|
|
9
|
-
from exploitfarm._init_exposed import nicenessify, get_config, random_str, print, Store
|
|
10
|
-
from exploitfarm._init_exposed import os_unix, os_windows, session
|
|
11
|
-
|
|
12
|
-
# Exported functions
|
|
13
|
-
__all__ = [
|
|
14
|
-
"try_tcp_connection",
|
|
15
|
-
"AttackMode",
|
|
16
|
-
"SetupStatus",
|
|
17
|
-
"get_host",
|
|
18
|
-
"get_config",
|
|
19
|
-
"random_str",
|
|
20
|
-
"session",
|
|
21
|
-
"print",
|
|
22
|
-
"nicenessify",
|
|
23
|
-
"Prio",
|
|
24
|
-
"FlagStatus",
|
|
25
|
-
"Store",
|
|
26
|
-
"DEV_MODE",
|
|
27
|
-
"os_unix",
|
|
28
|
-
"os_windows",
|
|
29
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|