CVE-2024-57041: Stored XSS in NodeBB

Posted on Jan 22, 2025

Stored XSS Exploit

This discovery is particularly exciting as it marks a significant professional milestone: my first CVE!

Steps

  1. Create an admin and another attacker user.

  2. The attacker navigates to their profile ‘about me’ section and can post script tags into their ‘about me’. XSS

  3. The attacker can inject this payload: <script>alert("NodeBB Hacked!")</script> XSS XSS

  4. Once posted, the page will not yet reflect any of the stored script tags. XSS

  5. As an administrator, log into the application and visit the attacker users home page.

  6. Report the user. XSS XSS

  7. Navigate to the ‘Flagged Content’ in the ‘Moderator Tools’ drop down in the admin settings. XSS

  8. Click on the user that has the stored script tags in their ‘about me’. XSS

  9. The stored XSS attack will trigger XSS XSS

Further Analysis: Code Review

Exploiting a vulnerability is one thing, but understanding the design and the fix for the issue is an entirely different skill.

After reporting the vulnerability to the security team, the developers chose to mitigate the issue by incorporating the validator npm package. This package includes a function called validator.escape. According to the npm documentation, the escape function performs the following transformation:

Function Description
escape(input) replace <, >, &, ‘, " and / with HTML entities.

The Fix

The developers passed the user-provided aboutme data through validator.escape to sanitize it. Additionally, the data was wrapped with the String() function to ensure it is always converted to a string data type.

Here’s the resulting line of code:

userData.aboutme = validator.escape(String(userData.aboutme));

This line is then appeaned to the current code block
```js
Flags.getTarget = async function (type, id, uid) {
	if (type === 'user') {
		const userData = await user.getUserData(id);
		userData.aboutme = validator.escape(String(userData.aboutme));
		return userData && userData.uid ? userData : {};
	}
	if (type === 'post') {
		let postData = await posts.getPostData(id);
		if (!postData) {
			return {};
		}
		postData = await posts.parsePost(postData);
		postData = await topics.addPostData([postData], uid);
		return postData[0];
	}
	throw new Error('[[error:invalid-data]]');
};

For more information, the git commit can be found here.