recon-setup 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- recon_setup-0.1.0/.github/workflows/publish.yml +37 -0
- recon_setup-0.1.0/.gitignore +20 -0
- recon_setup-0.1.0/LICENSE +21 -0
- recon_setup-0.1.0/PKG-INFO +12 -0
- recon_setup-0.1.0/README.md +103 -0
- recon_setup-0.1.0/pyproject.toml +20 -0
- recon_setup-0.1.0/recon_setup/__init__.py +0 -0
- recon_setup-0.1.0/recon_setup/main.py +185 -0
- recon_setup-0.1.0/recon_setup/utils/__init__.py +0 -0
- recon_setup-0.1.0/recon_setup/utils/active_directory.py +93 -0
- recon_setup-0.1.0/recon_setup/utils/builders.py +670 -0
- recon_setup-0.1.0/recon_setup/utils/commands.py +69 -0
- recon_setup-0.1.0/recon_setup/utils/context.py +101 -0
- recon_setup-0.1.0/recon_setup/utils/file_config.py +55 -0
- recon_setup-0.1.0/recon_setup/utils/hostfile.py +135 -0
- recon_setup-0.1.0/recon_setup/utils/htb_cli.py +172 -0
- recon_setup-0.1.0/recon_setup/utils/logger.py +20 -0
- recon_setup-0.1.0/recon_setup/utils/spray.py +106 -0
- recon_setup-0.1.0/recon_setup/utils/tasks.py +206 -0
- recon_setup-0.1.0/recon_setup/watchers/__init__.py +0 -0
- recon_setup-0.1.0/recon_setup/watchers/creds_watcher.py +46 -0
- recon_setup-0.1.0/recon_setup/watchers/nmap_watcher.py +59 -0
- recon_setup-0.1.0/tests/__init__.py +0 -0
- recon_setup-0.1.0/tests/test_bug_fixes.py +249 -0
- recon_setup-0.1.0/tests/test_builders.py +812 -0
- recon_setup-0.1.0/tests/test_entry_point.py +13 -0
- recon_setup-0.1.0/tests/test_htb_cli.py +417 -0
- recon_setup-0.1.0/uv.lock +715 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build-and-publish:
|
|
10
|
+
name: Build and Publish
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
id-token: write
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout code
|
|
19
|
+
uses: actions/checkout@v5
|
|
20
|
+
with:
|
|
21
|
+
ref: ${{ github.event_name == 'release' && github.event.release.tag_name || github.ref }}
|
|
22
|
+
|
|
23
|
+
- name: Set up Python
|
|
24
|
+
uses: actions/setup-python@v5
|
|
25
|
+
with:
|
|
26
|
+
python-version: '3.11'
|
|
27
|
+
|
|
28
|
+
- name: Install build dependencies
|
|
29
|
+
run: |
|
|
30
|
+
python -m pip install --upgrade pip
|
|
31
|
+
python -m pip install build
|
|
32
|
+
|
|
33
|
+
- name: Build package
|
|
34
|
+
run: python -m build
|
|
35
|
+
|
|
36
|
+
- name: Publish to PyPI (OIDC / Trusted Publishing)
|
|
37
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mojo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: recon-setup
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Automate the setup and enumeration process for offensive security labs.
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: impacket
|
|
8
|
+
Requires-Dist: libtmux==0.53.1
|
|
9
|
+
Requires-Dist: requests
|
|
10
|
+
Requires-Dist: rich
|
|
11
|
+
Requires-Dist: urllib3
|
|
12
|
+
Requires-Dist: watchdog
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# recon-setup
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
**recon-setup** is a CLI tool that automates tmux session creation, nmap scanning, and port-driven enumeration for offensive security labs.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- [x] Structured tmux session with dedicated panes for OpenVPN, Ligolo, file serving, nmap, and task logging.
|
|
11
|
+
- [x] Port-driven automation: watches nmap output in real time and dispatches enumeration tasks as ports are discovered.
|
|
12
|
+
- [x] Credential-aware handlers: pass initial credentials to pre-populate authenticated scans across SMB, LDAP, Kerberos, MSSQL, and more.
|
|
13
|
+
- [x] Credential watcher: monitors `creds.txt` for new entries and re-runs relevant handlers automatically.
|
|
14
|
+
- [x] HTB CLI integration: spawn machines directly by name without manually retrieving an IP.
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
- tmux
|
|
19
|
+
- `/opt/scripts/scan_machine.py` — nmap wrapper script that drives the scan and emits port results to stdout
|
|
20
|
+
- Tools used by enumeration handlers: `nxc`, `bloodhound-ce-python`, `bloodhound-cli`, `bloodyAD`, `certipy`, `aliasr`, `kerbrute`, `hashcat`, `ffuf`, `feroxbuster`, `nuclei`, `wpscan`, `powerview`, `impacket`
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Latest commit (GitHub)
|
|
26
|
+
uv tool install git+https://github.com/Mojo8898/recon-setup
|
|
27
|
+
pipx install git+https://github.com/Mojo8898/recon-setup
|
|
28
|
+
|
|
29
|
+
# Local development
|
|
30
|
+
git clone https://github.com/Mojo8898/recon-setup
|
|
31
|
+
cd recon-setup
|
|
32
|
+
uv sync
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
usage: recon-setup [-h] [-v VPN_PATH] [-s SESSION_PATH] [-i IP]
|
|
39
|
+
[--spawn SPAWN] [-n] [-a] [-u USERNAME] [-p PASSWORD] [-d]
|
|
40
|
+
session_name
|
|
41
|
+
|
|
42
|
+
Automate the setup and enumeration process for offensive security labs.
|
|
43
|
+
|
|
44
|
+
options:
|
|
45
|
+
-h, --help show this help message and exit
|
|
46
|
+
|
|
47
|
+
Session Arguments:
|
|
48
|
+
Arguments related to session configuration
|
|
49
|
+
|
|
50
|
+
session_name Name of the tmux session to be created
|
|
51
|
+
-v VPN_PATH, --vpn_path VPN_PATH
|
|
52
|
+
Path of your VPN file
|
|
53
|
+
-s SESSION_PATH, --session_path SESSION_PATH
|
|
54
|
+
Path to where the session will be created (default:
|
|
55
|
+
/workspace/machines)
|
|
56
|
+
-i IP, --ip IP IP address of the target machine
|
|
57
|
+
|
|
58
|
+
HTB CLI Arguments:
|
|
59
|
+
Arguments related to HTB CLI functionality
|
|
60
|
+
|
|
61
|
+
--spawn SPAWN Spawn the target machine using the HTB CLI instead of
|
|
62
|
+
providing an IP (requires htb-cli)
|
|
63
|
+
-n, --new_release Wait for the scheduled release time (7pm UTC) and
|
|
64
|
+
spawn automatically
|
|
65
|
+
|
|
66
|
+
Automation Arguments:
|
|
67
|
+
Arguments related to automated tasking
|
|
68
|
+
|
|
69
|
+
-a, --automate Optional flag to enable automated tasks on the fly
|
|
70
|
+
from nmap scan results
|
|
71
|
+
-u USERNAME, --username USERNAME
|
|
72
|
+
Username to supply automated tasks (AD only)
|
|
73
|
+
-p PASSWORD, --password PASSWORD
|
|
74
|
+
Password to supply automated tasks (AD only)
|
|
75
|
+
-d, --debug Enable debug mode for automation
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Standard session without automation:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
recon-setup pirate -v ~/vpn/lab.ovpn -i 10.10.11.40
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Session with full automation and initial credentials:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
recon-setup pirate -v ~/vpn/lab.ovpn -i 10.10.11.40 -a -u pentest -p 'Password123!'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Spawn an HTB machine by name and automate:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
recon-setup pirate -v ~/vpn/lab.ovpn --spawn pirate -a
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Contributing
|
|
97
|
+
|
|
98
|
+
Contributions are welcome. Feel free to open an issue for bugs or feature requests.
|
|
99
|
+
|
|
100
|
+
## Acknowledgments
|
|
101
|
+
|
|
102
|
+
- Built on top of [libtmux](https://github.com/tmux-python/libtmux)
|
|
103
|
+
- Enumeration handlers integrate [aliasr](https://github.com/Mojo8898/aliasr), [NetExec](https://github.com/Pennyw0rth/NetExec), and the [Impacket](https://github.com/fortra/impacket) suite
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "recon-setup"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Automate the setup and enumeration process for offensive security labs."
|
|
5
|
+
requires-python = ">=3.10"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"impacket",
|
|
8
|
+
"libtmux==0.53.1",
|
|
9
|
+
"requests",
|
|
10
|
+
"rich",
|
|
11
|
+
"urllib3",
|
|
12
|
+
"watchdog",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
recon-setup = "recon_setup.main:main"
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["hatchling"]
|
|
20
|
+
build-backend = "hatchling.build"
|
|
File without changes
|
|
@@ -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
|