java安全编码指南之:对象构建操作

所属分类: 软件编程 / java 阅读数: 239
收藏 0 赞 0 分享

简介

程序员肯定是不缺对象的,因为随时都可以构建一个,对象多了肯定会出现点安全问题,一起来看看在java的对象构建中怎么保证对象的安全性吧。

构造函数的异常

考虑下面的一个例子:

public class SensitiveOperation {

  public SensitiveOperation(){
    if(!doSecurityCheck()){
      throw new SecurityException("Security check failed!");
    }
  }

  //Security check return false
  private boolean doSecurityCheck(){
    return false;
  }

  public void storeMoney(){
    System.out.println("Store 1000000 RMB!");
  }
}

上面的例子中,我们在构造函数中做了一个securityCheck,因为这个securityCheck返回的值是false,所以会抛出SecurityException。

看下调用的例子:

  public static void main(String[] args) {
    SensitiveOperation sensitiveOperation = new SensitiveOperation();
    sensitiveOperation.storeMoney();
  }

这个调用会抛出下面的异常:

Exception in thread "main" java.lang.SecurityException: Security check failed!
 at com.flydean.SensitiveOperation.<init>(SensitiveOperation.java:11)
 at com.flydean.SensitiveUsage.main(SensitiveUsage.java:10)

那么问题来了,上面的这个class是不是安全的呢?

Finalizer Attack

上面的class不是final的,所以我们可以构造一个class去继承它。然后考虑这样一个问题,当构造函数抛出异常之后,会执行什么操作呢?

如果该对象已经被构建了,那么这个对象在GC的时候需要执行finalize方法。那么我们是不是可以在finalize方法中绕过安全检查呢?

看下面的例子:

public class SensitiveOperationFinalizer extends SensitiveOperation{

  public SensitiveOperationFinalizer(){
  }

  @Override
  protected void finalize() {
    System.out.println("We can still do store Money action!");
    this.storeMoney();
    System.exit(0);
  }
}

上的例子中,我们继承了SensitiveOperation,并且实现了finalize方法,在finalize中,我们调用了storeMoney。看下运行的代码:

  public void testFinalizer() throws InterruptedException {
    try {
    SensitiveOperation sensitiveOperation = new SensitiveOperationFinalizer();
      sensitiveOperation.storeMoney();
    }catch (Exception e){
      System.out.println(e.getMessage());
    }
    System.gc();
    Thread.sleep(10000);
  }

运行结果:

Security check failed!
We can still do store Money action!
Store 1000000 RMB!

可以看到,虽然我们构造函数抛出了异常,但是storeMoney的操作还是被执行了!

这个操作就叫做Finalizer Attack。

解决Finalizer Attack

怎么解决这个构造函数抛出异常的问题呢?这里给大家介绍几种解决方法。

使用final class

如果使用final class,那么类是不能够被继承的,问题自然就解决了。

public final class SensitiveOperationFinal {

  public SensitiveOperationFinal(){
    if(!doSecurityCheck()){
      throw new SecurityException("Security check failed!");
    }
  }

  //Security check return false
  private boolean doSecurityCheck(){
    return false;
  }

  public void storeMoney(){
    System.out.println("Store 1000000 RMB!");
  }
}

使用final finalize方法

因为子类想要重写finalize方法,如果我们的父类中finalize方法定义为final,也可以解决这个问题。

public final class SensitiveOperationFinal {

  public SensitiveOperationFinal(){
    if(!doSecurityCheck()){
      throw new SecurityException("Security check failed!");
    }
  }

  //Security check return false
  private boolean doSecurityCheck(){
    return false;
  }

  public void storeMoney(){
    System.out.println("Store 1000000 RMB!");
  }
  
  final protected void finalize() {
  }
}

使用flag变量

我们可以在对象构建完毕的时候设置一个flag变量,然后在每次安全操作的时候都去判断一下这个flag变量,这样也可以避免之前提到的问题:

public class SensitiveOperationFlag {

  private volatile boolean flag= false;

  public SensitiveOperationFlag(){
    if(!doSecurityCheck()){
      throw new SecurityException("Security check failed!");
    }
    flag=true;
  }

  //Security check return false
  private boolean doSecurityCheck(){
    return false;
  }

  public void storeMoney(){
    if(!flag){
      System.out.println("Object is not initiated yet!");
      return;
    }
    System.out.println("Store 1000000 RMB!");
  }
}

注意,这里flag需要设置为volatile,只有这样才能保证构造函数在flag设置之前执行。也就是说需要保证happens-before特性。

使用this或者super

在JDK6或者更高版本中,如果对象的构造函数在java.lang.Object构造函数退出之前引发异常,则JVM将不会执行该对象的finalize方法。

因为Java确保java.lang.Object构造函数在任何构造函数的第一条语句之上或之前执行。如果构造函数中的第一个语句是对超类的构造函数或同一个类中的另一个构造函数的调用,则java.lang.Object构造函数将在该调用中的某个位置执行。否则,Java将在该构造函数的代码中的任何一个执行之前执行超类的默认构造函数,并且将通过隐式调用执行java.lang.Object构造函数。

也就是说如果异常发生在构造函数中的第一条this或者super中的时候,JVM将不会调用对象的finalize方法:

public class SensitiveOperationThis {

  public SensitiveOperationThis(){
    this(doSecurityCheck());
  }

  private SensitiveOperationThis(boolean secure) {
  }

  //Security check return false
  private static boolean doSecurityCheck(){
     throw new SecurityException("Security check failed!");
  }

  public void storeMoney(){
    System.out.println("Store 1000000 RMB!");
  }
}

本文的例子:

learn-java-base-9-to-20/tree/master/security

以上这篇java安全编码指南之:对象构建操作就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

更多精彩内容其他人还在看

Java实现一个简单的文件上传案例示例代码

这篇文章主要介绍了Java实现一个简单的文件上传案例,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

详解在spring中使用JdbcTemplate操作数据库的几种方式

这篇文章主要介绍了详解在spring中使用JdbcTemplate操作数据库的几种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

java中的switch case语句使用详解

这篇文章主要介绍了java中的switch case语句使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

深入理解Java 线程池

这篇文章主要介绍了Java 线程池的相关资料,文中讲解非常细致,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
收藏 0 赞 0 分享

详解mybatis #{}和${}的区别、传参、基本语法

这篇文章主要介绍了mybatis #{}和${}的区别、传参、基本语法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

MyBatis 中 ${}和 #{}的正确使用方法(千万不要乱用)

这篇文章主要介绍了MyBatis 中 ${}和 #{}的正确使用方法,本文给大家提到了MyBatis 中 ${}和 #{}的区别,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

详解Mybatis中的 ${} 和 #{}区别与用法

这篇文章主要介绍了Mybatis中的 ${} 和 #{}区别与用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

Maven安装过程图文详解

这篇文章主要介绍了Maven安装过程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
收藏 0 赞 0 分享

SpringBoot登录拦截配置详解(实测可用)

这篇文章主要介绍了SpringBoot登录拦截配置详解(实测可用),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

JAVA 内存溢出案例汇总

这篇文章主要介绍了JAVA 内存溢出案例的汇总,文中讲解非常细致,帮助各位工作学习时避免内存溢出,感兴趣的朋友可以了解下
收藏 0 赞 0 分享
查看更多