服务部署

经过本地测试,通过Nginx直接编译安装Lua,在脚本实际执行过程中,很多方法不支持,无法达到预期结果。本文通过编译安装Tengine来实现。

Tengine是由淘宝发起的Web服务器项目。它在Nginx的基础上,针对大访问量网站的需求,添加了很多高级功能和特性,使用上与Nginx并无区别,而且对Lua的兼容性比较好。

本文所使用版本如下:

luajit2-2.1-20230911.tar.gz
tengine-3.0.0.tar.gz
lua-resty-core-0.1.28.tar.gz
lua-resty-lrucache-0.13.tar.gz
ngx_devel_kit-0.3.2.tar.gz
lua-cjson-2.1.0.13.tar.gz
lua-nginx-module-0.10.26.tar.gz
ngx_security_headers-0.0.11.tar.gz

1、解压各资源

tar -zxvf luajit2-2.1-20230911.tar.gz
tar -zxvf tengine-3.0.0.tar.gz
tar -zxvf ngx_devel_kit-0.3.2.tar.gz
tar -zxvf lua-resty-core-0.1.28.tar.gz
tar -zxvf lua-resty-lrucache-0.13.tar.gz
tar -zxvf lua-cjson-2.1.0.13.tar.gz
tar -zxvf lua-nginx-module-0.10.26.tar.gz
tar -zxvf ngx_security_headers-0.0.11.tar.gz

2、安装luajit

cd luajit2-2.1-20230911
make && make install
#导入环境变量
export LUAJIT_LIB=/usr/local/lib
export LUAJIT_INC=/usr/local/include/luajit-2.1

3、安装lua-resty-core、lua-resty-lrucache

cd lua-resty-core-0.1.28
make install LUA_LIB_DIR=/usr/local/share/lua/5.1

cd lua-resty-lrucache-0.13
make install LUA_LIB_DIR=/usr/local/share/lua/5.1

4、安装Tengine

cd tengine-3.0.0

./configure --prefix=/usr/local/tengine --add-module=../ngx_security_headers-0.0.11 --add-module=../ngx_devel_kit-0.3.2 --add-module=../lua-nginx-module-0.10.26

make && make install

5、访问测试

  • 进入/usr/local/tengine/conf目录下找到配置文件,更改nginx.conf配置文件。
  • 进入/usr/local/tengine/sbin目录,执行./nginx命令,启动服务。
  • 浏览器访问端口,查看是否启动成功。如果开启防火墙,记得打开端口。

6、安装cjson库

cd lua-cjson-2.1.0.13

vi Makefile
#修改路径为luajit安装路径
LUA_INCLUDE_DIR ?=   $(PREFIX)/include/luajit-2.1

make && make install

7、编写脚本

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;
    server_tokens off;
    security_headers on;
    hide_server_tokens on;

    server {
        listen       8080;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;
		
        #add_header Set-Cookie "Path=/; HttpOnly; Secure;SameSite=Strict";
    	add_header X-Download-Options "value";
    	add_header X-Permitted-Cross-Domain-Policies "value";
    	add_header Pragma no-store;
    	add_header Cache-Control "no-store";
    	add_header Referrer-Policy strict-origin-when-cross-origin;
        add_header Strict-Transport-Security "max-age=63072000" always;
        add_header X-Content-Type-Options nosniff;


		location ~ ^/.+/$ {
			return 403;
		}

		if ($request_uri ~* "\.\.") {
			return 403;
		}


        location / {
			lua_need_request_body on;
			default_type 'text/html';
			access_by_lua_block {
				local cjson = require "cjson";
				-- 校验referer
				local referer = ngx.var.http_referer;
				if refer then
					local _, _, host = string.find(referer, "^(https?://[^/]+)");
					local oldReferer = 'http://127.0.0.1:8080';
				    if oldRefererHttps ~= host then
					    ngx.status = 401;
						ngx.exit(ngx.HTTP_UNAUTHORIZED);
					end
				end 

				local request_method = ngx.req.get_method();
				-- 禁用delete请求
				if request_method == "DELETE" then
					ngx.status = 401;
					ngx.exit(ngx.HTTP_UNAUTHORIZED);
				end
				
				-- 校验get请求
				if request_method == "GET" then
					-- 获取get请求中参数名
					local args = ngx.req.get_uri_args();
					-- 检验参数中是否包含_
					if args["_"] then
						ngx.status = 401;
						ngx.exit(ngx.HTTP_UNAUTHORIZED);
					end

					-- 遍历参数值中是否包含sql关键字
					for index,value in pairs(args) do
						if type(value) == "string" and (value:find("and") or value:find("or")) then
							ngx.status = 401;
							ngx.exit(ngx.HTTP_UNAUTHORIZED);
						end
					end
				end
				
				-- 校验post请求
				if request_method == "POST" then
					-- 定义不允许的参数名列表
					local forbidden_params = {"isadmin","is_admin","_isadmin","isadmin_","issso","is_sso"};
					-- 读取 POST 请求体
					ngx.req.read_body();
					local data = ngx.req.get_body_data();
					local jsonData = cjson.decode(data);
					for index,value in pairs(jsonData) do
					   -- 检验参数名
					   for i,v in ipairs(forbidden_params) do
						  if v == index then
								ngx.status = 401;
								ngx.exit(ngx.HTTP_UNAUTHORIZED);
						   end
					   end
					   --检验参数值
					   if type(value) == "string" and (value:find("and") or value:find("or")) then
						   ngx.status = 401;
						   ngx.exit(ngx.HTTP_UNAUTHORIZED);
					   end
					end
				end
			}

            proxy_cookie_path / "/; Secure;HttpOnly;SameSite=Strict";
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-Proto https;
            proxy_set_header REMOTE-HOST $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass http://127.0.0.1:8081;
        }
        
        #error_page  404              /404.html;
        # redirect server error pages to the static page /50x.html
        #error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

    }

}

8、脚本测试

  • 重启Nginx,进入/usr/local/tengine/sbin目录,执行./nginx -s reload 命令。
  • 发起请求添加参数测试,只有脚本校验通过后请求才会到实际后端。