拖更一年之际2333
我以为这辈子只有在CTF中才会遇到Padding Oracle Attack和CBC字节翻转攻击,不想Shiro爆出了利用场景。
Shiro自1.2.5版本开始使用动态key,可利用Padding Oracle进行攻击。
了解到shiro存在相关漏洞后,快速去看了一下,但是我出现了两个疑问
两年前接触过这两个漏洞,Padding Oracle Attack限于这篇文章: https://www.freebuf.com/articles/web/15504.html 利用搜索引擎查到的资料也都是这个方式。
把所有的block块分开提交,可以通过控制前一块的block密文,从而控制后一块的明文,根据Padding的机制,可构造出一个bool条件,从而逐位得到明文,然后逐块得到所有明文。
利用条件及场景:padding错误和padding正确服务器可返回不一样的状态。
那回到shiro上来看
try { return cipher.doFinal(bytes); } catch (Exception e) { String msg = "Unable to execute 'doFinal' with cipher instance [" + cipher + "]."; throw new CryptoException(msg, e); }
try { ObjectInputStream ois = new ClassResolvingObjectInputStream(bis); @SuppressWarnings({"unchecked"}) T deserialized = (T) ois.readObject(); ois.close(); return deserialized; } catch (Exception e) { String msg = "Unable to deserialze argument byte array."; throw new SerializationException(msg, e); }
padding错误和padding正确都会抛出异常,虽然异常不同,但反馈到客户端的信息是完全一样的:
HTTP/1.1 302 Set-Cookie: rememberMe=deleteMe; Path=/samples_web_war; Max-Age=0; Expires=Tue, 20-Aug-2019 08:44:46 GMT Set-Cookie: JSESSIONID=D9AED5F5F1225ABBD02C7A4E238D710E; Path=/samples_web_war; HttpOnly Location: /samples_web_war/login.jsp;jsessionid=D9AED5F5F1225ABBD02C7A4E238D710E Content-Length: 0 Date: Wed, 21 Aug 2019 08:44:46 GMT Connection: close
整个攻击是为了伪造明文,假设获取到了原有明文( 因为原明文是shiro内一个固定类的序列化字符串,即使不使用Padding Oracle Attack攻击者也不难得到 ),接下来要进行CBC字节翻转攻击。
关于CBC字节翻转攻击,我以前做过相关的分析: https://p0sec.net/index.php/archives/99/
和前面的Padding Oracle Attack原理一样,但是现在知道了原明文,那么就可以控制某个Block解密后的明文为任意明文, 但是前一个Block解密后的明文会被破坏,并且不可知内容 。所以想要完美的修改每一块的明文,就需要 每修改一个Block都能获取修改后的明文 ,直至到最前面的一个Block,即IV。
回到Shiro上,上面的条件并不能满足,Shiro并不会返回解密后的明文到客户端。
分析到这里,我一度感觉到,这个漏洞是假的把,带着疑惑,找到Github的一个参考: https://github.com/mwielgoszewski/python-paddingoracle
看完脚本,当时就懵逼了,还有这种利用方式?貌似国内的安全研究者都没有care,都是在讲上面说的利用。
还是先祭出两张神图:
为了方便讲,将图中没有标注值命个名。
Ciphertext经过block cipher decryption后的值称为Inermediary Value
以AES来说,每组16的字节(DES每组8字节),攻击者可以往原密文后面拼接32个字节,即两个Block,通过控制前一个Block,获取后一个Block的Inermediary Value。获取到后一个Block 的Inermediary Value后就可以通过控制前一个Block的密文,控制后一个Block的密文,简直是上面讲的Padding Oracle Attack和CBC字节翻转攻击的完美结合,区别就是上面会想办法获取原明文,再进行翻转,现在是完全重新构造一段密文。
来个实例
Ciphertext
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
Plaintext
0x7c 0x42 0xda 0x7b 0x84 0xa2 0x77 0xb1 0x31 0x37 0x1f 0xcd 0xbe 0x3e 0x23 0x1c 0x4a 0xec 0xf6 0xed 0x58 0xfc 0xdc 0xa4 0xa3 0x4a 0x39 0x25 0x39 0x5b 0xeb 0x42
。。。 。。。
Ciphertext
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x43 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
Plaintext
0x3d 0x10 0xc6 0xbc 0x68 0xe7 0xdd 0xc4 0xc1 0x75 0x65 0x85 0x6a 0x3d 0xef 0x92 0x4a 0xec 0xf6 0xed 0x58 0xfc 0xdc 0xa4 0xa3 0x4a 0x39 0x25 0x39 0x5b 0xeb 0x01
这时候会padding正确,攻击者可以得到的值是:Ciphertext,Plaintext最后一位为0x01。
所以这时候Inermediary Value最后一位为0x43^0x01=0x42
期望获取倒数第二位的Inermediary Value,现在能控制最后一位为任意值,现在就先控制最后一位为0x02,Inermediary Value为0x42,要使明文为0x02,前一个Block的密文为0x42^0x02=0x40即可,如法炮制,获取倒数第二位
Ciphertext
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xe9 0x40 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
Plaintext
0x02 0x70 0xed 0x86 0x97 0x45 0x94 0xa5 0xe8 0x8a 0x26 0xbc 0x3c 0x40 0x9b 0xa2 0x4a 0xec 0xf6 0xed 0x58 0xfc 0xdc 0xa4 0xa3 0x4a 0x39 0x25 0x39 0x5b 0x02 0x02
可获取到倒数第二位的Inermediary Value为0xe9^0x02=0xeb
。。。 。。。
可获取到后一个Block的完整Inermediary Value
0x4a 0xec 0xf6 0xed 0x58 0xfc 0xdc 0xa4 0xa3 0x4a 0x39 0x25 0x39 0x5b 0xeb 0x42
然后通过修改前一个Block的密文,达到完全控制后一个Block,比如想要明文为"0123456789abcdef"
Ciphertext可构造为:
0x7a 0xdd 0xc4 0xde 0x6c 0xc9 0xea 0x93 0x9b 0x73 0x58 0x47 0x5a 0x3f 0x8e 0x24 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
这时候可以将前一步的前一个Block作为这一步的后一个Block:
Ciphertext
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x7a 0xdd 0xc4 0xde 0x6c 0xc9 0xea 0x93 0x9b 0x73 0x58 0x47 0x5a 0x3f 0x8e 0x24
Plaintext
0x7c 0x42 0xda 0x7b 0x84 0xa2 0x77 0xb1 0x31 0x37 0x1f 0xcd 0xbe 0x3e 0x23 0x1c 0xd2 0x8f 0xd3 0x51 0x40 0x10 0x3f 0x0d 0xd5 0x3e 0xb8 0x13 0x0d 0x96 0xa8 0x66
。。。 。。。
Ciphertext
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x67 0x7a 0xdd 0xc4 0xde 0x6c 0xc9 0xea 0x93 0x9b 0x73 0x58 0x47 0x5a 0x3f 0x8e 0x24
Plaintext
0x9b 0x8e 0x92 0x23 0x68 0x9d 0x51 0xef 0xe9 0x39 0x64 0x5f 0xfc 0x74 0xc8 0xd4 0xd2 0x8f 0xd3 0x51 0x40 0x10 0x3f 0x0d 0xd5 0x3e 0xb8 0x13 0x0d 0x96 0xa8 0x01
后面操作就一样了。
为什么这种方式在shiro中可以利用,需要看一下Java反序列化的数据格式
00000000: aced 0005 7372 0015 6f74 6865 722e 7265 ....sr..other.re 00000010: 6164 6f62 6a65 6374 2e55 7365 72a0 f0a4 adobject.User... 00000020: 387a 3bcc 8e03 0004 4900 0361 6765 4c00 8z;.....I..ageL. 00000030: 0862 6972 7468 6461 7974 0010 4c6a 6176 .birthdayt..Ljav 00000040: 612f 7574 696c 2f44 6174 653b 4c00 046e a/util/Date;L..n 00000050: 616d 6574 0012 4c6a 6176 612f 6c61 6e67 amet..Ljava/lang 00000060: 2f53 7472 696e 673b 4c00 0376 616c 7400 /String;L..valt. 00000070: 124c 6a61 7661 2f6c 616e 672f 4f62 6a65 .Ljava/lang/Obje 00000080: 6374 3b78 7000 0000 1773 7200 0e6a 6176 ct;xp....sr..jav 00000090: 612e 7574 696c 2e44 6174 6568 6a81 014b a.util.Datehj..K 000000a0: 5974 1903 0000 7870 7708 0000 016c b773 Yt....xpw....l.s 000000b0: 0351 7874 0006 686f 6c6c 6973 7078 .Qxt..hollispx
执行流程
来源: https://xz.aliyun.com/t/3847
关键点在于ObjectOutputStream是一个Stream,他会按格式以队列方式读下去, 后面拼接无关内容,不会影响反序列化
所以现在BOOL条件就出来了,拼接无关数据,padding 正确,能正常反序列化,padding错误抛出异常: throw new CryptoException(msg, e);