recon-setup 0.1.0__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.
- recon_setup/__init__.py +0 -0
- recon_setup/main.py +185 -0
- recon_setup/utils/__init__.py +0 -0
- recon_setup/utils/active_directory.py +93 -0
- recon_setup/utils/builders.py +670 -0
- recon_setup/utils/commands.py +69 -0
- recon_setup/utils/context.py +101 -0
- recon_setup/utils/file_config.py +55 -0
- recon_setup/utils/hostfile.py +135 -0
- recon_setup/utils/htb_cli.py +172 -0
- recon_setup/utils/logger.py +20 -0
- recon_setup/utils/spray.py +106 -0
- recon_setup/utils/tasks.py +206 -0
- recon_setup/watchers/__init__.py +0 -0
- recon_setup/watchers/creds_watcher.py +46 -0
- recon_setup/watchers/nmap_watcher.py +59 -0
- recon_setup-0.1.0.dist-info/METADATA +12 -0
- recon_setup-0.1.0.dist-info/RECORD +21 -0
- recon_setup-0.1.0.dist-info/WHEEL +4 -0
- recon_setup-0.1.0.dist-info/entry_points.txt +2 -0
- recon_setup-0.1.0.dist-info/licenses/LICENSE +21 -0
recon_setup/__init__.py
ADDED
|
File without changes
|
recon_setup/main.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import ctypes
|
|
5
|
+
import libtmux
|
|
6
|
+
import os
|
|
7
|
+
import signal
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from time import sleep
|
|
11
|
+
|
|
12
|
+
from recon_setup.utils.context import Context
|
|
13
|
+
from recon_setup.utils.htb_cli import spawn_machine
|
|
14
|
+
from recon_setup.watchers.nmap_watcher import watch_nmap
|
|
15
|
+
|
|
16
|
+
def set_death_signal(sig=signal.SIGHUP):
|
|
17
|
+
"""Send SIGHUP to the forked process when its parent dies"""
|
|
18
|
+
libc = ctypes.CDLL("libc.so.6")
|
|
19
|
+
PR_SET_PDEATHSIG = 1
|
|
20
|
+
libc.prctl(PR_SET_PDEATHSIG, sig)
|
|
21
|
+
|
|
22
|
+
def verify_connection(ip):
|
|
23
|
+
"""Verify connection with target IP."""
|
|
24
|
+
reachable = False
|
|
25
|
+
while not reachable:
|
|
26
|
+
try:
|
|
27
|
+
subprocess.check_call(
|
|
28
|
+
["ping", "-c1", "-W.5", ip],
|
|
29
|
+
stdout=subprocess.DEVNULL,
|
|
30
|
+
stderr=subprocess.DEVNULL
|
|
31
|
+
)
|
|
32
|
+
reachable = True
|
|
33
|
+
except subprocess.CalledProcessError:
|
|
34
|
+
reachable = False
|
|
35
|
+
sleep(0.5)
|
|
36
|
+
sleep(1)
|
|
37
|
+
|
|
38
|
+
def main():
|
|
39
|
+
# Initialize parser
|
|
40
|
+
parser = argparse.ArgumentParser(description="Automate the setup and enumeration process for offensive security labs.")
|
|
41
|
+
|
|
42
|
+
# Initialize session arguments
|
|
43
|
+
session_group = parser.add_argument_group("Session Arguments", "Arguments related to session configuration")
|
|
44
|
+
session_group.add_argument("session_name", type=str, help="Name of the tmux session to be created")
|
|
45
|
+
session_group.add_argument("-v", "--vpn_path", type=str, help="Path of your VPN file")
|
|
46
|
+
session_group.add_argument("-s", "--session_path", type=str, default="/workspace/machines", help="Path to where the session will be created (default: /workspace/machines)")
|
|
47
|
+
session_group.add_argument("-i", "--ip", type=str, help="IP address of the target machine")
|
|
48
|
+
|
|
49
|
+
# Initialize HTB CLI arguments
|
|
50
|
+
htb_cli_group = parser.add_argument_group("HTB CLI Arguments", "Arguments related to HTB CLI functionality")
|
|
51
|
+
htb_cli_group.add_argument("--spawn", type=str, help="Spawn the target machine via the HTB API instead of providing an IP (requires HTB_TOKEN env var)")
|
|
52
|
+
htb_cli_group.add_argument("-n", "--new_release", action="store_true", help="Wait until T-60s before the scheduled release time (7pm UTC) and spawn automatically via the HTB API")
|
|
53
|
+
|
|
54
|
+
# Initialize automation arguments
|
|
55
|
+
automation_group = parser.add_argument_group("Automation Arguments", "Arguments related to automated tasking")
|
|
56
|
+
automation_group.add_argument("-a", "--automate", action="store_true", help="Optional flag to enable automated tasks on the fly from nmap scan results")
|
|
57
|
+
automation_group.add_argument("-u", "--username", type=str, help="Username to supply automated tasks (AD only)")
|
|
58
|
+
automation_group.add_argument("-p", "--password", type=str, help="Password to supply automated tasks (AD only)")
|
|
59
|
+
automation_group.add_argument("-d", "--debug", action="store_true", help="Enable debug mode for automation")
|
|
60
|
+
|
|
61
|
+
# Initialize parser
|
|
62
|
+
args = parser.parse_args()
|
|
63
|
+
|
|
64
|
+
# Define local variables
|
|
65
|
+
session_name = args.session_name
|
|
66
|
+
vpn_path = os.path.abspath(args.vpn_path) if args.vpn_path is not None else None
|
|
67
|
+
session_path = args.session_path
|
|
68
|
+
ip = args.ip
|
|
69
|
+
spawn = args.spawn
|
|
70
|
+
new_release = args.new_release
|
|
71
|
+
automate = args.automate
|
|
72
|
+
user = args.username
|
|
73
|
+
password = args.password
|
|
74
|
+
debug = args.debug
|
|
75
|
+
|
|
76
|
+
# Define file paths
|
|
77
|
+
scan_script_file = "/opt/scripts/scan_machine.py"
|
|
78
|
+
target_dir = os.path.join(session_path, session_name)
|
|
79
|
+
nmap_dir = os.path.join(target_dir, "nmap")
|
|
80
|
+
log_dir = os.path.join(target_dir, "logs")
|
|
81
|
+
tmux_pipe_file = os.path.join(log_dir, "tmux_pipe.log")
|
|
82
|
+
log_file = os.path.join(log_dir, "task.log")
|
|
83
|
+
debug_file = os.path.join(log_dir, "debug.log")
|
|
84
|
+
users_file = os.path.join(target_dir, "valid_users.txt")
|
|
85
|
+
creds_file = os.path.join(target_dir, "creds.txt")
|
|
86
|
+
|
|
87
|
+
# Check for nmap wrapper script
|
|
88
|
+
if not os.path.isfile(scan_script_file):
|
|
89
|
+
print(f"Error: Required nmap wrapper script not found at {scan_script_file}", file=sys.stderr)
|
|
90
|
+
sys.exit(1)
|
|
91
|
+
|
|
92
|
+
# Initialize HTB machine if relevant arguments are included
|
|
93
|
+
if spawn:
|
|
94
|
+
ip = spawn_machine(spawn, new_release)
|
|
95
|
+
if not ip:
|
|
96
|
+
print("Error: HTB CLI failed to provide an IP")
|
|
97
|
+
sys.exit(1)
|
|
98
|
+
else:
|
|
99
|
+
ip = args.ip
|
|
100
|
+
|
|
101
|
+
# Initialize environment
|
|
102
|
+
os.makedirs(nmap_dir, exist_ok=True)
|
|
103
|
+
os.chdir(target_dir)
|
|
104
|
+
|
|
105
|
+
# Initialize tmux session
|
|
106
|
+
server = libtmux.Server()
|
|
107
|
+
session = server.new_session(
|
|
108
|
+
session_name=session_name,
|
|
109
|
+
window_name="services",
|
|
110
|
+
attach=False
|
|
111
|
+
)
|
|
112
|
+
initial_window = session.active_window
|
|
113
|
+
openvpn_pane = initial_window.active_pane
|
|
114
|
+
if os.path.isfile(vpn_path):
|
|
115
|
+
openvpn_pane.send_keys(f"sudo openvpn {vpn_path}")
|
|
116
|
+
ligolo_pane = openvpn_pane.split(direction=libtmux.pane.PaneDirection.Below)
|
|
117
|
+
ligolo_pane.send_keys("mkdir -p ligolo; cd ligolo; proxy -selfcert")
|
|
118
|
+
ip_pane = openvpn_pane.split(direction=libtmux.pane.PaneDirection.Right)
|
|
119
|
+
ip_pane.send_keys("command -v htb-gui &>/dev/null && htb-gui; ipa")
|
|
120
|
+
updog_pane = ligolo_pane.split(direction=libtmux.pane.PaneDirection.Right)
|
|
121
|
+
updog_pane.send_keys("updog -p 8000 -d ~/staging")
|
|
122
|
+
base_window = session.new_window(window_name=ip)
|
|
123
|
+
|
|
124
|
+
# Initialize panes
|
|
125
|
+
nmap_pane = base_window.active_pane
|
|
126
|
+
base_pane = base_window.split(direction=libtmux.pane.PaneDirection.Right)
|
|
127
|
+
|
|
128
|
+
# Fork process to attach to tmux session while executing additional tasks
|
|
129
|
+
pid = os.fork()
|
|
130
|
+
if pid == 0:
|
|
131
|
+
set_death_signal()
|
|
132
|
+
|
|
133
|
+
# Establish connection with VPN and initialize the base window
|
|
134
|
+
if os.path.isfile(vpn_path):
|
|
135
|
+
verify_connection(ip)
|
|
136
|
+
sleep(1)
|
|
137
|
+
session.select_window(2)
|
|
138
|
+
base_pane.select()
|
|
139
|
+
|
|
140
|
+
# Initialize automation if argument is provided
|
|
141
|
+
if automate:
|
|
142
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
143
|
+
for file in [tmux_pipe_file, debug_file, users_file, creds_file]:
|
|
144
|
+
if os.path.isfile(file):
|
|
145
|
+
os.remove(file)
|
|
146
|
+
|
|
147
|
+
# Clear log file while also making sure it exists
|
|
148
|
+
with open(log_file, 'w'):
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
# Initialize stdio depending on debug flag
|
|
152
|
+
with open(os.devnull, "wb") as null:
|
|
153
|
+
if debug:
|
|
154
|
+
with open(debug_file, "wb") as log:
|
|
155
|
+
os.dup2(null.fileno(), 0)
|
|
156
|
+
os.dup2(log.fileno(), 1)
|
|
157
|
+
os.dup2(log.fileno(), 2)
|
|
158
|
+
else:
|
|
159
|
+
os.dup2(null.fileno(), 0)
|
|
160
|
+
os.dup2(null.fileno(), 1)
|
|
161
|
+
os.dup2(null.fileno(), 2)
|
|
162
|
+
|
|
163
|
+
# Initialize context
|
|
164
|
+
context = Context(session, nmap_pane, nmap_dir, tmux_pipe_file, log_file, users_file, creds_file, ip)
|
|
165
|
+
if user and password:
|
|
166
|
+
context.add_initial_cred(user.lower(), password=password)
|
|
167
|
+
|
|
168
|
+
# Initialize task logging
|
|
169
|
+
log_pane = base_pane.split(size=20)
|
|
170
|
+
sleep(1)
|
|
171
|
+
log_pane.send_keys(f"clear && tail -n +0 -f {log_file}")
|
|
172
|
+
|
|
173
|
+
# Initialize nmap scanning and automation
|
|
174
|
+
os.remove(tmux_pipe_file)
|
|
175
|
+
nmap_pane.cmd("pipe-pane", f"cat >> {tmux_pipe_file}")
|
|
176
|
+
nmap_pane.send_keys(f"{scan_script_file} {ip}")
|
|
177
|
+
watch_nmap(context)
|
|
178
|
+
elif ip:
|
|
179
|
+
nmap_pane.send_keys(f"{scan_script_file} {ip}")
|
|
180
|
+
os._exit(0)
|
|
181
|
+
else:
|
|
182
|
+
os.execvp("tmux", ["tmux", "attach-session", "-t", session_name])
|
|
183
|
+
|
|
184
|
+
if __name__ == "__main__":
|
|
185
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
from recon_setup.utils.logger import write_log
|
|
5
|
+
|
|
6
|
+
_NEGATIVE = re.compile(r"\[\-\]")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def anonymous_bind(context):
|
|
10
|
+
cmd = ["nxc", "ldap", context.ip, "-u", "", "-p", ""]
|
|
11
|
+
try:
|
|
12
|
+
p = subprocess.run(cmd, capture_output=True, text=True, timeout=8, check=False)
|
|
13
|
+
except Exception as e:
|
|
14
|
+
write_log(context.log_file, f"NetExec ldap subprocess error: {e}", "ERROR")
|
|
15
|
+
return False
|
|
16
|
+
out = p.stdout or ""
|
|
17
|
+
return not _NEGATIVE.search(out)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def enum_smb_shares(context):
|
|
21
|
+
def run_nxc(user, password):
|
|
22
|
+
cmd = f"nxc smb {context.ip} -u {user} "
|
|
23
|
+
if password:
|
|
24
|
+
cmd += f"-p '{password}' -k "
|
|
25
|
+
else:
|
|
26
|
+
cmd += f"-p '' "
|
|
27
|
+
cmd += f"--shares"
|
|
28
|
+
# write_log(context.log_file, f"Running: {cmd}", "INFO")
|
|
29
|
+
try:
|
|
30
|
+
p = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=8, check=False)
|
|
31
|
+
return p.stdout or ""
|
|
32
|
+
except Exception as e:
|
|
33
|
+
write_log(context.log_file, f"Subprocess exception: {e}", "ERROR")
|
|
34
|
+
return ""
|
|
35
|
+
|
|
36
|
+
def parse_shares(raw):
|
|
37
|
+
lines = [ln.rstrip("\n") for ln in raw.splitlines()]
|
|
38
|
+
hdr_idx = None
|
|
39
|
+
for i, ln in enumerate(lines):
|
|
40
|
+
if "Share" in ln and "Permissions" in ln and "Remark" in ln:
|
|
41
|
+
hdr_idx = i
|
|
42
|
+
break
|
|
43
|
+
if hdr_idx is None or hdr_idx + 2 >= len(lines):
|
|
44
|
+
return []
|
|
45
|
+
|
|
46
|
+
header = lines[hdr_idx]
|
|
47
|
+
try:
|
|
48
|
+
c_share = header.index("Share")
|
|
49
|
+
c_perm = header.index("Permissions")
|
|
50
|
+
c_remark = header.index("Remark")
|
|
51
|
+
except ValueError:
|
|
52
|
+
return []
|
|
53
|
+
|
|
54
|
+
shares = []
|
|
55
|
+
for ln in lines[hdr_idx + 2 :]:
|
|
56
|
+
if "SMB" not in ln or not ln.strip() or ln.strip().startswith("-----"):
|
|
57
|
+
continue
|
|
58
|
+
pad = ln + " " * max(0, c_remark + 1 - len(ln))
|
|
59
|
+
name = pad[c_share:c_perm].strip()
|
|
60
|
+
perms = pad[c_perm:c_remark].strip()
|
|
61
|
+
remark = pad[c_remark:].strip()
|
|
62
|
+
if not name or name.startswith("-----"):
|
|
63
|
+
continue
|
|
64
|
+
access = [p.strip() for p in perms.split(",") if p.strip()] if perms else []
|
|
65
|
+
shares.append({"name": name, "remark": remark, "access": access})
|
|
66
|
+
|
|
67
|
+
return shares
|
|
68
|
+
|
|
69
|
+
# 1) user/pass
|
|
70
|
+
if context.creds_exist():
|
|
71
|
+
user, password = context.get_initial_cred()
|
|
72
|
+
if user is not None:
|
|
73
|
+
out = run_nxc(user, password)
|
|
74
|
+
shares = parse_shares(out)
|
|
75
|
+
if shares:
|
|
76
|
+
return shares, "user/pass"
|
|
77
|
+
write_log(context.log_file, "Share enum with provided credentials failed; trying null session", "INFO")
|
|
78
|
+
|
|
79
|
+
# 2) null
|
|
80
|
+
out = run_nxc("''", "")
|
|
81
|
+
shares = parse_shares(out)
|
|
82
|
+
if shares:
|
|
83
|
+
write_log(context.log_file, "Null authentication is enabled for reading shares", "SUCCESS")
|
|
84
|
+
return shares, "null"
|
|
85
|
+
|
|
86
|
+
# 3) guest
|
|
87
|
+
out = run_nxc("a", "")
|
|
88
|
+
shares = parse_shares(out)
|
|
89
|
+
if shares:
|
|
90
|
+
write_log(context.log_file, "Guest authentication is enabled for reading shares", "SUCCESS")
|
|
91
|
+
return shares, "guest"
|
|
92
|
+
|
|
93
|
+
return None, None
|