(OWASP汉化)攻击系列大全(十一):内容安全策略

最新版本 (mm/dd/yy): 08/31/2013
翻译至Content Security Policy

该文章在OWASP中属于待审查的文章
译者注:大家还可以了解下 同源策略

引言

CSP即内容安全策略。W3C规范提供了指示客户端浏览器从哪个位置 和/或 哪种类型的资源被允许加载的可能性。要定义加载行为,CSP规范使用“指令”,其中指令定义目标资源类型的加载行为。

本文基于W3C规范的1.1版。

可以使用HTTP响应头指定指令(服务器可以使用给定的资源表示形式发送多个CSP HTTP头字段,并且服务器可以使用相同资源或不同资源的不同表示形式发送不同的CSP头字段值)或HTML Meta标记,下面的HTTP标题由规范定义:

  • 内容安全策略(Content-Security-Policy):由W3C Specs定义为标准头,由Chrome版本25及更高版本,Firefox 23及更高版本,Opera 19及更高版本使用。
  • X-内容安全策略(X-Content-Security-Policy):由Firefox直到版本23和Internet Explorer版本10(部分实施内容安全策略)使用。
  • X-WebKit-内容安全策略(X-WebKit-CSP):由Chrome版本25使用

支持的指令有:

  • default-src:当未定义资源类型专用指令时定义所有资源类型的加载策略(回退)
  • script-src:定义受保护资源可以执行的脚本,
  • object-src:定义受保护资源可以从哪里加载插件,
  • style-src:定义用户应用于受保护资源的样式(CSS)
  • img-src:定义受保护资源可以加载图像的位置,
  • media-src:定义受保护资源可以加载视频和音频的位置,
  • frame-src:定义受保护资源可以嵌入帧的位置,
  • font-src:定义受保护资源可以加载字体的位置,
  • connect-src:定义受保护资源可以使用脚本接口加载哪些URI,
  • form-action:定义哪些URI可以用作HTML表单元素的操作,
  • sandbox:指定用户代理应用于受保护资源的HTML沙箱策略,
  • script-nonce:通过要求在脚本元素上存在指定的随机数来定义脚本执行,
  • plugin-types:定义可由受保护资源调用的插件集,方法是限制可嵌入资源的类型,
  • reflected-xss:指示用户代理激活或停用用于过滤或阻止反射的跨站点脚本攻击(XSS)的任何启发式扫描,相当于非标准X-XSS-Protection标头的效果,
  • report-uri:指定用户代理发送有关策略违规的报告的URI

HTML5Rocks有CSP的简介。浏览器支持在http://caniuse.com/#feat=contentsecuritypolicy

风险

CSP的风险有2个主要来源:
1. 策略配置错误,
2. 太宽松的策略。

对策

本文将重点提供JEE Web Filter的示例实现,以便对服务器返回的所有HTTP响应应用一组CSP策略。

这些策略将指示浏览器使用W3C规范中定义的所有HTTP头进行下面的加载行为:

  • 显式加载每种资源类型的定义
  • 资源只能从源域加载
  • 不允许内联样式
  • 对于JavaScript:
    • 内联脚本将被允许,因为内联脚本通常被使用(如果目标站点不使用这种类型的脚本,可以禁用内联脚本)
    • eval()函数将被允许,以便不中断使用流行的JavaScript库(例如:JQuery,JQueryUI,Sencha,…),因为它们使用eval()函数(这是我上次在CDN检查源代码的情况)
  • 生成一个随机不可猜测的脚本随机数,用于所有脚本标记(tags),
  • 插件类型只允许PDF和Flash,
  • 没有字体加载(可配置),
  • 启用浏览器XSS筛选功能。

    主要浏览器(Firefox / Chrome / IE)对CSP指令的支持不同。建议检查目标浏览器提供的支持(使用本文链接部分提供的站点)以配置CSP策略。下面的示例尝试提供一组策略,您可以根据这些策略添加特定于应用程序上下文的策略。

这个实现提供了一个选项来添加Firefox使用的CSP指令(Mozilla CSP指令)。

import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.binary.Hex;

/**
 * Sample filter implementation to define a set of Content Security Policies.<br/>
 * 
 * This implementation has a dependency on Commons Codec API.<br/>
 * 
 * This filter set CSP policies using all HTTP headers defined into W3C specification.<br/>
 * <br/>
 * This implementation is oriented to be easily understandable and easily adapted.<br/>
 * 
 */
@WebFilter("/*")
public class CSPPoliciesApplier implements Filter {

    /** Configuration member to specify if web app use web fonts */
    public static final boolean APP_USE_WEBFONTS = false;

    /** Configuration member to specify if web app use videos or audios */
    public static final boolean APP_USE_AUDIOS_OR_VIDEOS = false;

    /** Configuration member to specify if filter must add CSP directive used by Mozilla (Firefox) */
    public static final boolean INCLUDE_MOZILLA_CSP_DIRECTIVES = true;

    /** Filter configuration */
    @SuppressWarnings("unused")
    private FilterConfig filterConfig = null;

    /** List CSP HTTP Headers */
    private List<String> cspHeaders = new ArrayList<String>();

    /** Collection of CSP polcies that will be applied */
    private String policies = null;

    /** Used for Script Nonce */
    private SecureRandom prng = null;

    /**
     * Used to prepare (one time for all) set of CSP policies that will be applied on each HTTP response.
     * 
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    @Override
    public void init(FilterConfig fConfig) throws ServletException {
        // Get filter configuration
        this.filterConfig = fConfig;

        // Init secure random
        try {
            this.prng = SecureRandom.getInstance("SHA1PRNG");
        }
        catch (NoSuchAlgorithmException e) {
            throw new ServletException(e);
        }

        // Define list of CSP HTTP Headers
        this.cspHeaders.add("Content-Security-Policy");
        this.cspHeaders.add("X-Content-Security-Policy");
        this.cspHeaders.add("X-WebKit-CSP");

        // Define CSP policies
        // Loading policies for Frame and Sandboxing will be dynamically defined : We need to know if context use Frame
        List<String> cspPolicies = new ArrayList<String>();
        String originLocationRef = "'self'";
        // --Disable default source in order to avoid browser fallback loading using 'default-src' locations
        cspPolicies.add("default-src 'none'");
        // --Define loading policies for Scripts
        cspPolicies.add("script-src " + originLocationRef + " 'unsafe-inline' 'unsafe-eval'");
        if (INCLUDE_MOZILLA_CSP_DIRECTIVES) {
            cspPolicies.add("options inline-script eval-script");
            cspPolicies.add("xhr-src 'self'");
        }
        // --Define loading policies for Plugins
        cspPolicies.add("object-src " + originLocationRef);
        // --Define loading policies for Styles (CSS)
        cspPolicies.add("style-src " + originLocationRef);
        // --Define loading policies for Images
        cspPolicies.add("img-src " + originLocationRef);
        // --Define loading policies for Form
        cspPolicies.add("form-action " + originLocationRef);
        // --Define loading policies for Audios/Videos
        if (APP_USE_AUDIOS_OR_VIDEOS) {
            cspPolicies.add("media-src " + originLocationRef);
        }
        // --Define loading policies for Fonts
        if (APP_USE_WEBFONTS) {
            cspPolicies.add("font-src " + originLocationRef);
        }
        // --Define loading policies for Connection
        cspPolicies.add("connect-src " + originLocationRef);
        // --Define loading policies for Plugins Types
        cspPolicies.add("plugin-types application/pdf application/x-shockwave-flash");
        // --Define browser XSS filtering feature running mode
        cspPolicies.add("reflected-xss block");

        // Target formating
        this.policies = cspPolicies.toString().replaceAll("(\\[|\\])", "").replaceAll(",", ";").trim();
    }

    /**
     * Add CSP policies on each HTTP response.
     * 
     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain fchain) throws IOException, ServletException {
        HttpServletRequest httpRequest = ((HttpServletRequest) request);
        HttpServletResponse httpResponse = ((HttpServletResponse) response);

        /* Step 1 : Detect if target resource is a Frame */
        // Customize here according to your context...
        boolean isFrame = true;

        /* Step 2 : Add CSP policies to HTTP response */
        StringBuilder policiesBuffer = new StringBuilder(this.policies);

        // If resource is a frame add Frame/Sandbox CSP policy
        if (isFrame) {
            // Frame + Sandbox : Here sandbox allow nothing, customize sandbox options depending on your app....
            policiesBuffer.append(";").append("frame-src 'self';sandbox");
            if (INCLUDE_MOZILLA_CSP_DIRECTIVES) {
                policiesBuffer.append(";").append("frame-ancestors 'self'");
            }
        }

        // Add Script Nonce CSP Policy
        // --Generate a random number
        String randomNum = new Integer(this.prng.nextInt()).toString();
        // --Get its digest
        MessageDigest sha;
        try {
            sha = MessageDigest.getInstance("SHA-1");
        }
        catch (NoSuchAlgorithmException e) {
            throw new ServletException(e);
        }
        byte[] digest = sha.digest(randomNum.getBytes());
        // --Encode it into HEXA
        String scriptNonce = Hex.encodeHexString(digest);
        policiesBuffer.append(";").append("script-nonce ").append(scriptNonce);
        // --Made available script nonce in view app layer
        httpRequest.setAttribute("CSP_SCRIPT_NONCE", scriptNonce);

        // Add policies to all HTTP headers
        for (String header : this.cspHeaders) {
            httpResponse.setHeader(header, policiesBuffer.toString());
        }

        /* Step 3 : Let request continue chain filter */
        fchain.doFilter(request, response);
    }

    /**
     * {@inheritDoc}
     * 
     * @see javax.servlet.Filter#destroy()
     */
    @Override
    public void destroy() {
        // Not used
    }
}

工具

w3af审计工具(http://w3af.org) 包含插件,用于自动审计Web应用程序,以检查它们是否正确实施了CSP策略

将这种类型的工具纳入Web应用程序开发流程以便执行定期的自动一级检查(不要取代手动审计,而且必须定期执行手动审计),这非常有用。

您还可以使用CSP测试程序(浏览器扩展)构建并测试您的Web应用程序的策略。

信息链接