1. 程式人生 > >【Java編碼準則】の #02不要在客戶端儲存未加密的敏感資訊

【Java編碼準則】の #02不要在客戶端儲存未加密的敏感資訊

     當構建CS模式的應用程式時,在客戶端側儲存敏感資訊(例如使用者私要資訊)可能導致非授權的資訊洩漏。

     對於Web應用程式來說,最常見的洩漏問題是在客戶端使用cookies存放伺服器端獲取的敏感資訊。Cookies是由web伺服器建立的,它具有一個指定的有效時間,儲存在客戶端。當客戶端連線上伺服器端時,客戶端使用cookies中儲存的資訊向伺服器端進行認證,通過後伺服器端返回敏感資訊。

     在XSS攻擊下,Cookies不能保證敏感資訊的安全。無論是通過XSS攻擊,還是直接對客戶端的攻擊,攻擊者一旦獲取到Cookies,他就可以使用這個Cookies從伺服器端獲取敏感資訊。上面的風險存在時間窗,當Cookies存活超過指定的時間後(例如15分鐘),伺服器端會使會話無效,這時風險就不存在了。

     Cookies是一個短的字串,如果它包含了敏感的資訊,那麼這段資訊必須進行加密,敏感資訊包括使用者名稱,密碼,信用卡號碼,社會安全碼,以及其他任何個人標識資訊。關於管理密碼的更多細節,參見“#13使用雜湊函式來儲存密碼”。關於如何保證記憶體中敏感資訊保安的更多細節,參見“#01限制記憶體中敏感資料的生命週期”。

[不符合安全要求的程式碼示例]

     下面的程式碼中,login servlet將使用者名稱和密碼儲存在Cookies中,用於後續請求中標識使用者。

	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);
					
					// forword to welcome page
				} else {
					// set error and return
				}
			}
		} else {
			// no remember-me functionality selected
			// process with regular authentication;
			// if it fails set error and return
		}
		Array.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 = reuqest.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("uset", loginService.getUsername());
				Cookie loginCookie = new Cookie("rememberme", username + ";" + newRandom);
				response.addCookie(loginCookie);
				
				// forword to welcome page
			}
		} else {
			// no remember-me functionality selected
			// process with regular authentication;
			// if it fails set error and return
		}
		Array.fill(password, ' ');
	}

     伺服器端儲存使用者名稱和安全隨機字串的對映關係,當用戶選擇“記住我”時,doPost()函式檢查客戶端提供的Cookies中是否包含有效的使用者名稱和隨機字串對映對。如果對映對是正確的,伺服器端通過該使用者的認證,並使使用者跳轉到歡迎頁。如果認證沒有通過,伺服器端返回錯誤給客戶端。如果使用者選擇“記住我”,但客戶度沒有有效的Cookie導致認證失敗,那麼伺服器端會要求使用者使用認證資訊重新進行認證。如果認證成功,伺服器端會提供一個包含新的“記住我”特性的Cookie給客戶端。

     這個解決方案通過使當前會話無效並建立新的會話,可以避免固定會話攻擊。同時通過將會話訪問有效時間設定為15分鐘來將減少攻擊者實施會話劫持攻擊的時間窗長度。