Hi people.
Today we are going to deepen an old technique that I have been studying. It is an attack to get sudo in case someone else is connected to the same machine with the same user and has sudo unlocked. This technique was already documented by nongiach in the sudo_inject repository. However, that implementation requires several files and dependences for the attack, so, in order to simplify it and learning about the technique, I have developed sudohunt.
To get into the subject, sudo is a program used in Unix-like systems (like
GNU/Linux) that allows to execute programs as another user, root by default.
There is more info in the sudo manual page. Hence, being able to use sudo
means to be able to do whatever we want in the machine.
Therefore, let's imagine the following situation: We get a SSH key that allows
us to connect to a machine with the it user. After connecting to the machine,
we discover that it user is allowed to use sudo, but a password that we
don't know is required.
$ ssh -i itkey it@itserver
it@itserver:~$ sudo id
[sudo] password for it:
We can check if there is another person connected to the machine with the it
user with the w or who commands:
it@itserver:~$ w
14:16:16 up 15 min, 2 users, load average: 0.00, 0.00, 0.00
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
it pts/0 146.89.128.11 12:14 0.00s 0.01s ? w
it pts/2 192.168.2.12 12:05 20.00s 0.01s 0.01s -bash
We can see that there is someone else connected as it, so we can try to
inject a command into that user's session and unlock sudo if it is
unlocked in that another session. What do I mean by unlocked sudo? That it
doesn't ask for a password. When sudo is executed for the first time, it ask
for a password, but in order not to disturb the user, for the next 15 minutes by
default, sudo will executed without asking for a password.
We can validate this behavior by executing sudo twice in a row:
$ sudo id
[sudo] password for user:
uid=0(root) gid=0(root) groups=0(root)
$ sudo id
uid=0(root) gid=0(root) groups=0(root)
As we can see sudo only asks for a password the first time we execute it. How
does it do that? And how can we take advantage of this behavior?
Let's go step by step. To answer the first question, sudo keeps a record of
all authenticated sessions. It keeps a file for each user under the
/run/sudo/ts directory. We can list the files:
$ sudo ls -l /run/sudo/ts/
total 4
-rw------- 1 root it 224 Mar 3 14:51 it
We can see that there is a file for it user, that can only be read and written
by root. These files have a binary format described in the sudoers_timestamp
manual page. Nevertheless, we can read it with the sudohunt read command:
$ sudo ./sudohunt read
Timestamp file: /run/sudo/ts/it
version: 2
size: 56
type: 4 TS_LOCKEXCL
flags: 0
auth_uid: 0
sid: 0
start_time: 0.0
ts: 0.0
id: 0
version: 2
size: 56
type: 2 TS_TTY
flags: 0
auth_uid: 1000 it
sid: 2210 -bash
start_time: 931.530000000 (1933.998431745 seconds ago)
ts: 2865.526131254 (0.2300491 seconds ago)
tty: 34818 /dev/pts/2
We can notice that there are two records in the run/sudo/ts/it file. The first
one is a control register that we can ignore. We must focus on the second
register that shows that sudo is unlocked for the it user (with uid 1000)
in the session 2210 (whose leader program is a bash shell) which is executed
in the /dev/pts/2 terminal (that is coherent with the previous w command
output). Furthermore, we can see that 0.23 seconds have passed since the last
time that sudo was used, an expected time since the command was executed to
read the file.
Taking this into account, and answering our second question, the goal of our
attack will be writing a new record into the file that indicates that our
session has unlocked sudo in the last 15 minutes, in order to used it without
providing a password. sudohunt includes a write command that allows to write
new records into the sudo session files.
And how are we going to do this knowing that only root can add new records?
Remember that sudo allows to execute as root. Therefore, what we need to do is
to force to another session with sudo unlocked to write the new record for
us. To do this we are going to inject a process into the session of the person
who unlocked sudo that adds a new record for our session. We need to remember
that our session and the target session in which we try to inject is executed as
the same user: it.
By default, some GNU/Linux distros like Debian or Alma Linux allow an user to debug any of their processes with the ptrace syscall, even if they are in another session. This means that it is possible to read the target process memory, modify its registers or setting breakpoints, among other actions, so we can inject code (and thus processes) in other sessions.
To verify that, it is possible to use ptrace over processes in other
sessions we need to check that the value of the
/proc/sys/kernel/yama/ptrace_scope file is 0. For other cases, injections
with ptrace to other sessions are not possible.
Additionally to
/proc/sys/kernel/yama/ptrace_scopefile, there could be other security mechanisms like SELinux that will preventing us from use ptrace over other session processes. In this case we assume that there aren't any additional security measure.
Once we have verified that the requirements have been meet, we are going to try
to execute the sudo sudohunt write --pid <sesion> command into the session of
other user. This will write a new record into /run/sudo/ts/<user> file
that will grant sudo to the session with the <session> identifier (that will
be replace by our current session).
Therefore, the attack, executed with the sudohunt inject command will have the
following stages:
-
Process in other sessions of the same user are searched. We only need a process per session, so session leaders will be used, which are those processes which the pid is the same as the session identifier (sid) and with an associated terminal (interactive session).
-
Once the processes are located, the
sudo sudohunt writecommand is injected in each one. In the case that sudo is unblocked in the target session the previous command will grant sudo to our current session.
We can check this by executing sudohunt with our it user:
it@itserver:~$ ./sudohunt inject
Injection work. sudo may work now. If not, retry injection later.
it@itserver:~$ sudo id
uid=0(root) gid=0(root) groups=0(root)And we finally get root!!
You can watch an example of this attack in the following asciinema:
In case you are curious about the process injection, here is a more detailed procedure of what happens (if you are looking even for more detail, you should inspect the sudohunt source code):
-
Attach with ptrace to the target process,
bashin our case. -
Force bash to execute the fork syscall in order to create a child process so we can avoid disturb the person using it.
-
Attach to the child process.
-
Detach from the parent process.
-
Force the child process to execute the
sudo sudohunt writecommand through the execve syscall. A curious thing is that we need to detach from the child process before execve is called to allow sudo to be spawned as root, since in the debugged processes thesetuidflag (that allows to execute as root) is ignored.
Here is a little schema of the injection process:
session: 1001 session: 1337
.-----------------. 1. attach .------.
| sudohunt inject |------------->| bash |
'-----------------' 4. detach '------'
| |
| | 2.fork
| v
| 3. attach .------.
'--------------->| bash |
5. detach '------'
|
| 6. execve
v
.--------------------------------.
| sudo sudohunt write --pid 1001 |
'--------------------------------'
And that's all for now friends, I hope you liked this little article and see you!!