最近本司在推行Java 安全编程(当然过一阵也会有C、C++甚至别的语言)的半强制性培训。培训的内容和练习都非常棒,但是因为某些原因仍然有一些同学没用通过考核(70%+ points)。这给了一个可以在较长一段时间内灌水的话题了

java 控制台日志 清除 java控制台清空_生命周期

。今天第一讲。

java 控制台日志 清除 java控制台清空_代码示例_02

控制敏感信息的生命周期

内存中的敏感信息可能会成为被攻击的目标。一个可以在同一个服务器上运行代码的潜在敌手(永远不要相信任何人,Admin 并不意味着他可以/需要访问应用的所有数据,也不意味着他就必须[永远]是一个好人)可能可以访问这些数据,如果,

  • 存留敏感信息的对象没用清除内容或者使用后没有被回收
  • 内存页被OS换出到硬盘(比如因为休眠)
  • 在缓冲区(比如BufferedReader)中存储敏感信息,而OS可能在缓存或者内存中只有该缓冲区的多个拷贝。
  • 使用反射不当造成存有敏感信息的对象的生命周期不可控
  • 敏感信息存留在调试信息,日志文件,环境变量里,或者被thread dump或者core dump导出。

如果使用之后不被清楚,敏感信息很可能会泄露。为了降低这种分险,程序员需要减少敏感信息的生命周期。当然,完全的防御需要操作系统和JVM的支持,比如被标记为包含敏感信息的内存页不应被交换到磁盘。

不合规代码示例1.1

这段代码从控制台读取用户名和密码并把密码存储在String对象里。密码信息将一直处于易暴露状态直到相关内存被GC回收。

class Password {
  public static void main (String args[]) throws IOException {
    Console c = System.console();
    if (c == null) {
      System.err.println("No console.");
      System.exit(1);
    }
    String username = c.readLine("Enter your user name: ");
    String password = c.readLine("Enter your password: ");
    if (!verify(username, password)) {
      throw new SecurityException("Invalid Credentials");
    }
    // ...
  }
  // Dummy verify method, 
  private static final boolean verify(String username,  String password) {
    return true;
  }
}
合规代码示例1.1

合规的代码须用 Console.readPassword() 读取密码

class Password {
  public static void main (String args[]) throws IOException {
    Console c = System.console();
    if (c == null) {
      System.err.println("No console.");
      System.exit(1);
    }
    String username = c.readLine("Enter your user name: ");
    char[] password = c.readPassword("Enter your password: ");
    if (!verify(username, password)) {
      throw new SecurityException("Invalid Credentials");
    }
    // Clear the password
    Arrays.fill(password, ' ');
  }
  // Dummy verify method
  private static final boolean verify(String username, char[] password) {
    return true;
  }
}

Console.readPassword() 方法返回 char[] 而不是String。程序员可以在使用密码之后马上清空内容,另外,使用此方法还可以密码输入时在控制台回显。

不合规代码示例1.2

以下代码用 BufferedReader 包装 InputStreamReader 来从文件读取敏感信息。

void readData() throws IOException{
  BufferedReader br = new BufferedReader(new InputStreamReader(
    new FileInputStream("file")));
  // Read from the file
  String data = br.readLine();
}

BufferedReader.readLine() 对象返回 String 对象, 它会一直留存在String 常量池里。而 BufferedReader.read(char[], int, int) 方法会把结果读到 char 数组里。当然,这仍需要程序员在使用完敏感信息之后手工清除char数组。同样地,用BufferedReader 包装 FileReader 对象时也会遇到相同的问题。

合规代码示例1.2

可以用NIO 缓冲区来读取敏感信息。用完之后马上清除。

void readData(){
  ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
  try (FileChannel rdr =
       (new FileInputStream("file")).getChannel()) {
    while (rdr.read(buffer) > 0) {
      // Do something with the buffer
      buffer.clear();
    }
  } catch (Throwable e) {
    // Handle error
  }
}

注意,手工清除缓冲区是必须的 ,因为直接缓冲区不会被GC回收.