aes key长度_原创 | 浅谈Shiro反序列化获取Key的几种方式

388544486dc8640b575cef7eaac5b81a.png点击“关注”了解更多信息 ba81e9028d52ef9c9dd1f00e328b1088.png

关于Apache Shiro反序列化

d91b52ae23b7cb37e714289fe8d4caed.png

在shiro≤1.2.4版本,默认使⽤了CookieRememberMeManager,由于AES使用的key泄露,导致反序列化的cookie可控,从而引发反序列化攻击。(理论上只要AES加密钥泄露,都会导致反序列化漏洞) 利用的两个关键条件是key和可用gadget。1.2.4版本默认key为kPH+bIxk5D2deZiIxcaaaA==,当然也可以通过下面的方式自定义key:
private static final String ENCRYPTION_KEY = "3AvVhmFLUs0KTA3Kprsdag==";public CookieRememberMeManager rememberMeManager() {        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();        cookieRememberMeManager.setCookie(rememMeCookie());        // remeberMe cookie 加密的密钥 各个项目不一样 默认AES算法 密钥长度(128 256 512)        cookieRememberMeManager.setCipherKey(Base64.decode(ENCRYPTION_KEY));        return cookieRememberMeManager;    }

下面结合实战以及shiro的CookieRememberMeManaer的调用过程,浅谈获取Key的几种方式。

Shiro key的获取方式

d91b52ae23b7cb37e714289fe8d4caed.png

1

结合Dnslog与URLDNS

在进行漏洞探测的时候,一般会使用ysoserial-URLDNS-gadget结合dnslog进⾏检测,其不受JDK版本和相关的安全策略影响, 除非存在网络限制DNS不能出网

通过判断dnslog是否收到对应的请求,判断漏洞是否存在。这是获取key比较实用方法,通过在dnslog域名前加⼊对应key的randomNum,结合对应的dnslog记录,即可获取到应用对应的Shiro key了

例如下图通过结合Dnslog与URLDNS成果枚举出当前应用的key为kPH+bIxk5D2deZiIxcaaaA==:

862c487bc27adfaa19e8269b65784827.png

2

利用时间延迟或报错

结合Dnslog与URLDNS方法有一个前提是DNS能出网。那么在不出网的情况下就需要找一个替代的方案了。结合SQL盲注的思路,可以考虑执行如下代码结合时间延迟进行判断,若系统是linux系统,则睡眠10s:

try{   if(!(System.getProperty("os.name").toLowerCase().contains("win"))){     Thread.currentThread().sleep(10000L);     }   } catch(Exception e){}
同理,可以考虑结合触发Java异常进⾏判断,若系统返回对应的报错系统,或者返回通用的报错提示,说明当前的key和gadget组合是成功的:
String result = "shiro-Vul-Discover";throw new NoClassDefFoundError(new String(result));
上述的思路是通过执行相关的恶意代码来进行判断的,那么就需要在有相关的gadget的前提下才能进行key的枚举了。
例如下面的案例,使用CommonsBeanutils1结合时间延迟的方式成功枚举出当前key为4AvVhmFLUs0KTA3Kprsdag==:

7144256c50808699a1d2a1d7a431de48.png

同理,也可以使用报错的方式进行key的枚举:

e8c44e91d036e86d208c006b205ea7b0.png

这种方法的话存在一个比较棘手的点:枚举的次数多,耗时长。因为要结合可用gadget执行相关代码进行判断,那么假设字典的key个数为100个,那么枚举的次数就是gadget与key的笛卡尔积(10个gadget就耀枚举1000次),以下是一些常用的gadget:

URLDNSCommonsBeanutils1CommonsCollections*JRMPClientJRMPListenerC3P0Spring1......

自动化不稳定:例如部分场景报错时会统一返回登陆页面,在实际利用中很多情况下也仅仅是在登陆页面的接口进行检测,那么就可能会出现漏报误报的情况。所以在DNS不出网的情况下,这种方式比较繁琐。

3

结合CookieRememberMeManaer

shiro提供了记住我(RememberMe)的功能,关闭了浏览器下次再打开时还是能保存身份信息,使得无需再登录即可访问。
在登陆成功时,如果启用了RememberMe功能,shiro会在CookieRememberMeManaer类中将cookie中rememberMe字段内容进行序列化、AES加密、Base64编码操作。然后保存在cookie中。在关闭浏览器后,重新访问对应的业务接口,此时就是反过来的操作,解码,解密,然后序列化。最后获取到当前用户的身份信息。
简单看看具体的代码实现,看看能不能找到相关的思路来解决枚举key的问题。在获取到rememberMe后,会调用getRememberedPrincipals方法解密反序列化,得到用户凭证组信息:
protected PrincipalCollection getRememberedIdentity(SubjectContext subjectContext)  {    RememberMeManager rmm = getRememberMeManager();    if (rmm != null) {      try      {        return rmm.getRememberedPrincipals(subjectContext);      }  ......}
getRememberedPrincipals的具体实现:
public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext)  {    PrincipalCollection principals = null;    try    {      byte[] bytes = getRememberedSerializedIdentity(subjectContext);      if ((bytes != null) && (bytes.length > 0)) {        principals = convertBytesToPrincipals(bytes, subjectContext);      }    }    catch (RuntimeException re)    {      principals = onRememberedPrincipalFailure(re, subjectContext);    }    return principals;  }

在getRememberedSerializedIdentity方法里主要是对cookie里的相关内容进行base64解码,然后调用convertBytesToPrincipals方法进行解密操作:

protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext)  {    if (getCipherService() != null) {      bytes = decrypt(bytes);    }    return deserialize(bytes);  }


解密后就是对应的反序列化以及生成对应的用户凭证组的信息了。在调用上述方式时,如果抛出异常,则会调用onRememberedPrincipalFailure方法:

principals = onRememberedPrincipalFailure(re, subjectContext);
查看onRememberedPrincipalFailure的具体实现:
protected PrincipalCollection onRememberedPrincipalFailure(RuntimeException e, SubjectContext context){    if (log.isDebugEnabled()) {      log.debug("There was a failure while trying to retrieve remembered principals.  This could be due to a configuration problem or corrupted principals.  This could also be due to a recently changed encryption key.  The remembered identity will be forgotten and not used for this request.", e);    }    forgetIdentity(context);    throw e;  }

里面调用的是CookieRememberMeManager

类的forgetIdentity方法:

public void forgetIdentity(SubjectContext subjectContext)  {    if (WebUtils.isHttp(subjectContext))    {      HttpServletRequest request = WebUtils.getHttpRequest(subjectContext);      HttpServletResponse response = WebUtils.getHttpResponse(subjectContext);      forgetIdentity(request, response);    }  }  private void forgetIdentity(HttpServletRequest request, HttpServletResponse response)  {    getCookie().removeFrom(request, response);  }

然后调用removeFrom方法,这里具体是设置对应的

responseheader,也就是常见的

rememberMe=deleteMe:

public void removeFrom(HttpServletRequest request, HttpServletResponse response)  {    String name = getName();    String value = "deleteMe";    String comment = null;    String domain = getDomain();    String path = calculatePath(request);    int maxAge = 0;    int version = getVersion();    boolean secure = isSecure();    boolean httpOnly = false;    addCookieHeader(response, name, value, comment, domain, path, maxAge, version, secure, httpOnly);    log.trace("Removed '{}' cookie by setting maxAge=0", name);  }
结合前面的简单分析,可以知道,当从cookie中获取到rememberMe字段时,通过一系列的解码解密反序列化,成功话的会则得到用户凭证组信息。否则在response中返回Set-Cookie:rememberMe=deleteMe。
也就说,可以尝试构造好一个包含用户凭证组信息的伪造rememberMe的值,然后经过AES加密后进行请求。经过一系列的解码解密操作后,若此时返回包不返回Set-Cookie: rememberMe=deleteMe,说明当前的key是正确的。可以以此作为判断标准,使用不同密钥对这串序列化数据进行加密并发包,即可快速爆破获取到Shiro加密密钥。
这里做一个实验验证下以上的猜想,登录写好的环境http://localhost:8080/login,勾选rememberMe,登录成功后,看到一个key为rememberMecookie:
NMhQ5j+uiYfUA+gQF93wGknW88ru39LFDKiOmaAuphx7h+r/XUhlebml7+KNwfF0gIIOnJg6LA8xVpzPJTYknq/aYPeeDNJEVYX8DSUMNUh0nbCdHW1YNuFDdBNg6chk5nEZwkh7dG9k+uAnZEfpFbRTajQ4vEolbOktGAS+feNmpurL2P/0dpWwzsSGMZubiVs0ICMVt6CS3qvU8rKC22lbPILSqTiD5Ao+6YNCm19qm/6uQ7De2E+gmKmxGA9o/EsaRUE71wdiHdJbaDeNOQ5am8rXiejqtfEl5YHzeU2MEdxqo+POVUgaSal7O3FYhLjfn4U1nS97/VUHfY7mlz3iP9rU4KvIYjtB5RhbNwkgoFmtUY6MFyFaJNoOAwKBfkeVY0w7QoF7zo0P1HEA3G1XEBR7GeC4O/XAChMnDx7NYfm5D5RZuWWNkW8qI0U9n5UJXmpVsS1hB3vor0eB/5gO5USMy+ToHAW3bOB6REK1x3/U9IS82sY/aLv7aXBA
通过一系列的解密,上面的rememberMe解密后的序列化内容base64编码如下:
rO0ABXNyADJvcmcuYXBhY2hlLnNoaXJvLnN1YmplY3QuU2ltcGxlUHJpbmNpcGFsQ29sbGVjdGlvbqh/WCXGowhKAwABTAAPcmVhbG1QcmluY2lwYWxzdAAPTGphdmEvdXRpbC9NYXA7eHBzcgAXamF2YS51dGlsLkxpbmtlZEhhc2hNYXA0wE5cEGzA+wIAAVoAC2FjY2Vzc09yZGVyeHIAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAADHcIAAAAEAAAAAF0AAhpbmlSZWFsbXNyABdqYXZhLnV0aWwuTGlua2VkSGFzaFNldNhs11qV3SoeAgAAeHIAEWphdmEudXRpbC5IYXNoU2V0ukSFlZa4tzQDAAB4cHcMAAAAED9AAAAAAAABdAAEcm9vdHh4AHcBAXEAfgAFeBAQEBAQEBAQEBAQEBAQEBA=
可以看到里面包含了shiro的用户凭证组的信息:

ac4ebf244f86fc1cbabf6f495f69c81f.png

得到该内容后即可结合key字典进行AES加密,然后进行base64编码,作为rememberMe的值在请求中进行提交,然后根据respnose是否返回deleteMe判断key是否正确。

结合实际场景测试,当key正确时,response的header没有相关关键字:

6df4135be6707cdd609e11975ddf17aa.png

当key不正确时,返回包返回

Set-Cookie: rememberMe=deleteMe:

1252524e13030d2ce994b4f9b7729b75.png

综上,测试环境的shiro key为
4AvVhmFLUs0KTA3Kprsdag==。
相比前两种方式,该方式不受网络限制的影响,并且结合并发,效率上也有一定的保证。那么就可以先枚举出对应的key,然后结合实际情况,结合对应的gadget进行深入的漏洞检测/利用了。 01b7014569bde9779183cd9f023a96ad.png扫码关注我们在这里,探索技术与热爱 d91b52ae23b7cb37e714289fe8d4caed.png 443ee212bb492b40791e40661ca00919.gif点分享 3eecc8ee8b12145cabd9895e1000f157.gif点点赞 8e7025d16f2914666436ec0f20215359.gif点在看
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值