calebstewart / pwncat

Fancy reverse and bind shell handler
https://pwncat.readthedocs.io
MIT License
2.61k stars 256 forks source link

File read/write and prompt switch issues while using 'effective' user/group IDs #179

Open Mitul16 opened 3 years ago

Mitul16 commented 3 years ago

Bug Description

While trying to upload or download a file to which we have access through euid or egid, pwncat shows an access error.

pwncat version

$ pwncat --version
0.4.3

Target System (aka "victim")

Regular Kali Linux as a VM

Steps to Reproduce

Steps to reproduce the behavior:

  1. Prepare a SUID binary for bash owned by root
  2. Get a session in pwncat using a normal user (non-root)
  3. Use that SUID binary to elevate your privileges
  4. Upload a test file to /root
  5. See the error

Expected Behavior

pwncat should upload the test file without any access errors

Screenshots

Found during KoTH Screenshot from 2021-08-08 21-41-57

Read issue Screenshot from 2021-08-10 01-21-21

Write issue Screenshot from 2021-08-10 01-19-47

NOTE: I had updated the PROMPTS list for zsh and default, thus the prompt doesn't show root as the user ($(whoami))

Possible solution

Before performing upload or download, we need to refresh_uid() In pwncat/platform/linux.py

491 class LinuxPath(pathlib.PurePosixPath):
...
494     def readable(self):
495         """Test if a file is readable"""
496
497         # refresh the uid, to pick up any changes
498         self._target.refresh_uid()
499
...
504         # get the stats for the current path
505         _stat = self.stat()
506
507         file_uid = _stat.st_uid
508         file_gid = _stat.st_gid
509         file_mode = _stat.st_mode
510
511         # check for uid, euid, gid, egid  <<<<<
...
520     def writable(self):
521         """Test if the path is writable"""
522
523         # refresh the uid, to pick up any changes
524         self._target.refresh_uid()
...
530         # get the stats for the current path
531         _stat = self.stat()
...
Mitul16 commented 3 years ago

Even if we use refresh_uid() under readable() and writable() methods, we have one issue.

Screenshot from 2021-08-10 11-31-43 The first Input is refresh_uid() call, and the second Input is the file write using gtfobins.json database and abilities.


Screenshot from 2021-08-10 11-39-55


We surely want to exploit stuff with pwncat - man sh Screenshot from 2021-08-10 11-49-38

Mitul16 commented 3 years ago

There is one minor issue with readlink and shell resolution While I was testing my modifications with upload and download by checking euid and egid along with uid and gid, SGID binary seems to be causing a permission error on the symlink when I use C-d to switch to pwncat local prompt

Using a SGID binary

Screenshot from 2021-08-10 12-53-14

Pressed C-d here, and received some errors

Screenshot from 2021-08-10 12-53-38 Screenshot from 2021-08-10 12-53-49


Cannot readlink current process when using SGID binary

Screenshot from 2021-08-10 12-50-59

Any suggestions, is there anything that I am doing or assuming incorrectly?

Mitul16 commented 3 years ago

Again for shell resolution, there is another possible issue when the binary is removed

Screenshot from 2021-08-10 13-41-30

" (deleted)" is appended to exe's path

Mitul16 commented 3 years ago

Here is Hack The Box - Knife machine

Invoking a reverse shell Screenshot from 2021-08-12 14-35-36

pwncat listener Screenshot from 2021-08-12 14-34-39

I am using caleb/master branch

pwncat errors out, this is because of the unhandled OSError raised in readlink method

pwncat/platform/linux.py

1779     def readlink(self, path: str):
...
1788         except CalledProcessError as exc:
1789             raise OSError(f"Invalid argument: '{path}'") from exc  <<< HERE
1597     def interactive(self, value: bool):
...
1613             try:
1614                 # Get the PID of the running shell
1615                 pid = self.getenv("$")
1616                 # Grab the path to the executable representing the shell
1617                 self.shell = self.Path("/proc", pid, "exe").readlink()
1618             except (FileNotFoundError, PermissionError):
1619                 # Fall back to SHELL even though it's not really trustworthy
Mitul16 commented 3 years ago

Caleb, I hope there are no issues if I am taking up space here, you can surely clear any redundant stuff :sweat_smile:

Mitul16 commented 3 years ago

@calebstewart What do you think about this issue?

Using /bin/sh -c ... to run commands will drop privileges that were obtained by using a SUID / SGID binary So if we had access (read / write) to something then that might be lost with /bin/sh -c ...

Given uid=1000 gid=1000 euid=0, the following would fail

/bin/sh -c "dd if=/root/.ssh/id_rsa"

Whereas this would work

dd if=/root/.ssh/id_rsa

In my PR #180, now closed, I replaced /bin/sh -c with /bin/sh -p -c and that caused issues elsewhere For instance, it caused issues with /bin/sh -p -c "sudo ..." while using run enumerate

Should we add a check or extra commands to be tested while using enumerate and/or other modules?

Screenshot from 2021-09-20 16-08-29

Screenshot from 2021-09-20 16-09-07

calebstewart commented 3 years ago

I'm not sure what the best solution is here. There's fundamentally two problems detailed here:

  1. euid permissions are lost during Popen calls due to /bin/sh -c
  2. Processes with euid set cannot read /proc/$$/exe regardless of using /bin/sh -c

Lost Permissions during Popen

I think this can be solved by simply not using /bin/sh -c. Looking at the code, I don't think this is strictly speaking needed. The shell argument to Popen is documented as using /bin/sh -c to execute the command, but that is already effectively how commands are executed by pwncat anyway. I think this is fine to remove and effectively ignore the shell argument to Popen. I tried this locally, and at the very least all tests pass. I'm going to do a little more testing and then push a branch.

Failure to read /proc/$$/exe

This is a trickier problem. I'm not sure why we are unable to read that file. Interestingly, my local machine (Arch Linux), this doesn't happen, but in a Ubuntu container it does.

In the short-term, I think that catching the OSError is the best course forward. If readlink() raises an exception, we can simply catch the OSError and fallback to the SHELL environment variable as was intended if that method fails. This doesn't fix the problem, but it at least ensures pwncat doesn't crash and lose your session. A sad side-effect of this is that in this situation, we lose the nice syntax highlighting as pwncat ends up falling back to assuming SHELL=/bin/sh in most cases. This isn't the end of the world, and is mostly cosmetic, though.

In the long term, I need to implement a module to correct euid != uid situations. Since this doesn't always work, the above needs to be implement to ensure pwncat functions in that situation, but the real fix is just replacing uid with euid so we have the full permissions of the target user.

calebstewart commented 3 years ago

Example of /proc/$$/exe fix: image

Again, this is not ideal, but I think it's the best we can do until we fix the euid situation.

calebstewart commented 3 years ago

If you have some time to do some independent testing of the above pull request, I'd appreciate it. The tests pass, and some anecdotal testing seems positive, but I haven't thoroughly tested these changes yet.

vs45sharma commented 2 years ago

Upload error in pwncat-cs what can i do anyone have solution?

image