mirror of
https://github.com/TeaOSLab/EdgeNode.git
synced 2025-11-16 10:00:26 +08:00
WAF人机识别实现点击验证和滑动解锁验证/单个网站可以设置默认的人机识别方式
This commit is contained in:
@@ -257,7 +257,7 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
goNext, hasRequestBody, ruleGroup, ruleSet, err := w.MatchRequest(this, this.writer)
|
goNext, hasRequestBody, ruleGroup, ruleSet, err := w.MatchRequest(this, this.writer, this.web.FirewallRef.DefaultCaptchaType)
|
||||||
if forceLog && logRequestBody && hasRequestBody && ruleSet != nil && ruleSet.HasAttackActions() {
|
if forceLog && logRequestBody && hasRequestBody && ruleSet != nil && ruleSet.HasAttackActions() {
|
||||||
this.wafHasRequestBody = true
|
this.wafHasRequestBody = true
|
||||||
}
|
}
|
||||||
@@ -307,7 +307,7 @@ func (this *HTTPRequest) doWAFResponse(resp *http.Response) (blocked bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if this.web.FirewallPolicy != nil && this.web.FirewallPolicy.IsOn {
|
if this.web.FirewallPolicy != nil && this.web.FirewallPolicy.IsOn {
|
||||||
blocked := this.checkWAFResponse(this.web.FirewallPolicy, resp, forceLog, forceLogRequestBody, false)
|
blocked = this.checkWAFResponse(this.web.FirewallPolicy, resp, forceLog, forceLogRequestBody, false)
|
||||||
if blocked {
|
if blocked {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -315,7 +315,7 @@ func (this *HTTPRequest) doWAFResponse(resp *http.Response) (blocked bool) {
|
|||||||
|
|
||||||
// 公用的防火墙设置
|
// 公用的防火墙设置
|
||||||
if this.ReqServer.HTTPFirewallPolicy != nil && this.ReqServer.HTTPFirewallPolicy.IsOn {
|
if this.ReqServer.HTTPFirewallPolicy != nil && this.ReqServer.HTTPFirewallPolicy.IsOn {
|
||||||
blocked := this.checkWAFResponse(this.ReqServer.HTTPFirewallPolicy, resp, forceLog, forceLogRequestBody, this.web.FirewallRef.IgnoreGlobalRules)
|
blocked = this.checkWAFResponse(this.ReqServer.HTTPFirewallPolicy, resp, forceLog, forceLogRequestBody, this.web.FirewallRef.IgnoreGlobalRules)
|
||||||
if blocked {
|
if blocked {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package waf
|
package waf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||||
@@ -27,6 +28,8 @@ type CaptchaAction struct {
|
|||||||
|
|
||||||
CountLetters int8 `yaml:"countLetters" json:"countLetters"`
|
CountLetters int8 `yaml:"countLetters" json:"countLetters"`
|
||||||
|
|
||||||
|
CaptchaType firewallconfigs.CaptchaType `yaml:"captchaType" json:"captchaType"`
|
||||||
|
|
||||||
UIIsOn bool `yaml:"uiIsOn" json:"uiIsOn"` // 是否使用自定义UI
|
UIIsOn bool `yaml:"uiIsOn" json:"uiIsOn"` // 是否使用自定义UI
|
||||||
UITitle string `yaml:"uiTitle" json:"uiTitle"` // 消息标题
|
UITitle string `yaml:"uiTitle" json:"uiTitle"` // 消息标题
|
||||||
UIPrompt string `yaml:"uiPrompt" json:"uiPrompt"` // 消息提示
|
UIPrompt string `yaml:"uiPrompt" json:"uiPrompt"` // 消息提示
|
||||||
@@ -36,6 +39,22 @@ type CaptchaAction struct {
|
|||||||
UIFooter string `yaml:"uiFooter" json:"uiFooter"` // 页脚
|
UIFooter string `yaml:"uiFooter" json:"uiFooter"` // 页脚
|
||||||
UIBody string `yaml:"uiBody" json:"uiBody"` // 内容轮廓
|
UIBody string `yaml:"uiBody" json:"uiBody"` // 内容轮廓
|
||||||
|
|
||||||
|
OneClickUIIsOn bool `yaml:"oneClickUIIsOn" json:"oneClickUIIsOn"` // 是否使用自定义UI
|
||||||
|
OneClickUITitle string `yaml:"oneClickUITitle" json:"oneClickUITitle"` // 消息标题
|
||||||
|
OneClickUIPrompt string `yaml:"oneClickUIPrompt" json:"oneClickUIPrompt"` // 消息提示
|
||||||
|
OneClickUIShowRequestId bool `yaml:"oneClickUIShowRequestId" json:"oneClickUIShowRequestId"` // 是否显示请求ID
|
||||||
|
OneClickUICss string `yaml:"oneClickUICss" json:"oneClickUICss"` // CSS样式
|
||||||
|
OneClickUIFooter string `yaml:"oneClickUIFooter" json:"oneClickUIFooter"` // 页脚
|
||||||
|
OneClickUIBody string `yaml:"oneClickUIBody" json:"oneClickUIBody"` // 内容轮廓
|
||||||
|
|
||||||
|
SlideUIIsOn bool `yaml:"sliceUIIsOn" json:"sliceUIIsOn"` // 是否使用自定义UI
|
||||||
|
SlideUITitle string `yaml:"slideUITitle" json:"slideUITitle"` // 消息标题
|
||||||
|
SlideUIPrompt string `yaml:"slideUIPrompt" json:"slideUIPrompt"` // 消息提示
|
||||||
|
SlideUIShowRequestId bool `yaml:"SlideUIShowRequestId" json:"SlideUIShowRequestId"` // 是否显示请求ID
|
||||||
|
SlideUICss string `yaml:"slideUICss" json:"slideUICss"` // CSS样式
|
||||||
|
SlideUIFooter string `yaml:"slideUIFooter" json:"slideUIFooter"` // 页脚
|
||||||
|
SlideUIBody string `yaml:"slideUIBody" json:"slideUIBody"` // 内容轮廓
|
||||||
|
|
||||||
Lang string `yaml:"lang" json:"lang"` // 语言,zh-CN, en-US ...
|
Lang string `yaml:"lang" json:"lang"` // 语言,zh-CN, en-US ...
|
||||||
AddToWhiteList bool `yaml:"addToWhiteList" json:"addToWhiteList"` // 是否加入到白名单
|
AddToWhiteList bool `yaml:"addToWhiteList" json:"addToWhiteList"` // 是否加入到白名单
|
||||||
Scope string `yaml:"scope" json:"scope"`
|
Scope string `yaml:"scope" json:"scope"`
|
||||||
@@ -81,6 +100,10 @@ func (this *CaptchaAction) Init(waf *WAF) error {
|
|||||||
if len(this.Lang) == 0 {
|
if len(this.Lang) == 0 {
|
||||||
this.Lang = waf.DefaultCaptchaAction.Lang
|
this.Lang = waf.DefaultCaptchaAction.Lang
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(this.CaptchaType) == 0 {
|
||||||
|
this.CaptchaType = waf.DefaultCaptchaAction.CaptchaType
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -3,11 +3,16 @@ package waf
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||||
"github.com/dchest/captcha"
|
"github.com/dchest/captcha"
|
||||||
"github.com/iwind/TeaGo/logs"
|
"github.com/iwind/TeaGo/logs"
|
||||||
|
"github.com/iwind/TeaGo/rands"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
|
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -23,7 +28,7 @@ func NewCaptchaValidator() *CaptchaValidator {
|
|||||||
return &CaptchaValidator{}
|
return &CaptchaValidator{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *CaptchaValidator) Run(req requests.Request, writer http.ResponseWriter) {
|
func (this *CaptchaValidator) Run(req requests.Request, writer http.ResponseWriter, defaultCaptchaType firewallconfigs.ServerCaptchaType) {
|
||||||
var info = req.WAFRaw().URL.Query().Get("info")
|
var info = req.WAFRaw().URL.Query().Get("info")
|
||||||
if len(info) == 0 {
|
if len(info) == 0 {
|
||||||
req.ProcessResponseHeaders(writer.Header(), http.StatusBadRequest)
|
req.ProcessResponseHeaders(writer.Header(), http.StatusBadRequest)
|
||||||
@@ -71,16 +76,39 @@ func (this *CaptchaValidator) Run(req requests.Request, writer http.ResponseWrit
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var captchaType = captchaActionConfig.CaptchaType
|
||||||
|
if len(defaultCaptchaType) > 0 && defaultCaptchaType != firewallconfigs.ServerCaptchaTypeNone {
|
||||||
|
captchaType = defaultCaptchaType
|
||||||
|
}
|
||||||
|
|
||||||
if req.WAFRaw().Method == http.MethodPost && len(req.WAFRaw().FormValue("GOEDGE_WAF_CAPTCHA_ID")) > 0 {
|
if req.WAFRaw().Method == http.MethodPost && len(req.WAFRaw().FormValue("GOEDGE_WAF_CAPTCHA_ID")) > 0 {
|
||||||
this.validate(captchaActionConfig, policyId, groupId, setId, originURL, req, writer)
|
switch captchaType {
|
||||||
|
case firewallconfigs.CaptchaTypeOneClick:
|
||||||
|
this.validateOneClickForm(captchaActionConfig, policyId, groupId, setId, originURL, req, writer)
|
||||||
|
case firewallconfigs.CaptchaTypeSlide:
|
||||||
|
this.validateSlideForm(captchaActionConfig, policyId, groupId, setId, originURL, req, writer)
|
||||||
|
default:
|
||||||
|
this.validateVerifyCodeForm(captchaActionConfig, policyId, groupId, setId, originURL, req, writer)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 增加计数
|
// 增加计数
|
||||||
CaptchaIncreaseFails(req, captchaActionConfig, policyId, groupId, setId, CaptchaPageCodeShow)
|
CaptchaIncreaseFails(req, captchaActionConfig, policyId, groupId, setId, CaptchaPageCodeShow)
|
||||||
this.show(captchaActionConfig, req, writer)
|
this.show(captchaActionConfig, req, writer, captchaType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *CaptchaValidator) show(actionConfig *CaptchaAction, req requests.Request, writer http.ResponseWriter) {
|
func (this *CaptchaValidator) show(actionConfig *CaptchaAction, req requests.Request, writer http.ResponseWriter, captchaType firewallconfigs.ServerCaptchaType) {
|
||||||
|
switch captchaType {
|
||||||
|
case firewallconfigs.CaptchaTypeOneClick:
|
||||||
|
this.showOneClickForm(actionConfig, req, writer)
|
||||||
|
case firewallconfigs.CaptchaTypeSlide:
|
||||||
|
this.showSlideForm(actionConfig, req, writer)
|
||||||
|
default:
|
||||||
|
this.showVerifyCodesForm(actionConfig, req, writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *CaptchaValidator) showVerifyCodesForm(actionConfig *CaptchaAction, req requests.Request, writer http.ResponseWriter) {
|
||||||
// show captcha
|
// show captcha
|
||||||
var countLetters = 6
|
var countLetters = 6
|
||||||
if actionConfig.CountLetters > 0 && actionConfig.CountLetters <= 10 {
|
if actionConfig.CountLetters > 0 && actionConfig.CountLetters <= 10 {
|
||||||
@@ -168,7 +196,7 @@ func (this *CaptchaValidator) show(actionConfig *CaptchaAction, req requests.Req
|
|||||||
<img src="data:image/png;base64, ` + base64.StdEncoding.EncodeToString(buf.Bytes()) + `"/>` + `
|
<img src="data:image/png;base64, ` + base64.StdEncoding.EncodeToString(buf.Bytes()) + `"/>` + `
|
||||||
</div>
|
</div>
|
||||||
<div class="ui-input">
|
<div class="ui-input">
|
||||||
<p>` + msgPrompt + `</p>
|
<p class="ui-prompt">` + msgPrompt + `</p>
|
||||||
<input type="text" name="GOEDGE_WAF_CAPTCHA_CODE" id="GOEDGE_WAF_CAPTCHA_CODE" size="` + types.String(countLetters*17/10) + `" maxlength="` + types.String(countLetters) + `" autocomplete="off" z-index="1" class="input"/>
|
<input type="text" name="GOEDGE_WAF_CAPTCHA_CODE" id="GOEDGE_WAF_CAPTCHA_CODE" size="` + types.String(countLetters*17/10) + `" maxlength="` + types.String(countLetters) + `" autocomplete="off" z-index="1" class="input"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui-button">
|
<div class="ui-button">
|
||||||
@@ -176,7 +204,7 @@ func (this *CaptchaValidator) show(actionConfig *CaptchaAction, req requests.Req
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
` + requestIdBox + `
|
` + requestIdBox + `
|
||||||
` + msgFooter + ``
|
` + msgFooter
|
||||||
|
|
||||||
// Body
|
// Body
|
||||||
if actionConfig.UIIsOn {
|
if actionConfig.UIIsOn {
|
||||||
@@ -221,8 +249,7 @@ func (this *CaptchaValidator) show(actionConfig *CaptchaAction, req requests.Req
|
|||||||
_, _ = writer.Write([]byte(msgHTML))
|
_, _ = writer.Write([]byte(msgHTML))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *CaptchaValidator) validate(actionConfig *CaptchaAction, policyId int64, groupId int64, setId int64, originURL string, req requests.Request, writer http.ResponseWriter) (allow bool) {
|
func (this *CaptchaValidator) validateVerifyCodeForm(actionConfig *CaptchaAction, policyId int64, groupId int64, setId int64, originURL string, req requests.Request, writer http.ResponseWriter) (allow bool) {
|
||||||
|
|
||||||
var captchaId = req.WAFRaw().FormValue("GOEDGE_WAF_CAPTCHA_ID")
|
var captchaId = req.WAFRaw().FormValue("GOEDGE_WAF_CAPTCHA_ID")
|
||||||
if len(captchaId) > 0 {
|
if len(captchaId) > 0 {
|
||||||
var captchaCode = req.WAFRaw().FormValue("GOEDGE_WAF_CAPTCHA_CODE")
|
var captchaCode = req.WAFRaw().FormValue("GOEDGE_WAF_CAPTCHA_CODE")
|
||||||
@@ -255,3 +282,356 @@ func (this *CaptchaValidator) validate(actionConfig *CaptchaAction, policyId int
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *CaptchaValidator) showOneClickForm(actionConfig *CaptchaAction, req requests.Request, writer http.ResponseWriter) {
|
||||||
|
var lang = actionConfig.Lang
|
||||||
|
if len(lang) == 0 {
|
||||||
|
var acceptLanguage = req.WAFRaw().Header.Get("Accept-Language")
|
||||||
|
if len(acceptLanguage) > 0 {
|
||||||
|
langIndex := strings.Index(acceptLanguage, ",")
|
||||||
|
if langIndex > 0 {
|
||||||
|
lang = acceptLanguage[:langIndex]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(lang) == 0 {
|
||||||
|
lang = "en-US"
|
||||||
|
}
|
||||||
|
|
||||||
|
var msgTitle string
|
||||||
|
var msgPrompt string
|
||||||
|
var msgRequestId string
|
||||||
|
|
||||||
|
switch lang {
|
||||||
|
case "zh-CN":
|
||||||
|
msgTitle = "身份验证"
|
||||||
|
msgPrompt = "我不是机器人"
|
||||||
|
msgRequestId = "请求ID"
|
||||||
|
case "zh-TW":
|
||||||
|
msgTitle = "身份驗證"
|
||||||
|
msgPrompt = "我不是機器人"
|
||||||
|
msgRequestId = "請求ID"
|
||||||
|
default:
|
||||||
|
msgTitle = "Verify Yourself"
|
||||||
|
msgPrompt = "I'm not a robot"
|
||||||
|
msgRequestId = "Request ID"
|
||||||
|
}
|
||||||
|
|
||||||
|
var msgCss = ""
|
||||||
|
var requestIdBox = `<address>` + msgRequestId + `: ` + req.Format("${requestId}") + `</address>`
|
||||||
|
var msgFooter = ""
|
||||||
|
|
||||||
|
// 默认设置
|
||||||
|
if actionConfig.OneClickUIIsOn {
|
||||||
|
if len(actionConfig.OneClickUIPrompt) > 0 {
|
||||||
|
msgPrompt = actionConfig.OneClickUIPrompt
|
||||||
|
}
|
||||||
|
if len(actionConfig.OneClickUITitle) > 0 {
|
||||||
|
msgTitle = actionConfig.OneClickUITitle
|
||||||
|
}
|
||||||
|
if len(actionConfig.OneClickUICss) > 0 {
|
||||||
|
msgCss = actionConfig.OneClickUICss
|
||||||
|
}
|
||||||
|
if !actionConfig.OneClickUIShowRequestId {
|
||||||
|
requestIdBox = ""
|
||||||
|
}
|
||||||
|
if len(actionConfig.OneClickUIFooter) > 0 {
|
||||||
|
msgFooter = actionConfig.OneClickUIFooter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var captchaId = stringutil.Md5(req.WAFRemoteIP() + "@" + stringutil.Rand(32))
|
||||||
|
var nonce = rands.Int64()
|
||||||
|
if !ttlcache.SharedInt64Cache.Write("WAF_CAPTCHA:"+captchaId, nonce, fasttime.Now().Unix()+600) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var body = `<form method="POST" id="ui-form">
|
||||||
|
<input type="hidden" name="GOEDGE_WAF_CAPTCHA_ID" value="` + captchaId + `"/>
|
||||||
|
<div class="ui-input">
|
||||||
|
<div class="ui-checkbox" id="checkbox"></div>
|
||||||
|
<p class="ui-prompt">` + msgPrompt + `</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
` + requestIdBox + `
|
||||||
|
` + msgFooter
|
||||||
|
|
||||||
|
// Body
|
||||||
|
if actionConfig.OneClickUIIsOn {
|
||||||
|
if len(actionConfig.OneClickUIBody) > 0 {
|
||||||
|
var index = strings.Index(actionConfig.OneClickUIBody, "${body}")
|
||||||
|
if index < 0 {
|
||||||
|
body = actionConfig.OneClickUIBody + body
|
||||||
|
} else {
|
||||||
|
body = actionConfig.OneClickUIBody[:index] + body + actionConfig.OneClickUIBody[index+7:] // 7是"${body}"的长度
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var msgHTML = `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>` + msgTitle + `</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.addEventListener("load",function(){var t=document.getElementById("checkbox"),n=!1;t.addEventListener("click",function(){var e;t.className="ui-checkbox checked",n||(n=!0,(e=document.createElement("input")).setAttribute("name","nonce"),e.setAttribute("type","hidden"),e.setAttribute("value","` + types.String(nonce) + `"),document.getElementById("ui-form").appendChild(e),document.getElementById("ui-form").submit())})});
|
||||||
|
</script>
|
||||||
|
<style type="text/css">
|
||||||
|
form { width: 20em; margin: 0 auto; text-align: center; }
|
||||||
|
.ui-input { position: relative; padding-top: 1em; height: 2.2em; background: #eee; }
|
||||||
|
.ui-checkbox { width: 16px; height: 16px; border: 1px #999 solid; float: left; margin-left: 1em; cursor: pointer; }
|
||||||
|
.ui-checkbox.checked { background: #276AC6; }
|
||||||
|
.ui-prompt { float: left; margin: 0; margin-left: 0.5em; padding: 0; line-height: 1.2; }
|
||||||
|
address { margin-top: 1em; padding-top: 0.5em; border-top: 1px #ccc solid; text-align: center; clear: both; }
|
||||||
|
` + msgCss + `
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>` + body + `
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
req.ProcessResponseHeaders(writer.Header(), http.StatusOK)
|
||||||
|
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
writer.Header().Set("Content-Length", types.String(len(msgHTML)))
|
||||||
|
writer.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = writer.Write([]byte(msgHTML))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *CaptchaValidator) validateOneClickForm(actionConfig *CaptchaAction, policyId int64, groupId int64, setId int64, originURL string, req requests.Request, writer http.ResponseWriter) (allow bool) {
|
||||||
|
var captchaId = req.WAFRaw().FormValue("GOEDGE_WAF_CAPTCHA_ID")
|
||||||
|
var nonce = req.WAFRaw().FormValue("nonce")
|
||||||
|
if len(captchaId) > 0 {
|
||||||
|
var key = "WAF_CAPTCHA:" + captchaId
|
||||||
|
var cacheItem = ttlcache.SharedInt64Cache.Read(key)
|
||||||
|
ttlcache.SharedInt64Cache.Delete(key)
|
||||||
|
if cacheItem != nil {
|
||||||
|
// 清除计数
|
||||||
|
CaptchaDeleteCacheKey(req)
|
||||||
|
|
||||||
|
if cacheItem.Value == types.Int64(nonce) {
|
||||||
|
var life = CaptchaSeconds
|
||||||
|
if actionConfig.Life > 0 {
|
||||||
|
life = types.Int(actionConfig.Life)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加入到白名单
|
||||||
|
SharedIPWhiteList.RecordIP("set:"+strconv.FormatInt(setId, 10), actionConfig.Scope, req.WAFServerId(), req.WAFRemoteIP(), time.Now().Unix()+int64(life), policyId, false, groupId, setId, "")
|
||||||
|
|
||||||
|
req.ProcessResponseHeaders(writer.Header(), http.StatusSeeOther)
|
||||||
|
http.Redirect(writer, req.WAFRaw(), originURL, http.StatusSeeOther)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 增加计数
|
||||||
|
if !CaptchaIncreaseFails(req, actionConfig, policyId, groupId, setId, CaptchaPageCodeSubmit) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
req.ProcessResponseHeaders(writer.Header(), http.StatusSeeOther)
|
||||||
|
http.Redirect(writer, req.WAFRaw(), req.WAFRaw().URL.String(), http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *CaptchaValidator) showSlideForm(actionConfig *CaptchaAction, req requests.Request, writer http.ResponseWriter) {
|
||||||
|
var lang = actionConfig.Lang
|
||||||
|
if len(lang) == 0 {
|
||||||
|
var acceptLanguage = req.WAFRaw().Header.Get("Accept-Language")
|
||||||
|
if len(acceptLanguage) > 0 {
|
||||||
|
langIndex := strings.Index(acceptLanguage, ",")
|
||||||
|
if langIndex > 0 {
|
||||||
|
lang = acceptLanguage[:langIndex]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(lang) == 0 {
|
||||||
|
lang = "en-US"
|
||||||
|
}
|
||||||
|
|
||||||
|
var msgTitle string
|
||||||
|
var msgPrompt string
|
||||||
|
var msgRequestId string
|
||||||
|
|
||||||
|
switch lang {
|
||||||
|
case "zh-CN":
|
||||||
|
msgTitle = "身份验证"
|
||||||
|
msgPrompt = "滑动上面方块到右侧解锁"
|
||||||
|
msgRequestId = "请求ID"
|
||||||
|
case "zh-TW":
|
||||||
|
msgTitle = "身份驗證"
|
||||||
|
msgPrompt = "滑動上面方塊到右側解鎖"
|
||||||
|
msgRequestId = "請求ID"
|
||||||
|
default:
|
||||||
|
msgTitle = "Verify Yourself"
|
||||||
|
msgPrompt = "Slide to Unlock"
|
||||||
|
msgRequestId = "Request ID"
|
||||||
|
}
|
||||||
|
|
||||||
|
var msgCss = ""
|
||||||
|
var requestIdBox = `<address>` + msgRequestId + `: ` + req.Format("${requestId}") + `</address>`
|
||||||
|
var msgFooter = ""
|
||||||
|
|
||||||
|
// 默认设置
|
||||||
|
if actionConfig.OneClickUIIsOn {
|
||||||
|
if len(actionConfig.OneClickUIPrompt) > 0 {
|
||||||
|
msgPrompt = actionConfig.OneClickUIPrompt
|
||||||
|
}
|
||||||
|
if len(actionConfig.OneClickUITitle) > 0 {
|
||||||
|
msgTitle = actionConfig.OneClickUITitle
|
||||||
|
}
|
||||||
|
if len(actionConfig.OneClickUICss) > 0 {
|
||||||
|
msgCss = actionConfig.OneClickUICss
|
||||||
|
}
|
||||||
|
if !actionConfig.OneClickUIShowRequestId {
|
||||||
|
requestIdBox = ""
|
||||||
|
}
|
||||||
|
if len(actionConfig.OneClickUIFooter) > 0 {
|
||||||
|
msgFooter = actionConfig.OneClickUIFooter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var captchaId = stringutil.Md5(req.WAFRemoteIP() + "@" + stringutil.Rand(32))
|
||||||
|
var nonce = rands.Int64()
|
||||||
|
if !ttlcache.SharedInt64Cache.Write("WAF_CAPTCHA:"+captchaId, nonce, fasttime.Now().Unix()+600) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var body = `<form method="POST" id="ui-form">
|
||||||
|
<input type="hidden" name="GOEDGE_WAF_CAPTCHA_ID" value="` + captchaId + `"/>
|
||||||
|
<div class="ui-input" id="input">
|
||||||
|
<div class="ui-progress-bar" id="progress-bar"></div>
|
||||||
|
<div class="ui-handler" id="handler"></div>
|
||||||
|
<div class="ui-handler-placeholder" id="handler-placeholder"></div>
|
||||||
|
<img alt="" src="data:image/jpeg;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAACHklEQVR4nO3cu4oUQRgF4EI3UhDXRNRQUFnBC97XwIcRfQf3ZfYdTAxMFMwMzUw18q6JCOpaxayBTItdDd3V9v99cOKt5pyZnWlmJiUAAAAAAAAAAAAAAAAAAAAAgOgO5pzLOdn6IEzvXs6bnL39PM+53PRETKaUv9eRLzm3G56LCWykPx/5XSPYbnY6Rncm/b383/mcc6vVARnXqfTvARjBwpUXfH1HcLPRGRlRebVf/tf3GcGnZASLVF7olUe4Z4LAakZQnglutDkmYzICjAAjINWP4HqbYzImI8AIqBvBx2QEi1Q7gmttjsmYjAAjoH4EV9sckzEZAUaAEZDqRvAh50qbYzKm5iMoH3F+kPNi/w/I9PmW+g2g5G3Ohc4mBziQ87jij8s8Ur6TcLqjz2r3Z3AxMixP1uus93AGFyLD8jPn6HqldR7N4EJk+AA21yutszODC5FhedbRZ7UjOS9ncDFSl/LO4WxHn4Mcy9nN+TqDC5N+5Y9yQ6j80sWmTJ47qe5GkFvCC3IxrW7s9CnfR8YWRvmBKT8w5Qem/MAuJeWHVcp/l5QfkvIDU35gyg+spnzfDF4Y5QdWfjtQ+UEpPzDlB1bKf5+UH5LyAzufVu/f+77P90mehSmfylV+UIdzfiRP+2GdSB754b1Oyg/tblJ+eGUEr9Kq+O85T3O2mp6IJo7nHGp9CAAAAAAAAAAAAAAAAAAAAADgf/ILsUB70laSdmQAAAAASUVORK5CYII="/>
|
||||||
|
</div>
|
||||||
|
<p class="ui-prompt">` + msgPrompt + `</p>
|
||||||
|
</form>
|
||||||
|
` + requestIdBox + `
|
||||||
|
` + msgFooter
|
||||||
|
|
||||||
|
// Body
|
||||||
|
if actionConfig.OneClickUIIsOn {
|
||||||
|
if len(actionConfig.OneClickUIBody) > 0 {
|
||||||
|
var index = strings.Index(actionConfig.OneClickUIBody, "${body}")
|
||||||
|
if index < 0 {
|
||||||
|
body = actionConfig.OneClickUIBody + body
|
||||||
|
} else {
|
||||||
|
body = actionConfig.OneClickUIBody[:index] + body + actionConfig.OneClickUIBody[index+7:] // 7是"${body}"的长度
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var msgHTML = `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>` + msgTitle + `</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.addEventListener("load",function(){var n=document.getElementById("input"),s=document.getElementById("handler"),o=document.getElementById("progress-bar"),d=!1,u=0,t=n.offsetLeft,c=s.offsetLeft,i=n.offsetWidth-s.offsetWidth-s.offsetLeft,f=!1;function e(e){e.preventDefault(),d=!0,u=null!=e.touches&&0<e.touches.length?e.touches[0].clientX-t:e.offsetX}function l(e){var t;d&&(t=e.x,null!=e.touches&&0<e.touches.length&&(t=e.touches[0].clientX),(t=t-n.offsetLeft-u)<c?t=c:i<t&&(t=i),s.style.cssText="margin-left: "+t+"px",0<t&&(o.style.cssText="width: "+(t+s.offsetWidth+4)+"px"))}function r(e){var t;d=d&&!1,s.offsetLeft<i-4?(s.style.cssText="margin-left: "+c+"px",n.style.cssText="background: #eee",o.style.cssText="width: 0px"):(s.style.cssText="margin-left: "+i+"px",n.style.cssText="background: #a5dc86",f||(f=!0,(t=document.createElement("input")).setAttribute("name","nonce"),t.setAttribute("type","hidden"),t.setAttribute("value","` + types.String(nonce) + `"),document.getElementById("ui-form").appendChild(t),document.getElementById("ui-form").submit()))}void 0!==document.ontouchstart?(s.addEventListener("touchstart",e),document.addEventListener("touchmove",l),document.addEventListener("touchend",r)):(s.addEventListener("mousedown",e),window.addEventListener("mousemove",l),window.addEventListener("mouseup",r))});
|
||||||
|
</script>
|
||||||
|
<style type="text/css">
|
||||||
|
form { width: 20em; margin: 5em auto; text-align: center; }
|
||||||
|
.ui-input {
|
||||||
|
height: 4em;
|
||||||
|
background: #eee;
|
||||||
|
border: 1px #ccc solid;
|
||||||
|
text-align: left;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-input .ui-progress-bar {
|
||||||
|
background: #a5dc86;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-handler, .ui-handler-placeholder {
|
||||||
|
width: 3.6em;
|
||||||
|
height: 3.6em;
|
||||||
|
margin: 0.2em;
|
||||||
|
background: #276AC6;
|
||||||
|
border-radius: 0.6em;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 10;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-handler-placeholder {
|
||||||
|
background: none;
|
||||||
|
border: 1px #ccc dashed;
|
||||||
|
position: absolute;
|
||||||
|
right: -1px;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-input img {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 100%;
|
||||||
|
left: 8em;
|
||||||
|
opacity: 5%;
|
||||||
|
}
|
||||||
|
.ui-prompt { float: left; margin: 1em 0; padding: 0; line-height: 1.2; }
|
||||||
|
address { margin-top: 1em; padding-top: 0.5em; border-top: 1px #ccc solid; text-align: center; clear: both; }
|
||||||
|
` + msgCss + `
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>` + body + `
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
req.ProcessResponseHeaders(writer.Header(), http.StatusOK)
|
||||||
|
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
writer.Header().Set("Content-Length", types.String(len(msgHTML)))
|
||||||
|
writer.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = writer.Write([]byte(msgHTML))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *CaptchaValidator) validateSlideForm(actionConfig *CaptchaAction, policyId int64, groupId int64, setId int64, originURL string, req requests.Request, writer http.ResponseWriter) (allow bool) {
|
||||||
|
var captchaId = req.WAFRaw().FormValue("GOEDGE_WAF_CAPTCHA_ID")
|
||||||
|
var nonce = req.WAFRaw().FormValue("nonce")
|
||||||
|
if len(captchaId) > 0 {
|
||||||
|
var key = "WAF_CAPTCHA:" + captchaId
|
||||||
|
var cacheItem = ttlcache.SharedInt64Cache.Read(key)
|
||||||
|
ttlcache.SharedInt64Cache.Delete(key)
|
||||||
|
if cacheItem != nil {
|
||||||
|
// 清除计数
|
||||||
|
CaptchaDeleteCacheKey(req)
|
||||||
|
|
||||||
|
if cacheItem.Value == types.Int64(nonce) {
|
||||||
|
var life = CaptchaSeconds
|
||||||
|
if actionConfig.Life > 0 {
|
||||||
|
life = types.Int(actionConfig.Life)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加入到白名单
|
||||||
|
SharedIPWhiteList.RecordIP("set:"+strconv.FormatInt(setId, 10), actionConfig.Scope, req.WAFServerId(), req.WAFRemoteIP(), time.Now().Unix()+int64(life), policyId, false, groupId, setId, "")
|
||||||
|
|
||||||
|
req.ProcessResponseHeaders(writer.Header(), http.StatusSeeOther)
|
||||||
|
http.Redirect(writer, req.WAFRaw(), originURL, http.StatusSeeOther)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 增加计数
|
||||||
|
if !CaptchaIncreaseFails(req, actionConfig, policyId, groupId, setId, CaptchaPageCodeSubmit) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
req.ProcessResponseHeaders(writer.Header(), http.StatusSeeOther)
|
||||||
|
http.Redirect(writer, req.WAFRaw(), req.WAFRaw().URL.String(), http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func (this *RuleGroup) RemoveRuleSet(id int64) {
|
|||||||
this.RuleSets = result
|
this.RuleSets = result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *RuleGroup) MatchRequest(req requests.Request) (b bool, hasRequestBody bool, set *RuleSet, err error) {
|
func (this *RuleGroup) MatchRequest(req requests.Request) (b bool, hasRequestBody bool, resultSet *RuleSet, err error) {
|
||||||
if !this.hasRuleSets {
|
if !this.hasRuleSets {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -93,7 +93,7 @@ func (this *RuleGroup) MatchRequest(req requests.Request) (b bool, hasRequestBod
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *RuleGroup) MatchResponse(req requests.Request, resp *requests.Response) (b bool, hasRequestBody bool, set *RuleSet, err error) {
|
func (this *RuleGroup) MatchResponse(req requests.Request, resp *requests.Response) (b bool, hasRequestBody bool, resultSet *RuleSet, err error) {
|
||||||
if !this.hasRuleSets {
|
if !this.hasRuleSets {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ func (this *WAF) MoveOutboundRuleGroup(fromIndex int, toIndex int) {
|
|||||||
this.Outbound = result
|
this.Outbound = result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *WAF) MatchRequest(req requests.Request, writer http.ResponseWriter) (goNext bool, hasRequestBody bool, group *RuleGroup, sets *RuleSet, err error) {
|
func (this *WAF) MatchRequest(req requests.Request, writer http.ResponseWriter, defaultCaptchaType firewallconfigs.ServerCaptchaType) (goNext bool, hasRequestBody bool, resultGroup *RuleGroup, resultSet *RuleSet, err error) {
|
||||||
if !this.hasInboundRules {
|
if !this.hasInboundRules {
|
||||||
return true, hasRequestBody, nil, nil, nil
|
return true, hasRequestBody, nil, nil, nil
|
||||||
}
|
}
|
||||||
@@ -251,7 +251,7 @@ func (this *WAF) MatchRequest(req requests.Request, writer http.ResponseWriter)
|
|||||||
var rawPath = req.WAFRaw().URL.Path
|
var rawPath = req.WAFRaw().URL.Path
|
||||||
if rawPath == CaptchaPath {
|
if rawPath == CaptchaPath {
|
||||||
req.DisableAccessLog()
|
req.DisableAccessLog()
|
||||||
captchaValidator.Run(req, writer)
|
captchaValidator.Run(req, writer, defaultCaptchaType)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +284,7 @@ func (this *WAF) MatchRequest(req requests.Request, writer http.ResponseWriter)
|
|||||||
return true, hasRequestBody, nil, nil, nil
|
return true, hasRequestBody, nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *WAF) MatchResponse(req requests.Request, rawResp *http.Response, writer http.ResponseWriter) (goNext bool, hasRequestBody bool, group *RuleGroup, set *RuleSet, err error) {
|
func (this *WAF) MatchResponse(req requests.Request, rawResp *http.Response, writer http.ResponseWriter) (goNext bool, hasRequestBody bool, resultGroup *RuleGroup, resultSet *RuleSet, err error) {
|
||||||
if !this.hasOutboundRules {
|
if !this.hasOutboundRules {
|
||||||
return true, hasRequestBody, nil, nil, nil
|
return true, hasRequestBody, nil, nil, nil
|
||||||
}
|
}
|
||||||
@@ -310,7 +310,7 @@ func (this *WAF) MatchResponse(req requests.Request, rawResp *http.Response, wri
|
|||||||
return true, hasRequestBody, nil, nil, nil
|
return true, hasRequestBody, nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save save to file path
|
// Save to file path
|
||||||
func (this *WAF) Save(path string) error {
|
func (this *WAF) Save(path string) error {
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
return errors.New("path should not be empty")
|
return errors.New("path should not be empty")
|
||||||
@@ -385,7 +385,7 @@ func (this *WAF) FindCheckpointInstance(prefix string) checkpoints.CheckpointInt
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start start
|
// Start
|
||||||
func (this *WAF) Start() {
|
func (this *WAF) Start() {
|
||||||
for _, checkpoint := range this.checkpointsMap {
|
for _, checkpoint := range this.checkpointsMap {
|
||||||
checkpoint.Start()
|
checkpoint.Start()
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ func (this *WAFManager) ConvertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
|
|||||||
FailBlockTimeout: policy.CaptchaOptions.FailBlockTimeout,
|
FailBlockTimeout: policy.CaptchaOptions.FailBlockTimeout,
|
||||||
FailBlockScopeAll: policy.CaptchaOptions.FailBlockScopeAll,
|
FailBlockScopeAll: policy.CaptchaOptions.FailBlockScopeAll,
|
||||||
CountLetters: policy.CaptchaOptions.CountLetters,
|
CountLetters: policy.CaptchaOptions.CountLetters,
|
||||||
|
CaptchaType: policy.CaptchaOptions.CaptchaType,
|
||||||
UIIsOn: policy.CaptchaOptions.UIIsOn,
|
UIIsOn: policy.CaptchaOptions.UIIsOn,
|
||||||
UITitle: policy.CaptchaOptions.UITitle,
|
UITitle: policy.CaptchaOptions.UITitle,
|
||||||
UIPrompt: policy.CaptchaOptions.UIPrompt,
|
UIPrompt: policy.CaptchaOptions.UIPrompt,
|
||||||
|
|||||||
Reference in New Issue
Block a user