github.com/TeaOSLab/EdgeNode@v1.3.8/internal/waf/action_captcha.go (about)

     1  package waf
     2  
     3  import (
     4  	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
     5  	"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
     6  	"github.com/TeaOSLab/EdgeNode/internal/utils"
     7  	"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
     8  	"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
     9  	wafutils "github.com/TeaOSLab/EdgeNode/internal/waf/utils"
    10  	"github.com/iwind/TeaGo/types"
    11  	"net/http"
    12  	"net/url"
    13  	"strings"
    14  	"time"
    15  )
    16  
    17  const (
    18  	CaptchaSeconds = 600 // 10 minutes
    19  	CaptchaPath    = "/WAF/VERIFY/CAPTCHA"
    20  )
    21  
    22  type CaptchaAction struct {
    23  	BaseAction
    24  
    25  	Life              int32 `yaml:"life" json:"life"`
    26  	MaxFails          int   `yaml:"maxFails" json:"maxFails"`                   // 最大失败次数
    27  	FailBlockTimeout  int   `yaml:"failBlockTimeout" json:"failBlockTimeout"`   // 失败拦截时间
    28  	FailBlockScopeAll bool  `yaml:"failBlockScopeAll" json:"failBlockScopeAll"` // 是否全局有效
    29  
    30  	CountLetters int8 `yaml:"countLetters" json:"countLetters"`
    31  
    32  	CaptchaType firewallconfigs.CaptchaType `yaml:"captchaType" json:"captchaType"`
    33  
    34  	UIIsOn          bool   `yaml:"uiIsOn" json:"uiIsOn"`                   // 是否使用自定义UI
    35  	UITitle         string `yaml:"uiTitle" json:"uiTitle"`                 // 消息标题
    36  	UIPrompt        string `yaml:"uiPrompt" json:"uiPrompt"`               // 消息提示
    37  	UIButtonTitle   string `yaml:"uiButtonTitle" json:"uiButtonTitle"`     // 按钮标题
    38  	UIShowRequestId bool   `yaml:"uiShowRequestId" json:"uiShowRequestId"` // 是否显示请求ID
    39  	UICss           string `yaml:"uiCss" json:"uiCss"`                     // CSS样式
    40  	UIFooter        string `yaml:"uiFooter" json:"uiFooter"`               // 页脚
    41  	UIBody          string `yaml:"uiBody" json:"uiBody"`                   // 内容轮廓
    42  
    43  	OneClickUIIsOn          bool   `yaml:"oneClickUIIsOn" json:"oneClickUIIsOn"`                   // 是否使用自定义UI
    44  	OneClickUITitle         string `yaml:"oneClickUITitle" json:"oneClickUITitle"`                 // 消息标题
    45  	OneClickUIPrompt        string `yaml:"oneClickUIPrompt" json:"oneClickUIPrompt"`               // 消息提示
    46  	OneClickUIShowRequestId bool   `yaml:"oneClickUIShowRequestId" json:"oneClickUIShowRequestId"` // 是否显示请求ID
    47  	OneClickUICss           string `yaml:"oneClickUICss" json:"oneClickUICss"`                     // CSS样式
    48  	OneClickUIFooter        string `yaml:"oneClickUIFooter" json:"oneClickUIFooter"`               // 页脚
    49  	OneClickUIBody          string `yaml:"oneClickUIBody" json:"oneClickUIBody"`                   // 内容轮廓
    50  
    51  	SlideUIIsOn          bool   `yaml:"sliceUIIsOn" json:"sliceUIIsOn"`                   // 是否使用自定义UI
    52  	SlideUITitle         string `yaml:"slideUITitle" json:"slideUITitle"`                 // 消息标题
    53  	SlideUIPrompt        string `yaml:"slideUIPrompt" json:"slideUIPrompt"`               // 消息提示
    54  	SlideUIShowRequestId bool   `yaml:"SlideUIShowRequestId" json:"SlideUIShowRequestId"` // 是否显示请求ID
    55  	SlideUICss           string `yaml:"slideUICss" json:"slideUICss"`                     // CSS样式
    56  	SlideUIFooter        string `yaml:"slideUIFooter" json:"slideUIFooter"`               // 页脚
    57  	SlideUIBody          string `yaml:"slideUIBody" json:"slideUIBody"`                   // 内容轮廓
    58  
    59  	GeeTestConfig *firewallconfigs.GeeTestConfig `yaml:"geeTestConfig" json:"geeTestConfig"` // 极验设置 MUST be struct
    60  
    61  	Lang           string `yaml:"lang" json:"lang"`                     // 语言,zh-CN, en-US ...
    62  	AddToWhiteList bool   `yaml:"addToWhiteList" json:"addToWhiteList"` // 是否加入到白名单
    63  	Scope          string `yaml:"scope" json:"scope"`
    64  }
    65  
    66  func (this *CaptchaAction) Init(waf *WAF) error {
    67  	if waf.DefaultCaptchaAction != nil {
    68  		if this.Life <= 0 {
    69  			this.Life = waf.DefaultCaptchaAction.Life
    70  		}
    71  		if this.MaxFails <= 0 {
    72  			this.MaxFails = waf.DefaultCaptchaAction.MaxFails
    73  		}
    74  		if this.FailBlockTimeout <= 0 {
    75  			this.FailBlockTimeout = waf.DefaultCaptchaAction.FailBlockTimeout
    76  		}
    77  		this.FailBlockScopeAll = waf.DefaultCaptchaAction.FailBlockScopeAll
    78  
    79  		if this.CountLetters <= 0 {
    80  			this.CountLetters = waf.DefaultCaptchaAction.CountLetters
    81  		}
    82  
    83  		this.UIIsOn = waf.DefaultCaptchaAction.UIIsOn
    84  		if len(this.UITitle) == 0 {
    85  			this.UITitle = waf.DefaultCaptchaAction.UITitle
    86  		}
    87  		if len(this.UIPrompt) == 0 {
    88  			this.UIPrompt = waf.DefaultCaptchaAction.UIPrompt
    89  		}
    90  		if len(this.UIButtonTitle) == 0 {
    91  			this.UIButtonTitle = waf.DefaultCaptchaAction.UIButtonTitle
    92  		}
    93  		this.UIShowRequestId = waf.DefaultCaptchaAction.UIShowRequestId
    94  		if len(this.UICss) == 0 {
    95  			this.UICss = waf.DefaultCaptchaAction.UICss
    96  		}
    97  		if len(this.UIFooter) == 0 {
    98  			this.UIFooter = waf.DefaultCaptchaAction.UIFooter
    99  		}
   100  		if len(this.UIBody) == 0 {
   101  			this.UIBody = waf.DefaultCaptchaAction.UIBody
   102  		}
   103  		if len(this.Lang) == 0 {
   104  			this.Lang = waf.DefaultCaptchaAction.Lang
   105  		}
   106  
   107  		if len(this.CaptchaType) == 0 {
   108  			this.CaptchaType = waf.DefaultCaptchaAction.CaptchaType
   109  		}
   110  	}
   111  
   112  	return nil
   113  }
   114  
   115  func (this *CaptchaAction) Code() string {
   116  	return ActionCaptcha
   117  }
   118  
   119  func (this *CaptchaAction) IsAttack() bool {
   120  	return false
   121  }
   122  
   123  func (this *CaptchaAction) WillChange() bool {
   124  	return true
   125  }
   126  
   127  func (this *CaptchaAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, req requests.Request, writer http.ResponseWriter) PerformResult {
   128  	// 是否在白名单中
   129  	if SharedIPWhiteList.Contains(wafutils.ComposeIPType(set.Id, req), this.Scope, req.WAFServerId(), req.WAFRemoteIP()) {
   130  		return PerformResult{
   131  			ContinueRequest: true,
   132  		}
   133  	}
   134  
   135  	// 检查Cookie值
   136  	var fullCookieName = captchaCookiePrefix + "_" + types.String(set.Id)
   137  	cookie, err := req.WAFRaw().Cookie(fullCookieName)
   138  	if err == nil && cookie != nil && len(cookie.Value) > 0 {
   139  		var info = &AllowCookieInfo{}
   140  		err = info.Decode(cookie.Value)
   141  		if err == nil && set.Id == info.SetId && info.ExpiresAt > fasttime.Now().Unix() {
   142  			// 重新记录到白名单
   143  			SharedIPWhiteList.RecordIP(wafutils.ComposeIPType(set.Id, req), this.Scope, req.WAFServerId(), req.WAFRemoteIP(), info.ExpiresAt, waf.Id, false, group.Id, set.Id, "")
   144  
   145  			return PerformResult{
   146  				ContinueRequest: true,
   147  			}
   148  		}
   149  	}
   150  
   151  	var refURL = req.WAFRaw().URL.String()
   152  
   153  	// 覆盖配置
   154  	if strings.HasPrefix(refURL, CaptchaPath) {
   155  		var info = req.WAFRaw().URL.Query().Get("info")
   156  		if len(info) > 0 {
   157  			var oldArg = &InfoArg{}
   158  			decodeErr := oldArg.Decode(info)
   159  			if decodeErr == nil && oldArg.IsValid() {
   160  				refURL = oldArg.URL
   161  			} else {
   162  				// 兼容老版本
   163  				m, err := utils.SimpleDecryptMap(info)
   164  				if err == nil && m != nil {
   165  					refURL = m.GetString("url")
   166  				}
   167  			}
   168  		}
   169  	}
   170  
   171  	var captchaConfig = &InfoArg{
   172  		ActionId:         this.ActionId(),
   173  		Timestamp:        time.Now().Unix(),
   174  		URL:              refURL,
   175  		PolicyId:         waf.Id,
   176  		GroupId:          group.Id,
   177  		SetId:            set.Id,
   178  		UseLocalFirewall: waf.UseLocalFirewall && (this.FailBlockScopeAll || this.Scope == firewallconfigs.AllowScopeGlobal),
   179  	}
   180  	info, err := utils.SimpleEncryptObject(captchaConfig)
   181  	if err != nil {
   182  		remotelogs.Error("WAF_CAPTCHA_ACTION", "encode captcha config failed: "+err.Error())
   183  		return PerformResult{
   184  			ContinueRequest: true,
   185  		}
   186  	}
   187  
   188  	// 占用一次失败次数
   189  	CaptchaIncreaseFails(req, this, waf.Id, group.Id, set.Id, CaptchaPageCodeInit, waf.UseLocalFirewall && (this.FailBlockScopeAll || this.Scope == firewallconfigs.FirewallScopeGlobal))
   190  
   191  	req.DisableStat()
   192  	req.ProcessResponseHeaders(writer.Header(), http.StatusTemporaryRedirect)
   193  	http.Redirect(writer, req.WAFRaw(), CaptchaPath+"?info="+url.QueryEscape(info)+"&from="+url.QueryEscape(refURL), http.StatusTemporaryRedirect)
   194  
   195  	return PerformResult{}
   196  }