Apache Shiro 1.2.4 远程代码执行分析与利用

admin 2021年12月4日23:43:55评论171 views字数 9627阅读32分5秒阅读模式

0x00 前言

Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理功能,可为任何应用提供安全保障 – 从命令行应用、移动应用到大型网络及企业应用。Shiro为解决应用安全的如下四要素提供了相应的API:

  • 认证 – 用户身份识别,常被称为用户“登录”;

  • 授权 – 访问控制;

  • 密码加密 – 保护或隐藏数据防止被偷窥;

  • 会话管理 – 用户相关的时间敏感的状态。

Shiro还支持一些辅助特性,如Web应用安全、单元测试和多线程,它们的存在强化了这四个要素。本文重点分析2015年11月19号报告的1.2.4版本中存在的一个反序列化导致的远程代码执行的漏洞。

0x01 分析

根据SHIRO-550(https://issues.apache.org/jira/browse/SHIRO-550)报告中的描述,默认情况下,shiro使用CookieRememberMeManager类对用户的身份信息的进行序列化,加密以及编码。因此,当系统收到一个未认证的用户的请求时,将会按照下面的过程来寻找已记住的身份信息:

  1. 获取rememberMe cookie的值

  2. Base64解码

  3. 使用AES解密

  4. 使用ObjectInputStream进行反序列化

然而,默认的AES加密的密钥却是硬编码在源码里。这就意味着,任何能够看到源代码的人都知道默认的密钥什么。一旦攻击者构造了一个恶意的对象,利用上面处理过程的反过程(序列化-AES加密-Base64编码)将恶意代码作为cookie发送至服务器端这就造成了由反序列化引起的远程代码执行的漏洞。

下面我将重点分析一下这个漏洞造成的过程。

从报告描述中可以发现这个漏洞主要是因为CookieRememberMeManager类引起的,找到github上shiro 1.2.4源码。

CookieRememberMeManager.java:


public class CookieRememberMeManager extends AbstractRememberMeManager {      ...      /**      * Base64-encodes the specified serialized byte array and sets that base64-encoded String as the cookie value.      * <p/>      * The {@code subject} instance is expected to be a {@link WebSubject} instance with an HTTP Request/Response pair      * so an HTTP cookie can be set on the outgoing response.  If it is not a {@code WebSubject} or that      * {@code WebSubject} does not have an HTTP Request/Response pair, this implementation does nothing.      *      * @param subject    the Subject for which the identity is being serialized.      * @param serialized the serialized bytes to be persisted.      */     protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {          if (!WebUtils.isHttp(subject)) {             if (log.isDebugEnabled()) {                 String msg = "Subject argument is not an HTTP-aware instance.  This is required to obtain a servlet " +                         "request and response in order to set the rememberMe cookie. Returning immediately and " +                         "ignoring rememberMe operation.";                 log.debug(msg);             }             return;         }           HttpServletRequest request = WebUtils.getHttpRequest(subject);         HttpServletResponse response = WebUtils.getHttpResponse(subject);          //base 64 encode it and store as a cookie:         String base64 = Base64.encodeToString(serialized);          Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies         Cookie cookie = new SimpleCookie(template);         cookie.setValue(base64);         cookie.saveTo(request, response);     }      ...      /**      * Returns a previously serialized identity byte array or {@code null} if the byte array could not be acquired.      * This implementation retrieves an HTTP cookie, Base64-decodes the cookie value, and returns the resulting byte      * array.      * <p/>      * The {@code SubjectContext} instance is expected to be a {@link WebSubjectContext} instance with an HTTP      * Request/Response pair so an HTTP cookie can be retrieved from the incoming request.  If it is not a      * {@code WebSubjectContext} or that {@code WebSubjectContext} does not have an HTTP Request/Response pair, this      * implementation returns {@code null}.      *      * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that      *                       is being used to construct a {@link Subject} instance.  To be used to assist with data      *                       lookup.      * @return a previously serialized identity byte array or {@code null} if the byte array could not be acquired.      */     protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) {          if (!WebUtils.isHttp(subjectContext)) {             if (log.isDebugEnabled()) {                 String msg = "SubjectContext argument is not an HTTP-aware instance.  This is required to obtain a " +                         "servlet request and response in order to retrieve the rememberMe cookie. Returning " +                         "immediately and ignoring rememberMe operation.";                 log.debug(msg);             }             return null;         }          WebSubjectContext wsc = (WebSubjectContext) subjectContext;         if (isIdentityRemoved(wsc)) {             return null;         }          HttpServletRequest request = WebUtils.getHttpRequest(wsc);         HttpServletResponse response = WebUtils.getHttpResponse(wsc);          String base64 = getCookie().readValue(request, response);         // Browsers do not always remove cookies immediately (SHIRO-183)         // ignore cookies that are scheduled for removal         if (Cookie.DELETED_COOKIE_VALUE.equals(base64)) return null;          if (base64 != null) {             base64 = ensurePadding(base64);             if (log.isTraceEnabled()) {                 log.trace("Acquired Base64 encoded identity [" + base64 + "]");             }             byte[] decoded = Base64.decode(base64);             if (log.isTraceEnabled()) {                 log.trace("Base64 decoded byte array length: " + (decoded != null ? decoded.length : 0) + " bytes.");             }             return decoded;         } else {             //no cookie set - new site visitor?             return null;         }     }

分析这个类后,我们发现CookieRememberMeManager类实际上继承了父类AbstractRememberMeManager并且正如上面描述的过程使用getRememberedSerializedIdentity方法对获取到的请求进行Base64解码返回序列化对象。

而AbstractRememberMeManager类直接将AES加密的密钥写在源码里,并且调用DefaultSerializer类来实现序列化操作

AbstractRememberMeManager.java:

public abstract class AbstractRememberMeManager implements RememberMeManager {      /**      * private inner log instance.      */     private static final Logger log = LoggerFactory.getLogger(AbstractRememberMeManager.class);      /**      * The following Base64 string was generated by auto-generating an AES Key:      * <pre>      * AesCipherService aes = new AesCipherService();      * byte[] key = aes.generateNewKey().getEncoded();      * String base64 = Base64.encodeToString(key);      * </pre>      * The value of 'base64' was copied-n-pasted here:      */     private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");... ...      /**      * Default constructor that initializes a {@link DefaultSerializer} as the {@link #getSerializer() serializer} and      * an {@link AesCipherService} as the {@link #getCipherService() cipherService}.      */     public AbstractRememberMeManager() {         this.serializer = new DefaultSerializer<PrincipalCollection>();         this.cipherService = new AesCipherService();         setCipherKey(DEFAULT_CIPHER_KEY_BYTES);     }

继续分析DefaultSerializer类,在反序列化方法deserialize里,我们看到了熟悉的readObject(),这也正是远程代码执行漏洞产生的原因。

DefaultSerializer.java:


public class DefaultSerializer<T> implements Serializer<T> {      /**      * This implementation serializes the Object by using an {@link ObjectOutputStream} backed by a      * {@link ByteArrayOutputStream}.  The {@code ByteArrayOutputStream}'s backing byte array is returned.      *      * @param o the Object to convert into a byte[] array.      * @return the bytes representing the serialized object using standard JVM serialization.      * @throws SerializationException wrapping a {@link IOException} if something goes wrong with the streams.      */     public byte[] serialize(T o) throws SerializationException {         if (o == null) {             String msg = "argument cannot be null.";             throw new IllegalArgumentException(msg);         }         ByteArrayOutputStream baos = new ByteArrayOutputStream();         BufferedOutputStream bos = new BufferedOutputStream(baos);          try {             ObjectOutputStream oos = new ObjectOutputStream(bos);             oos.writeObject(o);             oos.close();             return baos.toByteArray();         } catch (IOException e) {             String msg = "Unable to serialize object [" + o + "].  " +                     "In order for the DefaultSerializer to serialize this object, the [" + o.getClass().getName() + "] " +                     "class must implement java.io.Serializable.";             throw new SerializationException(msg, e);         }     }      /**      * This implementation deserializes the byte array using a {@link ObjectInputStream} using a source      * {@link ByteArrayInputStream} constructed with the argument byte array.      *      * @param serialized the raw data resulting from a previous {@link #serialize(Object) serialize} call.      * @return the deserialized/reconstituted object based on the given byte array      * @throws SerializationException if anything goes wrong using the streams.      */     public T deserialize(byte[] serialized) throws SerializationException {         if (serialized == null) {             String msg = "argument cannot be null.";             throw new IllegalArgumentException(msg);         }         ByteArrayInputStream bais = new ByteArrayInputStream(serialized);         BufferedInputStream bis = new BufferedInputStream(bais);         try {             ObjectInputStream ois = new ClassResolvingObjectInputStream(bis);             @SuppressWarnings({"unchecked"})             T deserialized = (T) ois.readObject();             ois.close();             return deserialized;         } catch (Exception e) {             String msg = "Unable to deserialze argument byte array.";             throw new SerializationException(msg, e);         }     }}

总结一下漏洞产生的过程如下:

  1. CookieRememberMeManager类接收到客户端的rememberMe cookie的请求

  2. 使用getRememberedSerializedIdentity方法对获取到的请求进行Base64解码返回序列化对象

  3. 调用AbstractRememberMeManager类并使用硬编码的密钥对序列化对象进行AES解密

  4. 调用DefaultSerializer类中的deserialize方法实现反序列化操作,从而造成远程代码执行

0x02 利用

2.1 搭建实验环境

首先,从Github上下载Shiro 1.2.4的源代码:

git clone https://github.com/apache/shiro.git cd shiro git checkout shiro-root-1.2.4cd samples/web

接着,编辑pom.xml文件,添加存在漏洞的jar包如下:

<!-- 设置maven的编译环境 -->      <properties>         <maven.compiler.source>1.6</maven.compiler.source>         <maven.compiler.target>1.6</maven.compiler.target>     </properties>      <dependencies>         <dependency>             <groupId>javax.servlet</groupId>             <artifactId>jstl</artifactId>             <!-- 此处需设置版本为1.2 -->             <version>1.2</version>             <scope>runtime</scope>         </dependency>         ...        <!-- 添加存在漏洞的commons-collections包 -->         <dependency>             <groupId>org.apache.commons</groupId>             <artifactId>commons-collections4</artifactId>             <version>4.0</version>         </dependency>     </dependencies>

然后,安装和配置maven并设置maven的编译环境。可参考http://shiro-user.582556.n2.nabble.com/Help-td7580772.html,新建文件”~/.m2/toolchains.xml”包含以下内容:

<toolchains>   <toolchain>     <type>jdk</type>     <provides>       <version>1.6</version>       <vendor>sun</vendor>     </provides>     <configuration>       <!-- this can be anything 1.6+, I tested with java 1.8 on a mac -->       <jdkHome>/absolute/path/to/java/home</jdkHome>     </configuration>   </toolchain></toolchains>

编译存在漏洞环境为war包:


mvn package

编译成功后,将target目录下生成的war文件部署到你的web服务器上(如:tomcat)如下图所示:

Apache Shiro 1.2.4 远程代码执行分析与利用

2.2 编写漏洞利用

根据以上的分析,我编写了如下的工具可用于检测是否存在漏洞。

Apache Shiro 1.2.4 远程代码执行分析与利用

单个网址检测:

hackUtils.py -o http://www.shiro.com/

Apache Shiro 1.2.4 远程代码执行分析与利用

批量网址检测:

hackUtils.py -o urls.txt

Apache Shiro 1.2.4 远程代码执行分析与利用

0x03 修补方案

升级到Shiro 1.2.5 或者 2.0.0 版本。

参考

https://issues.apache.org/jira/browse/SHIRO-550

作者博客:http://avfisher.win

转载自:http://avfisher.win/archives/584


Q群:242410171
论坛:http://bbs.whitecell-club.org
欢迎投稿以及咨询问题。

版权声明:该科普文章属于WhiteCellClub团队的安全小飞侠原创,转载须申明!

Apache Shiro 1.2.4 远程代码执行分析与利用

本文始发于微信公众号(WhiteCellClub):Apache Shiro 1.2.4 远程代码执行分析与利用

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月4日23:43:55
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Apache Shiro 1.2.4 远程代码执行分析与利用http://cn-sec.com/archives/489497.html

发表评论

匿名网友 填写信息