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.
Files changed (47) hide show
  1. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/PKG-INFO +23 -10
  2. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/README.md +22 -8
  3. exploitfarm-1.7.8/exploitfarm/__init__.py +91 -0
  4. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/flags.py +1 -0
  5. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/startxploit.py +9 -2
  6. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/utils/reqs.py +14 -3
  7. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/xfarm.py +20 -19
  8. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/xploit.py +32 -16
  9. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm.egg-info/PKG-INFO +23 -10
  10. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm.egg-info/requires.txt +0 -1
  11. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/requirements.txt +0 -1
  12. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/setup.py +1 -1
  13. exploitfarm-1.7.6/exploitfarm/__init__.py +0 -29
  14. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/MANIFEST.in +0 -0
  15. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/__main__.py +0 -0
  16. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/_init_exposed.py +0 -0
  17. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/__init__.py +0 -0
  18. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/client.py +0 -0
  19. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/dbtypes.py +0 -0
  20. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/enums.py +0 -0
  21. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/exploit.py +0 -0
  22. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/exploit_template.py +0 -0
  23. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/groups.py +0 -0
  24. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/response.py +0 -0
  25. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/service.py +0 -0
  26. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/submitter.py +0 -0
  27. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/models/teams.py +0 -0
  28. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/__init__.py +0 -0
  29. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/config.py +0 -0
  30. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/exploitdownload.py +0 -0
  31. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/exploitinit.py +0 -0
  32. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/group_prejoin.py +0 -0
  33. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/group_select.py +0 -0
  34. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/login.py +0 -0
  35. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/shared_attack_group.py +0 -0
  36. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/tui/template.py +0 -0
  37. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/utils/__init__.py +0 -0
  38. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/utils/cmd.py +0 -0
  39. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/utils/config.py +0 -0
  40. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/utils/exploit.py +0 -0
  41. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/utils/templates.py +0 -0
  42. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm/utils/windows_close_fix.py +0 -0
  43. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm.egg-info/SOURCES.txt +0 -0
  44. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm.egg-info/dependency_links.txt +0 -0
  45. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/exploitfarm.egg-info/top_level.txt +0 -0
  46. {exploitfarm-1.7.6 → exploitfarm-1.7.8}/setup.cfg +0 -0
  47. {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.6
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 different for each exploit id. You can use it to store some data you need to save and change for every exploit execution.
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
- store = Store()
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) -> bytes`: Retrieve a value by key.
126
- - `set(key: str, value: bytes, timeout: int = HTTP_TIMEOUT)`: Store a value by key.
127
- - `delete(key: str, timeout: int = HTTP_TIMEOUT)`: Delete a key.
128
- - `keys(timeout: int = HTTP_TIMEOUT) -> list[str]`: List all stored keys.
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 different for each exploit id. You can use it to store some data you need to save and change for every exploit execution.
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
- store = Store()
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) -> bytes`: Retrieve a value by key.
89
- - `set(key: str, value: bytes, timeout: int = HTTP_TIMEOUT)`: Store a value by key.
90
- - `delete(key: str, timeout: int = HTTP_TIMEOUT)`: Delete a key.
91
- - `keys(timeout: int = HTTP_TIMEOUT) -> list[str]`: List all stored keys.
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
- time_delta = str(dt.now(datetime.timezone.utc) - team_info.last.end_time).split(".")[0]
159
- last_execution_time = str(team_info.last.end_time - team_info.last.start_time).split(".")[0]
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 orjson
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
- def jsonify(data: Any) -> str:
34
- return orjson.loads(orjson.dumps(data))
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 orjson
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
- no_latest_groups = (
127
- len(
128
- [
129
- g
130
- for g in g.config.reqs.groups()
131
- if g["exploit"] == str(exploit_config.uuid) and g["commit"] == "latest"
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
- if current_hash in [
141
- ele["hash"] for ele in g.config.reqs.exploit_source_log(exploit_config.uuid)
142
- ]: # If the current commit is pushed
143
- return True
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 that are using the latest commit, "
147
- "this command is trying to push a new version of the exploit. "
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 = orjson.loads(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 orjson
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 = orjson.loads(f.read())
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
- return ExploitConfig.read(base_dir)
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
- env["XFARM_TEAM"] = orjson.dumps(team).decode()
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
- command,
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=True,
565
+ shell=False,
559
566
  )
560
567
  nicenessify(pid=proc.pid)
561
568
  else:
562
569
  proc = subprocess.Popen(
563
- command,
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=True,
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
- if not read_function:
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
- value = proc.stdout.read()
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(orjson.dumps({"server_id": g.server_id, "queue": attacks}))
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.6
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 different for each exploit id. You can use it to store some data you need to save and change for every exploit execution.
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
- store = Store()
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) -> bytes`: Retrieve a value by key.
126
- - `set(key: str, value: bytes, timeout: int = HTTP_TIMEOUT)`: Store a value by key.
127
- - `delete(key: str, timeout: int = HTTP_TIMEOUT)`: Delete a key.
128
- - `keys(timeout: int = HTTP_TIMEOUT) -> list[str]`: List all stored keys.
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
 
@@ -12,4 +12,3 @@ python-socketio[client]>=5.14.1
12
12
  textual>=3.0.0
13
13
  typer>=0.16
14
14
  xfarm
15
- orjson
@@ -12,4 +12,3 @@ python-socketio[client]>=5.14.1
12
12
  textual>=3.0.0
13
13
  typer>=0.16
14
14
  xfarm
15
- orjson
@@ -10,7 +10,7 @@ with open("requirements.txt", "r", encoding="utf-8") as f:
10
10
  if not ele.strip().startswith("#") and ele.strip() != ""
11
11
  ]
12
12
 
13
- VERSION = "1.7.6"
13
+ VERSION = "1.7.8"
14
14
 
15
15
  setuptools.setup(
16
16
  name="exploitfarm",
@@ -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