Detect and Report Insecure Cookies
Hunt CWE-614 vulnerabilities, calculate CVSS scores, and write a bug bounty report.
Introduction
30 Second Summary
Every time you log in to a website, your browser stores a tiny piece of data that proves you are who you say you are. If that data is sent insecurely, anyone on the same network can grab it and impersonate you.
In this project, you will build a deliberately vulnerable web app, hunt for a specific cookie security flaw using the same tools and methodology real bug bounty hunters use, and write a professional vulnerability disclosure report.
What You'll Build
You will build a vulnerable login app in Express.js, detect CWE-614 using browser DevTools, remediate the vulnerability, and produce a polished bug bounty report with CVSS scoring.
By the end of this project, you'll have:
- A working vulnerable Express.js app that demonstrates CWE-614 (insecure session cookies) in action.
- A professional vulnerability disclosure report documenting the finding with CVSS 3.1 scoring and proof-of-concept steps.
- A remediated version of the app with Secure, HttpOnly, and SameSite attributes verified through DevTools.
- Secret Mission: Build an automated cookie security auditing script that scans multiple endpoints for missing security attributes.
Are there any prerequisites?
You should have a basic understanding of HTTP and web applications. You will also need Node.js, npm, VS Code, and Chrome (or Edge) installed on your machine.
Before We Start
Before We Start...
Set Up the Vulnerable Target App
Bug bounty hunters need a target to test against. In this step, you will build your own deliberately vulnerable web app so you can practice detection techniques in a safe, controlled environment.
The app is a simple login system built with Express.js. Its session cookie is intentionally misconfigured to demonstrate CWE-614 in action.
In this step, get ready to:
- Initialize a Node.js project with Express and express-session installed.
- Build a login app with intentionally insecure cookie configuration.
- Start the server and confirm the login page loads in your browser.
Initialize the project and install dependencies
Every Node.js project starts with a package.json file that tracks your dependencies. You will create a new project directory and install the two packages the vulnerable app needs.
- Open a terminal in VS Code by pressing Ctrl+` (backtick key, top-left of your keyboard).
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir cwe-614-demo
cd cwe-614-demo
- Initialize a new Node.js project by running:
npm init -y
What does this command do?
The -y flag tells npm to accept all defaults without prompting you. This creates a package.json with the project name set to the folder name (cwe-614-demo).
- Install the two dependencies your app needs by running:
npm install express@5.2.1 express-session@1.19.0
- Confirm the packages installed by running:
ls node_modules
You should see a long list of folders including express and express-session.
Install failed or command not found?
Make sure you are inside the cwe-614-demo folder before running npm install. Run pwd to check your current directory.
If npm is not recognized, Node.js may not be installed correctly. Revisit the Node.js installer from nodejs.org and restart your terminal afterwards.
help me troubleshoot my npm install error
- Open the project folder in VS Code by clicking File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the cwe-614-demo folder, and click Open.
Build the vulnerable Express app
The app needs a session cookie that is deliberately misconfigured. You will set secure: false, httpOnly: false, and sameSite: false so the cookie has no protection at all.
- Create a new file called server.js in the root of your cwe-614-demo folder by clicking the new file icon in the VS Code Explorer sidebar.
- Add the following code to server.js to import Express and configure the vulnerable session middleware:
const express = require('express');
const session = require('express-session');
const app = express();
app.use(express.urlencoded({ extended: true }));
// VULNERABLE: Session cookie configured WITHOUT security attributes
app.use(session({
secret: 'insecure-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: false,
httpOnly: false,
sameSite: false,
maxAge: 3600000
}
}));
What does this code do?
This is where the vulnerability lives. The express-session middleware manages session cookies for every visitor.
- secure: false means the cookie can be sent over unencrypted HTTP connections, letting attackers on the same network intercept it.
- httpOnly: false means JavaScript can read the cookie, so any XSS vulnerability instantly exposes the session token.
- sameSite: false means the cookie is sent with cross-site requests, opening the door to CSRF attacks.
- Below the session middleware, add the login page route and login handler:
// Login page
app.get('/', (req, res) => {
if (req.session.user) {
return res.redirect('/dashboard');
}
res.send(`
<!DOCTYPE html>
<html>
<head><title>Login</title></head>
<body>
<h1>Vulnerable Login App</h1>
<p>CWE-614 Demonstration Target</p>
<form method="POST" action="/login">
<label>Username: <input type="text" name="username" /></label><br/><br/>
<label>Password: <input type="password" name="password" /></label><br/><br/>
<button type="submit">Login</button>
</form>
</body>
</html>
`);
});
// Login handler
app.post('/login', (req, res) => {
const { username, password } = req.body;
if (username && password) {
req.session.user = { username };
req.session.loginTime = new Date().toISOString();
return res.redirect('/dashboard');
}
res.redirect('/');
});
What does this code do?
These two routes handle the login flow.
- GET / shows the login form. If the user already has a session, it redirects them to the dashboard.
- POST /login stores the username in the session and redirects to the dashboard. This is the moment the insecure cookie gets set.
- Below the login handler, add the dashboard, logout route, and server startup:
// Dashboard (requires session)
app.get('/dashboard', (req, res) => {
if (!req.session.user) {
return res.redirect('/');
}
res.send(`
<!DOCTYPE html>
<html>
<head><title>Dashboard</title></head>
<body>
<h1>Dashboard</h1>
<p>Welcome, ${req.session.user.username}!</p>
<p>Login time: ${req.session.loginTime}</p>
<p>Session ID (exposed in cookie): check DevTools</p>
<a href="/logout">Logout</a>
</body>
</html>
`);
});
// Logout
app.get('/logout', (req, res) => {
req.session.destroy(() => {
res.redirect('/');
});
});
app.listen(3000, () => {
console.log('Vulnerable target app running on http://localhost:3000');
console.log('Open DevTools and inspect cookies after logging in.');
});
What does this code do?
These routes complete the app.
- GET /dashboard shows a welcome page that displays session data. It redirects unauthenticated visitors back to login.
- GET /logout destroys the session and sends the user back to the login page.
- app.listen(3000) starts the server on port 3000.
- Save server.js.
✔️ Awesome, I've got everything!
Great. Double check you have saved server.js before moving on.
ⓧ I'd like to double check the full code
Here is the complete server.js file. Compare it with yours and fix any differences.
const express = require('express');
const session = require('express-session');
const app = express();
app.use(express.urlencoded({ extended: true }));
// VULNERABLE: Session cookie configured WITHOUT security attributes
app.use(session({
secret: 'insecure-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: false,
httpOnly: false,
sameSite: false,
maxAge: 3600000
}
}));
// Login page
app.get('/', (req, res) => {
if (req.session.user) {
return res.redirect('/dashboard');
}
res.send(`
<!DOCTYPE html>
<html>
<head><title>Login</title></head>
<body>
<h1>Vulnerable Login App</h1>
<p>CWE-614 Demonstration Target</p>
<form method="POST" action="/login">
<label>Username: <input type="text" name="username" /></label><br/><br/>
<label>Password: <input type="password" name="password" /></label><br/><br/>
<button type="submit">Login</button>
</form>
</body>
</html>
`);
});
// Login handler
app.post('/login', (req, res) => {
const { username, password } = req.body;
if (username && password) {
req.session.user = { username };
req.session.loginTime = new Date().toISOString();
return res.redirect('/dashboard');
}
res.redirect('/');
});
// Dashboard (requires session)
app.get('/dashboard', (req, res) => {
if (!req.session.user) {
return res.redirect('/');
}
res.send(`
<!DOCTYPE html>
<html>
<head><title>Dashboard</title></head>
<body>
<h1>Dashboard</h1>
<p>Welcome, ${req.session.user.username}!</p>
<p>Login time: ${req.session.loginTime}</p>
<p>Session ID (exposed in cookie): check DevTools</p>
<a href="/logout">Logout</a>
</body>
</html>
`);
});
// Logout
app.get('/logout', (req, res) => {
req.session.destroy(() => {
res.redirect('/');
});
});
app.listen(3000, () => {
console.log('Vulnerable target app running on http://localhost:3000');
console.log('Open DevTools and inspect cookies after logging in.');
});
Start the server and verify it is running
- Start the server by running this command in your terminal:
node server.js
You should see this output in your terminal:
Vulnerable target app running on http://localhost:3000
Open DevTools and inspect cookies after logging in.
- Open Chrome (or Edge) and navigate to http://localhost:3000.
You should see a page titled "Vulnerable Login App" with a username field, password field, and a Login button.
Page not loading?
Make sure the terminal still shows the server running and has no error messages. If you see an error about the port being in use, another process is occupying port 3000.
Double check the URL is exactly http://localhost:3000 with no trailing path. If you edited server.js after starting the server, stop it with Ctrl+C and run node server.js again.
help me troubleshoot why my Express server won't start
Your vulnerable target is live. Next up, you will use browser DevTools to hunt for the insecure cookie and gather proof that CWE-614 is present.
Detect the Vulnerability Using DevTools
Your vulnerable target app is live and serving insecure session cookies. Now it's time to put on your security researcher hat and hunt for the flaw using the same tool professionals reach for first: browser Developer Tools.
In a real bug bounty engagement, you would inspect HTTP response headers immediately after authenticating with a target. The Set-Cookie header reveals exactly which security attributes are present or missing on a session cookie.
In this step, get ready to:
- Capture the Set-Cookie header in the Network tab after logging in.
- Inspect cookie properties in the Application tab to confirm missing security attributes.
- Prove the session cookie is exposed to JavaScript via the Console tab.
Log in and capture the Set-Cookie header
When a server creates a session, it sends a Set-Cookie header in the HTTP response telling the browser to store the cookie. By inspecting this header, you can see exactly which security attributes the server applied.
- Switch back to Chrome (or Edge) with your app open at http://localhost:3000.
- Press Cmd+Option+I (macOS) or F12 (Windows) to open DevTools.
- Click the Network tab at the top of the DevTools panel.
- In the login form on the page, type admin in the Username field.
- Type password123 in the Password field.
- Click Login to submit the form.
The Network panel now shows the requests your browser made. You need to find the POST request that triggered the login.
- In the Network panel's request list, click the login entry (the POST to /login).
- Click the Headers tab in the request detail pane on the right.
- Scroll down to the Response Headers section.
- Find the Set-Cookie header.
What should I see?
The Set-Cookie header shows something like connect.sid=s%3A...; Path=/; Max-Age=3600. Notice what is NOT there: no Secure, no HttpOnly, and no SameSite attribute.
This is your first piece of evidence that CWE-614 is present. The session cookie can be transmitted over unencrypted HTTP connections.
Don't see the login request?
Make sure the Network tab was open before you clicked Login. DevTools only captures requests that happen while the tab is active. If you missed it, clear the Network log, go back to http://localhost:3000, and log in again.
If you see multiple entries, look for the one with a 302 status code and method POST. That is the login response that sets the cookie.
Help me find the Set-Cookie header in my Network tab
Inspect the cookie in the Application tab
The Network tab showed you the raw header. The Application tab gives you a structured view of every cookie stored for the current origin, with dedicated columns for each security attribute.
- Click the Application tab at the top of DevTools.
- In the left sidebar, expand Storage, then expand Cookies.
- Click http://localhost:3000 to view cookies for your app.
You should see a table with a row for the connect.sid cookie. Focus on three columns in that row.
- Check the HttpOnly column. It should be unchecked (empty).
- Check the Secure column. It should be unchecked (empty).
- Check the SameSite column. It should be blank.
What does this confirm?
Three missing security attributes on a session cookie is a serious finding. The unchecked Secure column means the cookie can be sent over plaintext HTTP. The unchecked HttpOnly means JavaScript can read the cookie. The blank SameSite means the browser may send it on cross-site requests.
This is the definitive evidence that CWE-614 is present. On a bug bounty target, this screenshot alone would form the core of your proof-of-concept.
Verify the cookie is accessible via JavaScript
The missing HttpOnly attribute has a direct, exploitable consequence. Any JavaScript running on the page can read the session cookie. In a real attack, an attacker who finds a cross-site scripting (XSS) vulnerability could steal the session token with a single line of code.
- Click the Console tab in DevTools.
- Type the following command and press Enter:
document.cookie
You should see a string containing connect.sid= followed by the full session token value. This proves the session ID is fully exposed to client-side scripts.
Why is this dangerous?
If an attacker can inject JavaScript into the page (via XSS), they can run document.cookie and send the stolen session token to their own server. The attacker then replays that token to impersonate the victim. This attack chain is called session hijacking.
A properly set HttpOnly attribute would prevent this entirely. The browser would refuse to expose the cookie to JavaScript, even if malicious code was injected into the page.
document.cookie returns an empty string?
Make sure you are logged in and on the /dashboard page. If you are still on the login form, the session cookie may not have been set yet. Log in first, then try document.cookie again on the dashboard.
If you cleared cookies earlier in the Application tab, you will need to log in again to get a fresh session.
Help me see my session cookie in the Console
You have now captured concrete evidence of CWE-614 from three angles: the raw response header, the structured cookie table, and live JavaScript access. Next, you will assess the real-world exploitability of this finding and calculate its CVSS severity score.
Verify Exploitability and Assess Impact
You have hard evidence that the session cookie is missing its security attributes. In a real bug bounty engagement, the next step is proving that the flaw is actually exploitable.
A finding without a clear attack scenario and severity score is easy for a triage team to dismiss. In this step, you will document exactly how an attacker exploits CWE-614 and assign it an industry-standard CVSS 3.1 score that communicates the risk.
In this step, get ready to:
- Document the real-world attack scenario for CWE-614.
- Calculate the CVSS 3.1 Base Score for this finding.
Document the attack scenario for CWE-614
Before you calculate a score, you need to articulate how an attacker would actually exploit this vulnerability in the wild. This is what makes the difference between a theoretical weakness and a paid bounty.
Here is the core problem: because the session cookie has no Secure attribute, the browser will happily send it over an unencrypted HTTP connection.
How does the attack work?
Even if a site uses HTTPS everywhere, an attacker on the same network can force the browser to leak the cookie. The attack works in three stages:
- The attacker positions themselves on the same network as the victim (for example, public Wi-Fi at a coffee shop). This is a Man-in-the-Middle position.
- The attacker injects an HTTP reference to the target domain. This can happen through DNS spoofing, response injection, or a compromised ad network. The victim's browser attempts an HTTP request to the target.
- Because the cookie lacks the Secure flag, the browser sends it in cleartext over that HTTP request. The attacker captures the session token and replays it to hijack the victim's session.
This attack is called Session Sidejacking (CAPEC-102). It enables full account takeover without needing the victim's credentials.
- Take note of this attack scenario. You will include it in your disclosure report in Step 5.
Why does this matter if the site already uses HTTPS?
OWASP documents this exact scenario: the Secure attribute should be used even when the web application is entirely deployed over HTTPS. Without it, a browser tricked into making one HTTP request leaks the entire session.
Your vulnerable app also exposes the cookie to JavaScript (missing HttpOnly) and to cross-site requests (missing SameSite). These compound the risk by opening additional theft vectors through XSS and CSRF.
Calculate the CVSS 3.1 score for this finding
Bug bounty programs use CVSS 3.1 scores to determine payout tiers. A Medium-severity finding with a clean report can still earn a meaningful reward. Let's score your finding.
- Open the official FIRST.org CVSS 3.1 calculator in a new browser tab:
- https://www.first.org/cvss/calculator/3-1
The calculator presents eight Base Score metrics. You will select a value for each one based on how CWE-614 is exploited.
- Set Attack Vector to Network (N). The attacker exploits this over the network, not physically.
- Set Attack Complexity to Low (L). The attacker only needs a MITM position, which is trivial on public Wi-Fi.
- Set Privileges Required to None (N). The attacker does not need an account on the target application.
- Set User Interaction to Required (R). The victim must visit an HTTP link for the cookie to leak.
- Set Scope to Unchanged (U). The vulnerability only affects the vulnerable component's own session.
- Set Confidentiality to High (H). The session token gives full access to the victim's account.
- Set Integrity to None (N). Stealing the cookie does not directly modify data.
- Set Availability to None (N). The attack does not cause a denial of service.
After selecting all eight values, the calculator displays a Base Score of 6.5 with a severity rating of Medium.
The full vector string is:
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
What does each part of the vector mean?
- AV:N = Attack Vector: Network. Exploitable remotely over the internet.
- AC:L = Attack Complexity: Low. No special conditions beyond a shared network.
- PR:N = Privileges Required: None. No account needed on the target.
- UI:R = User Interaction: Required. Victim must visit an HTTP link.
- S:U = Scope: Unchanged. Impact stays within the session component.
- C:H = Confidentiality: High. Full session token exposed.
- I:N = Integrity: None. No direct data modification.
- A:N = Availability: None. No service disruption.
- Confirm your calculator shows a Base Score of 6.5 and the vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N.
Why 6.5 and not higher?
The score is Medium rather than High because User Interaction is Required. The victim must be tricked into making an HTTP request. If the cookie also leaked without any user action (for example, via an automatic background request), the score would jump higher.
Real-world CVEs with CWE-614 typically score between 4.3 and 7.5 depending on the application context. A 6.5 on a session cookie for a production app is a credible, reportable finding.
Calculator showing a different score?
Double-check that Confidentiality is set to High, not Low. A session token grants full account access, which counts as High confidentiality impact.
Also confirm Scope is Unchanged, not Changed. The vulnerability only affects the session component itself.
Help me understand why my CVSS score is different from 6.5.
You now have both the attack narrative and the severity score. Next up, you will fix the vulnerability by applying all three cookie security attributes and verify the fix in DevTools.
Remediate and Verify the Fix
You've identified the vulnerability, documented the attack scenario, and calculated a CVSS score. Now it's time to switch from attacker to defender.
In this step, you'll apply the three cookie security attributes that eliminate CWE-614. Then you'll verify through DevTools that each fix is working exactly as intended.
In this step, get ready to:
- Apply Secure, HttpOnly, and SameSite attributes to the session cookie.
- Verify the fixed Set-Cookie header in DevTools.
- Confirm JavaScript can no longer access the session cookie.
Update the session configuration with security attributes
The fix for CWE-614 is straightforward. You need to set three attributes on the session cookie that tell the browser how to protect it.
- In VS Code, open server.js.
- Select all the code (Cmd+A on macOS or Ctrl+A on Windows) and replace it with the following:
const express = require('express');
const session = require('express-session');
const app = express();
app.use(express.urlencoded({ extended: true }));
// FIXED: Session cookie configured WITH all security attributes
app.use(session({
secret: 'use-a-strong-random-secret-in-production',
resave: false,
saveUninitialized: false,
name: '__Host-sessionId',
cookie: {
secure: true,
httpOnly: true,
sameSite: 'strict',
maxAge: 3600000,
path: '/'
}
}));
// Login page
app.get('/', (req, res) => {
if (req.session.user) {
return res.redirect('/dashboard');
}
res.send(`
<!DOCTYPE html>
<html>
<head><title>Login</title></head>
<body>
<h1>Secure Login App</h1>
<p>CWE-614 Remediated</p>
<form method="POST" action="/login">
<label>Username: <input type="text" name="username" /></label><br/><br/>
<label>Password: <input type="password" name="password" /></label><br/><br/>
<button type="submit">Login</button>
</form>
</body>
</html>
`);
});
// Login handler
app.post('/login', (req, res) => {
const { username, password } = req.body;
if (username && password) {
req.session.user = { username };
req.session.loginTime = new Date().toISOString();
return res.redirect('/dashboard');
}
res.redirect('/');
});
// Dashboard (requires session)
app.get('/dashboard', (req, res) => {
if (!req.session.user) {
return res.redirect('/');
}
res.send(`
<!DOCTYPE html>
<html>
<head><title>Dashboard</title></head>
<body>
<h1>Dashboard</h1>
<p>Welcome, ${req.session.user.username}!</p>
<p>Login time: ${req.session.loginTime}</p>
<p>Session cookie is now HttpOnly - not visible to JavaScript</p>
<a href="/logout">Logout</a>
</body>
</html>
`);
});
// Logout
app.get('/logout', (req, res) => {
req.session.destroy(() => {
res.redirect('/');
});
});
app.listen(3000, () => {
console.log('Secure target app running on http://localhost:3000');
console.log('Cookie now has Secure, HttpOnly, and SameSite=Strict attributes.');
});
What do these security attributes do?
- secure: true tells the browser to only send this cookie over HTTPS connections. An attacker sniffing HTTP traffic will never see it.
- httpOnly: true blocks JavaScript from reading the cookie. Even if an attacker injects an XSS script, they cannot steal the session token.
- sameSite: 'strict' prevents the browser from sending the cookie on any cross-site request. This shuts down CSRF attacks that try to ride on an existing session.
- name: '__Host-sessionId' uses the __Host- prefix, which enforces that the cookie must have Secure set, must not specify a Domain, and must use Path=/. This is the OWASP-recommended pattern for session IDs in production.
Why does this still work on localhost?
Modern browsers exempt localhost from Secure attribute enforcement. The cookie will still be set during local development even though you are not using HTTPS.
However, the __Host- prefix may be rejected by Chrome on localhost since its requirements are stricter than the Secure attribute alone. If you find the cookie is not being set in the next substep, temporarily change the name to 'sessionId' for local testing. The security attributes (secure, httpOnly, sameSite) will still apply and demonstrate the fix.
- Save server.js.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
const express = require('express');
const session = require('express-session');
const app = express();
app.use(express.urlencoded({ extended: true }));
// FIXED: Session cookie configured WITH all security attributes
app.use(session({
secret: 'use-a-strong-random-secret-in-production',
resave: false,
saveUninitialized: false,
name: '__Host-sessionId',
cookie: {
secure: true,
httpOnly: true,
sameSite: 'strict',
maxAge: 3600000,
path: '/'
}
}));
// Login page
app.get('/', (req, res) => {
if (req.session.user) {
return res.redirect('/dashboard');
}
res.send(`
<!DOCTYPE html>
<html>
<head><title>Login</title></head>
<body>
<h1>Secure Login App</h1>
<p>CWE-614 Remediated</p>
<form method="POST" action="/login">
<label>Username: <input type="text" name="username" /></label><br/><br/>
<label>Password: <input type="password" name="password" /></label><br/><br/>
<button type="submit">Login</button>
</form>
</body>
</html>
`);
});
// Login handler
app.post('/login', (req, res) => {
const { username, password } = req.body;
if (username && password) {
req.session.user = { username };
req.session.loginTime = new Date().toISOString();
return res.redirect('/dashboard');
}
res.redirect('/');
});
// Dashboard (requires session)
app.get('/dashboard', (req, res) => {
if (!req.session.user) {
return res.redirect('/');
}
res.send(`
<!DOCTYPE html>
<html>
<head><title>Dashboard</title></head>
<body>
<h1>Dashboard</h1>
<p>Welcome, ${req.session.user.username}!</p>
<p>Login time: ${req.session.loginTime}</p>
<p>Session cookie is now HttpOnly - not visible to JavaScript</p>
<a href="/logout">Logout</a>
</body>
</html>
`);
});
// Logout
app.get('/logout', (req, res) => {
req.session.destroy(() => {
res.redirect('/');
});
});
app.listen(3000, () => {
console.log('Secure target app running on http://localhost:3000');
console.log('Cookie now has Secure, HttpOnly, and SameSite=Strict attributes.');
});
Restart the server and verify the fix in DevTools
The running server is still using the old code. You need to restart it for the changes to take effect.
- Switch back to the terminal where your server is running.
- Press Ctrl+C to stop the server.
- Restart the server by running:
node server.js
- You should see the message: Secure target app running on http://localhost:3000.
Now log in again and inspect the response headers to confirm the fix is applied.
- In Chrome (or Edge), open http://localhost:3000.
- Open DevTools by pressing F12.
- Click the Network tab.
- Enter any username and password in the login form, then click Login.
- In the Network tab, find the POST /login request and click it.
- Click the Headers subtab and scroll to Response Headers.
- Look at the Set-Cookie header. It should now include HttpOnly, Secure, and SameSite=Strict.
What should I see?
The Set-Cookie header should look something like: __Host-sessionId=s%3A...; Path=/; Expires=...; HttpOnly; Secure; SameSite=Strict. Each attribute you added now appears after the cookie value.
If you changed the cookie name to sessionId for local testing, the name will differ but the security attributes should still be present.
- Now navigate to Application > Cookies > http://localhost:3000 in DevTools.
- Verify that the Secure and HttpOnly columns now show checkmarks.
Don't see the cookie at all?
If no session cookie appears in the Application tab, Chrome may be rejecting the __Host- prefix on localhost. Change name: '__Host-sessionId' to name: 'sessionId' in server.js, save, restart the server, and log in again. The security attributes will still apply correctly.
Still having trouble verifying?
Confirm JavaScript can no longer read the session cookie
In the previous step, you proved that document.cookie exposed the session ID. The httpOnly: true attribute should now block that access entirely.
- In DevTools, click the Console tab.
- Type the following and press Enter:
document.cookie
- The output should be an empty string "". The session cookie no longer appears because HttpOnly prevents JavaScript access.
Why does this matter for attackers?
Without HttpOnly, an XSS vulnerability anywhere on the site lets an attacker run document.cookie and exfiltrate the session token. With HttpOnly set, that attack path is completely closed. The cookie still functions normally for HTTP requests. It is just invisible to client-side scripts.
Still seeing the session cookie in the console?
Make sure you restarted the server after saving the updated server.js. Also clear your existing cookies by right-clicking in Application > Cookies and selecting Clear all cookies. Then log in fresh.
Help me debug why document.cookie still shows my session
Your app is now hardened against CWE-614. The session cookie cannot leak over HTTP, cannot be stolen via XSS, and cannot be abused through cross-site requests. Next up, you'll document everything you found in a professional bug bounty disclosure report.
Write the Bug Bounty Disclosure Report
You have identified, verified, and fixed the CWE-614 vulnerability. Now comes the part that actually gets you paid in a bug bounty program.
A clear, professional disclosure report transforms a finding into actionable intelligence for the security team reviewing it. The better structured your report, the faster it gets triaged and the more likely you receive a payout.
In this step, get ready to:
- Structure a vulnerability report with standard disclosure sections and CVSS scoring.
- Write proof-of-concept steps and a clear impact statement.
- Add remediation recommendations and authoritative references.
Structure the report with standard disclosure sections
Bug bounty platforms like HackerOne and Bugcrowd expect reports to follow a predictable structure. Reviewers triage hundreds of submissions. A well-organized header with severity, CVSS vector, and CWE classification helps them immediately assess priority.
- In VS Code, create a new file called vulnerability-report.md in your project folder.
- Paste the following report header into the file:
# Vulnerability Disclosure Report
## Title
Sensitive Session Cookie Missing Secure Attribute (CWE-614)
## Severity
Medium (CVSS 3.1 Base Score: 6.5)
## CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
## CWE Classification
CWE-614: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute
## Affected Component
Session management - Set-Cookie response header for session identifier
## Description
The application sets a session cookie without the Secure attribute during HTTPS sessions. This allows the session cookie to be transmitted in plaintext over HTTP connections, enabling network-based attackers to intercept the session token and hijack authenticated sessions.
Additionally observed:
- Missing HttpOnly attribute (session cookie accessible via JavaScript, enabling XSS-based theft)
- Missing SameSite attribute (cookie sent on cross-site requests, enabling CSRF attacks)
What does each section do?
- Title names the vulnerability precisely. Including the CWE ID lets the reviewer cross-reference known weakness databases instantly.
- Severity and CVSS Vector give a standardized risk score. The vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N breaks down to: Network attack vector, Low complexity, No privileges needed, User interaction required, Unchanged scope, High confidentiality impact.
- CWE Classification links to the MITRE Common Weakness Enumeration so the team can look up related attack patterns.
- Description explains what is wrong and lists all related issues found alongside the primary vulnerability.
What is CVSS?
CVSS (Common Vulnerability Scoring System) is a standardized framework for rating vulnerability severity on a 0-10 scale. Bug bounty programs use CVSS scores to determine payout tiers. A score of 6.5 falls in the Medium range, which typically corresponds to vulnerabilities that require some user interaction but still have high impact on confidentiality.
- Save vulnerability-report.md.
File not appearing in the Explorer?
Make sure you created the file inside the same folder where server.js lives. If your VS Code Explorer panel is collapsed, click the top icon in the left sidebar to expand it.
Help me find where to create files in VS Code
Write the proof-of-concept and impact sections
The proof-of-concept (PoC) is the evidence section. It tells the reviewer exactly how to reproduce the issue, step by step. The impact section explains what an attacker gains, translating a technical finding into business risk.
- In vulnerability-report.md, add the following sections below the Description:
## Proof of Concept
1. Navigate to the target application login page
2. Open browser DevTools (F12) and switch to the Network tab
3. Submit valid credentials via the login form
4. Inspect the POST /login response headers
5. Observe the Set-Cookie header: `connect.sid=<value>; Path=/; Max-Age=3600`
6. Note the absence of Secure, HttpOnly, and SameSite attributes
7. Switch to Console tab and run `document.cookie` - session ID is exposed
## Impact
An attacker positioned on the same network (e.g., public Wi-Fi) can intercept the session cookie by:
1. Injecting an HTTP reference to the target domain (e.g., via DNS spoofing or response injection)
2. The browser sends the cookie in cleartext over the HTTP request
3. The attacker captures the session token and replays it to hijack the victim's session
This enables full account takeover without requiring the victim's credentials.
Why does the PoC matter so much?
Many valid vulnerability reports get rejected because the reviewer cannot reproduce the issue. A numbered step-by-step PoC removes ambiguity. Notice how each step names the exact tool (DevTools), the exact tab (Network), and the exact observable result (missing attributes in the header).
The Impact section connects the technical finding to a real-world consequence. "Full account takeover" communicates severity far more effectively than "cookie is insecure."
- Save vulnerability-report.md.
Seeing raw markdown instead of a preview?
To preview your markdown in VS Code, open the file and press Cmd+Shift+V (macOS) or Ctrl+Shift+V (Windows). This opens a rendered preview tab so you can confirm formatting looks correct.
Help me preview my markdown report in VS Code
Add remediation recommendations and references
The remediation section shows you understand how to fix the issue, not just find it. Including authoritative references from OWASP and MITRE gives the receiving team confidence in your recommendation.
- In vulnerability-report.md, add the following sections below the Impact:
## Remediation
1. Set the `Secure` attribute on all session cookies to prevent transmission over HTTP
2. Set the `HttpOnly` attribute to prevent JavaScript access (mitigates XSS-based theft)
3. Set `SameSite=Strict` to prevent cross-site cookie leakage
4. Consider using the `__Host-` cookie name prefix for additional protection
5. Implement HSTS (HTTP Strict Transport Security) to prevent HTTP downgrade attacks
## References
- https://cwe.mitre.org/data/definitions/614.html
- https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
- https://owasp.org/www-community/controls/SecureCookieAttribute
What makes a strong remediation section?
Notice the recommendations go beyond just fixing the reported issue. Including HttpOnly, SameSite=Strict, and the __Host- prefix shows defense-in-depth thinking. Programs often award higher bounties when the researcher demonstrates they understand the full fix, not just the minimum patch.
The references section cites official sources. The OWASP Session Management Cheat Sheet and the MITRE CWE-614 entry are the standard references for this class of vulnerability.
- Save vulnerability-report.md.
- Preview the file with Cmd+Shift+V (macOS) or Ctrl+Shift+V (Windows). You should see a fully rendered report with clear section headings and numbered PoC steps.
References not rendering as links?
Markdown preview may not auto-link bare URLs in all themes. The URLs are still correct as plain text references in the report. Bug bounty platforms will render them as clickable links when you submit.
Help me format my markdown references correctly
✔️ Awesome, I've got everything!
Great. Double check you have saved vulnerability-report.md and that it contains all sections from Title through References.
ⓧ I'd like to double check the full code
Here is the complete vulnerability-report.md file for comparison:
# Vulnerability Disclosure Report
## Title
Sensitive Session Cookie Missing Secure Attribute (CWE-614)
## Severity
Medium (CVSS 3.1 Base Score: 6.5)
## CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
## CWE Classification
CWE-614: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute
## Affected Component
Session management - Set-Cookie response header for session identifier
## Description
The application sets a session cookie without the Secure attribute during HTTPS sessions. This allows the session cookie to be transmitted in plaintext over HTTP connections, enabling network-based attackers to intercept the session token and hijack authenticated sessions.
Additionally observed:
- Missing HttpOnly attribute (session cookie accessible via JavaScript, enabling XSS-based theft)
- Missing SameSite attribute (cookie sent on cross-site requests, enabling CSRF attacks)
## Proof of Concept
1. Navigate to the target application login page
2. Open browser DevTools (F12) and switch to the Network tab
3. Submit valid credentials via the login form
4. Inspect the POST /login response headers
5. Observe the Set-Cookie header: `connect.sid=<value>; Path=/; Max-Age=3600`
6. Note the absence of Secure, HttpOnly, and SameSite attributes
7. Switch to Console tab and run `document.cookie` - session ID is exposed
## Impact
An attacker positioned on the same network (e.g., public Wi-Fi) can intercept the session cookie by:
1. Injecting an HTTP reference to the target domain (e.g., via DNS spoofing or response injection)
2. The browser sends the cookie in cleartext over the HTTP request
3. The attacker captures the session token and replays it to hijack the victim's session
This enables full account takeover without requiring the victim's credentials.
## Remediation
1. Set the `Secure` attribute on all session cookies to prevent transmission over HTTP
2. Set the `HttpOnly` attribute to prevent JavaScript access (mitigates XSS-based theft)
3. Set `SameSite=Strict` to prevent cross-site cookie leakage
4. Consider using the `__Host-` cookie name prefix for additional protection
5. Implement HSTS (HTTP Strict Transport Security) to prevent HTTP downgrade attacks
## References
- https://cwe.mitre.org/data/definitions/614.html
- https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
- https://owasp.org/www-community/controls/SecureCookieAttribute
You now have a complete, professional vulnerability disclosure report. This is the same structure used on platforms like HackerOne and Bugcrowd. Next up, put your detection skills to the test with an automated cookie auditing script.
Secret mission
Build an Automated Cookie Auditor
You just detected CWE-614 manually using DevTools. That works for a single page, but real bug bounty hunters audit dozens of endpoints at once. Can you automate the detection workflow into a reusable script?
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.
Resources you used:
- Node.js server process running on port 3000.
- Project folder containing server.js, cookie-auditor.js, vulnerability-report.md, package.json, and node_modules/.
Keep everything running
No action needed. Choose this if you're still actively building or want to experiment with the vulnerable and remediated server configurations.
- Your server continues running on port 3000.
- Your project files stay on your Desktop.
- You can re-run the cookie auditor or modify the server at any time.
Pause - I'll come back to this later
Shut down the running server to free up port 3000. Your project files stay on disk so you can restart later.
- Switch to the terminal where the server is running.
- Press Ctrl+C to stop the server process.
- Your project files remain in place. To resume later, navigate to the project folder and run node server.js again.
Delete - I don't want to use this again
Remove all project files and stop the running server. This frees up disk space and port 3000.
- Switch to the terminal where the server is running and press Ctrl+C to stop it.
- Delete the project folder by running this command (macOS/Linux):
cd ~/Desktop
rm -rf cwe-614-demo
On Windows, run this instead:
cd ~\Desktop
rmdir /s /q cwe-614-demo
- Confirm the folder is gone by running ls (macOS/Linux) or dir (Windows) on your Desktop. You should no longer see the project folder listed.
Nice Work!
Nice Work!
Nice work! You just completed the full bug bounty detection lifecycle, from identifying a real-world cookie vulnerability to writing a professional disclosure report.
You've learned how to:
- Detect CWE-614 using browser DevTools by inspecting Set-Cookie response headers in the Network and Application tabs.
- Remediate insecure session cookies by applying Secure, HttpOnly, and SameSite=Strict attributes in Express.js.
- Write a professional vulnerability disclosure report with CVSS 3.1 scoring, proof-of-concept steps, impact analysis, and remediation recommendations.
- Secret Mission: Built an automated cookie security auditor that scans multiple endpoints for missing security attributes.
Ready to quiz yourself?