Skip to content

Commit

Permalink
Add root_escape for --get-file and --put-file
Browse files Browse the repository at this point in the history
  • Loading branch information
NeffIsBack committed Mar 2, 2025
1 parent 67ce02e commit df4e992
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 28 deletions.
92 changes: 65 additions & 27 deletions nxc/protocols/nfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
ACCESS3_READ,
ACCESS3_MODIFY,
ACCESS3_EXECUTE,
MNT3ERR_ACCES,
NFSSTAT3,
NFS3ERR_NOENT,
NF3REG,
Expand Down Expand Up @@ -348,17 +349,35 @@ def get_file(self):
self.nfs3 = NFSv3(self.host, nfs_port, self.args.nfs_timeout, self.auth)
self.nfs3.connect()

# Mount the NFS share
mnt_info = self.mount.mnt(remote_dir_path, self.auth)

# Update the UID for the file
attrs = self.nfs3.getattr(mnt_info["mountinfo"]["fhandle"], auth=self.auth)
self.auth["uid"] = attrs["attributes"]["uid"]
dir_handle = mnt_info["mountinfo"]["fhandle"]
# Mount the NFS share or get the root handle
if self.root_escape and not self.args.share:
mount_fh = self.escape_fh
elif not self.args.share:
self.logger.fail("No root escape possible, please specify a share")
return
else:
mnt_info = self.mount.mnt(self.args.share, self.auth)
if mnt_info["status"] != 0:
self.logger.fail(f"Error mounting share {self.args.share}: {NFSSTAT3[mnt_info['status']]}")
return
mount_fh = mnt_info["mountinfo"]["fhandle"]

# Iterate over the path until we hit the file
curr_fh = mount_fh
for sub_path in remote_file_path.lstrip("/").split("/"):
# Update the UID for the next object and get the handle
self.update_auth(mount_fh)
res = self.nfs3.lookup(curr_fh, sub_path, auth=self.auth)

# Check for a bad path
if "resfail" in res and res["status"] == NFS3ERR_NOENT:
self.logger.fail(f"Unknown path: {remote_file_path!r}")
return

# Get the file handle and file size
dir_data = self.nfs3.lookup(dir_handle, file_name, auth=self.auth)
file_handle = dir_data["resok"]["object"]["data"]
curr_fh = res["resok"]["object"]["data"]
# If response is file then break
if res["resok"]["obj_attributes"]["attributes"]["type"] == NF3REG:
break

# Handle files over the default chunk size of 1024 * 1024
offset = 0
Expand All @@ -367,7 +386,7 @@ def get_file(self):
# Loop until we have read the entire file
with open(local_file_path, "wb+") as local_file:
while not eof:
file_data = self.nfs3.read(file_handle, offset, auth=self.auth)
file_data = self.nfs3.read(curr_fh, offset, auth=self.auth)

if "resfail" in file_data:
raise Exception("Insufficient Permissions")
Expand Down Expand Up @@ -395,41 +414,63 @@ def put_file(self):
"""Uploads a file to the NFS share"""
local_file_path = self.args.put_file[0]
remote_file_path = self.args.put_file[1]
file_name = ""
remote_dir_path, file_name = os.path.split(remote_file_path)

# Check if local file is exist
if not os.path.isfile(local_file_path):
self.logger.fail(f"{local_file_path} does not exist.")
return

# Do a bit of smart handling for the file paths
file_name = local_file_path.split("/")[-1] if "/" in local_file_path else local_file_path
if not remote_file_path.endswith("/"):
remote_file_path += "/"

self.logger.display(f"Uploading from {local_file_path} to {remote_file_path}")
try:
# Connect to NFS
nfs_port = self.portmap.getport(NFS_PROGRAM, NFS_V3)
self.nfs3 = NFSv3(self.host, nfs_port, self.args.nfs_timeout, self.auth)
self.nfs3.connect()

# Mount the NFS share to create the file
mnt_info = self.mount.mnt(remote_file_path, self.auth)
dir_handle = mnt_info["mountinfo"]["fhandle"]
# Mount the NFS share or get the root handle
if self.root_escape and not self.args.share:
mount_fh = self.escape_fh
elif not self.args.share:
self.logger.fail("No root escape possible, please specify a share")
return
else:
mnt_info = self.mount.mnt(self.args.share, self.auth)
if mnt_info["status"] != 0:
self.logger.fail(f"Error mounting share {self.args.share}: {NFSSTAT3[mnt_info['status']]}")
return
mount_fh = mnt_info["mountinfo"]["fhandle"]

# Iterate over the path
curr_fh = mount_fh
for sub_path in remote_dir_path.lstrip("/").split("/"):
self.update_auth(mount_fh)
res = self.nfs3.lookup(curr_fh, sub_path, auth=self.auth)

# If the path does not exist, create it
if "resfail" in res and res["status"] == NFS3ERR_NOENT:
self.logger.display(f"Creating directory '/{sub_path}/'")
res = self.nfs3.mkdir(curr_fh, sub_path, 0o777, auth=self.auth)
if res["status"] != 0:
self.logger.fail(f"Error creating directory '/{sub_path}/': {NFSSTAT3[res['status']]}")
return
else:
curr_fh = res["resok"]["obj"]["handle"]["data"]
continue

curr_fh = res["resok"]["object"]["data"]

# Update the UID from the directory
attrs = self.nfs3.getattr(dir_handle, auth=self.auth)
self.auth["uid"] = attrs["attributes"]["uid"]
self.update_auth(curr_fh)

# Checking if file_name already exists on remote file path
lookup_response = self.nfs3.lookup(dir_handle, file_name, auth=self.auth)
lookup_response = self.nfs3.lookup(curr_fh, file_name, auth=self.auth)

# If success, file_name does not exist on remote machine. Else, trying to overwrite it.
if lookup_response["resok"] is None:
# Create file
self.logger.display(f"Trying to create {remote_file_path}{file_name}")
res = self.nfs3.create(dir_handle, file_name, create_mode=1, mode=0o777, auth=self.auth)
res = self.nfs3.create(curr_fh, file_name, create_mode=1, mode=0o777, auth=self.auth)
if res["status"] != 0:
raise Exception(NFSSTAT3[res["status"]])
else:
Expand All @@ -441,9 +482,6 @@ def put_file(self):
if ans.lower() in ["y", "yes", ""]:
self.logger.display(f"{file_name} already exists on {remote_file_path}. Trying to overwrite it...")
file_handle = lookup_response["resok"]["object"]["data"]
else:
self.logger.fail(f"Uploading was not successful. The {file_name} is exist on {remote_file_path}")
return

try:
with open(local_file_path, "rb") as file:
Expand Down
2 changes: 1 addition & 1 deletion nxc/protocols/nfs/proto_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ def proto_args(parser, parents):
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("--share", help="Specify a share, e.g. for --ls, --get-file, --put-file")
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("--share", help="Specify a share, e.g. for --ls")
dgroup.add_argument("--ls", const="/", nargs="?", metavar="PATH", help="List files in the specified NFS share. Example: --ls /")
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")
Expand Down

0 comments on commit df4e992

Please sign in to comment.