D^3CTF 2019 Web ezts Official WriteUp

D^3CTF 2019 Web ezts Official WriteUp

没错,我就是 ezts 的傻逼出题人(求不被打

出题时间有点紧,题目描述在一开始也出了点问题,对不起各位师傅了

出题思路

作为一个整天摸鱼的 web 菜狗,实在是太有幸能在这种大型比赛出题了(感谢一下咕咕咕的老大 嘤

这道题的出题思路其实是源自日常无聊的日站行为。源头是 ThinkPHP 3.2.x 爆出的一枚 ORM 框架 bind 注入漏洞。在用了这个漏洞把某站的数据库给DROP了以后,引发了我的一些思考:ORM 框架真的安全吗?

ORM,对象关系映射,我一般将其理解为一种开发的思想,即代码和数据解耦。但是,代码是人写的,库也是人用的。ORM 库真的就没有漏洞吗?用 ORM 库就不会写出漏洞吗?显然不是

同样,针对现在的开发,大量使用的第三方库,会不会造成其他漏洞?

最后,拿到一个 shell 后,运维的配置会不会产生其他潜在的漏洞?

因此,这道题基本的出题思路就是:

  1. 通过开发者误用的 ORM 框架加上其本来的漏洞产生 SQL 注入,从而拿到关键数据
  2. 由于第三方库导致的一个语言级别漏洞拿到 shell
  3. 由于运维人员对 sudo 的错误配置产生权限提升

而映射到题目上,基本的解题思路就是:

  1. Koa 框架所使用的 Sequelize ORM 框架本身的漏洞加上开发者错误的代码导致搜索功能产生了一个 SQL 注入漏洞,通过该漏洞注出 admin 的密码
  2. 登陆 admin,发现有管理页面。在管理页面可以修改用户的资料。其实可以发现用户资料形式是以 JSON 在数据库存储的,在 admin 修改时存在合并操作。这里存在一个合并对象导致的原型链污染
  3. 原型链污染可以干嘛呢?本题没有编写相关的利用代码。而实际上产生漏洞的代码在使用的渲染引擎 ejs 上,通过对 ejs 的参数修改,我们可以直接拿到 shell
  4. 拿到 shell 后,用户实为低权限用户。但是 flag 却是 root 用户 0400 权限,也没有相关读取的二进制文件。有些师傅在这使用内核提权卡住了,实际上拿到 root 用户的最方便的方法不是 sudo 么?这里就是运维对 sudo 的一个错误配置,结合近期爆出的一枚 sudo 的 CVE 达到提权

下面会给出详细的 WP 和题目运维过程中产生的一些思考

针对题目描述问题,在仔细思考以后,确实有点不妥,但是我还是得说明一下

原题描述

ORM $eq No SQLi?

修改后描述

ORM $eq Not SQLi?

在比赛过程中有师傅反馈误以为是 NoSQL 注入,但是如果是 NoSQL 注入,题目意思不就变成 使用ORM框架等于NoSQL注入 ?这显然不是有点奇怪么?

而且在搜索界面的一个 ' 就可以触发 500 响应,难道不是 SQL 注入的一个直接提醒么?

Write Up

根据出题思路,我将 WP 分为五个 stage

Stage 1 信息收集

Web 永远逃不掉信息收集。打开这个题目开始,这个网站有登陆,注册的功能。注册完成后登陆进用户界面,发现有增加档案,搜索档案的功能

同时,如果使用了 fuzz 工具,可以发现存在 /admin/ 路由,也就意味着这题可能的思路就是访问到管理界面。注意到 Cookiekoa 的键名,基本可以判断后端是 Node.JsKoa 框架

根据题目描述,基本可以判定这题使用了 ORM 框架,谷歌随便一搜 Koa 的或者 Node.Js 的 ORM 框架,就可以找到 Sequelize 这个库。也就是说,我们可以大致猜测出后端使用了哪些框架了

在搜索界面,注意到 ' 可以触发 500 响应,基本可以判定这里存在一个 SQL 注入漏洞

Stage 2 SQL 注入

根据我们信息收集的结果,可以对搜索功能进行进一步的 fuzz,但是尝试了多种 SQL 注入 payload 都无果。这里想到猜测的后段 ORM 框架 Sequelize。那么我们去 Snyk.io 去找一下这个框架有没有洞

显然,这个框架有一个较近的 CVE: CVE-2019-10752,而 Snyk.io 直接给出了相应的 PoC。那么后面问题就很简单了,随便把 PoC 改成时间盲注,发现存在延时,就可以直接用布尔盲注注出数据了

这里也不放出脚本了,没有任何过滤的裸的注入,注出第一个用户,也就是 admin,拿到密码就可以直接登陆后台管理了

Stage 3 原型链污染

登陆进入管理后台后,发现有管理用户数据和查询用户数据的功能。而管理用户数据可以直接对用户数据修改。这里注意到查询出的用户数据是 JSON 格式的,也就是说数据库中用户数据大概也是直接存放的。

有个小细节就是管理界面直接会车无效,需要点提交按钮,这个是我前端没写好的锅(

然后修改用户数据,可以发现提交数据格式必须也是 JSON 格式,而提交的 JSON 会被合并进原来的数据,或者说,会创建新的数据。这里的 JSON 合并也就是 js 的对象合并操作,很容易想到原型链污染这个漏洞

为了测试,我们可以先发一个小的 PoC

{"content": {"constructor": {"prototype": {"a": "b"}}}}

提交之后,再查看用户数据,可以发现键 content 中的数据消失了,这里可以初步认为存在原型链污染漏洞

(事实上,如果后端是 express,原型链污染的数据会在 HTTP 响应头中显示出来)

还有更多的 PoC,比如

{"content": {"constructor": {"prototype": {"username": "asd"}}}}

可以发现刷新后立刻 500,等待服务重启之后 asd 用户也离奇消失了

这是原型链污染在其他依赖库中产生的一些副作用,而实际上,针对这题原型链污染,我对 Sequelize 库进行了修改,防止污染之后 ORM 框架出错(因为污染的数据会以键的形式插入到查询语句中)

到这,我们已经基本确定存在原型链污染漏洞了

Stage 4 ejs getshell

有了原型链污染了,我们可以做些啥呢。这里其实是参考了 X-NUCA 比赛过程中 hard_js 一题的非预期解。ejs 模版框架存在代码注入的问题,在遇到原型链污染时,我们可以将 js 代码注入到渲染模版中,最后引发命令执行,拿到 shell

具体的过程这里也不在赘述,可以参考我的这两篇博客

下面一篇有详细的代码调试跟进和分析

Stage 5 提权

拿到 shell 后,有师傅发现用户为 node,而根目录下 flag 的文件权限为 0400。通常情况下,一般会预留一个 readflag 的文件来读取 flag,但是本题并没有。

很多师傅想到提权,但是事实上本题 Linux 内核非常新,目前没有提权洞(应该,除了潜在漏洞或 0day),而且作为一个 Web 题,在 docker 容器内 pwn 内核提权不是一个很奇怪的思路么?

而事实上,最简单的获取 root 权限的办法,不就是 sudo 么?

同样的,这里使用了非常新的 sudo 的 CVE: CVE-2019-14287。我在本地调试 PoC 通过以后,临时决定给这个题加点最后的难度,把 sudo 这个漏洞加进来了

那么后续就很简单了

sudo -l # 查看当前用户 sudo 配置
sudo -u#-1 /bin/cat /flag

运维过程中的思考

在运维过程中,遇到了很多蜜汁的问题,而很多在我看来完全不能理解,大概这就是上帝视角的感受吧。其实可能当我也拿到这个题的时候,我也会犯同样的错误

首先的一个就是,题目描述带来的问题。上面我也解释过了,确实挺奇怪的。但是就给我做,对于搜索功能里面一个单引号就能 500 的点我是永远不能放下不看的(这不是摆明了的 SQL 注入么???

其次,为啥会有队开扫描器暴力扫描?我觉得稍微信息收集一下就可以发现后端是 Koa 框架,扫描很明显就没有任何用处了啊。更有甚者,开了多线程高强度分布式扫描,以至于服务器 5分钟不到处理了 260k 个请求(这让我觉得 Node.Js 还是能做生产环境的

重新回到注入上面,为什么用 SQL 注入注出管理员的密码,然后还在管理界面尝试 NoSQL 注入?难道后会用两个数据库?个人表示不是很能理解

关于原型链污染,我在流量中看到了几种污染的 payload,但是是不能用的,这种其实本地调试一下就可以发现这个问题(可能用毒瘤 js/ts 的人实在是太少了

关于提权,这没啥好说的,其实在最后放出的 sudo 的提示,是想帮一些师傅一把,还是可以去尝试一下的,但是不知道为啥没有师傅愿意再去做了(可能是觉得题目太傻逼了,这个我得背锅

总结

这道题在出题时,我预期是一道非常简单的题,以至于预期分数是 400 分,但是没想到最后只有 1 解,可能是我在某些方面考虑欠妥,没有给予足够的提示和引导

此题的源码会在我的 GitHub 上调整为 Public,官方应该也会放出源码

最后,还是感谢师傅们愿意做我的题

发表评论

发表回复

*

沙发空缺中,还不快抢~