mirror of
				https://github.com/1Panel-dev/1Panel.git
				synced 2025-10-31 03:07:34 +08:00 
			
		
		
		
	feat(waf): 增加全局设置 (#4088)
This commit is contained in:
		
							parent
							
								
									fe717d993b
								
							
						
					
					
						commit
						21f24f2e10
					
				
					 12 changed files with 224 additions and 33 deletions
				
			
		|  | @ -166,3 +166,7 @@ CutWebsiteLogSuccess: "{{ .name }} website log cut successfully, backup path {{ | |||
| #toolbox | ||||
| ErrNotExistUser: "The current user does not exist. Please modify and retry!" | ||||
| ErrBanAction: "Setting failed, the current {{ .name }} service is unavailable, please check and try again!" | ||||
| 
 | ||||
| #waf | ||||
| ErrScope: "Modification of this configuration is not supported" | ||||
| ErrStateChange: "State modification failed" | ||||
|  | @ -167,3 +167,7 @@ CutWebsiteLogSuccess: "{{ .name }} 網站日誌切割成功,備份路徑 {{ .p | |||
| #toolbox | ||||
| ErrNotExistUser: "當前使用者不存在,請修改後重試!" | ||||
| ErrBanAction: "設置失敗,當前 {{ .name }} 服務不可用,請檢查後重試!" | ||||
| 
 | ||||
| #waf | ||||
| ErrScope: "不支援修改此配置" | ||||
| ErrStateChange: "狀態修改失敗" | ||||
|  |  | |||
|  | @ -165,4 +165,8 @@ CutWebsiteLogSuccess: "{{ .name }} 网站日志切割成功,备份路径 {{ .p | |||
| 
 | ||||
| #toolbox | ||||
| ErrNotExistUser: "当前用户不存在,请修改后重试!" | ||||
| ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!" | ||||
| ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!" | ||||
| 
 | ||||
| #waf | ||||
| ErrScope: "不支持修改此配置" | ||||
| ErrStateChange: "状态修改失败" | ||||
							
								
								
									
										56
									
								
								frontend/src/components/config-card/index.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								frontend/src/components/config-card/index.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| <template> | ||||
|     <el-card class="config-card"> | ||||
|         <div class="config-header"> | ||||
|             <span>{{ header }}</span> | ||||
|             <slot name="header-r" /> | ||||
|         </div> | ||||
|         <el-text type="info">{{ description }}</el-text> | ||||
|         <div class="config-content"> | ||||
|             <slot name="content-r" /> | ||||
|         </div> | ||||
|         <div class="config-form"> | ||||
|             <slot name="content-form" /> | ||||
|         </div> | ||||
|     </el-card> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts"> | ||||
| defineOptions({ name: 'ConfigCard' }); | ||||
| defineProps({ | ||||
|     header: String, | ||||
|     description: String, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .config-card { | ||||
|     cursor: pointer; | ||||
|     margin: 10px; | ||||
|     .config-header { | ||||
|         margin-bottom: 18px; | ||||
|         display: flex; | ||||
|         justify-content: space-between; | ||||
|         align-items: center; | ||||
|         span { | ||||
|             font-weight: normal; | ||||
|             font-size: 18px; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     .config-content { | ||||
|         display: flex; | ||||
|         justify-content: flex-end; | ||||
|     } | ||||
| 
 | ||||
|     .config-form { | ||||
|         margin-top: 20px; | ||||
|     } | ||||
| } | ||||
| .config-card { | ||||
|     border: var(--panel-border) !important; | ||||
|     &:hover { | ||||
|         cursor: pointer; | ||||
|         border: 1px solid var(--el-color-primary) !important; | ||||
|     } | ||||
| } | ||||
| </style> | ||||
|  | @ -2157,7 +2157,7 @@ const message = { | |||
|                 request: 'request', | ||||
|                 count4xx: '4xx quantity', | ||||
|                 count5xx: '5xx quantity', | ||||
|                 todayStatus: 'Today status', | ||||
|                 todayStatus: 'Today Status', | ||||
|                 reqMap: 'Request map (30 days)', | ||||
|                 resource: 'source', | ||||
|                 count: 'Quantity', | ||||
|  | @ -2167,6 +2167,45 @@ const message = { | |||
|                 interceptCount: 'Interception number', | ||||
|                 requestTrends: 'Request Trends (7 days)', | ||||
|                 interceptTrends: 'Intercept Trends (7 days)', | ||||
|                 whiteList: 'whitelist', | ||||
|                 blackList: 'blacklist', | ||||
|                 ipBlackListHelper: 'IPs in the blacklist cannot access the website', | ||||
|                 ipWhiteListHelper: 'IPs in the whitelist are not restricted by any rules', | ||||
|                 uaBlackListHelper: 'Requests carrying User-Agent in the blacklist will be intercepted', | ||||
|                 uaWhiteListHelper: 'Requests carrying User-Agent in the whitelist are not restricted by any rules', | ||||
|                 urlBlackListHelper: 'Requests for URLs in the blacklist will be intercepted', | ||||
|                 urlWhiteListHelper: 'Requests for URLs in the whitelist are not restricted by any rules', | ||||
|                 ccHelper: | ||||
|                     'If any website has been requested more than {1} times in {0} seconds, this IP will be blocked for {2} seconds', | ||||
|                 blockTime: 'Block time', | ||||
|                 attackHelper: | ||||
|                     'The cumulative interception exceeds {1} times within {0} seconds, block this IP for {2} seconds', | ||||
|                 notFoundHelper: | ||||
|                     'The cumulative request returned 404 more than {1} times within {0} seconds, block this IP for {2} seconds', | ||||
|                 frequencyLimit: 'frequency limit', | ||||
|                 regionLimit: 'region limit', | ||||
|                 defaultRule: 'Default rule', | ||||
|                 accessFrequencyLmit: 'Access frequency limit', | ||||
|                 attackLimit: 'Attack frequency limit', | ||||
|                 notFoundLimit: '404 frequency limit', | ||||
|                 urlLimit: 'URL frequency limit', | ||||
|                 urlLimitHelper: 'Set access frequency for a single URL', | ||||
|                 sqliDefense: 'SQL injection defense', | ||||
|                 sqliHelper: 'Identify SQL injection in requests and intercept', | ||||
|                 xssHelper: 'Identify XSS in the request and intercept it', | ||||
|                 xssDefense: 'XSS Defense', | ||||
|                 uaDefense: 'Malicious User-Agent Rule', | ||||
|                 uaHelper: 'Contains common malicious crawler rules', | ||||
|                 argsDefense: 'Malicious parameter rules', | ||||
|                 argsHelper: 'Prohibit malicious parameters in requests', | ||||
|                 cookieDefense: 'Malicious Cookie Rule', | ||||
|                 cookieHelper: 'Prohibit malicious cookies from being carried in requests', | ||||
|                 headerDefense: 'Malicious Header Rule', | ||||
|                 headerHelper: 'Prohibit requests from containing malicious headers', | ||||
|                 httpRule: 'HTTP request method rules', | ||||
|                 httpHelper: 'Restrict the request method type of the website', | ||||
|                 geoRule: 'Regional access restrictions', | ||||
|                 geoHelper: 'Restrict access to your website from certain regions', | ||||
|             }, | ||||
|             monitor: { | ||||
|                 name: 'Website Monitoring', | ||||
|  |  | |||
|  | @ -2025,6 +2025,42 @@ const message = { | |||
|                 interceptCount: '攔截數', | ||||
|                 requestTrends: '請求趨勢(7天)', | ||||
|                 interceptTrends: '攔截趨勢(7天)', | ||||
|                 whiteList: '白名單', | ||||
|                 blackList: '黑名單', | ||||
|                 ipBlackListHelper: '黑名單中的 IP 無法存取網站', | ||||
|                 ipWhiteListHelper: '白名單中的 IP 不受任何規則限制', | ||||
|                 uaBlackListHelper: '攜帶黑名單中的 User-Agent 的請求將被攔截', | ||||
|                 uaWhiteListHelper: '攜帶白名單中的 User-Agent 的請求不受任何規則限制', | ||||
|                 urlBlackListHelper: '請求黑名單中的 URL 將被攔截', | ||||
|                 urlWhiteListHelper: '請求白名單中的 URL 請求不受任何規則限制', | ||||
|                 ccHelper: '{0} 秒內累積請求任意網站超過 {1} 次,封鎖此 IP {2} 秒', | ||||
|                 blockTime: '封鎖時間', | ||||
|                 attackHelper: '{0} 秒內累計攔截超過 {1} 次,封鎖此 IP {2} 秒', | ||||
|                 notFoundHelper: '{0} 秒內累計請求回傳 404 超過 {1} 次,封鎖此 IP {2} 秒', | ||||
|                 frequencyLimit: '頻率限制', | ||||
|                 regionLimit: '地區限制', | ||||
|                 defaultRule: '預設規則', | ||||
|                 accessFrequencyLmit: '存取頻率限制', | ||||
|                 attackLimit: '攻擊頻率限制', | ||||
|                 notFoundLimit: '404 頻率限制', | ||||
|                 urlLimit: 'URL 頻率限制', | ||||
|                 urlLimitHelper: '為單一 URL 設定存取頻率', | ||||
|                 sqliDefense: 'SQL 注入防禦', | ||||
|                 sqliHelper: '辨識請求中的 SQL 注入並攔截', | ||||
|                 xssHelper: '辨識請求中的 XSS 並攔截', | ||||
|                 xssDefense: 'XSS 防禦', | ||||
|                 uaDefense: '惡意 User-Agent 規則', | ||||
|                 uaHelper: '包含常見的惡意爬蟲規則', | ||||
|                 argsDefense: '惡意參數規則', | ||||
|                 argsHelper: '在禁止請求中攜帶惡意參數', | ||||
|                 cookieDefense: '惡意 Cookie 規則', | ||||
|                 cookieHelper: '禁止請求中攜帶惡意 Cookie', | ||||
|                 headerDefense: '惡意 Header 規則', | ||||
|                 headerHelper: '禁止請求中攜帶惡意 Header', | ||||
|                 httpRule: 'HTTP 請求方法規則', | ||||
|                 httpHelper: '限制網站的請求方法類型', | ||||
|                 geoRule: '地區存取限制', | ||||
|                 geoHelper: '限制某些地區造訪你的網站', | ||||
|             }, | ||||
|             monitor: { | ||||
|                 name: '網站監控', | ||||
|  |  | |||
|  | @ -2026,6 +2026,42 @@ const message = { | |||
|             interceptCount: '拦截数', | ||||
|             requestTrends: '请求趋势(7日)', | ||||
|             interceptTrends: '拦截趋势(7日)', | ||||
|             whiteList: '白名单', | ||||
|             blackList: '黑名单', | ||||
|             ipBlackListHelper: '黑名单中的 IP 无法访问网站', | ||||
|             ipWhiteListHelper: '白名单中的 IP 不受任何规则限制', | ||||
|             uaBlackListHelper: '携带黑名单中的 User-Agent 的请求将被拦截', | ||||
|             uaWhiteListHelper: '携带白名单中的 User-Agent 的请求不受任何规则限制', | ||||
|             urlBlackListHelper: '请求黑名单中的 URL 将被拦截', | ||||
|             urlWhiteListHelper: '请求白名单中的 URL 请求不受任何规则限制', | ||||
|             ccHelper: '{0} 秒内累计请求任意网站超过 {1} 次,封锁此 IP {2} 秒', | ||||
|             blockTime: '封禁时间', | ||||
|             attackHelper: '{0} 秒内累计拦截超过 {1} 次,封锁此 IP {2} 秒', | ||||
|             notFoundHelper: '{0} 秒内累计请求返回 404 超过 {1} 次,封锁此 IP {2} 秒', | ||||
|             frequencyLimit: '频率限制', | ||||
|             regionLimit: '地区限制', | ||||
|             defaultRule: '默认规则', | ||||
|             accessFrequencyLmit: '访问频率限制', | ||||
|             attackLimit: '攻击频率限制', | ||||
|             notFoundLimit: '404 频率限制', | ||||
|             urlLimit: 'URL 频率限制', | ||||
|             urlLimitHelper: '为单个 URL 设置访问频率', | ||||
|             sqliDefense: 'SQL 注入防御', | ||||
|             sqliHelper: '识别请求中的 SQL 注入并拦截', | ||||
|             xssHelper: '识别请求中的 XSS 并拦截', | ||||
|             xssDefense: 'XSS 防御', | ||||
|             uaDefense: '恶意 User-Agent 规则', | ||||
|             uaHelper: '包含常见的恶意爬虫规则', | ||||
|             argsDefense: '恶意参数规则', | ||||
|             argsHelper: '禁止请求中携带恶意参数', | ||||
|             cookieDefense: '恶意 Cookie 规则', | ||||
|             cookieHelper: '禁止请求中携带恶意 Cookie', | ||||
|             headerDefense: '恶意 Header 规则', | ||||
|             headerHelper: '禁止请求中携带恶意 Header', | ||||
|             httpRule: 'HTTP 请求方法规则', | ||||
|             httpHelper: '限制网站的请求方法类型', | ||||
|             geoRule: '地区访问限制', | ||||
|             geoHelper: '限制某些地区访问你的网站', | ||||
|         }, | ||||
|         monitor: { | ||||
|             name: '网站监控', | ||||
|  |  | |||
|  | @ -117,7 +117,7 @@ | |||
|     "action": "deny", | ||||
|     "code": 403, | ||||
|     "type": "fileExtCheck", | ||||
|     "extList": [ | ||||
|     "rules": [ | ||||
|       "php", | ||||
|       "jsp", | ||||
|       "asp", | ||||
|  |  | |||
|  | @ -531,7 +531,7 @@ function _M.post_check() | |||
|                 local match = ngx_re_match(m[0], 'Content-Disposition: form-data; (.+)filename="(.+)\\.(.*)"', 'ijo') | ||||
|                 if match then | ||||
|                     local extension = match[3] | ||||
|                     for _, ext in ipairs(rule.extList) do | ||||
|                     for _, ext in ipairs(rule.rules) do | ||||
|                         if extension == ext then | ||||
|                             exec_action(rule) | ||||
|                         end | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| local geoip = require "geoip" | ||||
| local sub_str = string.sub | ||||
| local pairs = pairs | ||||
| local insert_table = table.insert | ||||
|  | @ -6,6 +7,7 @@ local ipairs = ipairs | |||
| local type = type | ||||
| local find_str = string.find | ||||
| local gmatch_str = string.gmatch | ||||
| local cjson = require "cjson" | ||||
| 
 | ||||
| local _M = {} | ||||
| 
 | ||||
|  | @ -114,6 +116,32 @@ function _M.get_real_ip() | |||
|     return "unknown" | ||||
| end | ||||
| 
 | ||||
| function _M.get_geo_ip(ip) | ||||
|     if _M.is_intranet_address(ip) then | ||||
|         return { | ||||
|             country = { ["zh"] = "内网", ["en"] = "intranet" }, | ||||
|             province = { ["zh"] = "内网", ["en"] = "intranet" }, | ||||
|             city = { ["zh"] = "内网", ["en"] = "intranet" }, | ||||
|             longitude = 0, | ||||
|             latitude = 0, | ||||
|             iso = "local" | ||||
|         } | ||||
|     else | ||||
|         geoip.init() | ||||
|         local geo_res = geoip.lookup(ip) | ||||
|         local msg = "访问 IP  " .. ip | ||||
|         if geo_res.country then | ||||
|             msg = msg .. " 国家 " .. cjson.encode(geo_res.country) | ||||
|         end | ||||
|         if geo_res.province then | ||||
|             msg = msg .. " 省份 " .. cjson.encode(geo_res.province) | ||||
|         end | ||||
|         ngx.log(ngx.ERR, msg) | ||||
|         return  geo_res | ||||
|          | ||||
|     end | ||||
| end | ||||
| 
 | ||||
| function _M.get_header(headerKey) | ||||
|     return ngx.req.get_headers(20000)[headerKey] | ||||
| end | ||||
|  |  | |||
|  | @ -215,6 +215,16 @@ if config.is_waf_on() then | |||
|     count_not_found() | ||||
|     local is_attack = ngx.ctx.is_attack | ||||
|      | ||||
|     if not ngx.ctx.ip then | ||||
|         ngx.ctx.ip = utils.get_real_ip() | ||||
|         ngx.ctx.geoip = utils.get_geo_ip(ngx.ctx.ip) | ||||
|         local ua = utils.get_header("user-agent") | ||||
|         if not ua then | ||||
|             ua = "" | ||||
|         end | ||||
|     end | ||||
|      | ||||
|      | ||||
|     local wafdb = utils.get_wafdb(config.waf_db_path) | ||||
|     if wafdb ~= nil then | ||||
|         count_req_status(wafdb,is_attack) | ||||
|  |  | |||
|  | @ -42,20 +42,7 @@ local function get_website_key() | |||
|     return s_name | ||||
| end | ||||
| 
 | ||||
| local function get_geo_ip(ip) | ||||
|     if utils.is_intranet_address(ip) then | ||||
|         return { | ||||
|             country = { ["zh"] = "内网", ["en"] = "intranet" }, | ||||
|             province = { ["zh"] = "内网", ["en"] = "intranet" }, | ||||
|             city = { ["zh"] = "内网", ["en"] = "intranet" }, | ||||
|             longitude = 0, | ||||
|             latitude = 0, | ||||
|             iso = "local" | ||||
|         } | ||||
|     else | ||||
|         return geoip.lookup(ip) | ||||
|     end | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| local function init() | ||||
|     local ip = utils.get_real_ip() | ||||
|  | @ -64,21 +51,10 @@ local function init() | |||
|     if not ua then | ||||
|         ua = "" | ||||
|     end | ||||
| 
 | ||||
|     geoip.init() | ||||
|      | ||||
|     ngx.ctx.ua = ua | ||||
|     ngx.ctx.geoip = get_geo_ip(ip) | ||||
| 
 | ||||
|     local msg = "访问 IP  " .. ip | ||||
|     if ngx.ctx.geoip.country then | ||||
|         msg = msg .. " 国家 " .. cjson.encode(ngx.ctx.geoip.country) | ||||
|     end | ||||
|     if ngx.ctx.geoip.province then | ||||
|         msg = msg .. " 省份 " .. cjson.encode(ngx.ctx.geoip.province) | ||||
|     end | ||||
|     ngx.log(ngx.ERR, msg) | ||||
| 
 | ||||
|     ngx.ctx.geoip = utils.get_geo_ip(ip) | ||||
|      | ||||
|     ngx.ctx.website_key = get_website_key() | ||||
|     ngx.ctx.method = ngx.req.get_method() | ||||
|     ngx.ctx.content_type = utils.get_header("content-type") | ||||
|  | @ -144,8 +120,6 @@ local function waf_api() | |||
|     end | ||||
| end | ||||
| 
 | ||||
| ngx.log(ngx.ERR,"access waf") | ||||
| 
 | ||||
| if config.is_waf_on() then | ||||
|     init() | ||||
|     waf_api() | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue