在应用程序中添加HTTP请求头可以提高Web应用程序的安全性。本文介绍如何通过定义EnvoyFilter添加HTTP请求头。

前提条件

背景信息

OWASP提供了最佳实践指南和编程框架,描述了如何使用安全请求头保护应用程序的安全。HTTP请求头的基准配置如下。
表 1. HTTP请求头基准配置
HTTP请求头 默认值 描述
Content-Security-Policy frame-ancestors none; 防止其他网站进行Clickjacking攻击。
X-XSS-Protection 1; mode=block 激活浏览器的XSS过滤器(如果可用),检测到XSS时阻止渲染。
X-Content-Type-Options Nosniff 禁用浏览器的内容嗅探。
Referrer-Policy no-referrer 禁用自动发送引荐来源。
X-Download-Options noopen 禁用旧版本IE中的自动打开下载功能。
X-DNS-Prefetch-Control off 禁用对页面上的外部链接的推测性DNS解析。
Server envoy 由Istio的入口网关自动设置。
X-Powered-by 无默认值 去掉该值来隐藏潜在易受攻击的应用程序服务器的名称和版本。
Feature-Policy

camera ‘none’;

microphone ‘none’;

geolocation ‘none’;

encrypted-media ‘none’;

payment ‘none’;

speaker ‘none’;

usb ‘none’;

控制可以在浏览器中使用的功能和API。
以Bookinfo应用程序为例(详情请参见部署应用到ASM实例),通过curl命令可以看到应用程序的HTTP请求头信息如下。
curl -I http://{入口网关服务的IP地址}/productpage
HTTP/1.1 200 OK
content-type: text/html; charset=utf-8
content-length: 5183
server: istio-envoy
date: Tue, 28 Jan 2020 08:15:21 GMT
x-envoy-upstream-service-time: 28

可以看到,默认情况下,示例应用程序的入口首页请求并没有包含表 1中所述的安全相关的HTTP请求头。通过Istio EnvoyFilter可以快速添加安全相关的HTTP请求头。

操作步骤

  1. 定义EnvoyFilter。
    执行如下命令,部署Istio EnvoyFilter。
    kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: EnvoyFilter
    metadata:
      name: addheader-into-ingressgateway
      namespace: istio-system
    spec:
      workloadSelector:
        # select by label in the same namespace
        labels:
          istio: ingressgateway
      configPatches:
        # The Envoy config you want to modify
      - applyTo: HTTP_FILTER
        match:
          context: GATEWAY
          listener:
            filterChain:
              filter:
                name: "envoy.http_connection_manager"
                subFilter:
                  name: "envoy.router"
        patch:
          operation: INSERT_BEFORE
          value: # lua filter specification
            name: envoy.lua
            typed_config:
              "@type": "type.googleapis.com/envoy.config.filter.http.lua.v2.Lua"
              inlineCode: |-
                function envoy_on_response(response_handle)
                    function hasFrameAncestors(rh)
                    s = rh:headers():get("Content-Security-Policy");
                    delimiter = ";";
                    defined = false;
                    for match in (s..delimiter):gmatch("(.-)"..delimiter) do
                        match = match:gsub("%s+", "");
                        if match:sub(1, 15)=="frame-ancestors" then
                        return true;
                        end
                    end
                    return false;
                    end
                    if not response_handle:headers():get("Content-Security-Policy") then
                    csp = "frame-ancestors none;";
                    response_handle:headers():add("Content-Security-Policy", csp);
                    elseif response_handle:headers():get("Content-Security-Policy") then
                    if not hasFrameAncestors(response_handle) then
                        csp = response_handle:headers():get("Content-Security-Policy");
                        csp = csp .. ";frame-ancestors none;";
                        response_handle:headers():replace("Content-Security-Policy", csp);
                    end
                    end
                    if not response_handle:headers():get("X-Frame-Options") then
                    response_handle:headers():add("X-Frame-Options", "deny");
                    end
                    if not response_handle:headers():get("X-XSS-Protection") then
                    response_handle:headers():add("X-XSS-Protection", "1; mode=block");
                    end
                    if not response_handle:headers():get("X-Content-Type-Options") then
                    response_handle:headers():add("X-Content-Type-Options", "nosniff");
                    end
                    if not response_handle:headers():get("Referrer-Policy") then
                    response_handle:headers():add("Referrer-Policy", "no-referrer");
                    end
                    if not response_handle:headers():get("X-Download-Options") then
                    response_handle:headers():add("X-Download-Options", "noopen");
                    end
                    if not response_handle:headers():get("X-DNS-Prefetch-Control") then
                    response_handle:headers():add("X-DNS-Prefetch-Control", "off");
                    end
                    if not response_handle:headers():get("Feature-Policy") then
                    response_handle:headers():add("Feature-Policy",
                                                    "camera 'none';"..
                                                    "microphone 'none';"..
                                                    "geolocation 'none';"..
                                                    "encrypted-media 'none';"..
                                                    "payment 'none';"..
                                                    "speaker 'none';"..
                                                    "usb 'none';");
                    end
                    if response_handle:headers():get("X-Powered-By") then
                    response_handle:headers():remove("X-Powered-By");
                    end
                end
    EOF
  2. 验证HTTP请求头。
    通过如下curl命令验证安全HTTP请求头是否添加成功。
    curl -I http://{入口网关服务的IP地址}/productpage
    HTTP/1.1 200 OK
    content-type: text/html; charset=utf-8
    content-length: 4183
    server: istio-envoy
    date: Tue, 28 Jan 2020 09:07:01 GMT
    x-envoy-upstream-service-time: 17
    content-security-policy: frame-ancestors none;
    x-frame-options: deny
    x-xss-protection: 1; mode=block
    x-content-type-options: nosniff
    referrer-policy: no-referrer
    x-download-options: noopen
    x-dns-prefetch-control: off
    feature-policy: camera 'none';microphone 'none';geolocation 'none';encrypted-media 'none';payment 'none';speaker 'none';usb 'none';
    可以看到示例应用程序的入口首页请求已经包含了表 1中所述的安全相关的HTTP请求头。