| 5 min read
On January 26 of 2021, Qualys
published
a new vulnerability
discovered on sudo
,
a tool used to perform actions as other users
(most commonly as root
) on Linux-based systems.
Although Qualys provided a very good analysis of the vulnerability, they didn’t state how they found it.
In this post, we will show a way to discover this kind of bugs using AFL++, a community-fork of American Fuzzy Lop, a fuzzer that uses compile-time instrumentation and genetic algorithms to find, among other things, security bugs.
Preparing the environment
First, we need to install AFL
. You just have to clone the
repo and follow the
instructions. The only necessary change I made was to specify a version
for the libstdc++-dev
package. It needs to be the same as the gcc
compiler on your system:
$ git clone https://github.com/AFLplusplus/AFLplusplus.git
$ cd AFLplusplus/
$ gcc --version
gcc (Debian 10.2.1-6) 10.2.1 20210110
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ sudo apt install build-essential python3-dev automake flex bison libglib2.0-dev libpixman-1-dev clang python3-setuptools clang llvm llvm-dev libstdc++-10-dev
$ make distrib
$ sudo make install
And check the installation with:
$ afl-gcc --version
afl-cc ++3.01a by Michal Zalewski, Laszlo Szekeres, Marc Heuse - mode: GCC-GCC
gcc (Debian 10.2.1-6) 10.2.1 20210110
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Now, we need to download one of the
affected versions of sudo
. We will use 1.9.5p1
.
wget -c https://www.sudo.ws/dist/sudo-1.9.5p1.tar.gz
tar xzf sudo-1.9.5p1.tar.gz
That’s it, we have everything we need!
Patching sudo for fuzzing purposes
AFL
uses instrumented fuzzing only on binaries built with their
compilers. Instrumented mode helps AFL
perform coverage-guided fuzzing
and generate mutating input based on the measured behavior of previous
payloads.
However, AFL
will expect parameters from the standard input and files
only.
sudo
uses command-line arguments, which is not compatible with AFL
.
However, there is a
way
provided by AFL
to fuzz that kind of binaries: A C
header that
converts a standard input payload to argv[]
parameters.
To do that, we just need to:
-
Copy the
AFLplusplus/utils/argv_fuzzing/argv-fuzz-inl.h
file to the main source ofsudo
. -
Modify the
main()
function ofsudo
to call theAFL_INIT_ARGV()
macro.
~/sudo-1.9.5p1$ cp ../AFLplusplus/utils/argv_fuzzing/argv-fuzz-inl.h src/
diff -urN sudo-1.9.5p1.orig/src/sudo.c sudo-1.9.5p1/src/sudo.c
--- sudo-1.9.5p1.orig/src/sudo.c 2021-01-09 15:12:16.000000000 -0500
+++ sudo-1.9.5p1/src/sudo.c 2021-02-01 09:20:58.481966614 -0500
@@ -65,6 +65,7 @@
#include "sudo.h"
#include "sudo_plugin.h"
#include "sudo_plugin_int.h"
+#include "argv-fuzz-inl.h"
/*
* Local variables
@@ -149,6 +150,7 @@
int
main(int argc, char *argv[], char *envp[])
{
+ AFL_INIT_ARGV();
int nargc, status = 0;
char **nargv, **env_add, **user_info;
char **command_info = NULL, **argv_out = NULL, **user_env_out = NULL;
This will work by converting all the expected argv[]
array from
standard input with parameters separated by a \0
byte and terminating
the array with a \0\0
.
We also need to disable the sudo
password prompt; otherwise, the
fuzzing will hang.
diff -urN sudo-1.9.5p1.orig/plugins/sudoers/auth/sudo_auth.c sudo-1.9.5p1/plugins/sudoers/auth/sudo_auth.c
--- sudo-1.9.5p1.orig/plugins/sudoers/auth/sudo_auth.c 2020-12-16 20:33:43.000000000 -0500
+++ sudo-1.9.5p1/plugins/sudoers/auth/sudo_auth.c 2021-02-01 09:24:36.476083963 -0500
@@ -260,6 +260,8 @@
debug_return_int(-1);
}
+ return 0;
+
/* Enable suspend during password entry. */
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
Now, we can build our patched sudo
. As it needs to be built with AFL
compilers, we must overwrite the CC
environment variable. We may also
want to enable debugging symbols, and finally we should install it on a
isolated path so we can safely remove it when we finish our fuzzing
session. We can do that by issuing:
CFLAGS="-g" LDFLAGS="-g" CC=afl-clang-fast ./configure --prefix=/fuzz/sudo
make
sudo make install
This will install our modified sudo
on /fuzz/sudo
. To check that our
installation worked, along with the patches, just type:
echo -ne "sudo\0id\0\0" | /fuzz/sudo/bin/sudo
uid=0(root) gid=0(root) groups=0(root)
Great, now it’s fuzzing time!
Fuzzing sudo
When using AFL
, I recommend having a separate directory on which you
can store the inputs and outputs for each fuzzed binary. I will create
mine at $HOME/fuzz/sudo
.
~/fuzz$ mkdir -p sudo/{input,output}
The output
directory will be on where AFL
will store the fuzzing
state. As this directory will be extensively written to, it is
recommended to use a RAM-based filesystem to improve performance and
avoid damaging SSD
disks.
~/fuzz/sudo$ sudo mount -t tmpfs tmpfs output
In the input
directory, we will create initial payloads for sudo
:
~/fuzz/sudo$ echo -ne "sudo\0id\0\0" > input/payload1
~/fuzz/sudo$ echo -ne "sudoedit\0id\0\0" > input/payload2
Fuzzing is CPU-intensive, but you can use parallel fuzzing with AFL
. I
used an 8-core PC and launched a Master
AFL
instance:
~/fuzz/sudo$ afl-fuzz -i input/ -o output/ -M fuzz01 /fuzz/sudo/bin/sudo
And launched 6 Slave
instances on different consoles:
~/fuzz/sudo$ afl-fuzz -i input/ -o output/ -S fuzz02 /fuzz/sudo/bin/sudo
~/fuzz/sudo$ afl-fuzz -i input/ -o output/ -S fuzz03 /fuzz/sudo/bin/sudo
~/fuzz/sudo$ afl-fuzz -i input/ -o output/ -S fuzz04 /fuzz/sudo/bin/sudo
~/fuzz/sudo$ afl-fuzz -i input/ -o output/ -S fuzz05 /fuzz/sudo/bin/sudo
~/fuzz/sudo$ afl-fuzz -i input/ -o output/ -S fuzz06 /fuzz/sudo/bin/sudo
It looked like this:
And just after a few minutes of fuzzing, one of the slaves showed 3 crashes!
You can find here the payloads that caused the crashes:
~/fuzz/sudo$ ls output/fuzz03/crashes/id\:00000*
4 output/fuzz03/crashes/id:000000,sig:06,src:000002+000209,time:276568,op:splice,rep:2
4 output/fuzz03/crashes/id:000001,sig:06,src:000125,time:404770,op:havoc,rep:8
4 output/fuzz03/crashes/id:000002,sig:06,src:000305,time:1623276,op:arith8,pos:20,val:-24
If we examine the contents of these payloads, we can see that they all
invoked sudoedit
with the -s
and -i
flags. AFL
mutated the
original input payloads and eventually triggered the bug found by
Qualys.
We can also replicate the crash by simply passing the offending payloads
to our sudo
:
~/fuzz/sudo$ /fuzz/sudo/bin/sudo < output/fuzz03/crashes/id:000000,sig:06,src:000002+000209,time:276568,op:splice,rep:2
malloc(): invalid size (unsorted)
Aborted
And you can use GDB
to start the exploitation process:
Conclusion
It is easy to find crashes on software using AFL
if you have the
source code. What is unbelievable is that it took 10 years for a bug
like this to be found on sudo
!
Share
Recommended blog posts
You might be interested in the following related posts.