cookie setSecure详解
1. 前言
最近项目用Sparrow Health System检测漏洞,发现存在一个setSecure安全漏洞问题,于是网上搜索了一下,这里记录一下。
2. 问题
在cas中或其他web开发中,会碰到安全cookie的概念,因为CAS中TGT是存放在安全cookie中的。下面是安全cookie 的理解:
- Set-Cookie 的 secure 属性就是处理这方面的情况用的,它表示创建的 cookie 只能在 HTTPS 连接中被浏览器传递到服务器端进行会话验证,如果是 HTTP 连接则不会传递该信息,所以绝对不会被窃听到。
在setSecure(true); 的情况下,只有https才传递到服务器端。http是不会传递的。
setSecure(true)意味着"指示浏览器仅通过 HTTPS 连接传回 cookie。这可以确保 cookie ID 是安全的,且仅用于使用 HTTPS 的网站。如果启用此功能,则 HTTP 上的会话 Cookie 将不再起作用。"
j2ee servlet的接口中也定义了Cookie对象,也有其方法setSecue(false);
关于setSecure的问题,在http连接下:
- 当setSecure(true)时,浏览器端的cookie会不会传递到服务器端?
- 当setSecure(true)时,服务器端的cookie会不会传递到浏览器端?
- 答案:1)不会 ; 2)会
原理:服务器端的Cookie对象是java中的对象,请不要和浏览器端的cookie文件混淆了。服务器端的Cookie对象是方便java程序员包装一个浏览器端的cookie文件。一旦包装好,就放到response对象中,在转换成http头文件。在传递到浏览器端。这时就会在浏览器的临时文件中创建一个cookie文件。
但我们再次访问网页时,才查看浏览器端的cookie文件中的secure值,如果是true,但是http连接,这个cookie就不会传到服务器端。当然这个过程对浏览器是透明的。其他人是不会知道的。
总结如下:cookie的secure值为true时,在http中是无效的;在https中才有效
3. Cookie设置
实际上,Servlet提供的HttpSession
本质上就是通过一个名为JSESSIONID
的Cookie来跟踪用户会话的。除了这个名称外,其他名称的Cookie我们可以任意使用。
如果我们想要设置一个Cookie,例如,记录用户选择的语言,可以编写一个LanguageServlet
:
@WebServlet(urlPatterns = "/pref")
public class LanguageServlet extends HttpServlet {
private static final Set<String> LANGUAGES = Set.of("en", "zh");
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String lang = req.getParameter("lang");
if (LANGUAGES.contains(lang)) {
// 创建一个新的Cookie:
Cookie cookie = new Cookie("lang", lang);
// 该Cookie生效的路径范围:
cookie.setPath("/");
// 该Cookie有效期:
cookie.setMaxAge(8640000); // 8640000秒=100天
// 将该Cookie添加到响应:
resp.addCookie(cookie);
}
resp.sendRedirect("/");
}
}
创建一个新Cookie时,除了指定名称和值以外,通常需要设置setPath("/")
,浏览器根据此前缀决定是否发送Cookie。如果一个Cookie调用了setPath("/user/")
,那么浏览器只有在请求以/user/
开头的路径时才会附加此Cookie。通过setMaxAge()
设置Cookie的有效期,单位为秒,最后通过resp.addCookie()
把它添加到响应。
如果访问的是https网页,还需要调用setSecure(true)
,否则浏览器不会发送该Cookie。
因此,务必注意:浏览器在请求某个URL时,是否携带指定的Cookie,取决于Cookie是否满足以下所有要求:
- URL前缀是设置Cookie时的Path;
- Cookie在有效期内;
- Cookie设置了
setSecure(true)
时必须以https访问。
我们可以在浏览器看到服务器发送的Cookie:
如果我们要读取Cookie,例如,在IndexServlet
中,读取名为lang
的Cookie以获取用户设置的语言,可以写一个方法如下:
private String parseLanguageFromCookie(HttpServletRequest req) {
// 获取请求附带的所有Cookie:
Cookie[] cookies = req.getCookies();
// 如果获取到Cookie:
if (cookies != null) {
// 循环每个Cookie:
for (Cookie cookie : cookies) {
// 如果Cookie名称为lang:
if (cookie.getName().equals("lang")) {
// 返回Cookie的值:
return cookie.getValue();
}
}
}
// 返回默认值:
return "en";
}
可见,读取Cookie主要依靠遍历HttpServletRequest
附带的所有Cookie。
4. setSecure测试
下面我自己再实验一下:
4.1 http访问测试
我自己的电脑用的Tomcat,首先,把cookie-secure的值设为false,创建一个session:
1 public void service(HttpServletRequest request, HttpServletResponse response) {
2 // 创建cookie
3 Cookie cookie = new Cookie("key", "value");
4 // 关键地方:cookie设为false
5 cookie.setSecure(false);
6 response.addCookie(cookie);
7 // 从request中取得cookie,并输出
8 Cookie[] cookies = request.getCookies();
9 for (Cookie c : cookies) {
10 System.out.println(c.getValue());
11 }
12 RequestDispatcher dispatcher = request
13 .getRequestDispatcher("index.jsp");
14 try {
15 dispatcher.forward(request, response);
16 } catch (ServletException e) {
17 e.printStackTrace();
18 } catch (IOException e) {
19 e.printStackTrace();
20 }
21 }
好,我们访问一下这个地址:http://localhost:8080/servlet/index.do
用Fiddler来看一下客户端发出的信息:
好,我们再访问一下前面的地址:
这个时候我们可以看到客户端发出的cookie里面,有key=value,说明客户端把cookie发给了服务器端。
4.2 https访问测试
那如果我们在服务器端设置cookie.setSecure(true)呢?
先上代码:
1 public void service(HttpServletRequest request, HttpServletResponse response) {
2 // 创建cookie
3 Cookie cookie = new Cookie("key", "value");
4 // 关键地方:cookie设为false
5 cookie.setSecure(true);
6 response.addCookie(cookie);
7 // 从request中取得cookie,并输出
8 Cookie[] cookies = request.getCookies();
9 if (cookies != null) {
10 for (Cookie c : cookies) {
11 System.out.println(c.getValue());
12 }
13 }
14 RequestDispatcher dispatcher = request
15 .getRequestDispatcher("index.jsp");
16 try {
17 dispatcher.forward(request, response);
18 } catch (ServletException e) {
19 e.printStackTrace();
20 } catch (IOException e) {
21 e.printStackTrace();
22 }
23 }
第一次http访问:
这是request的信息。
服务器的返回信息如下:
我们可以看到服务器发给客户端的信息中有这么一行:Set-Cookie:key=value; Secure
多了一个Secure。
第二次http访问:
我们发现key=value这个cookie已经没有了。
第三次https访问:
那我们用https协议头来试着访问一下看看:
我们看到key=value又发给了服务器端了。
5. 另外
cookie中的数据通常会包含用户的隐私数据,首先要保证数据的保密性,其次要保证数据不能被伪造或者篡改,基于这两点,我们通常需要对cookie内容进行加密,加密方式一般使用对称加密(单密钥,如DES)或非对称加密(一对密钥,如RSA),密钥需要保存在服务器端一个安全的地方,这样,别人不知道密钥时,无法对数据进行解密,也无法伪造或篡改数据。
另外,像上文提到的,重要的cookie数据需要设置成HttpOnly的,避免跨站脚本获取你的cookie,保证了cookie在浏览器端的安全性。
还有我们可以设置cookie只作用于安全的协议(https),JavaEE中可以通过Cookie类的setSecure(boolean flag)来设置,设置后,该cookie只会在https下发送,而不会再http下发送,保证了cookie在服务器端的安全性,
6. 例子
import javax.servlet.http.Cookie; //導入方法依賴的package包/類
/**
* Creates a new session cookie for the given session ID
*
* @param context The Context for the web application
* @param sessionId The ID of the session for which the cookie will be
* created
* @param secure Should session cookie be configured as secure
*/
public static Cookie createSessionCookie(Context context,
String sessionId, boolean secure) {
SessionCookieConfig scc =
context.getServletContext().getSessionCookieConfig();
// NOTE: The priority order for session cookie configuration is:
// 1. Context level configuration
// 2. Values from SessionCookieConfig
// 3. Defaults
Cookie cookie = new Cookie(
SessionConfig.getSessionCookieName(context), sessionId);
// Just apply the defaults.
cookie.setMaxAge(scc.getMaxAge());
cookie.setComment(scc.getComment());
if (context.getSessionCookieDomain() == null) {
// Avoid possible NPE
if (scc.getDomain() != null) {
cookie.setDomain(scc.getDomain());
}
} else {
cookie.setDomain(context.getSessionCookieDomain());
}
// Always set secure if the request is secure
if (scc.isSecure() || secure) {
cookie.setSecure(true);
}
// Always set httpOnly if the context is configured for that
if (scc.isHttpOnly() || context.getUseHttpOnly()) {
cookie.setHttpOnly(true);
}
String contextPath = context.getSessionCookiePath();
if (contextPath == null || contextPath.length() == 0) {
contextPath = scc.getPath();
}
if (contextPath == null || contextPath.length() == 0) {
contextPath = context.getEncodedPath();
}
if (context.getSessionCookiePathUsesTrailingSlash()) {
// Handle special case of ROOT context where cookies require a path of
// '/' but the servlet spec uses an empty string
// Also ensure the cookies for a context with a path of /foo don't get
// sent for requests with a path of /foobar
if (!contextPath.endsWith("/")) {
contextPath = contextPath + "/";
}
} else {
// Only handle special case of ROOT context where cookies require a
// path of '/' but the servlet spec uses an empty string
if (contextPath.length() == 0) {
contextPath = "/";
}
}
cookie.setPath(contextPath);
return cookie;
}
/**
* 往客戶端寫入Cookie
* @param name cookie參數名
* @param value cookie參數值
* @param maxAge 有效時間,int(單位秒),0:刪除Cookie,-1:頁麵關閉時刪除cookie
* @param path 與cookie一起傳輸的虛擬路徑
* @param domain 與cookie關聯的域
* @param isSecure 是否在https請求時才進行傳輸
* @param isHttpOnly 是否隻能通過http訪問
*/
public void addCookie(String name, String value, int maxAge, String path, String domain, boolean isSecure, boolean isHttpOnly)
{
Cookie cookie = new Cookie(name, value);
cookie.setMaxAge(maxAge);
cookie.setPath(path);
if(maxAge > 0 && domain != null)
{
cookie.setDomain(domain);
}
cookie.setSecure(isSecure);
try
{
Cookie.class.getMethod("setHttpOnly", boolean.class);
cookie.setHttpOnly(isHttpOnly);
}
catch(Exception e)
{
System.out.println("MyCookie ignore setHttpOnly Method");
}
response.addCookie(cookie);
}
参考
cookie setSecure详解_wangyunpeng0319的博客-CSDN博客_cookie.setsecure