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

     1  // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
     2  
     3  package waf
     4  
     5  import (
     6  	"crypto/md5"
     7  	"fmt"
     8  	"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
     9  	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
    10  	"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
    11  	"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
    12  	"github.com/iwind/TeaGo/types"
    13  	"net/http"
    14  	"strings"
    15  	"time"
    16  )
    17  
    18  type JSCookieAction struct {
    19  	BaseAction
    20  
    21  	Life             int32  `yaml:"life" json:"life"`
    22  	MaxFails         int    `yaml:"maxFails" json:"maxFails"`                 // 最大失败次数
    23  	FailBlockTimeout int    `yaml:"failBlockTimeout" json:"failBlockTimeout"` // 失败拦截时间
    24  	Scope            string `yaml:"scope" json:"scope"`
    25  
    26  	FailBlockScopeAll bool `yaml:"failBlockScopeAll" json:"failBlockScopeAll"`
    27  }
    28  
    29  func (this *JSCookieAction) Init(waf *WAF) error {
    30  
    31  	if waf.DefaultJSCookieAction != nil {
    32  		if this.Life <= 0 {
    33  			this.Life = waf.DefaultJSCookieAction.Life
    34  		}
    35  		if this.MaxFails <= 0 {
    36  			this.MaxFails = waf.DefaultJSCookieAction.MaxFails
    37  		}
    38  		if this.FailBlockTimeout <= 0 {
    39  			this.FailBlockTimeout = waf.DefaultJSCookieAction.FailBlockTimeout
    40  		}
    41  		if len(this.Scope) == 0 {
    42  			this.Scope = waf.DefaultJSCookieAction.Scope
    43  		}
    44  
    45  		this.FailBlockScopeAll = waf.DefaultJSCookieAction.FailBlockScopeAll
    46  	}
    47  
    48  	if len(this.Scope) == 0 {
    49  		this.Scope = firewallconfigs.FirewallScopeGlobal
    50  	}
    51  
    52  	return nil
    53  }
    54  
    55  func (this *JSCookieAction) Code() string {
    56  	return ActionJavascriptCookie
    57  }
    58  
    59  func (this *JSCookieAction) IsAttack() bool {
    60  	return false
    61  }
    62  
    63  func (this *JSCookieAction) WillChange() bool {
    64  	return true
    65  }
    66  
    67  func (this *JSCookieAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, req requests.Request, writer http.ResponseWriter) PerformResult {
    68  	// 是否在白名单中
    69  	if SharedIPWhiteList.Contains("set:"+types.String(set.Id), this.Scope, req.WAFServerId(), req.WAFRemoteIP()) {
    70  		return PerformResult{
    71  			ContinueRequest: true,
    72  		}
    73  	}
    74  
    75  	nodeConfig, err := nodeconfigs.SharedNodeConfig()
    76  	if err != nil {
    77  		return PerformResult{
    78  			ContinueRequest: true,
    79  		}
    80  	}
    81  
    82  	var life = this.Life
    83  	if life <= 0 {
    84  		life = 3600
    85  	}
    86  
    87  	// 检查Cookie
    88  	var cookieName = "ge_js_validator_" + types.String(set.Id)
    89  	cookie, err := req.WAFRaw().Cookie(cookieName)
    90  	if err == nil && cookie != nil {
    91  		var cookieValue = cookie.Value
    92  		if len(cookieValue) > 10 {
    93  			var pieces = strings.Split(cookieValue, "@")
    94  			if len(pieces) == 3 {
    95  				var timestamp = pieces[0]
    96  				var sum = pieces[2]
    97  				if types.Int64(timestamp) >= time.Now().Unix()-int64(life) && fmt.Sprintf("%x", md5.Sum([]byte(timestamp+"@"+types.String(set.Id)+"@"+nodeConfig.NodeId))) == sum {
    98  					return PerformResult{
    99  						ContinueRequest: true,
   100  					}
   101  				}
   102  			}
   103  		}
   104  	}
   105  
   106  	req.ProcessResponseHeaders(writer.Header(), http.StatusOK)
   107  
   108  	writer.Header().Set("Content-Type", "text/html; charset=utf-8")
   109  	writer.Header().Set("Cache-Control", "no-cache")
   110  
   111  	var timestamp = types.String(time.Now().Unix())
   112  
   113  	var cookieValue = timestamp + "@" + types.String(set.Id) + "@" + fmt.Sprintf("%x", md5.Sum([]byte(timestamp+"@"+types.String(set.Id)+"@"+nodeConfig.NodeId)))
   114  	var respHTML = `<!DOCTYPE html>
   115  <html>
   116  <head>
   117  <title></title>
   118  <meta charset="UTF-8"/>
   119  <script type="text/javascript">
   120  document.cookie = "` + cookieName + `=` + cookieValue + `; path=/; max-age=` + types.String(life) + `;";
   121  window.location.reload();
   122  </script>
   123  </head>
   124  <body>
   125  </body>
   126  </html>`
   127  	writer.Header().Set("Content-Length", types.String(len(respHTML)))
   128  	writer.WriteHeader(http.StatusOK)
   129  	_, _ = writer.Write([]byte(respHTML))
   130  
   131  	// 记录失败次数
   132  	this.increaseFails(req, waf.Id, group.Id, set.Id, waf.UseLocalFirewall && (this.FailBlockScopeAll || this.Scope == firewallconfigs.FirewallScopeGlobal))
   133  
   134  	return PerformResult{}
   135  }
   136  
   137  func (this *JSCookieAction) increaseFails(req requests.Request, policyId int64, groupId int64, setId int64, useLocalFirewall bool) (goNext bool) {
   138  	var maxFails = this.MaxFails
   139  	var failBlockTimeout = this.FailBlockTimeout
   140  
   141  	if maxFails <= 0 {
   142  		maxFails = 10 // 默认10次
   143  	} else if maxFails <= 5 {
   144  		maxFails = 5 // 不能小于3,防止意外刷新出现
   145  	}
   146  	if failBlockTimeout <= 0 {
   147  		failBlockTimeout = 1800 // 默认1800s
   148  	}
   149  
   150  	var key = "WAF:JS_COOKIE:FAILS:" + req.WAFRemoteIP() + ":" + types.String(req.WAFServerId()) + ":" + req.WAFRaw().URL.String()
   151  
   152  	var countFails = counters.SharedCounter.IncreaseKey(key, 300)
   153  	if int(countFails) >= maxFails {
   154  		SharedIPBlackList.RecordIP(IPTypeAll, firewallconfigs.FirewallScopeServer, req.WAFServerId(), req.WAFRemoteIP(), time.Now().Unix()+int64(failBlockTimeout), policyId, useLocalFirewall, groupId, setId, "JS_COOKIE验证连续失败超过"+types.String(maxFails)+"次")
   155  		return false
   156  	}
   157  
   158  	return true
   159  }