Skip to content

Commit 7afb4af

Browse files
authored
Merge branch 'main' into sepauli/fix-keepass_trigger
2 parents 4e0b44f + 5197a0e commit 7afb4af

27 files changed

Lines changed: 669 additions & 389 deletions
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
name: Feature request
3+
about: Request a new feature or enhancement
4+
title: ''
5+
labels: ''
6+
assignees: ''
7+
8+
---
9+
**Please Describe The Problem To Be Solved**
10+
(Replace This Text: Please present a concise description of the problem to be addressed by this feature request. Please be clear what parts of the problem are considered to be in-scope and out-of-scope.)
11+
12+
**(Optional): Suggest A Solution**
13+
(Replace This Text: A concise description of your preferred solution. Things to address include:
14+
* Details of the technical implementation
15+
* Tradeoffs made in design decisions
16+
* Caveats and considerations for the future
17+
18+
If there are multiple solutions, please present each one separately. Save comparisons for the very end.)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
name: Pull request
3+
about: Update code to fix a bug or add an enhancement/feature
4+
title: ''
5+
labels: ''
6+
assignees: ''
7+
8+
---
9+
## Description
10+
11+
Please include a summary of the change and which issue is fixed, or what the enhancement does.
12+
Please also include relevant motivation and context.
13+
List any dependencies that are required for this change.
14+
15+
## Type of change
16+
Please delete options that are not relevant.
17+
- [ ] Bug fix (non-breaking change which fixes an issue)
18+
- [ ] New feature (non-breaking change which adds functionality)
19+
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
20+
- [ ] This change requires a documentation update
21+
- [ ] This requires a third party update (such as Impacket, Dploot, lsassy, etc)
22+
23+
## How Has This Been Tested?
24+
Please describe the tests that you ran to verify your changes (e2e, single commands, etc)
25+
Please also list any relevant details for your test configuration, such as your locally running machine Python version & OS, as well as the target(s) you tested against, including software versions
26+
27+
If you are using poetry, you can easily run tests via:
28+
`poetry run python tests/e2e_tests.py -t $TARGET -u $USER -p $PASSWORD`
29+
There are additional options like `--errors` to display ALL errors (some may not be failures), `--poetry` (output will include the poetry run prepended), `--line-num $START-$END $SINGLE` for only running a subset
30+
31+
## Screenshots (if appropriate):
32+
Screenshots are always nice to have and can give a visual representation of the change.
33+
If appropriate include before and after screenshot(s) to show which results are to be expected.
34+
35+
## Checklist:
36+
37+
- [ ] I have ran Ruff against my changes (via poetry: `poetry run python -m ruff check . --preview`, use `--fix` to automatically fix what it can)
38+
- [ ] I have added or updated the tests/e2e_commands.txt file if necessary
39+
- [ ] New and existing e2e tests pass locally with my changes
40+
- [ ] My code follows the style guidelines of this project (should be covered by Ruff above)
41+
- [ ] If reliant on third party dependencies, such as Impacket, dploot, lsassy, etc, I have linked the relevant PRs in those projects
42+
- [ ] I have performed a self-review of my own code
43+
- [ ] I have commented my code, particularly in hard-to-understand areas
44+
- [ ] I have made corresponding changes to the documentation (PR here: https://github.com/Pennyw0rth/NetExec-Wiki)

.github/workflows/test.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ jobs:
2626
python-version: ${{ matrix.python-version }}
2727
cache: poetry
2828
cache-dependency-path: poetry.lock
29+
- name: Install with pipx
30+
run: |
31+
pipx install . --python python${{ matrix.python-version }}
2932
- name: Install poetry
3033
run: |
3134
pipx install poetry --python python${{ matrix.python-version }}
@@ -45,4 +48,4 @@ jobs:
4548
poetry run netexec mssql 127.0.0.1
4649
poetry run netexec ssh 127.0.0.1
4750
poetry run netexec ftp 127.0.0.1
48-
poetry run netexec smb 127.0.0.1 -M veeam
51+
poetry run netexec smb 127.0.0.1 -M veeam

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
data/nxc.db
22
hash_spider_default.sqlite3
3+
hash_spider_testing.sqlite3
34
*.bak
45
*.log
56
.venv

netexec.spec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ a = Analysis(
4242
'nxc.helpers.bash',
4343
'nxc.helpers.bloodhound',
4444
'nxc.helpers.msada_guids',
45+
'nxc.helpers.ntlm_parser',
4546
'paramiko',
4647
'pypsrp.client',
4748
'pywerview.cli.helpers',

nxc/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def gen_cli_args():
4646

4747
parser.add_argument("-t", type=int, dest="threads", default=256, help="set how many concurrent threads to use (default: 256)")
4848
parser.add_argument("--timeout", default=None, type=int, help="max timeout in seconds of each thread (default: None)")
49-
parser.add_argument("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each connection (default: None)")
49+
parser.add_argument("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each authentication (default: None)")
5050
parser.add_argument("--no-progress", action="store_true", help="Not displaying progress bar during scan")
5151
parser.add_argument("--verbose", action="store_true", help="enable verbose output")
5252
parser.add_argument("--debug", action="store_true", help="enable debug level information")

nxc/connection.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from nxc.config import pwned_label
1010
from nxc.helpers.logger import highlight
11+
from nxc.loaders.moduleloader import ModuleLoader
1112
from nxc.logger import nxc_logger, NXCAdapter
1213
from nxc.context import Context
1314
from nxc.protocols.ldap.laps import laps_search
@@ -107,18 +108,6 @@ def __init__(self, args, db, host):
107108
self.logger.info(f"Error resolving hostname {self.hostname}: {e}")
108109
return
109110

110-
if args.jitter:
111-
jitter = args.jitter
112-
if "-" in jitter:
113-
start, end = jitter.split("-")
114-
jitter = (int(start), int(end))
115-
else:
116-
jitter = (0, int(jitter))
117-
118-
value = random.choice(range(jitter[0], jitter[1]))
119-
self.logger.debug(f"Doin' the jitterbug for {value} second(s)")
120-
sleep(value)
121-
122111
try:
123112
self.proto_flow()
124113
except Exception as e:
@@ -150,16 +139,7 @@ def create_conn_obj(self):
150139
def check_if_admin(self):
151140
return
152141

153-
def kerberos_login(
154-
self,
155-
domain,
156-
username,
157-
password="",
158-
ntlm_hash="",
159-
aesKey="",
160-
kdcHost="",
161-
useCache=False,
162-
):
142+
def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False):
163143
return
164144

165145
def plaintext_login(self, domain, username, password):
@@ -178,6 +158,7 @@ def proto_flow(self):
178158
self.enum_host_info()
179159
if self.print_host_info() and (self.login() or (self.username == "" and self.password == "")):
180160
if hasattr(self.args, "module") and self.args.module:
161+
self.load_modules()
181162
self.logger.debug("Calling modules")
182163
self.call_modules()
183164
else:
@@ -211,7 +192,7 @@ def call_modules(self):
211192
It iterates over the modules specified in the command line arguments.
212193
For each module, it loads the module and creates a context object, then calls functions based on the module's attributes.
213194
"""
214-
for module in self.module:
195+
for module in self.modules:
215196
self.logger.debug(f"Loading module {module.name} - {module}")
216197
module_logger = NXCAdapter(
217198
extra={
@@ -395,7 +376,9 @@ def parse_credentials(self):
395376
return domain, username, owned, secret, cred_type, [None] * len(secret)
396377

397378
def try_credentials(self, domain, username, owned, secret, cred_type, data=None):
398-
"""Try to login using the specified credentials and protocol.
379+
"""
380+
Try to login using the specified credentials and protocol.
381+
With --jitter an authentication throttle can be applied.
399382
400383
Possible login methods are:
401384
- plaintext (/kerberos)
@@ -408,6 +391,18 @@ def try_credentials(self, domain, username, owned, secret, cred_type, data=None)
408391
return False
409392
if hasattr(self.args, "delegate") and self.args.delegate:
410393
self.args.kerberos = True
394+
395+
if self.args.jitter:
396+
jitter = self.args.jitter
397+
if "-" in jitter:
398+
start, end = jitter.split("-")
399+
jitter = (int(start), int(end))
400+
else:
401+
jitter = (0, int(jitter))
402+
value = jitter[0] if jitter[0] == jitter[1] else random.choice(range(jitter[0], jitter[1]))
403+
self.logger.debug(f"Throttle authentications: sleeping {value} second(s)")
404+
sleep(value)
405+
411406
with sem:
412407
if cred_type == "plaintext":
413408
if self.args.kerberos:
@@ -496,3 +491,12 @@ def login(self):
496491

497492
def mark_pwned(self):
498493
return highlight(f"({pwned_label})" if self.admin_privs else "")
494+
495+
def load_modules(self):
496+
self.logger.info(f"Loading modules for target: {self.host}")
497+
loader = ModuleLoader(self.args, self.db, self.logger)
498+
self.modules = []
499+
500+
for module_path in self.module_paths:
501+
module = loader.init_module(module_path)
502+
self.modules.append(module)

nxc/helpers/bloodhound.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,8 @@ def add_user_bh(user, domain, logger, config):
5252
_add_with_domain(user_info, domain, tx, logger)
5353
except AuthError:
5454
logger.fail(f"Provided Neo4J credentials ({config.get('BloodHound', 'bh_user')}:{config.get('BloodHound', 'bh_pass')}) are not valid.")
55-
exit()
5655
except ServiceUnavailable:
5756
logger.fail(f"Neo4J does not seem to be available on {uri}.")
58-
exit()
5957
except Exception as e:
6058
logger.fail(f"Unexpected error with Neo4J: {e}")
6159
finally:

nxc/logger.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def setup_debug_logging():
3030
root_logger.setLevel(logging.INFO)
3131
elif debug_args.debug:
3232
nxc_logger.logger.setLevel(logging.DEBUG)
33-
root_logger.setLevel(logging.DEBUG)
33+
root_logger.setLevel(logging.INFO)
3434
else:
3535
nxc_logger.logger.setLevel(logging.ERROR)
3636
root_logger.setLevel(logging.ERROR)
@@ -53,13 +53,15 @@ def __init__(self, formatter=None, *args, **kwargs):
5353

5454
def emit(self, record):
5555
"""Overrides the emit method of the RichHandler class so we can set the proper pathname and lineno"""
56+
# for some reason in RDP, the exc_text is None which leads to a KeyError in Python logging
57+
record.exc_text = record.getMessage() if record.exc_text is None else record.exc_text
58+
5659
if hasattr(record, "caller_frame"):
5760
frame_info = inspect.getframeinfo(record.caller_frame)
5861
record.pathname = frame_info.filename
5962
record.lineno = frame_info.lineno
6063
super().emit(record)
6164

62-
6365
def no_debug(func):
6466
"""Stops logging non-debug messages when we are in debug mode
6567
It creates a temporary logger and logs the message to the console and file

nxc/modules/adcs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ def options(self, context, module_options):
2727
SERVER PKI Enrollment Server to enumerate templates for. Default is None, use CN name
2828
BASE_DN The base domain name for the LDAP query
2929
"""
30-
self.context = context
3130
self.regex = re.compile("(https?://.+)")
3231

3332
self.server = None
@@ -39,6 +38,7 @@ def options(self, context, module_options):
3938

4039
def on_login(self, context, connection):
4140
"""On a successful LDAP login we perform a search for all PKI Enrollment Server or Certificate Templates Names."""
41+
self.context = context
4242
if self.server is None:
4343
search_filter = "(objectClass=pKIEnrollmentService)"
4444
else:

0 commit comments

Comments
 (0)