Skip to content

Commit

Permalink
Merge pull request #366 from termanix/NFS-Protocol
Browse files Browse the repository at this point in the history
New Protocol NFS
  • Loading branch information
NeffIsBack authored Oct 3, 2024
2 parents aef2675 + 90cc0e5 commit 27313a0
Show file tree
Hide file tree
Showing 9 changed files with 528 additions and 3 deletions.
4 changes: 4 additions & 0 deletions nxc/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ def print_host_info(self):
def create_conn_obj(self):
return

def disconnect(self):
return

def check_if_admin(self):
return

Expand Down Expand Up @@ -234,6 +237,7 @@ def proto_flow(self):
else:
self.logger.debug("Calling command arguments")
self.call_cmd_args()
self.disconnect()

def call_cmd_args(self):
"""Calls all the methods specified by the command line arguments
Expand Down
379 changes: 379 additions & 0 deletions nxc/protocols/nfs.py

Large diffs are not rendered by default.

Empty file added nxc/protocols/nfs/__init__.py
Empty file.
94 changes: 94 additions & 0 deletions nxc/protocols/nfs/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from pathlib import Path
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy import MetaData, Table
from sqlalchemy.exc import (
IllegalStateChangeError,
NoInspectionAvailable,
NoSuchTableError,
)
from nxc.logger import nxc_logger
import sys


class database:
def __init__(self, db_engine):
self.CredentialsTable = None
self.HostsTable = None
self.LoggedinRelationsTable = None
self.SharesTable = None

self.db_engine = db_engine
self.db_path = self.db_engine.url.database
self.protocol = Path(self.db_path).stem.upper()
self.metadata = MetaData()
self.reflect_tables()

session_factory = sessionmaker(bind=self.db_engine, expire_on_commit=True)
Session = scoped_session(session_factory)
self.sess = Session()

@staticmethod
def db_schema(db_conn):
db_conn.execute(
"""CREATE TABLE "credentials" (
"id" integer PRIMARY KEY,
"username" text,
"password" text
)"""
)

db_conn.execute(
"""CREATE TABLE "hosts" (
"id" integer PRIMARY KEY,
"ip" text,
"hostname" text,
"port" integer
)"""
)
db_conn.execute(
"""CREATE TABLE "loggedin_relations" (
"id" integer PRIMARY KEY,
"cred_id" integer,
"host_id" integer,
FOREIGN KEY(cred_id) REFERENCES credentials(id),
FOREIGN KEY(host_id) REFERENCES hosts(id)
)"""
)
db_conn.execute(
"""CREATE TABLE "shares" (
"id" integer PRIMARY KEY,
"lir_id" integer,
"data" text,
FOREIGN KEY(lir_id) REFERENCES loggedin_relations(id)
)"""
)

def reflect_tables(self):
with self.db_engine.connect():
try:
self.CredentialsTable = Table("credentials", self.metadata, autoload_with=self.db_engine)
self.HostsTable = Table("hosts", self.metadata, autoload_with=self.db_engine)
self.LoggedinRelationsTable = Table("loggedin_relations", self.metadata, autoload_with=self.db_engine)
self.SharesTable = Table("shares", self.metadata, autoload_with=self.db_engine)
except (NoInspectionAvailable, NoSuchTableError):
print(
f"""
[-] Error reflecting tables for the {self.protocol} protocol - this means there is a DB schema mismatch
[-] This is probably because a newer version of nxc is being run on an old DB schema
[-] Optionally save the old DB data (`cp {self.db_path} ~/nxc_{self.protocol.lower()}.bak`)
[-] Then remove the {self.protocol} DB (`rm -f {self.db_path}`) and run nxc to initialize the new DB"""
)
sys.exit()

def shutdown_db(self):
try:
self.sess.close()
# due to the async nature of nxc, sometimes session state is a bit messy and this will throw:
# Method 'close()' can't be called here; method '_connection_for_bind()' is already in progress and
# this would cause an unexpected state change to <SessionTransactionState.CLOSED: 5>
except IllegalStateChangeError as e:
nxc_logger.debug(f"Error while closing session db object: {e}")

def clear_database(self):
for table in self.metadata.sorted_tables:
self.sess.execute(table.delete())
15 changes: 15 additions & 0 deletions nxc/protocols/nfs/db_navigator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from nxc.nxcdb import DatabaseNavigator, print_help


class navigator(DatabaseNavigator):
def do_clear_database(self, line):
if input("This will destroy all data in the current database, are you SURE you want to run this? (y/n): ") == "y":
self.db.clear_database()

def help_clear_database(self):
help_string = """
clear_database
THIS COMPLETELY DESTROYS ALL DATA IN THE CURRENTLY CONNECTED DATABASE
YOU CANNOT UNDO THIS COMMAND
"""
print_help(help_string)
12 changes: 12 additions & 0 deletions nxc/protocols/nfs/proto_args.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
def proto_args(parser, parents):
nfs_parser = parser.add_parser("nfs", help="NFS", parents=parents)
nfs_parser.add_argument("--port", type=int, default=111, help="NFS portmapper port (default: %(default)s)")
nfs_parser.add_argument("--nfs-timeout", type=int, default=30, help="NFS connection timeout (default: %(default)ss)")

dgroup = nfs_parser.add_argument_group("NFS Mapping/Enumeration", "Options for Mapping/Enumerating NFS")
dgroup.add_argument("--shares", action="store_true", help="List NFS shares")
dgroup.add_argument("--enum-shares", nargs="?", type=int, const=3, help="Authenticate and enumerate exposed shares recursively (default depth: %(const)s)")
dgroup.add_argument("--get-file", nargs=2, metavar="FILE", help="Download remote NFS file. Example: --get-file remote_file local_file")
dgroup.add_argument("--put-file", nargs=2, metavar="FILE", help="Upload remote NFS file with chmod 777 permissions to the specified folder. Example: --put-file local_file remote_file")

return parser
21 changes: 18 additions & 3 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ paramiko = "^3.3.1"
poetry-dynamic-versioning = "^1.2.0"
pyasn1-modules = "^0.3.0"
pylnk3 = "^0.4.2"
pynfsclient = { git = "https://github.com/Pennyw0rth/NfsClient" }
pypsrp = "^0.8.1"
pypykatz = "^0.6.8"
pywerview = "^0.3.3" # pywerview 5 requires libkrb5-dev installed which is not default on kali (as of 9/23)
Expand Down
5 changes: 5 additions & 0 deletions tests/e2e_commands.txt
Original file line number Diff line number Diff line change
Expand Up @@ -248,3 +248,8 @@ netexec ftp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --get test_file.txt
netexec ftp TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE --no-bruteforce
netexec ftp TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE --no-bruteforce --continue-on-success
netexec ftp TARGET_HOST -u TEST_USER_FILE -p TEST_PASSWORD_FILE
##### NFS
netexec nfs TARGETHOST -u "" -p "" --shares
netexec nfs TARGETHOST -u "" -p "" --enum-shares
netexec nfs TARGETHOST -u "" -p "" --get-file /NFStest/test/test.txt ../test.txt
netexec nfs TARGETHOST -u "" -p "" --put-file ../test.txt /NFStest/test

0 comments on commit 27313a0

Please sign in to comment.