《Java编码指南:编写安全可靠程序的75条建议》—— 指南2:不要在客户端存储未经加密的敏感数据

简介: 在使用客户端服务器模式构建应用程序时,如果客户端容易受到攻击,那么将用户凭证信息这样的敏感数据存储在客户端,会导致这些信息在未经授权的情况下被泄露。

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

指南2:不要在客户端存储未经加密的敏感数据

在使用客户端服务器模式构建应用程序时,如果客户端容易受到攻击,那么将用户凭证信息这样的敏感数据存储在客户端,会导致这些信息在未经授权的情况下被泄露。

对于Web应用程序,最常见的缓解这个问题的方法,就是为客户端提供一个cookie,将敏感信息存储在服务器上。这些cookie由Web服务器创建,只存储在客户端上,一段时间以后就会失效。当客户端重新连接至服务器时,必须提供相应的cookie,服务器用它来识别客户端,然后为识别后的客户端提供敏感信息。

cookie并不能防止敏感信息免受跨站脚本(Cross-Site Scripting,XSS)攻击。只要攻击者通过XSS攻击获得cookie,或者通过攻击客户端直接获得cookie,就可以使用这些cookie从服务器上获得敏感信息。如果一个会话超出限制时间(如15分钟)后,服务器端随即让该会话失效,那么这样的风险是可以被时间控制的。

一个cookie通常是一个短字符串。如果它包含敏感信息,那么这些信息应该被加密。敏感信息包括用户名、密码、信用卡号码、社会保险号以及任何可识别该用户的个人信息。更多关于密码管理的细节,参见指南13。更多有关保护存有敏感信息的内存的信息,参见指南1。

违规代码示例
在下面的违规代码示例中,登录程序将用户名和密码存储至cookie中,用以识别该用户并允许后续请求。

protected void doPost(HttpServletRequest request, 
  HttpServletResponse response) {

 // Validate input (omitted)

 String username = request.getParameter("username");
 char[] password = 
  request.getParameter("password").toCharArray();
 boolean rememberMe = 
  Boolean.valueOf(request.getParameter("rememberme"));

 LoginService loginService = new LoginServiceImpl();

 if (rememberMe) {
  if (request.getCookies()[0] != null && 
   request.getCookies()[0].getValue() != null) {
   String[] value = 
    request.getCookies()[0].getValue().split(";");

   if (!loginService.isUserValid(value[0], 
     value[1].toCharArray())) {
    // Set error and return
   } else {
    // Forward to welcome page
   }
  } else {
   boolean validated = 
    loginService.isUserValid(username, password);
   if (validated) {
    Cookie loginCookie = new Cookie("rememberme", username + 
             ";" + new String(password));
    response.addCookie(loginCookie);
    // ... forward to welcome page
   } else {
    // Set error and return
   }
  }
 } else {
  // No remember-me functionality selected
  // Proceed with regular authentication;
  // if it fails set error and return
 }
 Arrays.fill(password, ' ');
}```
不管怎样,用以上述方式实现“记住我”的功能,是不安全的,因为攻击者只要能访问客户端机器就能直接在客户端上获得这些敏感信息。这段代码也违背了指南13。

合规解决方案(会话)
在下面的合规解决方案中,程序是通过在cookie中只存储一个用户名和一个安全随机字符串来实现“记住我”功能的。同时程序还使用了HttpSession来管理会话状态。

protected void doPost(HttpServletRequest request, HttpServletResponse response) {

 // Validate input (omitted)

 String username = request.getParameter("username");
 char[] password =
  request.getParameter("password").toCharArray();
 boolean rememberMe = 
  Boolean.valueOf(request.getParameter("rememberme"));
 LoginService loginService = new LoginServiceImpl();
 boolean validated = false;
 if (rememberMe) {
  if (request.getCookies()[0] != null &&
    request.getCookies()[0].getValue() != null) {
   String[] value =
    request.getCookies()[0].getValue().split(";");
   if (value.length != 2) {
    // Set error and return
   }

   if (!loginService.mappingExists(value[0], value[1])) {
    // (username, random) pair is checked
    // Set error and return
   }
  } else {
   validated = loginService.isUserValid(username, password);

   if (!validated) {
    // Set error and return
   }
  }

  String newRandom = loginService.getRandomString();
  // Reset the random every time
  loginService.mapUserForRememberMe(username, newRandom);
  HttpSession session = request.getSession();
  session.invalidate();
  session = request.getSession(true);
  // Set session timeout to 15 minutes
  session.setMaxInactiveInterval(60 * 15);
  // Store user attribute and a random attribute
  // in session scope
  session.setAttribute("user", loginService.getUsername());
  Cookie loginCookie =
   new Cookie("rememberme", username + ";" + newRandom);
  response.addCookie(loginCookie);
  // ... forward to welcome page
 } else { // No remember-me functionality selected
  // ... authenticate using isUserValid(),
  // and if failed, set error
 }
 Arrays.fill(password, ' ');
}`
服务器维护了一个用户名与安全随机字符串之间的映射。当用户选择“记住我”时,doPost()方法会检查请求提供的cookie是否包含一个有效的用户名和随机字符串对。如果在映射中找到了这个用户名和随机字符串对,那么服务器会对这个用户进行身份验证,并在验证成功的情况下跳转到该用户的欢迎页面。如果没有找到,服务器会向客户端返回一个错误。如果用户选择了“记住我”,但客户端未能提供一个有效的cookie,那么服务器会要求用户使用他的凭据进行身份验证。如果身份验证成功,服务器会给客户端发送一个新的具有“记住我”特征的cookie。

上述解决方案,通过在验证完毕后立即令当前会话失效并重新创建一个新的会话,避免了“会话固化”攻击;另外,通过将客户端访问会话超时时间设置为15分钟,也减小了攻击者进行“会话劫持”攻击的机会。

适用性
在客户端存储未经加密的敏感信息,导致信息泄露给客户端的攻击者。

相关文章
|
Java
Java开发实现图片URL地址检验,如何编码?
【10月更文挑战第14天】Java开发实现图片URL地址检验,如何编码?
678 4
|
11月前
|
存储 算法 前端开发
如何使用 Vuex 插件来实现状态的加密存储?
如何使用 Vuex 插件来实现状态的加密存储?
426 124
|
存储 监控 前端开发
如何实现前端框架数据驱动方式的数据加密存储?
实现前端框架数据驱动方式的数据加密存储需要综合考虑多个因素,包括加密算法的选择、密钥管理、传输安全、服务器端处理等。通过合理的设计和实施,能够有效提高数据的安全性,保护用户的隐私和敏感信息。但需要注意的是,前端加密存储不能完全替代后端的安全措施,后端的安全防护仍然是不可或缺的。
564 53
|
人工智能 Java API
MCP客户端调用看这一篇就够了(Java版)
本文详细介绍了MCP(Model Context Protocol)客户端的开发方法,包括在没有MCP时的痛点、MCP的作用以及如何通过Spring-AI框架和原生SDK调用MCP服务。文章首先分析了MCP协议的必要性,接着分别讲解了Spring-AI框架和自研SDK的使用方式,涵盖配置LLM接口、工具注入、动态封装工具等步骤,并提供了代码示例。此外,还记录了开发过程中遇到的问题及解决办法,如版本冲突、服务连接超时等。最后,文章探讨了框架与原生SDK的选择,认为框架适合快速构建应用,而原生SDK更适合平台级开发,强调了两者结合使用的价值。
14599 33
MCP客户端调用看这一篇就够了(Java版)
|
Java
Java实现随机生成某个省某个市的身份证号?如何编码?
【10月更文挑战第18天】Java实现随机生成某个省某个市的身份证号?如何编码?
1384 5
|
Java
Java开发实现图片地址检验,如果无法找到资源则使用默认图片,如何编码?
【10月更文挑战第14天】Java开发实现图片地址检验,如果无法找到资源则使用默认图片,如何编码?
422 2
|
存储 网络协议 Java
Java获取客户端IP问题:返回127.0.0.1
总结:要解决Java获取客户端IP返回127.0.0.1的问题,首先要找出原因,再采取合适的解决方案。请参考上述方案来改进代码,确保在各种网络环境下都能正确获取客户端IP地址。希望本文对您有所帮助。
834 25
|
存储 Java 数据安全/隐私保护
Java技术栈揭秘:Base64加密和解密文件的实战案例
以上就是我们今天关于Java实现Base64编码和解码的实战案例介绍。希望能对你有所帮助。还有更多知识等待你去探索和学习,让我们一同努力,继续前行!
725 5
|
存储 缓存 Java
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
2664 3
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡