Skip to content

[Security] Server-Side Request Forgery (SSRF) in webapp-manager favicon downloader script (common.py) #375

@rgfegegeegege

Description

@rgfegegeegege

Title
[Security] Server-Side Request Forgery (SSRF) in webapp-manager favicon downloader script (common.py)

Description

Summary

The helper script /usr/lib/webapp-manager/common.py in the WebApp Manager package is vulnerable to Server-Side Request Forgery (SSRF) when executed directly from the command line.

The script accepts an arbitrary URL via sys.argv[1] and performs HTTP requests to that URL and derived paths using the requests library. No validation is performed to prevent requests to internal network resources, localhost, or private IP ranges.

This allows an attacker to trick a user into executing the script with a malicious URL, resulting in unintended server-side HTTP requests to internal services.

The vulnerability is present in the current version of WebApp Manager shipped with Linux Mint Cinnamon editions.

Affected Component

  • File: /usr/lib/webapp-manager/common.py
  • Entry point: if __name__ == "__main__": download_favicon(sys.argv[1])
  • Dependency: requests library

Vulnerability Details

The script is designed as a utility to download the best available favicon for a given website URL. When run directly, it accepts a single command-line argument (the target URL) and proceeds as follows:

  1. URL Normalization

    url = normalize_url(url)
    (scheme, netloc, path, _, _, _) = urllib.parse.urlparse(url)
    root_url = "%s://%s" % (scheme, netloc)

    The normalization extracts scheme and netloc but does not restrict allowed hosts or schemes beyond basic parsing.

  2. Primary Request
    The script performs a GET request to the provided URL:

    response = requests.get(url, timeout=3)
  3. HTML Parsing and Link Extraction
    If the response is successful, the HTML is parsed with BeautifulSoup to extract various favicon-related links (rel="icon", apple-touch-icon, etc.).

  4. Derived Requests
    For each discovered link, the script constructs a new URL (relative links are resolved against root_url) and performs additional GET requests:

    response = requests.get(link, timeout=3)
  5. Fallback Mechanisms
    The script also attempts /favicon.ico and a Google favicon API endpoint derived from the original URL.

All these requests are made without any restrictions on destination hosts or ports.

Proof of Concept

The vulnerability can be demonstrated with the following commands on a standard Linux Mint Cinnamon installation:

  1. Attempt connection to localhost on closed port

    /usr/bin/python3 /usr/lib/webapp-manager/common.py http://127.0.0.1:9999

    Output:

    HTTPConnectionPool(host='127.0.0.1', port=9999): Max retries exceeded with url: / (Caused by NewConnectionError(... Connection refused))
    

    → Confirms attempt to connect to localhost on arbitrary port.

  2. Local HTTP server verification
    Start a local server:

    python3 -m http.server 8000

    Then execute:

    /usr/bin/python3 /usr/lib/webapp-manager/common.py http://127.0.0.1:8000

    Server log:

    127.0.0.1 - - [07/Jan/2026 17:44:35] "GET / HTTP/1.1" 200 -
    127.0.0.1 - - [07/Jan/2026 17:44:35] "GET /favicon.ico HTTP/1.1" 404 -
    

    → Confirms successful outbound requests to localhost.

  3. Internal network target

    /usr/bin/python3 /usr/lib/webapp-manager/common.py http://192.168.1.1

    Output:

    HTTPConnectionPool(host='192.168.1.1', port=80): Max retries exceeded with url: / (Caused by ConnectTimeoutError(...))
    

    → Confirms attempt to contact private network device (common router gateway).

Impact

An attacker could exploit this vulnerability to:

  • Perform port scanning on the local machine and local network
  • Access internal web interfaces (e.g., router administration panels, local development servers)
  • Interact with internal APIs
  • In cloud environments, potentially access instance metadata services (http://169.254.169.254)

The attack requires the user to execute the script with a malicious URL (social engineering vector).

Recommended Mitigation

Implement host validation at the beginning of the CLI execution block:

import ipaddress

def is_forbidden_host(url):
    try:
        parsed = urllib.parse.urlparse(url)
        hostname = parsed.hostname
        if hostname in ['localhost', '127.0.0.1', '::1']:
            return True
        if hostname:
            ip = ipaddress.ip_address(hostname)
            return ip.is_private or ip.is_loopback or ip.is_multicast or ip.is_reserved
    except Exception:
        pass
    return False

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: common.py <url>")
        sys.exit(1)
    
    url = sys.argv[1]
    if is_forbidden_host(url):
        print("Error: Requests to private/local addresses are not allowed")
        sys.exit(1)
    
    download_favicon(url)

Alternative minimal approach: disable direct execution entirely:

if __name__ == "__main__":
    print("This script is intended for internal use only")
    sys.exit(1)

Conclusion

While the vulnerable code path is not exercised during normal GUI usage of WebApp Manager, the ability to execute the script directly with arbitrary URLs constitutes a valid Server-Side Request Forgery vulnerability.

No evidence of in-the-wild exploitation has been observed.

This report is submitted responsibly to improve the security posture of a widely-used Linux Mint component.

Thank you

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions