zl程序教程

您现在的位置是:首页 >  后端

当前栏目

《Java编码指南:编写安全可靠程序的75条建议》—— 指南1:限制敏感数据的生命周期

JAVA编码程序 指南 建议 编写 限制 生命周期
2023-09-11 14:17:34 时间

本节书摘来异步社区《Java编码指南:编写安全可靠程序的75条建议》一书中的第1章,第1.1节,作者:【美】Fred Long(弗雷德•朗), Dhruv Mohindra(德鲁•莫欣达), Robert C.Seacord(罗伯特 C.西科德), Dean F.Sutherland(迪恩 F.萨瑟兰), David Svoboda(大卫•斯沃博达),更多章节内容可以访问云栖社区“异步社区”公众号查看。

指南1:限制敏感数据的生命周期

内存中的敏感信息很容易受到攻击,导致泄漏。对于以下条件,不管应用程序满足哪一个,能在应用程序所在的系统上执行代码的攻击者,都能访问这些数据。

使用对象来存储敏感数据,但是内容在使用后没有被清除或没有被垃圾收集器回收。
具有可以被操作系统按需(例如,为了执行内存管理任务或者为了支持系统休眠)交换到磁盘的内存页面。
在缓冲区(如BufferedReader)中有敏感数据。这些缓冲区持有敏感数据在操作系统缓存或内存中的副本。
使用了一些反射机制来控制敏感数据的流动,这些反射技巧可能规避掉系统对该数据的生命周期限制。
通过调试信息、日志文件、环境变量、线程转储和核心转储等方式暴露敏感数据。
如果内存中包含的敏感数据在使用后没有及时被清除,那么这些数据将极有可能被泄露。为了降低敏感数据泄露的风险,程序必须尽可能地最小化敏感数据的生命周期。

完全缓解(即对内存数据万无一失的保护)需要底层操作系统和Java虚拟机的支持。例如,如果将敏感数据交换至磁盘是一个问题,那么就需要有一个禁用交换和休眠的安全操作系统。

违规代码示例
在以下违规代码示例中,程序从控制台读取用户名和密码信息,并将密码存储在一个String对象中。在垃圾收集器回收这个String对象关联的内存之前,凭证信息会一直处于暴露状态。

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, always returns true

 private static final boolean verify(String username, 

 String password) {

 return true;

合规解决方案

下面的合规解决方案使用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, always returns true
private static final boolean verify(String username,
char[] password) {
return true;
}
}`
Console.readPassword()方法允许密码以字符序列的形式返回,而不是以String对象的形式。因此,程序员可以在使用密码后立即将其从数组中清除。同时这个方法也禁止将密码输出到控制台。

违规代码示例
下面的违规代码示例使用了一个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对象的形式返回敏感数据,导致数据在不被需要后仍然会存留很久。BufferedReader.read(char[], int, int)方法可以读取和填充一个char数组,不过,它需要程序员在使用完数组中的敏感数据后,手动将其清除,否则就会导致泄漏。另外,尽管BufferedReader是用来包装FileReader对象的,但它同样也会遭遇相似的陷阱。

合规解决方案

在下面的合规解决方案中,程序使用了一个直接分配的新I/O(new I/O,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
}
}`
注意,必须手动清除缓冲数据,因为垃圾收集器不会回收直接缓冲区。

适用性
对敏感数据生命周期限制失败,导致信息泄露。

本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。


编写Java程序模拟简单的计算器。(面向对象思想) 定义名为Number的类其中有两个整型数据成员n1和n2应声明为私有,编写构造方法赋予n1和n2初始值,再为该类定义加addition()、减subtration()、乘multiplication()、除division()等公有实例方法,分别对两个成员变量执行加、减、乘、除的运算。
异步社区 异步社区(www.epubit.com)是人民邮电出版社旗下IT专业图书旗舰社区,也是国内领先的IT专业图书社区,致力于优质学习内容的出版和分享,实现了纸书电子书的同步上架,于2015年8月上线运营。公众号【异步图书】,每日赠送异步新书。