我们相信:世界是美好的,你是我也是。 来玩一下解压小游戏吧!

关于跨站脚本攻击,对于目前的情形来说,是越来越难以成立了。目前的浏览器环境下,对于越权调用脚本的行为,必须要求:被攻击的网站,错误的设置了cors,才有可能调用成功了。本文以nginx配置的角度,看看如何配置cors。什么样的情况下,配置才可能被恶意利用呢?

苏南大叔:Nginx配置,如何支持 CORS?多来源域名安全配置 - nginx-proxy-cors
Nginx配置,如何支持 CORS?多来源域名安全配置(图2-1)

苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。测试环境:win10chrome@135.0.7049.41php@8.2.9ntsnginx@1.15.11CORS(跨域资源共享)是现代浏览器的一项安全功能,用于限制从一个域访问另一个域的资源,配置Nginx支持CORS是一个常见的需求。

CORS

CORS的实质就是:服务器端返回几个特殊的header,并且在接收到options请求的时候,返回特定的header及状态。对应的header有很多变化,允许服务器声明哪些来源可以访问其资源。

核心内容就是:允许什么样的网站给自己发请求,可以发送什么样的请求(由客户端所在的浏览器控制发送)。

通过设置HTTP响应头,服务器可以控制跨域请求的行为。常见的CORS相关头包括:

  • Access-Control-Allow-Origin:指定允许访问的来源域名(例如:https://newsn.net)。
  • Access-Control-Allow-Methods:指定允许的HTTP方法(如GETPOSTOPTIONS)。
  • Access-Control-Allow-Headers:指定允许的自定义请求头。
  • Access-Control-Allow-Credentials:是否允许发送Cookie

配置 CORS

Nginx中,可以通过添加响应头来支持CORS。值得说明的是:这些特殊的header,不一定必须由nginx发送,服务端脚本发送也是可以的。只不过,一般的情况下,nginx布置在最前端,所以由它进行配置比较方便。

并且需要明确的观点是:

  • nginx默认用来配置普通静态文件的,而静态文件是不需要设置cors的,而且静态文件自身也无法定制header
  • 需要设置cors的,都是后端功能。而后端功能自身就可以添加header。当然也可以通过“反代”挂载在nginx下,由nginx统一配置header
  • nginx反代功能可以按请求特征单独配置,也可以放在某个location /{}下面。cors的设置需要和反代的部分放置到一起,才可以生效。

参考文章:

配置示例

以下是一个基本的配置示例。(注意:nginx配置文件的变化很多,这并不是唯一正确答案)

server {
    listen       80;
    server_name  ttt;
    location / {
        root   /usr/share/nginx/html;
        index  index.php index.html index.htm;
    }
    location ~ \.php$ {
        # root   /var/www/html;
        # autoindex  off;
        fastcgi_pass   lamp-php-container:9000;
        fastcgi_index  index.php;
        fastcgi_split_path_info  ^((?U).+\.php)(/?.+)$;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        fastcgi_param  PATH_INFO  $fastcgi_path_info;
        fastcgi_param  PATH_TRANSLATED  $document_root$fastcgi_path_info;
        include        fastcgi_params;
        # 动态跨域处理
        set $cors_origin '';
        if ($http_origin ~* (http://h|http://newsn.net|https://newsn.net)) {
            set $cors_origin $http_origin;
        }
        # 添加跨域响应头
        add_header 'Access-Control-Allow-Origin' $cors_origin always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,access-control-allow-origin, authority, content-type, version-info, X-Requested-With' always;
        # 处理 OPTIONS 请求
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' $cors_origin always;
            add_header 'Access-Control-Allow-Credentials' 'true' always;
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,access-control-allow-origin, authority, content-type, version-info, X-Requested-With' always;
            add_header 'Access-Control-Max-Age' 1728000 always;
            add_header 'Content-Length' 0;
            return 204;
        }
    }
}

苏南大叔:Nginx配置,如何支持 CORS?多来源域名安全配置 - options测试
Nginx配置,如何支持 CORS?多来源域名安全配置(图2-2)

配置的框架模版是这样的:

server {
    listen 80;
    server_name newsn.net;
    location / {
        # 静态资源类设置
        location ~* \.(html|css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|otf|eot|ttf|txt|json)$ {
            try_files $uri $uri/ =404;
        }
        # cors设置
        # 反向代理
    }
}

或者(以php为例):

server {
    listen 80;
    server_name newsn.net;
    location / {
        # ..
    }
    location ~ \.php(.*)$ {
        # cors设置
        # 反向代理
    }
}

CORS 设置

下面的代码里面,可以看到options的作用范围内和范围外,有相同的代码,不要试图省略掉其中一个。nginx的配置和大家的想像不一样。

# 添加 CORS 相关头
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
# 处理预检请求
if ($request_method = 'OPTIONS') {
    add_header 'Access-Control-Allow-Origin' '*';
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
    return 204;
}

豪华版设置:

if ($request_method = 'OPTIONS') {
    add_header 'Access-Control-Allow-Origin' 'http://h';
    add_header 'Access-Control-Allow-Credentials' 'true';
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,access-control-allow-origin, authority, content-type, version-info, X-Requested-With';
    add_header 'Content-Type' 'text/plain charset=utf-8';
    add_header 'Content-Length' 0;
    return 204;
}
add_header 'Access-Control-Allow-Origin' 'http://h';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,access-control-allow-origin, authority, content-type, version-info, X-Requested-With';

反向代理

cors的配置,需要和反向代理的配置,放在一起。否则就很有可能会让人怀疑人生了。为啥没生效?

最常见的情况

反代非php代码的时候,一般就是修改backend_service的地址(或者说端口号)。

location / {
    #...
    # 反向代理到后端服务
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
  • nodejs,一般启动端口3000
  • java,一般启动端口8080
  • Python(FlaskDjango),一般启动端口5000

php反代

phpPHP-FPM)的情况,相对特殊。可以使用以下配置:

location / {
    #...
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}

或者:

location ~ \.php(.*)$ {
    fastcgi_pass   127.0.0.1:9001;
    fastcgi_index  index.php;
    fastcgi_split_path_info  ^((?U).+\.php)(/?.+)$;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    fastcgi_param  PATH_INFO  $fastcgi_path_info;
    fastcgi_param  PATH_TRANSLATED  $document_root$fastcgi_path_info;
    include        fastcgi_params;
}

参考文章:

技术细节:通配符 星

运维或程序员,为了图省事,一般会设置Access-Control-Allow-Origin*。这并不是推荐的做法,因为这样就给一些网站非法调用接口留下来口子。

add_header 'Access-Control-Allow-Origin' '*';

正确的做法是,确定合法的调用网站,然后配置:

add_header 'Access-Control-Allow-Origin' 'http://h';

或者设置多个允许调用的网站时,是做了一个动态筛选。

set $cors_origin '';
if ($http_origin ~* (http://h|http://newsn.net|https://newsn.net)) {
    set $cors_origin $http_origin;
}
add_header 'Access-Control-Allow-Origin' $cors_origin always;

技术细节:always

Nginx 配置中,always 是一个指令修饰符,用于确保特定的响应头始终被添加到响应中,无论响应的状态码是什么。
作用:
默认情况下,add_header 指令只会在响应状态码为 2xx(成功)或 3xx(重定向)时生效。
如果你希望无论状态码(如 4xx5xx)如何,都强制添加响应头,就需要使用 always 修饰符。

所以,在本文的语境里面,加不加always,都没有什么问题。

题外话:php设置headers

如果是服务器端代码设置头信息,这里有个php的例子:

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    header('Access-Control-Allow-Origin: https://h'); // 允许的来源
    header('Access-Control-Allow-Credentials: true'); // 允许携带Cookie
    header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); // 允许的HTTP方法
    header('Access-Control-Allow-Headers: Content-Type'); // 允许的自定义头部
    exit; // 结束 OPTIONS 请求
}

结语

Nginx 中配置 CORS,是跨域接口对接的必备设置项目,请注意把Access-Control-Allow-Origin设置为*的时候,可能会引发的安全问题。更多nginx配置文章,请点击苏南大叔博客:

如果本文对您有帮助,或者节约了您的时间,欢迎打赏瓶饮料,建立下友谊关系。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。

 【福利】 腾讯云最新爆款活动!1核2G云服务器首年50元!

 【源码】本文代码片段及相关软件,请点此获取更多信息

 【绝密】秘籍文章入口,仅传授于有缘之人   nginx