├── README.MD ├── config.lua ├── init.lua ├── main.lua └── rules ├── blackCookieArgs ├── blackGetArgs ├── blackPostArgs ├── blackUa ├── blackUri └── whiteUri /README.MD: -------------------------------------------------------------------------------- 1 | # LUAWAF 2 | A web application firewall(WAF) written in lua. 3 | ## How to use 4 | 1. Install OpenResty 5 | ```shell 6 | yum update -y 7 | yum install readline-devel pcre-devel openssl-devel perl gcc automake autoconf libtool make epel-release redis -y 8 | cd Downloads 9 | wget https://openresty.org/download/openresty-1.15.8.1.tar.gz 10 | tar -xzvf openresty-1.15.8.1.tar.gz 11 | cd openresty-1.15.8.1 12 | ./configure --with-luajit\ 13 | --with-http_iconv_module 14 | gmake 15 | gmake install 16 | echo export PATH=$PATH:/usr/local/openresty/nginx/sbin >> /etc/profile 17 | source /etc/profile 18 | ``` 19 | 2. Clone this project 20 | ```shell 21 | git clone https://github.com/wubonetcn/luawaf.git 22 | cp luawaf /usr/local/openresty/nginx/conf/ 23 | ``` 24 | 3. Edit `/usr/local/openresty/nginx/conf/nginx.conf` 25 | ``` 26 | vim /usr/local/openresty/nginx/conf/nginx.conf 27 | ``` 28 | /usr/local/openresty/nginx/conf/nginx.conf 29 | ``` 30 | #user nobody; 31 | worker_processes 1; 32 | 33 | error_log logs/error.log; 34 | #error_log logs/error.log notice; 35 | #error_log logs/error.log info; 36 | 37 | #pid logs/nginx.pid; 38 | 39 | 40 | events { 41 | worker_connections 1024; 42 | } 43 | 44 | 45 | http { 46 | include mime.types; 47 | default_type application/octet-stream; 48 | 49 | #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 50 | # '$status $body_bytes_sent "$http_referer" ' 51 | # '"$http_user_agent" "$http_x_forwarded_for"'; 52 | 53 | #access_log logs/access.log main; 54 | 55 | sendfile on; 56 | #tcp_nopush on; 57 | 58 | #keepalive_timeout 0; 59 | keepalive_timeout 65; 60 | 61 | #gzip on; 62 | 63 | server { 64 | listen 80; 65 | server_name localhost; 66 | 67 | #charset koi8-r; 68 | 69 | #access_log logs/host.access.log main; 70 | 71 | location / { 72 | root html; 73 | index index.html index.htm; 74 | } 75 | 76 | #error_page 404 /404.html; 77 | 78 | # redirect server error pages to the static page /50x.html 79 | # 80 | error_page 500 502 503 504 /50x.html; 81 | location = /50x.html { 82 | root html; 83 | } 84 | 85 | # proxy the PHP scripts to Apache listening on 127.0.0.1:80 86 | # 87 | #location ~ \.php$ { 88 | # proxy_pass http://127.0.0.1; 89 | #} 90 | 91 | # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 92 | # 93 | #location ~ \.php$ { 94 | # root html; 95 | # fastcgi_pass 127.0.0.1:9000; 96 | # fastcgi_index index.php; 97 | # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; 98 | # include fastcgi_params; 99 | #} 100 | 101 | # deny access to .htaccess files, if Apache's document root 102 | # concurs with nginx's one 103 | # 104 | #location ~ /\.ht { 105 | # deny all; 106 | #} 107 | } 108 | 109 | 110 | # another virtual host using mix of IP-, name-, and port-based configuration 111 | # 112 | #server { 113 | # listen 8000; 114 | # listen somename:8080; 115 | # server_name somename alias another.alias; 116 | 117 | # location / { 118 | # root html; 119 | # index index.html index.htm; 120 | # } 121 | #} 122 | 123 | 124 | # HTTPS server 125 | # 126 | #server { 127 | # listen 443 ssl; 128 | # server_name localhost; 129 | 130 | # ssl_certificate cert.pem; 131 | # ssl_certificate_key cert.key; 132 | 133 | # ssl_session_cache shared:SSL:1m; 134 | # ssl_session_timeout 5m; 135 | 136 | # ssl_ciphers HIGH:!aNULL:!MD5; 137 | # ssl_prefer_server_ciphers on; 138 | 139 | # location / { 140 | # root html; 141 | # index index.html index.htm; 142 | # } 143 | #} 144 | 145 | 146 | lua_package_path "/usr/local/openresty/nginx/conf/luawaf/?.lua;/usr/local/openresty/lualib/?.lua;;"; 147 | lua_shared_dict limit 10m; 148 | init_by_lua_file /usr/local/openresty/nginx/conf/luawaf/init.lua; 149 | access_by_lua_file /usr/local/openresty/nginx/conf/luawaf/main.lua; 150 | 151 | } 152 | ``` 153 | 4. Run Nginx 154 | ``` 155 | nginx 156 | ``` 157 | Enjoy it! 158 | ## Fix bug 159 | Please read this post. 160 | [《openresty的unescape_uri函数处理百分号后面字符的小特性》](https://www.cnxct.com/openresty-unescape_uri-feature-to-decode-char-after-percent-sign/) 161 | 162 | -------------------------------------------------------------------------------- /config.lua: -------------------------------------------------------------------------------- 1 | --WAF日志开关 2 | WafLog = "on" 3 | --日志文件夹 4 | LogPath = "/usr/local/openresty/nginx/logs/luawaf/" 5 | RulePath = "/usr/local/openresty/nginx/conf/luawaf/rules/" 6 | --配置白名单IP列表 7 | WhiteIpList = { "127.0.0.1" } 8 | --配置黑名单IP列表 9 | BlackIpList = { "1.0.0.1" } 10 | --是否开启CC攻击检查 11 | CheckCC = "off" 12 | --配置CC攻击频率阈值,次/秒 13 | CCFrequency = "100/60" 14 | --是否开启白名单URI检查 15 | CheckWhiteUri = "on" 16 | --是否开启黑名单URI检查 17 | CheckBlackUri = "on" 18 | --是否开启黑名单Cookie检查 19 | CheckBlackCookie = "on" 20 | --是否开启POST黑名单信息检查 21 | CheckBlackPostArgs = "on" 22 | --配置POST文件上传后缀黑名单 23 | BlackFileExt = { "php", "jsp" } 24 | --是否返回拦截页面 25 | Return403Page = "on" 26 | --配置403页面 27 | html = [[ 28 | 29 | 30 | 403 Forbidden 31 | 37 | 38 | 39 | 40 | 41 | 51 | 52 |
42 | 48 | 49 |
43 |

403

44 |

请求被拦截

45 |

你可能在网站管理员的黑名单里,或者你发起了不正常的请求。
46 | 联系管理员

47 |
50 |
53 | 54 | 55 | ]] 56 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 该版本的WAF默认使用redis存储拦截规则等配置信息,后期测试如性能受限将改用文件存储。生产环境中,Redis中的规则将由控制台控制。 3 | Redis存储拦截规则的优点是能够实时应用规则改动,缺点是比文件存储消耗资源多。是一个空间换时间的举措。 4 | --]] 5 | --[[ 6 | 该模块的主要作用是在nginx启动时加载变量和函数。 7 | --]] 8 | 9 | require("config") 10 | --------------------------------------------------------初始化 11 | --规则读取函数 12 | function read_rule(var) 13 | file = io.open(rulePath .. '/' .. var, "r") 14 | if file == nil then 15 | return 16 | end 17 | t = {} 18 | for line in file:lines() do 19 | table.insert(t, line) 20 | end 21 | file:close() 22 | return (t) 23 | end 24 | local optionIsOn = function(options) 25 | return options == "on" and true or false 26 | end 27 | -------------------------------------------------------变量集合 28 | whiteIpList = WhiteIpList 29 | blackIpList = BlackIpList 30 | CCFrequency = CCFrequency 31 | blackFileExt = BlackFileExt 32 | logPath = LogPath 33 | rulePath = RulePath 34 | checkBlackUri = optionIsOn(CheckBlackUri) 35 | checkBlackPostArgs = optionIsOn(CheckBlackPostArgs) 36 | checkBlackCookie = optionIsOn(CheckBlackCookie) 37 | checkWhiteUri = optionIsOn(CheckWhiteUri) 38 | PathInfoFix = optionIsOn(PathInfoFix) 39 | wafLog = optionIsOn(WafLog) 40 | checkCC = optionIsOn(CheckCC) 41 | return403Page = optionIsOn(Return403Page) 42 | blackUri = read_rule("blackUri") 43 | blackGetArgs = read_rule("blackGetArgs") 44 | blackUa = read_rule("blackUa") 45 | whiteUri = read_rule("whiteUri") 46 | blackPostArgs = read_rule("blackPostArgs") 47 | blackCookieArgs = read_rule("blackCookieArgs") 48 | 49 | -------------------------------------------------------变量集合 50 | 51 | -------------------------------------------------------工具函数 52 | --获取客户端IP 53 | function getClientIp() 54 | IP = ngx.var.remote_addr 55 | if IP == nil then 56 | IP = "unknown" 57 | end 58 | return IP 59 | end 60 | 61 | --写本地文件 62 | function write(file, msg) 63 | local fd = io.open(file, "ab") 64 | if fd == nil then 65 | return 66 | end 67 | fd:write(msg) 68 | fd:flush() 69 | fd:close() 70 | end 71 | 72 | --记录日志 73 | function log(method, url, data, Reason) 74 | if wafLog then 75 | local realIp = getClientIp() 76 | local ua = ngx.var.http_user_agent 77 | local servername = ngx.var.server_name 78 | local time = ngx.localtime() 79 | if ua then 80 | line = realIp .. " [" .. time .. "] \"" .. method .. " " .. servername .. url .. "\" \"" .. data .. "\" \"" .. ua .. "\" \"" .. Reason .. "\"\n" 81 | else 82 | line = realIp .. " [" .. time .. "] \"" .. method .. " " .. servername .. url .. "\" \"" .. data .. "\" - \"" .. Reason .. "\"\n" 83 | end 84 | local filename = logPath .. '/' .. servername .. "_" .. ngx.today() .. ".log" 85 | write(filename, line) 86 | end 87 | end 88 | 89 | --输出禁止访问页面 90 | function say_html() 91 | if return403Page then 92 | ngx.header.content_type = "text/html" 93 | ngx.say(html) 94 | ngx.exit(403) 95 | end 96 | end 97 | 98 | --检查黑名单文件后缀名 99 | function fileExtCheck(ext) 100 | local items = Set(blackFileExt) 101 | ext = string.lower(ext) 102 | if ext then 103 | for rule in pairs(items) do 104 | if ngx.re.match(ext, rule, "isjo") then 105 | return true 106 | end 107 | end 108 | end 109 | return false 110 | end 111 | 112 | --工具函数,转换table的格式 113 | function Set (list) 114 | local set = {} 115 | for _, l in ipairs(list) do 116 | set[l] = true 117 | end 118 | return set 119 | end 120 | 121 | --检查POST请求中URL编码的数据 122 | function checkEscapedData(data) 123 | for _, rule in pairs(blackPostArgs) do 124 | if rule ~= "" and data ~= "" and ngx.re.match(ngx.unescape_uri(ngx.unescape_uri(ngx.unescape_uri(data))), rule, "isjo") then 125 | 126 | return true 127 | end 128 | end 129 | return false 130 | end 131 | 132 | --检查是否是multipart/form-data编码的数据 133 | function isMultipartFormData() 134 | local header = ngx.req.get_headers()["content-type"] 135 | if not header then 136 | return nil 137 | end 138 | 139 | if type(header) == "table" then 140 | header = header[1] 141 | end 142 | 143 | --这里修改了源程序的匹配规则,因为当前浏览器已经不再在boundary=后面加""了,故删去。 144 | --为何这样匹配?因为浏览器表单设置enctype="multipart/form-data"时,传任何参数或者文件都会在content-type请求头里加入一个字符串。 145 | --这个字符串类似content-type:multipart/form-data; boundary=--------------------------924633240806182131117927 146 | --enctype="x-www-form-urlencoded"时,也可以传文件或者键值对给后端,但是传文件的功能受限,单独传文件将以二进制形式传递文件,除非文件中再次声明文件名,否则文件名将被忽略,也就是说除非后端程序刻意读取发送者刻意加的文件名,这种编码不会传递文件名,主动读取属于后端白名单需求,不予保护。 147 | local m = string.match(header, ";%s*boundary=([^\"]+)") 148 | if m then 149 | return m 150 | end 151 | 152 | return string.match(header, ";%s*boundary=([^\",;]+)") 153 | end 154 | 155 | 156 | -------------------------------------------------------工具函数 157 | --- 158 | ---函数顺序:按照检查顺序 159 | --- 160 | -------------------------------------------------------函数集合 161 | --白名单IP检查 162 | function isWhiteIp() 163 | if next(whiteIpList) ~= nil then 164 | for _, ip in pairs(whiteIpList) do 165 | if getClientIp() == ip then 166 | return true 167 | end 168 | end 169 | end 170 | return false 171 | end 172 | 173 | --黑名单IP检查 174 | function isBlackIp() 175 | if next(blackIpList) ~= nil then 176 | for _, ip in pairs(blackIpList) do 177 | if getClientIp() == ip then 178 | 179 | return true 180 | 181 | end 182 | end 183 | end 184 | return false 185 | end 186 | 187 | --CC攻击检查 188 | function isCcAttack() 189 | --开启检查CC开关才检查CC 190 | if checkCC then 191 | local uri = ngx.var.uri 192 | --CCcount/CCseconds,CCseconds 秒内有CCcount访问 193 | --CC阈值 194 | CCcount = tonumber(string.match(CCFrequency, '(.*)/')) 195 | --CC时间阈值 196 | CCseconds = tonumber(string.match(CCFrequency, '/(.*)')) 197 | --特定IP访问特定网址作为判断CC的键 198 | local token = getClientIp() .. uri 199 | --创建一个nginx字典,用来记录访问次数 200 | local limit = ngx.shared.limit 201 | --获取键对应的访问次数 202 | local req, _ = limit:get(token) 203 | --第一次访问或者到时低频访问req为nil 204 | if req then 205 | --CC时间阈值内访问次数大于次数阈值,就是CC攻击 206 | if req > CCcount then 207 | return true 208 | --未达到阈值,访问次数加一 209 | else 210 | limit:incr(token, 1) 211 | end 212 | else 213 | --第一次或者超过阈值键失效则新建记录。 214 | --在字典中存入token作为键,初始值为1,生存时间为CC时间阈值 215 | limit:set(token, 1, CCseconds) 216 | end 217 | end 218 | return false 219 | end 220 | 221 | --检查是不是扫描器 222 | function isScanner() 223 | --[[ 224 | Acunetix-Aspect 225 | Acunetix-Aspect-Password 226 | Acunetix-Aspect-Queries 227 | 以上三个是AWVS漏洞扫描工具的自带的请求头参数,是其特有字段。可借助这种特征识别出AWVS并及时阻止扫描。 228 | 以上三个字段有值,即发现AWVS扫描器。 229 | --]] 230 | if type(ngx.req.get_headers()['http_Acunetix_Aspect']) ~= "nil" then 231 | return true 232 | elseif type(ngx.req.get_headers()['http_Acunetix-Aspect-Password']) ~= "nil" then 233 | return true 234 | elseif type(ngx.req.get_headers()['http_Acunetix-Aspect-Queries']) ~= "nil" then 235 | return true 236 | --X_Scan_Memo是X-Scan漏洞扫描器的特征字段,发现其有值,即发现X-Scan漏洞扫描器。 237 | elseif type(ngx.req.get_headers()['http_X_Scan_Memo']) ~= "nil" then 238 | return true 239 | else 240 | return false 241 | end 242 | end 243 | 244 | --白名单URI检查 245 | function isWhiteUri() 246 | if checkWhiteUri then 247 | if whiteUri ~= nil then 248 | for _, rule in pairs(whiteUri) do 249 | if ngx.re.match(ngx.var.uri, rule, "isjo") then 250 | return true 251 | end 252 | end 253 | end 254 | end 255 | return false 256 | end 257 | 258 | --检查是否是黑名单中的UA 259 | function isBlackUa() 260 | local ua = ngx.var.http_user_agent 261 | if ua ~= nil then 262 | for _, rule in pairs(blackUa) do 263 | if rule ~= "" and ngx.re.match(ua, rule, "isjo") then 264 | 265 | return true 266 | end 267 | end 268 | end 269 | return false 270 | end 271 | 272 | --检查是否是黑名单中的URI 273 | function isBlackUri() 274 | if checkBlackUri then 275 | for _, rule in pairs(blackUri) do 276 | if rule ~= "" and ngx.re.match(ngx.var.request_uri, rule, "isjo") then 277 | 278 | return true 279 | end 280 | end 281 | end 282 | return false 283 | end 284 | 285 | --检查GET请求中是否有恶意字符串 286 | function isBlackGetArgs() 287 | for _, rule in pairs(blackGetArgs) do 288 | local args = ngx.req.get_uri_args() 289 | for key, val in pairs(args) do 290 | if type(val) == 'table' then 291 | local t = {} 292 | for k, v in pairs(val) do 293 | if v == true then 294 | v = "" 295 | end 296 | table.insert(t, v) 297 | end 298 | data = table.concat(t, " ") 299 | else 300 | data = val 301 | end 302 | if data and type(data) ~= "boolean" and rule ~= "" and ngx.re.match(ngx.unescape_uri(ngx.unescape_uri(ngx.unescape_uri(data))), rule, "isjo") then 303 | return true 304 | end 305 | end 306 | end 307 | return false 308 | end 309 | 310 | --检查Cookie中是否有恶意字符串 311 | function isBlackCookie() 312 | local ck = ngx.var.http_cookie 313 | if checkBlackCookie and ck then 314 | for _, rule in pairs(blackCookieArgs) do 315 | if rule ~= "" and ngx.re.match(ck, rule, "isjo") then 316 | return true 317 | end 318 | end 319 | end 320 | return false 321 | end 322 | 323 | function isBlackPostArgs() 324 | if checkBlackPostArgs then 325 | if ngx.req.get_method() == "POST" then 326 | --如果客户端使用了multipart/form-data,则说明表单中可能有文件,进入以下检查。 327 | if isMultipartFormData() then 328 | if isBlackMultipartFormData() then 329 | return true 330 | else 331 | return false 332 | end 333 | --如果客户端使用了x-www-form-urlencoded,一般说明请求中没有文件,只有键值对。如果是传输文件,那么文件没有文件名,只有文件二进制数据,也不排除后端程序使用这种方式传输文件,从文件二进制数据中获取文件名,这种后端主动白名单行为不在WAF保护范围内。 334 | else 335 | if isBlackXWwwFormUrlencodedData() then 336 | return true 337 | else 338 | return false 339 | end 340 | 341 | end 342 | end 343 | else 344 | return false 345 | end 346 | end 347 | 348 | --检查POST的multipart/form-data表单中是否有恶意字符串 349 | function isBlackMultipartFormData() 350 | --打开一个SOCKET用来接收数据 351 | local sock, err = ngx.req.socket() 352 | if not sock then 353 | return false 354 | end 355 | --为当前请求创建一个新请求体并初始化一个缓存区,大小为128KB。 356 | ngx.req.init_body(128 * 1024) 357 | --设置接收超时时间0,一旦接收不到数据就停止,不等待。 358 | sock:settimeout(0) 359 | --从headers里获取请求体大小 360 | local content_length = nil 361 | content_length = tonumber(ngx.req.get_headers()['content-length']) 362 | --默认接收块大小是4KB 363 | local chunk_size = 4096 364 | --如果请求体比默认接收块还小,就接收请求体大小的数据即可。 365 | if content_length < chunk_size then 366 | chunk_size = content_length 367 | end 368 | --size是当前已经接收的请求体总大小 369 | local size = 0 370 | --要是当前接收的请求体大小小于总大小就继续接收 371 | while size < content_length do 372 | --从打开的socket里读取默认接受块大小的数据 373 | --兼顾TCP和UDP的写法,UDP没有partial只有两个参数 374 | local data, err, partial = sock:receive(chunk_size) 375 | data = data or partial 376 | if not data then 377 | return false 378 | end 379 | --将读取到的数据追加到请求体里 380 | ngx.req.append_body(data) 381 | --检查当前读到的数据块 382 | --当前POST请求的某一参数在黑名单中,即checkEscapedData(data)为true,执行then后语句,记录恶意请求信息,返回错误页面,请求截断;当前POST请求的某一参数不在黑名单中,checkEscapedData(data)为false,程序向下运行; 383 | --检查不通过,结束读取,返回错误页面。 384 | if checkEscapedData(data) then 385 | return true 386 | end 387 | --给当前已经接收的请求体总大小加上刚刚读取到的数据大小。 388 | size = size + string.len(data) 389 | --使用效率较高的nginx的正则匹配,原作者代码中多写了一个\,已改正。 390 | --这一步是为了得到文件的后缀名 391 | local m = ngx.re.match(data, [[Content-Disposition: form-data;(.+)filename="(.+)\.(.*)"]], 'ijo') 392 | --原作者的规则并不能匹配同一表单中上传的多个文件,试了一早上,并没有改成匹配多文件的。 393 | --这里遗留一个BUG,待修复。 394 | if m then 395 | if fileExtCheck(m[3]) then 396 | return true 397 | end 398 | checkNext = false 399 | --如果POST请求中没有文件,将执行以下检查 400 | else 401 | --如果POST请求中有键值对,继续检查 402 | if ngx.re.match(data, "Content-Disposition:(.+)", 'isjo') then 403 | checkNext = true 404 | end 405 | if checkNext then 406 | --当前POST请求的某一参数在黑名单中,即checkEscapedData(data)为true,执行then后语句,记录恶意请求信息,返回错误页面,请求截断;当前POST请求的某一参数不在黑名单中,checkEscapedData(data)为false,程序向下运行; 407 | if checkEscapedData(data) then 408 | 409 | return true 410 | end 411 | end 412 | end 413 | --将最后剩余的请求体大小和当前默认块大小比较,如果剩余的比4KB还小最后一次就只读取剩余大小的数据。 414 | local less = content_length - size 415 | if less < chunk_size then 416 | chunk_size = less 417 | end 418 | end 419 | --结束multipart/form-data编码的POST表单读取 420 | ngx.req.finish_body() 421 | end 422 | 423 | --检查POST的x-www-form-urlencoded表单中是否有恶意字符串 424 | function isBlackXWwwFormUrlencodedData() 425 | --读取POST参数时,要先读取请求体 426 | ngx.req.read_body() 427 | --获取POST键值对 428 | local args = ngx.req.get_post_args() 429 | --如果键值对为空,直接通过 430 | if not args then 431 | return false 432 | end 433 | --有值,遍历检查每一个键值对 434 | for key, val in pairs(args) do 435 | --如果值时一个table或者数组,那么把其中的所有值都用,连接成一个字符串data; 436 | if type(val) == "table" then 437 | --三维数组跳过检测,这里遗留一个BUG,以后优化。 438 | if type(val[1]) == "table" then 439 | return false 440 | end 441 | data = table.concat(val, ", ") 442 | --如果值是一个字符串,那么就执行检查。 443 | else 444 | data = val 445 | end 446 | --先检查value再检查key 447 | if data and type(data) ~= "boolean" then 448 | if checkEscapedData(data) then 449 | return true 450 | end 451 | if checkEscapedData(key) then 452 | return true 453 | end 454 | return false 455 | end 456 | end 457 | 458 | end 459 | 460 | 461 | 462 | 463 | 464 | 465 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 遗留BUG: 3 | 1. [《openresty的unescape_uri函数处理百分号后面字符的小特性》](https://www.cnxct.com/openresty-unescape_uri-feature-to-decode-char-after-percent-sign/) 未解决 可以按照该篇文章中的方法重新编译OpenResty解决。 4 | ]] 5 | if isWhiteIp() then 6 | elseif isBlackIp() then 7 | ngx.exit(403) 8 | elseif isCcAttack() then 9 | ngx.exit(503) 10 | elseif isScanner() then 11 | ngx.exit(444) 12 | elseif isWhiteUri() then 13 | elseif isBlackUa() then 14 | log('UA', ngx.var.request_uri, "-", "当前UA在黑名单中") 15 | say_html() 16 | elseif isBlackUri() then 17 | log('GET', ngx.var.request_uri, "-", "当前请求的URI在黑名单中") 18 | say_html() 19 | elseif isBlackGetArgs() then 20 | log('GET', ngx.var.request_uri, "-", "当前GET请求的某一参数在黑名单中") 21 | say_html() 22 | elseif isBlackCookie() then 23 | log('Cookie', ngx.var.request_uri, "-", "当前cookie中的某一参数在黑名单中") 24 | say_html() 25 | elseif isBlackPostArgs() then 26 | log('POST', ngx.var.request_uri, data, "当前POST请求的某一参数在黑名单中") 27 | say_html() 28 | else 29 | return 30 | end 31 | -------------------------------------------------------------------------------- /rules/blackCookieArgs: -------------------------------------------------------------------------------- 1 | \.\./ 2 | \:\$ 3 | \$\{ 4 | select.+(from|limit) 5 | (?:(union(.*?)select)) 6 | having|rongjitest 7 | sleep\((\s*)(\d*)(\s*)\) 8 | benchmark\((.*)\,(.*)\) 9 | base64_decode\( 10 | (?:from\W+information_schema\W) 11 | (?:(?:current_)user|database|schema|connection_id)\s*\( 12 | (?:etc\/\W*passwd) 13 | into(\s+)+(?:dump|out)file\s* 14 | group\s+by.+\( 15 | xwork.MethodAccessor 16 | (?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\( 17 | xwork\.MethodAccessor 18 | (gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\:\/ 19 | java\.lang 20 | \$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\[ 21 | -------------------------------------------------------------------------------- /rules/blackGetArgs: -------------------------------------------------------------------------------- 1 | \.\./ 2 | \:\$ 3 | \$\{ 4 | select.+(from|limit) 5 | (?:(union(.*?)select)) 6 | having|rongjitest 7 | sleep\((\s*)(\d*)(\s*)\) 8 | benchmark\((.*)\,(.*)\) 9 | base64_decode\( 10 | (?:from\W+information_schema\W) 11 | (?:(?:current_)user|database|schema|connection_id)\s*\( 12 | (?:etc\/\W*passwd) 13 | into(\s+)+(?:dump|out)file\s* 14 | group\s+by.+\( 15 | xwork.MethodAccessor 16 | (?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\( 17 | xwork\.MethodAccessor 18 | (gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\:\/ 19 | java\.lang 20 | \$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\[ 21 | \<(iframe|script|body|img|layer|div|meta|style|base|object|input|svg|a) 22 | (onmouseover|onerror|onload)\= 23 | -------------------------------------------------------------------------------- /rules/blackPostArgs: -------------------------------------------------------------------------------- 1 | select.+(from|limit) 2 | (?:(union(.*?)select)) 3 | having|rongjitest 4 | sleep\((\s*)(\d*)(\s*)\) 5 | benchmark\((.*)\,(.*)\) 6 | base64_decode\( 7 | (?:from\W+information_schema\W) 8 | (?:(?:current_)user|database|schema|connection_id)\s*\( 9 | (?:etc\/\W*passwd) 10 | into(\s+)+(?:dump|out)file\s* 11 | group\s+by.+\( 12 | xwork.MethodAccessor 13 | (?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\( 14 | xwork\.MethodAccessor 15 | (gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\:\/ 16 | java\.lang 17 | \$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\[ 18 | \<(iframe|script|body|img|layer|div|meta|style|base|object|input|svg|a) 19 | (onmouseover|onerror|onload)\= 20 | -------------------------------------------------------------------------------- /rules/blackUa: -------------------------------------------------------------------------------- 1 | (HTTrack|harvest|audit|dirbuster|pangolin|nmap|sqln|-scan|hydra|Parser|libwww|BBBike|sqlmap|w3af|owasp|Nikto|fimap|havij|PycURL|zmeu|BabyKrokodil|netsparker|httperf|bench| SF/) 2 | -------------------------------------------------------------------------------- /rules/blackUri: -------------------------------------------------------------------------------- 1 | \.(svn|htaccess|bash_history) 2 | \.(bak|inc|old|mdb|sql|backup|java|class)$ 3 | (vhost|bbs|host|wwwroot|www|site|root|hytop|flashfxp).*\.rar 4 | (phpmyadmin|jmx-console|jmxinvokerservlet) 5 | java\.lang 6 | /(attachments|upimg|images|css|uploadfiles|html|uploads|templets|static|template|data|inc|forumdata|upload|includes|cache|avatar)/(\\w+).(php|jsp) 7 | -------------------------------------------------------------------------------- /rules/whiteUri: -------------------------------------------------------------------------------- 1 | ^/123/$ 2 | --------------------------------------------------------------------------------