[java安全基础 01]SQL+反序列化

tomcat

Servlet

什么是servlet

Java Servlet是运行在 Web 服务器或应用服务器上的程序.它是作为来自Web浏览器或其他HTTP客户端的请求和HTTP服务器上的数据库或应用程序之间的中间层。

servlet的功能:

  • 创建并返回基于客户请求的动态HTML页面
  • 与数据库进行通信(业务逻辑处理)

如何使用servlet

servlet本身是一组接口,自定义一个类,并且实现servlet接口,这个类就具备了接受客户端请求以及做出响应的功能

image-20221123201730073

浏览器不能直接访问servlet文件,只能通过映射的方式来简介访问servlet,映射需要开发者手动配置

基于文件的配置方式

image-20221123203747966

基于注解的配置

image-20221123203807281

servlet生命周期

先查找是不是创建了,创建了就不再初始化,而是使用之前的

销毁方法是在关闭tomcat服务时关闭

首先访问的时候是没有servlet对象的,访问的时候,底层原理才通过反射创造了一个方法,通过构造方法创造一个servlet对象,然后再执行初始化操作,初始化完了,再执行service方法,而service方法可以多次调用。

Servlet的层次结构

Servlet >GenericServlet > HttpServletHTTP

GenericServlet:只保留了Service方法
HttpServletHTTP:实现了GET和POST方法的分流

请求有很多种类型,常用的有四种:

GET读取
POST保存
PUT修改
DELETE删除

真正的开发环境中基本只会用到service方法,其他方法没必要写出来,显得冗余,实际上servlet已经提供了部分实现类来帮助高效的编程和维护,所以不需要直接实现servlet接口,只需要继承HttpServlet

重写doGet和doPOST

RESTFUL

GenericServlet 实现 Servlet 接口,同时为它的子类屏蔽了不常用的方法,子类只需要重写service方法即可。HttpServlet继承GenericServlet,根据请求类型进行分发处理,GET进入doGET方法, POST进入 doPOST方法。开发者自定义的Servlet类只需要继承 HttpServlet即可,重新doGET 和doPOST

JSP

jsp本质上就是一个servlet,jsp主要负责用户交互,将最终的界面呈现给用户,html+js+css+ava的混合文件

<%--这里可以穿插java
这里能够解决页面展示
jsp在项目中会转换为一个.jsp.java文件
--%>

image-20221123211416862

单纯从开发的角度看,jsp就是在HTML中嵌入java程序,具体的嵌入方式由3种:

  1. jsp脚本,执行java逻辑代码<% java代码%>
  2. jsp声明:定义java方法

<%!

声明java 方法

%>

  1. jsp表达式:把java对象直接输出到HTML页面中

<%= java变量%>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <%! public String test(){
    return "this is test function()";
  }%>
  <%! String str = test();%>
  <%= str%>
  </body>
<%--这里可以穿插java
这里能够解决页面展示
jsp在项目中会转换为一个.jsp.java文件
--%>
</html>

jsp内置对象

request:表示一次请求,HttpServletRequest
response:表示一次响应,HttpServletResponse
pageContext:页面上下文,获取页面信息,PageConetext
session:表示一次会话,保存用户信息,httpSession
application:表示当前web应用,全局对象,保存所有用户共享信息,ServletContext
config:当前JSP对应的Serlvet的ServletConfig对象,获取当前Servlet的信息
out:向浏览器输出数据,JspWriter
page:当前JSP对应的Servlet对象,Servlet
exception:表示JSP页面发生的异常,exception

jsp本质是一个servlet,servlet可以捕获异常,servlet就是java代码嘛。
jsp不是一个java程序,它通过exception来捕获异常 

常用的是:request,response,session,application,pageContext

request常用方法

1. String getParameter(String key)获取用户端传来的参数
2. Void setAtteribute(String keyObject vale)通过键值对的形式保存数据
3. Object getAttribute(String key)通过key取出value
4. RequestDispatcher getRequestDispatcher(String path)返回一个RequestDispatcher对象,该对象的forward方法用于请求转发
5. String[] getParameterValues()获取客户端传来的多个同名参数
6. void setCharacterEncoding(String charset)指定每个请求的编码

dispach分配机制

在同一个请求在服务器的传递(url没有变化),也叫做服务器跳转,和重定向不一样

dispach1.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>dispach</title>
    <h1>dispach2</h1>
</head>
<body>
<% String name = (String) request.getAttribute("user");
out.write(name);%>
</body>
</html>
	
dispach2.jsp
<%--
  Created by IntelliJ IDEA.
  User: 40447
  Date: 2022-11-23
  Time: 21:47
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>dispach1</title>
</head>
<body>
<% String name = request.getParameter("name");
    request.setAttribute("user",name);
    request.getRequestDispatcher("./Dispach2.jsp").forward(request,response);

%>
</body>
</html>

request重定向

sendRedirect(String path)重定向页面直接的跳转

转发getRequestDispatcher和重定向sendRedirect的区别:

转发是将同一个请求传给下一个页面,重定向是创建一个新的请求传给下一个页面,之前的请求结束生命周期。·

转发:同一个请求在服务器之间传递,地址栏不变,也叫服务器跳转。

重定向:由客户端发送一次新的请求来访问跳转后的目标资源,地址栏改变,也叫客户端跳转。

如果两个页面之间需要通过request 来传值,则必须使用转发,不能使用重定向。用户登录,如果用户名和密码正确,则跳转到首页(转发),并且展示用户名,否则重新回到登陆页面(重定向)

dispach1.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>dispach1</title>
</head>
<body>
<% String name = request.getParameter("name");
    request.setAttribute("user",name);
//    request.getRequestDispatcher("./Dispach2.jsp").forward(request,response);
    response.sendRedirect("Dispach2.jsp");
%>
</body>
</html>
这样写是会报错的,但是由于报错也可以知道,这个jsp是会转化为java

转发,同一个请求在服务器中传递,可以数据传递,但是重定向是客户端发起一次新的请求,url会变化

jsp内置对象作用域

page作用域:对应内置对象时pageContext
	只在当前作用域有效
request作用域:对应内置对象的request
	在一次请求内有效
session作用域:对应内置对象的session
	一次session中有效,一次session有多个request
application作用域:对应的内置对象application
	application最大的,即便关闭了重新进浏览器,也是保存着的
网站访问量统计
<%
Inter count = (Integer)application.getAttribute("count");
if(count == null){
count = 1;
application.setAttribute("count",count);
}else{
count++;
application.setAttribute("count",count);
}
%>

springboot

什么是spring

spring回一个2003年兴起的一个轻量级java开发框架

spring是为了 解决企业级应用开发的复杂性而创建的,简化开发

spring如何简化java开发

为了降低java开发的复杂性,spring采用了以下4种关键策略

  1. 基于pojo的清零及和最小侵入性编程,所有东西都是bean
  2. 通过IOC,依赖注入(DI)和面向接口实现松耦合
  3. 基于切面(AOP)和管理进行声明式编程
  4. 通过切面和末班减少样式代码,RedisTemplate,XXXtemplate

什么是springboot

Spring框架是Java平台上的一种开源应用框架.提供具有控制反转特性的容器。

Spring Boot基于Spring 开发, Spirng Boot本身并不提供 Spring框架的核心特性以及扩展功能.只是用于快速、敏捷地开发新一代基于Spring框架的应用程序。

约定大于配置的核心思想

Spring Boot的主要优点:

  • 为所有Spring开发者更快的入门

  • 开箱即用,提供各种默认配翟来简化项目配置

  • 内嵌式容器简化Web项目

  • 没有冗余代码生成和XML配置的要求

简单来说,springboot就是易于开发的spring

项目结构分析:
通过上面步骤完成了基础项目的创建。就会自动生成以下文件。
1、程序的主启动类
2、一个application.properties配置文件
3、一个测试类
4、一个pom.xml

运行原理

pom.xml

image-20221124083303129

image-20221124083338473

主启动类

//SpringBootApplication来标注一个主程序类
//说明这是一个SpringBoot应用
@SpringBootApplication
public class Springbootdemo01Application {
//主启动类
    public static void main(String[] args) {
        //这是启动了一个服务,而不是启动了一个方法
        SpringApplication.run(Springbootdemo01Application.class, args);
    }
}

四个原注解

@Target({ElementType.TYPE}) //ElementType.TYPE 表示注解可以声明在入口之前
@Retention(RetentionPolicy.RUNTIME) //定义注解的生命周期,此处是指,程序不管一直活
@Documented //标志给java的工具记录
@Inherited //标志子类继承
@SpringBootConfiguration  //springboot的配置类
	最终通过组件定义配置类
@EnableAutoConfiguration  // 自动配置类
        
@Import({AutoConfigurationImportSelector.class}) // 自动配置选择器

image-20221124085154009

image-20221124085213728

sql注入

JDBC介绍

java database connectivity是一个独立于特定数据库的管理系统,通用的SQL主句库存取和凑在哦公共接口

定义了一组标准,为访问不同数据库提供了同意的途径

JDBC API

内容:供开发者调用的接口

  • DriverManager接口
  • Connection接口
  • Statement接口
  • ResultSet接口

DriverManager类

作用:驱动管理器,主要负责管理驱动

DriverManager负责驱动程序管理,数据库驱动则是为了应用程序服务的,所以DriverManager的重要任务就是提供连接的获取

在调用getConnection方法时,DriverManager会试着从初始化时加载到哪些驱动程序以及使用于当前applet或应用程序相同的类加载器,显示加载的那些驱动程序中查找合适的驱动程序

Class.forName("com.mysql.cj.jdbc.Driver")

image-20221124094523282

Connection connection = DriverManager.getConnection(url,Sqluser,Sqlpass)

image-20221124094614343

ResultSet resultset = statement.executeQuery(sql);

大乌鱼了,不知道为什么SQL连接不上了,配置2个多小时,解决方法:

照着这个新起了一个项目https://blog.csdn.net/yc11223344/article/details/123226302,然后导入servlet.jar,并且把mysql的那个jar也放到了这里的bin。

大乌鱼了,不知道为什么SQL连接上了,配置了1min,解决方法,把什么mevan包删除完,重新导入tomcat/bin/中的servlet.jar和mysql的那个jar(随便放哪都行)

package com.v1nt.sqldemo;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.*;

@WebServlet("/sqldemo")
public class sqltest extends HttpServlet {

    //根据参数名获取参数
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        String user = req.getParameter("user");
        String pass = req.getParameter("pass");

        try {
//            load driver

            Class.forName("com.mysql.cj.jdbc.Driver");
            resp.getWriter().write(user+"<br>");

            String url = "jdbc:mysql://localhost:3306/security?&useSSL=false&serverTimezone=UTC";
            String Sqluser = "root";
            String Sqlpass = "123456";

//            获取数据库一次链接
            Connection connection = DriverManager.getConnection(url,Sqluser,Sqlpass);

//            String sql = "select * from users where username = '" + user + "' " +" and password = '" + pass+"';";
            String sql = "select * from users where username = ? and password = ?";
//            System.out.println(sql);
            resp.getWriter().write(sql+"<br/>");
            //修复代码
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1,user);
            preparedStatement.setString(2,pass);
            ResultSet resultSet = preparedStatement.executeQuery();
            //执行SQL
//            Statement statement = connection.createStatement();
//            ResultSet resultSet = statement.executeQuery(sql);
            if(resultSet.next()){
                resp.getWriter().write(resultSet.getString("username"));
                resp.getWriter().write("login secuss");
            }else {
                resp.getWriter().write("login failed");
            }

        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }

    public sqltest()throws SQLException{

    }
}

预编译:

sQL语句在程序运行前已经进行了预编译,在程序运行时第一次操作数据库之前,sQL语句已经被数据库分析编译和优化,对应的执行计划也会缓存下来并允许数据库已参数化的形式进行查询,当运行时动态地把参数传给PreprareStatement时,即便参数里有敏感字符如or '1=1'也数据库会作为一个参数一个字段的属性值来处。理而不会作为一个sQL指令,如此,就起到了sQL注入的作用了

image-20221124180417261

XXE

XXE(XML External Entity Injection)全称为XML外部实体注入当允许引用外部实体且存在输入点时.恶意攻击者即可构造恶意内容访问服务器资源等操作

xml基础知识

XML用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型.是—种允许用户对自己的标记语言进行定义的源语言。此. XML文档结构包括xML声明、DTD文档类型定义(可选)、文档元素

image-20221124180728967

image-20221124180756363

image-20221124195944529

反序列化

java反射

如果是看函数什么的,可以看第二个文章,那里全是代码,这里光是看定义确实难顶。

Java安全很大一部分可以从反序列化漏洞说起,而反序列化漏洞又可以从反射说起。

JAVA反射机制是在运行状态中,对于任意一个类.都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。反射就是把java类中的各种成分映射成一个个的Java对象(体现了java的万物皆对象的思想)

为什么需要反射Java

中的两种编译类型

  • 静态编译:在编译时确定类型,绑定对象即通过。

  • 动态编译:运行时确定类型,绑定对象。动态编译最大限度地发挥了Java的灵活性,体现了多态的应用,可以减低类之间的耦合性。

Java反射是Java被视为动态语言的一个关键性质。

换句话说.程序在运行时的行为都是固定的。如果想在运行时改变.就需要反射这东西了;

反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

如何获取反射

1.获取反射中的class对象(java.lang.Class)

2.通过反射创建类对象

3.通过反射获取类属性、方法、构造器

1. 获取反射中的Class对象

在反射中,要获取一个类或调用一个类的方法,我们首先需要获取到该类的Class对象。

Class类对象是:

对一个声明了的类的类对象
举例:
public class A{
	properties
	methods
}
A类的Class类对象,记录了A类型的信息,一个类(不是实例对象)只有一个Class对象;

大概可以理解成:
A是定义了"水",A的实例定义了是什么水,例如"可乐,雪碧,咖啡"
Class类对象,是记录了A类型的信息并且可以获取A中的属性方法,例如A是什么,A可以怎么用;

获取Class类对象有三个方法

  • 使用Class.forName静态方法: Class clz = cloass.forName("java.lang.String");

  • 使用.class方法 Class clz = String.class;

  • 使用类对象的getClass()方法 String str = new String("Hello");Class clz =str.getClass()

forName有两个函数重载:

Class forName(String name)
Class forName(String,**boolean**initialize,ClassLoader loader)

一个有趣的点:使用功能.class来创建Class对象的引用时,不会自动初始化该Class对象,使用forName()会自动初始化该Class对象;

例子:

public class TrainPrint {
    {
        System.out.println("Enpty block initial %s\n",this.getClass());
    }
    static {
        System.out.println("Static initial %s\n",TrainPrint.class);
    }
    public TrainPrint(){
        System.out.println("Intital %s\n",this.getClass());
    }
}

加载一个类的时候,首先执行static代码,因为执行的时候会先执行静态方法,接着执行空代码块(又叫,构造代码块,初始化块),最后才会执行构造方法;

image-20221124225352866

2.通过反射创建类的对象

两种方式
通过Class对象的newInstance()方法

Class clz = Apple.class;
Apple apple = (Apple)clz.newInstance();

通过Constructor对象的newInstance()方法

Class clz = Apple.class;
Constructor constructor = clz.getConstructot();
Apple apple = (Apple)constructor.newInstance();

实例

Person.java

package com.javaser.base;

public class Person {
    public String name;
    public int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

package com.javaser.base;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ReflectionTest {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Person ksPerson = new Person("狂三", 18);
        Class<? extends Person> perCls = ksPerson.getClass();
        // 反射就是操作Class

        // 从原型class里实现实例化对象
//        Person per = perCls.newInstance();
        // 有参数的实例化需要使用构造器
        Constructor<? extends Person> personCon = perCls.getConstructor(String.class, int.class);
        Person sxPerson = personCon.newInstance("十香", 18);
        System.out.println(sxPerson);
        System.out.println(ksPerson);
    }
}

注意:通过CONstructor对象创建类对象可以选择特定构造方法,而通过Class对象则只能使用默认的无参的公有构造方法。

问:如果构造方法私有,如何解决?

在类对象中,有getDeclaremethod类似,带declare的,用他们来进行定义来获取
constructor.setAccessable(true);

3.通过放射获取类属性、方法、构造器

getMethod和invoke方法

  • Method Class.getMethod(String name, Class<?>... parameterTypes)的作用是获得对象所声明的公开方法

该方法的第一个参数name是要获得方法的名字,第二个参数parameterTypes是按声明顺序标识该方法形参类型。

标识该方法形参类型。

public Object invoke(Object obj, Object... args)是Method类的方法

该方法的参数一个是Object类型,也就是调用该方法的对象,第二个参数是一个可变参数类型

Class clazz = Class.forName("java.lang.Runtime");/获取反射中的class对象
Method getRuntimeMethod = clazz.getMethod("getRuntime");//获取反射类的方法
Object runtime = getRuntimeMethod.invoke(clazz);//调用方法获取runtime类

getDeclared方法系列的放射

与普通的getMethod,getConstructor区别是

  • getMethod系列方法获取的是当前类中所有公共发发,包括父类继承的方法
  • getDeclaredMethod系列方法获取的是当期那类中"声明"的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了
Class clazz = Class.forName("java.lang.Runtime");
Constructor m = clazz.getDeclaredConstructor();
m.setAccessible(true);
clazz.getMethod("exec",String.class).invoke(m.newInstance(),"calc.exec");

注意:这里使用了一个方法setAccessible,这个是必须的。我们获取到一个私有方法后,必须使用setAccessible修改它的作用域,否在仍然不可调用

java 反序列化

认识Java序列化与反序列化

  • 序列化就是把对象的状态信息转换为字节序列(即可以存储或传输的形式)过程

  • 反序列化即逆过程,由字节流还原成对象

注: 字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序

为什么需要java反序列化?

把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中

在网络上传送对象的字节序列

为什么会产生安全问题?

只要服务端反序列化数据,客户端传递类的readObject中代码就会自动执行,基于攻击者在服务器上运行代码的能力

可能的入口形式:

  1. 入口类的readObject方法直接调用危险方法(少见)
  2. 入口类参数包含可控类,该类有危险方法,readObject时调用
  3. 入口类参数中包含可控类,该类调用其他有危险函数的类

比如类型定义为Object调用equals、hashcode、toString

重点:相同类型,同名函数

  1. 构造函数/静态代码块等类加载时隐式执行

共同利用条件:

继承serializeable
入口类(重写readObject参数类型宽泛,最好jdk自带)
调用链
执行类

满足这些的有HashMap等

HashMap

  1. 继承序列化:有可能会有需求

  2. 参数类型宽泛:有键值对,键值对类型宽泛

  3. 重写readObject方法:为什么需要重写:https://juejin.cn/post/6844903954774491144#heading-S

    B站的一个视频讲解

    HashMap的底层实现原理

    因为:HashMap为了保证键的唯一性,它就要计算键的hashcode(hash值),如果键是对象的话,在不同的JVM里面,不同的机器上,计算的hashcode是不一样的。所以需要将对象拆开,把每一个元素都拿出来单独计算,所以必须重新实现这个readObject方法。

    这也是简单说法,具体还是得看看底层

示例

创建一个person类,实现Serializable接口

public class Person implements Serializable{
    private static final long serialVersionUID = -ID;
    private int age;
    public String name;
    Privaete String sex;
    
    get...
    set...
}
//Serializable接口作用只是用来表示这个类需要进行序列化,并且Serializable接口没有提供任何方法。
//相当于用Serializable标识一下

image-20221125114912579

image-20221125115628420

testMain.java

package com.v1nt.serializerDemo;

import java.io.*;

public class serializerTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
//        SerializerPerson();
        DeserializerPerson();
    }

    public static void SerializerPerson() throws IOException {
        Person tom = new Person(18,"TOM","男");
        ObjectOutputStream objectoutputStream = new ObjectOutputStream(new FileOutputStream("./TEST.txt"));
        objectoutputStream.writeObject(tom);
        System.out.println("person对象序列化成功");
        objectoutputStream.close();
    }
    public static void DeserializerPerson() throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("./TEST.txt"));
        Person person = (Person) objectInputStream.readObject();
        System.out.println(person);
        System.out.println(person.getName());
        objectInputStream.close();
    }
}

Person.java

package com.v1nt.serializerDemo;

import java.io.Serializable;

public class Person implements Serializable {
    public static final long serialVersionUID  = -580L;
    public String name;
    private Integer age;
    private String sex;

    public Person( Integer age,String name, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

开始调试

下断点,调试,强制步入

image-20221125215704805

F7

image-20221125215735514

image-20221125215810857

F7

image-20221125215835341

F8

image-20221126081748449

F7,选第二个方法readOrdinaryObject,然后F8,F7看一看

image-20221126081926935

进入之后,F8,看到一个readNoProxyDesc,F7跟入看看

image-20221126082242713

image-20221126082333586

F8执行到这一句,有一个resolveClass方法,跟入看看

image-20221126082450464

从名字看,是一个决绝class内容的,这里有一个forName获取一个全类名

image-20221126082708150

然后跟到上面就行了,然后返回daoreadClassDesc

image-20221126082955993

可以看到这里有一个newInstance方法,这里就已经在创建对象了

image-20221126082940750

F8到图示,最后会调用readSerialData还原出属性之中的值,F7跟如看看

image-20221126083127754

F7跟入readSerialData后,F8走几步,走到defaultFiles,再F7跟入函数

image-20221126083650563

卧槽,发现更错代码了,该跟反序列化Person的,跟成了urldns的,是说怎么全是hashmap

不过前面步骤一样,重新走一遍就好

image-20221126084707564

F7走到此处

image-20221126084753042

这一步是对获取的到fields进行赋值,多走几步F8,好像得先获取一个整型数据,所以又走了很多步骤,但最后都会回到这里

image-20221126090759469

最后的setObjectfileValues是一个将Person具体还原对象

这就算是勉强跟完了反序列化链,这里就只是跟一下是怎么样的一个流程,在哪里赋值扫盲的。

补充

1. 静态成员不能被序列化
	序列化是针对对象属性的,静态成员变量属于类的
2.transient标识的不会被序列化
3.反序列化的机制就相当于在服务器上引入了一个执行代码的能力
4. MAP,HashMap

URLDNS链

反序列化安全问题

如果java应用对用户输出,即不可信数据做了反序列化处理,那么攻击者可以通过构造恶意输入,让反序列化产生非预期的对象,非预期的对象在产生过程中就有可能带来任意代码执行

Ysoserial工具

https://github.com/frohoff/ysoserial

ysoserial集合了各种java反序列化payload,它可以让用户根据自己选择的利用链,生成反序列化利用数据,通过这些数据发送给目标,从而执行用户预先定义的命令

URLDNS链

该链特点

  • 使用java内置的类构造,对第三方呢库没有依赖
  • 在目标没有回显的时候,能通过DNS请求得知是否存在反序列化漏洞

使用ysoserial工具生成payload

java1.8 -jar ysoserial.jar URLDNS "http://xxx.dnslog.cn">urldns.txt

编写测试类执行反序列化:

package com.v1nt.urldnsDemo;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;

public class urldnsTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\javastudy_pro\\tools\\ysoserial\\poc.txt"));
        Object oj = objectInputStream.readObject();
        System.out.println(oj);
    }
}

分析ysoserial工作原理

生成Paylod的类在GeneratePyload.java

在idea的project struction中可以导入jar包,然后在那里看就行

image-20221125154646056

getObject方法里就是gadget链的生成,这里返回的ht对象就是构造完成的恶意对象。

在URLDNS.java中有一个HashMap 定义的ht

image-20221126105700151

考虑到,这是反序列化触发的,所以在HashMap中应该是有个readObject方法,于是点进去看看

image-20221126114210619

开始调试

先不急,首先看到

image-20221126102744400

URLDNS.JAVA文件

还是下断点,然后强制步入

image-20221126100436699

走到此处获取协议,协议为http

image-20221126122800288

F8走几步,此处是获取主机地址

image-20221126122853567

F7步入方法看看

image-20221126122917852

F7继续步入

image-20221126123010699

F8走几步

image-20221126123108942

走到此处,会对域名进行解析返回域名(时间有点久了,估计不行了),然后dns链会接收到请求。(我就在此处下个断点,重新获取一个url),到这里了,也就算是跟完了UELdns

image-20221126123408297

我以为是连接寄了,原来是还得F8走一步

可以在DNSLOG看到:

image-20221126123456049

其它

有个关键字叫transient,被它修饰的不会参与序列化与反序列化的过程。

在URLDNS.JAVA代码中,就有被它修饰的。这样可以避免在进行序列化和反序列化中访问请求连接。

这个修饰只是在生成payload的时候有修饰

image-20221126175930788

重写了Handler,和其它的方法,都返回了null,或者就是空着不管。免得造成多的请求。

这里点点找找Handler,和它的函数

image-20221126173922553

然后进入URL.java,找到hashCode()

image-20221126175510627

看到是一个handler对象,在看handler的定义

image-20221126175541044

看到了transient,说明这里不参与反序列化

URLDNS反序列化链总结

我们使用工具生成的DNSLOG的poc,所以在导包之后看的,就应该是DNSLOG.JAVA

HashMap.readObject()
HashMap.putVal()
HashMap.hash()
URLhashCode()
URLStreamHandler.hashCode()
URLStreamHandler.getHostAddress()

关键字:transient:不序列化与反序列化
posted @ 2022-12-12 22:15  upstream_yu  阅读(111)  评论(0编辑  收藏  举报