从CTF题看SpEL表达式注入

前置知识

JWT相关知识

SpEL表达式

审计过程

拿到题高兴坏了,擅长的java审计,直接给了源码,但是到最后卡住了,思想不对,没做出来。。赛后复现了一遍

主要是三个路由,/,/login和/success,在login路由接收了个username,创建了一个auth的cookie,通过JwtUtils.getToken(username)获取auth的值

在/success路由处理了这个auth,分别把sub和jti对应的值分别赋值给了username,uuid,并且uuid不包含flag和/,然后调用SpelExpressionParser.parserExpression去执行SpEL表达式,这里的话他是调用了FileRead,但是由于他是通过拼接字符串的,所以存在SpEL表达式注入漏洞,只需要控制我们的uuid即可,也就是jwt中的jti的值

image-20221203200721490

通过名字可以判断FileRead应该是读取文件功能,直接进入这个FileRead,FileRead有重载函数,一个是获取的参数得是True,并且是Boolean类型的,会读取/flag,一个是字符串类型的读取一个文件,思路马上有了,就是构造一个JWT,然后让他的sub值是我们想要读取的文件名字,但是这里过滤了flag和/。目录遍历读取Flag是无法实现。

image-20221203201346450

我们最终的目的是要构造出来T(com.ctf.lander.Utils.OtherUtils).FileRead('../flag'),但是这里他会自己加个Base_dir,这里的Base_dir是个/tmp/,我们需要用../去返回到上面那级,所以需要构造的是../flag而不是/flag,这里我们可以通过T(java.lang.Character).toStirng(ASCII).concat()这样的方式去连接处一个../flag,所以大概的拼接的内容如下

T(java.lang.Character).toString(46).concat(T(java.lang.Character).toString(46)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(102)).concat(T(java.lang.Character).toString(108)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(103))

这个SpEL表达式的结果就是../flag,然后需要闭合前后单引号,就变成了

'+T(java.lang.Character).toString(46).concat(T(java.lang.Character).toString(46)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(102)).concat(T(java.lang.Character).toString(108)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(103))+'

解题过程

远端环境关了,只能自己本地搭建了,把之前的/tmp/换成了D:\tmp,把flag放到了D盘下面,其他无改动

首先先访问/login随便post进去一个username,获取到auth。

image-20221203202356400

把这个auth复制到JSON Web Tokens - jwt.io,这里还需要我们填写一个key,key的值我们可以通过审计代码获取到。

image-20221203202504925

位于com/ctf/lander/Utils/JwtUtils.javagetToken方法,可以看到是用hs256加密的,密钥是通过CyberUtils.Md5()去生成,只需要调用一下这个方法即可获取了

image-20221203202620020
image-20221203202723970

然后把我们需要注入的Spel表达式替换到jti对应位置的值即可

image-20221203202820262

然后get请求success路由,带上这个auth的Cookie即可,这里的ch1e666就是我flag中的内容

image-20221203202853981