mirror of
https://gitee.com/samwaf/SamWaf.git
synced 2026-09-01 15:32:55 +08:00
+21
-1
@@ -123,4 +123,24 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
|
||||
wenlng/go-captcha
|
||||
|
||||
Apache License
|
||||
|
||||
Copyright (c) 2021 Awen <wengaolng@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Vendored
+7
@@ -63,6 +63,13 @@ func (wafCache *WafCache) SetWithTTlRenewTime(key string, value interface{}, ttl
|
||||
ttl: ttl,
|
||||
}
|
||||
}
|
||||
func (wafCache *WafCache) GetBytes(key string) ([]byte, error) {
|
||||
key1Value := wafCache.Get(key)
|
||||
if str, ok := key1Value.([]byte); ok {
|
||||
return str, nil
|
||||
}
|
||||
return nil, errors.New("数据不存在")
|
||||
}
|
||||
func (wafCache *WafCache) GetString(key string) (string, error) {
|
||||
key1Value := wafCache.Get(key)
|
||||
if str, ok := key1Value.(string); ok {
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ docker run --rm -v %cd%:/workspace samwaflocalcompile
|
||||
## 集成的三方库
|
||||
前端: 使用TDesign Vue Starter
|
||||
后端: gorm,excelize(360EntSecGroup-Skylar),godlp(bytedance),gin,gocron,
|
||||
grule-rule-engine,ip2region,sqlitedriver,viper,libinjection-go,corazawaf,go-acme/lego
|
||||
grule-rule-engine,ip2region,sqlitedriver,viper,libinjection-go,corazawaf,go-acme/lego,wenlng/go-captcha
|
||||
数据:
|
||||
ipv6(GeoLite2-Country.mmdb) by maxmind
|
||||
## TODO List
|
||||
|
||||
@@ -7,4 +7,6 @@ const (
|
||||
CACHE_TOKEN = "CACHE_TOKEN" //鉴权信息
|
||||
CACHE_DNS_BOT_IP = "CACHE_DNS_BOT_IP" //IP反向域名解析
|
||||
CACHE_DNS_NORMAL_IP = "CACHE_DNS_NORMAL_IP" //正常IP
|
||||
CACHE_CAPTCHA_TRY = "CACHE_CAPTCHA_TRY" //验证码临时
|
||||
CACHE_CAPTCHA_PASS = "CACHE_CAPTCHA_PASS" //通过验证的码
|
||||
)
|
||||
|
||||
@@ -29,6 +29,7 @@ var (
|
||||
GCONFIG_RECORD_DNS_BOT_EXPIRE_HOURS int64 = 24 //DNS bot有效期 单位小时 默认1天
|
||||
GCONFIG_RECORD_DNS_NORMAL_EXPIRE_HOURS int64 = 7 * 24 //DNS 正常有效期 单位小时 默认7天
|
||||
GCONFIG_RECORD_SPIDER_DENY int64 = 0 //爬虫禁止访问开关 默认 0 只检测不阻止访问 1 检测并阻止访问
|
||||
GCONFIG_RECORD_FAKE_SPIDER_CAPTCHA int64 = 0 //伪爬虫进行图形挑战开关 0 放过 1 显示图形验证码
|
||||
GCONFIG_RECORD_HIDE_SERVER_HEADER int64 = 1 // 是否隐藏Server头信息 1隐藏 0不隐藏
|
||||
GCONFIG_RECORD_FORCE_BIND_2FA int64 = 0 // 是否强制绑定双因素认证(1强制 0不强制)
|
||||
GCONFIG_RECORD_DEBUG_ENABLE int64 = 0 //调试开关 默认关闭
|
||||
|
||||
@@ -13,6 +13,7 @@ require (
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/go-acme/lego/v4 v4.20.4
|
||||
github.com/go-co-op/gocron v1.17.1
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/hyperjumptech/grule-rule-engine v1.15.0
|
||||
github.com/kardianos/service v1.2.2
|
||||
@@ -27,6 +28,8 @@ require (
|
||||
github.com/spf13/viper v1.18.2
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/twmb/franz-go v1.18.0
|
||||
github.com/wenlng/go-captcha-assets v1.0.5
|
||||
github.com/wenlng/go-captcha/v2 v2.0.3
|
||||
go.uber.org/zap v1.25.0
|
||||
golang.org/x/mod v0.21.0
|
||||
golang.org/x/net v0.33.0
|
||||
@@ -117,6 +120,7 @@ require (
|
||||
golang.org/x/arch v0.12.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect
|
||||
golang.org/x/image v0.16.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/tools v0.25.0 // indirect
|
||||
google.golang.org/protobuf v1.36.1 // indirect
|
||||
|
||||
@@ -102,6 +102,8 @@ github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL
|
||||
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
||||
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
@@ -258,6 +260,10 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ
|
||||
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/valllabh/ocsf-schema-golang v1.0.3 h1:eR8k/3jP/OOqB8LRCtdJ4U+vlgd/gk5y3KMXoodrsrw=
|
||||
github.com/valllabh/ocsf-schema-golang v1.0.3/go.mod h1:sZ3as9xqm1SSK5feFWIR2CuGeGRhsM7TR1MbpBctzPk=
|
||||
github.com/wenlng/go-captcha-assets v1.0.5 h1:TL+31Qe/kJwcuYyU+jHedjSTZnMu1XKgktKL++lH9Js=
|
||||
github.com/wenlng/go-captcha-assets v1.0.5/go.mod h1:zinRACsdYcL/S6pHgI9Iv7FKTU41d00+43pNX+b9+MM=
|
||||
github.com/wenlng/go-captcha/v2 v2.0.3 h1:QTZ39/gVDisPSgvL9O2X2HbTuj5P/z8QsdGB/aayg9c=
|
||||
github.com/wenlng/go-captcha/v2 v2.0.3/go.mod h1:5hac1em3uXoyC5ipZ0xFv9umNM/waQvYAQdr0cx/h34=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
@@ -280,6 +286,8 @@ golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw=
|
||||
golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
@@ -331,6 +339,7 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
|
||||
@@ -33,6 +33,7 @@ type WebLog struct {
|
||||
UNIX_ADD_TIME int64 `json:"unix_add_time" gorm:"index"` //添加日期unix
|
||||
RISK_LEVEL int `json:"risk_level"` //危险等级 0:正常 1:轻微 2:有害 3:严重 4:特别严重
|
||||
GUEST_IDENTIFICATION string `json:"guest_identification"` //访客身份识别
|
||||
IsBot int `json:"is_bot"` //是否是机器人 0 不是机器人 1 机器人
|
||||
TimeSpent int64 `json:"time_spent"` //用时
|
||||
NetSrcIp string `json:"net_src_ip"` //获取的原始IP
|
||||
SrcByteBody []byte `json:"src_byte_body"` //原始body信息
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"SamWaf/wafconfig"
|
||||
"SamWaf/wafdb"
|
||||
"SamWaf/wafenginecore"
|
||||
"SamWaf/wafenginecore/wafcaptcha"
|
||||
"SamWaf/wafinit"
|
||||
"SamWaf/wafmangeweb"
|
||||
"SamWaf/wafnotify"
|
||||
@@ -167,6 +168,8 @@ func (m *wafSystenService) run() {
|
||||
|
||||
//初始化cache
|
||||
global.GCACHE_WAFCACHE = cache.InitWafCache()
|
||||
//初始化验证码服务
|
||||
wafcaptcha.InitCaptchaService(global.GCACHE_WAFCACHE)
|
||||
//初始化锁写不锁度
|
||||
global.GWAF_MEASURE_PROCESS_DEQUEENGINE = cache.InitWafOnlyLockWrite()
|
||||
// 创建 Snowflake 实例
|
||||
|
||||
@@ -35,6 +35,7 @@ type Hosts struct {
|
||||
ResponseTimeOut int `json:"response_time_out"` //响应超时时间 默认60秒,为0则无限等待
|
||||
HealthyJSON string `json:"healthy_json"` //后端健康度检测 json
|
||||
InsecureSkipVerify int `json:"insecure_skip_verify"` //是否开启后端https证书有效性验证 默认 0 是校验 1 是不校验
|
||||
CaptchaJSON string `json:"captcha_json"` //验证码配置 json
|
||||
}
|
||||
|
||||
type HostsDefense struct {
|
||||
@@ -58,3 +59,10 @@ type HealthyConfig struct {
|
||||
ExpectedCodes string `json:"expected_codes"` // 预期状态码
|
||||
LastErrorReason string `json:"last_error_reason"` // 最后一次错误原因
|
||||
}
|
||||
|
||||
// CaptchaConfig 验证码配置
|
||||
type CaptchaConfig struct {
|
||||
IsEnableCaptcha int `json:"is_enable_captcha"` // 是否开启验证码 1开启 0关闭
|
||||
ExcludeURLs string `json:"exclude_urls"` // 排除验证码的URL列表
|
||||
ExpireTime int `json:"expire_time"` // 验证通过后的有效期(小时)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ type WafHostAddReq struct {
|
||||
ResponseTimeOut int `json:"response_time_out"` //响应超时时间
|
||||
HealthyJSON string `json:"healthy_json"` //后端健康度检测 json
|
||||
InsecureSkipVerify int `json:"insecure_skip_verify"` //是否开启后端https证书有效性验证 默认 0 是校验 1 是不校验
|
||||
|
||||
CaptchaJSON string `json:"captcha_json"` //验证码配置 json
|
||||
}
|
||||
|
||||
type WafHostDelReq struct {
|
||||
@@ -69,7 +69,7 @@ type WafHostEditReq struct {
|
||||
ResponseTimeOut int `json:"response_time_out"` //响应超时时间
|
||||
HealthyJSON string `json:"healthy_json"` //后端健康度检测 json
|
||||
InsecureSkipVerify int `json:"insecure_skip_verify"` //是否开启后端https证书有效性验证 默认 0 是校验 1 是不校验
|
||||
|
||||
CaptchaJSON string `json:"captcha_json"` //验证码配置 json
|
||||
}
|
||||
|
||||
type WafHostGuardStatusReq struct {
|
||||
|
||||
@@ -61,6 +61,7 @@ func (receiver *WafHostService) AddApi(wafHostAddReq request.WafHostAddReq) (str
|
||||
ResponseTimeOut: wafHostAddReq.ResponseTimeOut,
|
||||
HealthyJSON: wafHostAddReq.HealthyJSON,
|
||||
InsecureSkipVerify: wafHostAddReq.InsecureSkipVerify,
|
||||
CaptchaJSON: wafHostAddReq.CaptchaJSON,
|
||||
}
|
||||
global.GWAF_LOCAL_DB.Create(wafHost)
|
||||
return wafHost.Code, nil
|
||||
@@ -112,6 +113,7 @@ func (receiver *WafHostService) ModifyApi(wafHostEditReq request.WafHostEditReq)
|
||||
"ResponseTimeOut": wafHostEditReq.ResponseTimeOut,
|
||||
"HealthyJSON": wafHostEditReq.HealthyJSON,
|
||||
"InsecureSkipVerify": wafHostEditReq.InsecureSkipVerify,
|
||||
"CaptchaJSON": wafHostEditReq.CaptchaJSON,
|
||||
}
|
||||
err := global.GWAF_LOCAL_DB.Debug().Model(model.Hosts{}).Where("CODE=?", wafHostEditReq.CODE).Updates(hostMap).Error
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package wafbot
|
||||
|
||||
//TODO 拉取最新数据进行更新
|
||||
@@ -0,0 +1,53 @@
|
||||
package wafenginecore
|
||||
|
||||
import (
|
||||
"SamWaf/enums"
|
||||
"SamWaf/global"
|
||||
"SamWaf/innerbean"
|
||||
"SamWaf/wafenginecore/wafcaptcha"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// checkCaptchaToken 返回false 要验证信息 ,true 不验证信息
|
||||
func (waf *WafEngine) checkCaptchaToken(r *http.Request, log innerbean.WebLog) bool {
|
||||
// 首先从Cookie中获取验证标识
|
||||
cookie, err := r.Cookie("samwaf_captcha_token")
|
||||
if err == nil && cookie.Value != "" {
|
||||
// 检查缓存中是否存在该标识
|
||||
if global.GCACHE_WAFCACHE.IsKeyExist(enums.CACHE_CAPTCHA_PASS + cookie.Value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 如果Cookie中没有或无效,则检查请求头
|
||||
token := r.Header.Get("X-SamWaf-Captcha-Token")
|
||||
if token != "" {
|
||||
// 检查缓存中是否存在该标识
|
||||
if global.GCACHE_WAFCACHE.IsKeyExist(enums.CACHE_CAPTCHA_PASS + token) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
//是bot而且危险程度是0,那么不用进行验证码挑战
|
||||
if log.IsBot == 1 {
|
||||
if log.RISK_LEVEL == 0 {
|
||||
return true
|
||||
} else {
|
||||
if log.GUEST_IDENTIFICATION == "查询超时" || log.GUEST_IDENTIFICATION == "查询失败" {
|
||||
return true
|
||||
}
|
||||
//伪爬虫是否开启图形验证
|
||||
if global.GCONFIG_RECORD_FAKE_SPIDER_CAPTCHA == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 处理验证码
|
||||
func (waf *WafEngine) handleCaptchaRequest(w http.ResponseWriter, r *http.Request, expireTime int, log innerbean.WebLog) {
|
||||
// 使用验证码服务处理请求
|
||||
captchaService := wafcaptcha.GetService()
|
||||
captchaService.HandleCaptchaRequest(w, r, expireTime, log)
|
||||
}
|
||||
@@ -38,6 +38,7 @@ func (waf *WafEngine) CheckBot(r *http.Request, weblogbean *innerbean.WebLog, fo
|
||||
botResult = global.GCACHE_WAFCACHE.Get(enums.CACHE_DNS_BOT_IP + weblogbean.SRC_IP).(wafbot.BotResult)
|
||||
}
|
||||
if botResult.IsBot == true {
|
||||
weblogbean.IsBot = 1
|
||||
if botResult.IsNormalBot {
|
||||
weblogbean.GUEST_IDENTIFICATION = botResult.BotName
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
package wafcaptcha
|
||||
|
||||
/*
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/wenlng/go-captcha-assets/bindata/chars"
|
||||
"github.com/wenlng/go-captcha-assets/resources/fonts/fzshengsksjw"
|
||||
"github.com/wenlng/go-captcha-assets/resources/images"
|
||||
"github.com/wenlng/go-captcha/v2/base/option"
|
||||
"github.com/wenlng/go-captcha/v2/click"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var textCapt click.Captcha
|
||||
var lightTextCapt click.Captcha
|
||||
|
||||
func init() {
|
||||
builder := click.NewBuilder(
|
||||
click.WithRangeLen(option.RangeVal{Min: 4, Max: 6}),
|
||||
click.WithRangeVerifyLen(option.RangeVal{Min: 2, Max: 4}),
|
||||
//click.WithRangeLen(option.RangeVal{Min: 2, Max: 4}),
|
||||
//click.WithDisabledRangeVerifyLen(true),
|
||||
click.WithRangeThumbColors([]string{
|
||||
"#1f55c4",
|
||||
"#780592",
|
||||
"#2f6b00",
|
||||
"#910000",
|
||||
"#864401",
|
||||
"#675901",
|
||||
"#016e5c",
|
||||
}),
|
||||
click.WithRangeColors([]string{
|
||||
"#fde98e",
|
||||
"#60c1ff",
|
||||
"#fcb08e",
|
||||
"#fb88ff",
|
||||
"#b4fed4",
|
||||
"#cbfaa9",
|
||||
"#78d6f8",
|
||||
}),
|
||||
)
|
||||
|
||||
// fonts
|
||||
fonts, err := fzshengsksjw.GetFont()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// background images
|
||||
imgs, err := images.GetImages()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// thumb images
|
||||
//thumbImages, err := thumbs.GetThumbs()
|
||||
//if err != nil {
|
||||
// log.Fatalln(err)
|
||||
//}
|
||||
|
||||
// set resources
|
||||
builder.SetResources(
|
||||
click.WithChars(chars.GetChineseChars()),
|
||||
//click.WithChars([]string{
|
||||
// "1A",
|
||||
// "5E",
|
||||
// "3d",
|
||||
// "0p",
|
||||
// "78",
|
||||
// "DL",
|
||||
// "CB",
|
||||
// "9M",
|
||||
//}),
|
||||
//click.WithChars(chars.GetAlphaChars()),
|
||||
click.WithFonts([]*truetype.Font{fonts}),
|
||||
click.WithBackgrounds(imgs),
|
||||
//click.WithThumbBackgrounds(thumbImages),
|
||||
)
|
||||
textCapt = builder.Make()
|
||||
|
||||
// ============================
|
||||
|
||||
builder.Clear()
|
||||
builder.SetOptions(
|
||||
click.WithRangeLen(option.RangeVal{Min: 4, Max: 6}),
|
||||
click.WithRangeVerifyLen(option.RangeVal{Min: 2, Max: 4}),
|
||||
click.WithRangeThumbColors([]string{
|
||||
"#4a85fb",
|
||||
"#d93ffb",
|
||||
"#56be01",
|
||||
"#ee2b2b",
|
||||
"#cd6904",
|
||||
"#b49b03",
|
||||
"#01ad90",
|
||||
}),
|
||||
)
|
||||
builder.SetResources(
|
||||
click.WithChars(chars.GetChineseChars()),
|
||||
click.WithFonts([]*truetype.Font{fonts}),
|
||||
click.WithBackgrounds(imgs),
|
||||
)
|
||||
lightTextCapt = builder.Make()
|
||||
}
|
||||
|
||||
// GetClickBasicCaptData .
|
||||
func GetClickBasicCaptData(w http.ResponseWriter, r *http.Request) {
|
||||
var capt click.Captcha
|
||||
if r.URL.Query().Get("type") == "light" {
|
||||
capt = lightTextCapt
|
||||
} else {
|
||||
capt = textCapt
|
||||
}
|
||||
|
||||
captData, err := capt.Generate()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
dotData := captData.GetData()
|
||||
if dotData == nil {
|
||||
bt, _ := json.Marshal(map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "gen captcha data failed",
|
||||
})
|
||||
_, _ = fmt.Fprintf(w, string(bt))
|
||||
return
|
||||
}
|
||||
|
||||
var masterImageBase64, thumbImageBase64 string
|
||||
masterImageBase64, err = captData.GetMasterImage().ToBase64()
|
||||
if err != nil {
|
||||
bt, _ := json.Marshal(map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "base64 data failed",
|
||||
})
|
||||
_, _ = fmt.Fprintf(w, string(bt))
|
||||
return
|
||||
}
|
||||
|
||||
thumbImageBase64, err = captData.GetThumbImage().ToBase64()
|
||||
if err != nil {
|
||||
bt, _ := json.Marshal(map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "base64 data failed",
|
||||
})
|
||||
_, _ = fmt.Fprintf(w, string(bt))
|
||||
return
|
||||
}
|
||||
|
||||
dotsByte, _ := json.Marshal(dotData)
|
||||
key := GenUniqueId()
|
||||
cache.WriteCache(key, dotsByte)
|
||||
fmt.Println("dot>>>>>", string(dotsByte))
|
||||
|
||||
bt, _ := json.Marshal(map[string]interface{}{
|
||||
"code": 0,
|
||||
"captcha_key": key,
|
||||
"image_base64": masterImageBase64,
|
||||
"thumb_base64": thumbImageBase64,
|
||||
})
|
||||
|
||||
_, _ = fmt.Fprintf(w, string(bt))
|
||||
}
|
||||
|
||||
var num int64
|
||||
|
||||
const (
|
||||
Continuity = "20060102150405"
|
||||
)
|
||||
|
||||
func GenUniqueId() string {
|
||||
t := time.Now()
|
||||
s := t.Format(Continuity)
|
||||
m := t.UnixNano()/1e6 - t.UnixNano()/1e9*1e3
|
||||
ms := sup(m, 3)
|
||||
p := os.Getpid() % 1000
|
||||
ps := sup(int64(p), 3)
|
||||
i := atomic.AddInt64(&num, 1)
|
||||
r := i % 10000
|
||||
rs := sup(r, 4)
|
||||
n := fmt.Sprintf("%s%s%s%s", s, ms, ps, rs)
|
||||
return n
|
||||
}
|
||||
|
||||
func sup(i int64, n int) string {
|
||||
m := fmt.Sprintf("%d", i)
|
||||
for len(m) < n {
|
||||
m = fmt.Sprintf("0%s", m)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// CheckClickData .
|
||||
func CheckClickData(w http.ResponseWriter, r *http.Request) {
|
||||
code := 1
|
||||
_ = r.ParseForm()
|
||||
dots := r.Form.Get("dots")
|
||||
key := r.Form.Get("key")
|
||||
if dots == "" || key == "" {
|
||||
bt, _ := json.Marshal(map[string]interface{}{
|
||||
"code": code,
|
||||
"message": "dots or key param is empty",
|
||||
})
|
||||
_, _ = fmt.Fprintf(w, string(bt))
|
||||
return
|
||||
}
|
||||
|
||||
cacheDataByte := cache.ReadCache(key)
|
||||
if len(cacheDataByte) == 0 {
|
||||
bt, _ := json.Marshal(map[string]interface{}{
|
||||
"code": code,
|
||||
"message": "illegal key",
|
||||
})
|
||||
_, _ = fmt.Fprintf(w, string(bt))
|
||||
return
|
||||
}
|
||||
src := strings.Split(dots, ",")
|
||||
|
||||
var dct map[int]*click.Dot
|
||||
if err := json.Unmarshal(cacheDataByte, &dct); err != nil {
|
||||
bt, _ := json.Marshal(map[string]interface{}{
|
||||
"code": code,
|
||||
"message": "illegal key",
|
||||
})
|
||||
_, _ = fmt.Fprintf(w, string(bt))
|
||||
return
|
||||
}
|
||||
|
||||
chkRet := false
|
||||
if (len(dct) * 2) == len(src) {
|
||||
for i := 0; i < len(dct); i++ {
|
||||
dot := dct[i]
|
||||
j := i * 2
|
||||
k := i*2 + 1
|
||||
sx, _ := strconv.ParseFloat(fmt.Sprintf("%v", src[j]), 64)
|
||||
sy, _ := strconv.ParseFloat(fmt.Sprintf("%v", src[k]), 64)
|
||||
|
||||
chkRet = click.CheckPoint(int64(sx), int64(sy), int64(dot.X), int64(dot.Y), int64(dot.Width), int64(dot.Height), 0)
|
||||
if !chkRet {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if chkRet {
|
||||
code = 0
|
||||
}
|
||||
|
||||
bt, _ := json.Marshal(map[string]interface{}{
|
||||
"code": code,
|
||||
})
|
||||
_, _ = fmt.Fprintf(w, string(bt))
|
||||
return
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,436 @@
|
||||
package wafcaptcha
|
||||
|
||||
import (
|
||||
"SamWaf/cache"
|
||||
"SamWaf/common/zlog"
|
||||
"SamWaf/enums"
|
||||
"SamWaf/global"
|
||||
"SamWaf/innerbean"
|
||||
"SamWaf/utils"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/golang/freetype/truetype"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"github.com/wenlng/go-captcha-assets/bindata/chars"
|
||||
"github.com/wenlng/go-captcha-assets/resources/fonts/fzshengsksjw"
|
||||
"github.com/wenlng/go-captcha-assets/resources/images"
|
||||
"github.com/wenlng/go-captcha/v2/base/option"
|
||||
"github.com/wenlng/go-captcha/v2/click"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
captchaService *CaptchaService
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// CaptchaService 验证码服务结构体
|
||||
type CaptchaService struct {
|
||||
cache *cache.WafCache
|
||||
//text
|
||||
textCapt click.Captcha
|
||||
lightTextCapt click.Captcha
|
||||
}
|
||||
|
||||
// InitCaptchaService 初始化验证码服务,传入缓存引用
|
||||
func InitCaptchaService(cache *cache.WafCache) {
|
||||
once.Do(func() {
|
||||
captchaService = &CaptchaService{
|
||||
cache: cache,
|
||||
}
|
||||
captchaService.InitTextCapt()
|
||||
})
|
||||
}
|
||||
func (s *CaptchaService) InitTextCapt() {
|
||||
builder := click.NewBuilder(
|
||||
click.WithRangeLen(option.RangeVal{Min: 4, Max: 6}),
|
||||
click.WithRangeVerifyLen(option.RangeVal{Min: 2, Max: 4}),
|
||||
//click.WithRangeLen(option.RangeVal{Min: 2, Max: 4}),
|
||||
//click.WithDisabledRangeVerifyLen(true),
|
||||
click.WithRangeThumbColors([]string{
|
||||
"#1f55c4",
|
||||
"#780592",
|
||||
"#2f6b00",
|
||||
"#910000",
|
||||
"#864401",
|
||||
"#675901",
|
||||
"#016e5c",
|
||||
}),
|
||||
click.WithRangeColors([]string{
|
||||
"#fde98e",
|
||||
"#60c1ff",
|
||||
"#fcb08e",
|
||||
"#fb88ff",
|
||||
"#b4fed4",
|
||||
"#cbfaa9",
|
||||
"#78d6f8",
|
||||
}),
|
||||
)
|
||||
|
||||
// fonts
|
||||
fonts, err := fzshengsksjw.GetFont()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// background images
|
||||
imgs, err := images.GetImages()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// thumb images
|
||||
//thumbImages, err := thumbs.GetThumbs()
|
||||
//if err != nil {
|
||||
// log.Fatalln(err)
|
||||
//}
|
||||
|
||||
// set resources
|
||||
builder.SetResources(
|
||||
click.WithChars(chars.GetChineseChars()),
|
||||
//click.WithChars([]string{
|
||||
// "1A",
|
||||
// "5E",
|
||||
// "3d",
|
||||
// "0p",
|
||||
// "78",
|
||||
// "DL",
|
||||
// "CB",
|
||||
// "9M",
|
||||
//}),
|
||||
//click.WithChars(chars.GetAlphaChars()),
|
||||
click.WithFonts([]*truetype.Font{fonts}),
|
||||
click.WithBackgrounds(imgs),
|
||||
//click.WithThumbBackgrounds(thumbImages),
|
||||
)
|
||||
s.textCapt = builder.Make()
|
||||
|
||||
// ============================
|
||||
|
||||
builder.Clear()
|
||||
builder.SetOptions(
|
||||
click.WithRangeLen(option.RangeVal{Min: 4, Max: 6}),
|
||||
click.WithRangeVerifyLen(option.RangeVal{Min: 2, Max: 4}),
|
||||
click.WithRangeThumbColors([]string{
|
||||
"#4a85fb",
|
||||
"#d93ffb",
|
||||
"#56be01",
|
||||
"#ee2b2b",
|
||||
"#cd6904",
|
||||
"#b49b03",
|
||||
"#01ad90",
|
||||
}),
|
||||
)
|
||||
builder.SetResources(
|
||||
click.WithChars(chars.GetChineseChars()),
|
||||
click.WithFonts([]*truetype.Font{fonts}),
|
||||
click.WithBackgrounds(imgs),
|
||||
)
|
||||
s.lightTextCapt = builder.Make()
|
||||
}
|
||||
|
||||
// GetService 获取验证码服务实例
|
||||
func GetService() *CaptchaService {
|
||||
if captchaService == nil {
|
||||
zlog.Warn("验证码服务未初始化,请先调用 InitCaptchaService")
|
||||
// 返回一个空服务,避免空指针异常
|
||||
return &CaptchaService{}
|
||||
}
|
||||
return captchaService
|
||||
}
|
||||
|
||||
// HandleCaptchaRequest 处理验证码请求
|
||||
func (s *CaptchaService) HandleCaptchaRequest(w http.ResponseWriter, r *http.Request, expireTime int, weblog innerbean.WebLog) {
|
||||
// 从路径中提取验证码相关部分
|
||||
path := r.URL.Path
|
||||
// 记录访问日志
|
||||
zlog.Debug("验证码请求", zap.String("path", path), zap.String("method", r.Method), zap.String("remote_addr", r.RemoteAddr))
|
||||
|
||||
if strings.HasPrefix(path, "/samwaf_captcha/click_basic") {
|
||||
s.GetClickBasicCaptData(w, r)
|
||||
} else if strings.HasPrefix(path, "/samwaf_captcha/verify") {
|
||||
// 根据请求参数确定验证码类型
|
||||
captchaType := r.URL.Query().Get("type")
|
||||
s.VerifyCaptcha(w, r, captchaType, expireTime, weblog)
|
||||
} else if strings.HasPrefix(path, "/samwaf_captcha/") {
|
||||
cleanPath := strings.TrimPrefix(path, "/samwaf_captcha/")
|
||||
s.ServeStaticFile(w, r, cleanPath)
|
||||
} else {
|
||||
// 记录日志信息
|
||||
weblog.ACTION = "禁止"
|
||||
weblog.RULE = "显示图形验证码"
|
||||
global.GQEQUE_LOG_DB.Enqueue(weblog)
|
||||
// 默认显示验证码选择页面
|
||||
s.ShowCaptchaHomePage(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// ServeStaticFile 提供静态文件服务
|
||||
func (s *CaptchaService) ServeStaticFile(w http.ResponseWriter, r *http.Request, filePath string) {
|
||||
// 安全检查:防止路径遍历攻击
|
||||
if containsPathTraversal(filePath) {
|
||||
zlog.Warn("检测到路径遍历尝试", zap.String("path", filePath), zap.String("remote_addr", r.RemoteAddr))
|
||||
http.Error(w, "403 Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// 规范化文件路径,移除所有 ".." 和多余的斜杠
|
||||
cleanPath := path.Clean(filePath)
|
||||
|
||||
// 确保路径不以 "/" 或 "\" 开头,防止访问根目录
|
||||
if strings.HasPrefix(cleanPath, "/") || strings.HasPrefix(cleanPath, "\\") {
|
||||
cleanPath = cleanPath[1:]
|
||||
}
|
||||
|
||||
// 根据文件扩展名设置Content-Type
|
||||
if strings.HasSuffix(cleanPath, ".css") {
|
||||
w.Header().Set("Content-Type", "text/css")
|
||||
} else if strings.HasSuffix(cleanPath, ".js") {
|
||||
w.Header().Set("Content-Type", "application/javascript")
|
||||
} else if strings.HasSuffix(cleanPath, ".html") {
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
} else if strings.HasSuffix(cleanPath, ".png") {
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
} else if strings.HasSuffix(cleanPath, ".jpg") || strings.HasSuffix(cleanPath, ".jpeg") {
|
||||
w.Header().Set("Content-Type", "image/jpeg")
|
||||
}
|
||||
|
||||
// 构建安全的完整路径
|
||||
basePath := utils.GetCurrentDir() + "/data/captcha/"
|
||||
fullPath := filepath.Join(basePath, cleanPath)
|
||||
|
||||
// 再次验证路径是否在允许的目录内
|
||||
absBasePath, _ := filepath.Abs(basePath)
|
||||
absFullPath, _ := filepath.Abs(fullPath)
|
||||
|
||||
if !strings.HasPrefix(absFullPath, absBasePath) {
|
||||
zlog.Warn("检测到目录遍历尝试", zap.String("path", filePath), zap.String("fullPath", fullPath), zap.String("remote_addr", r.RemoteAddr))
|
||||
http.Error(w, "403 Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// 提供文件服务
|
||||
http.ServeFile(w, r, fullPath)
|
||||
}
|
||||
|
||||
// containsPathTraversal 检查路径中是否包含路径遍历尝试
|
||||
func containsPathTraversal(filePath string) bool {
|
||||
// 检查常见的路径遍历模式
|
||||
return strings.Contains(filePath, "../") ||
|
||||
strings.Contains(filePath, "..\\") ||
|
||||
strings.Contains(filePath, "%2e%2e%2f") || // ../
|
||||
strings.Contains(filePath, "%2e%2e/") || // ../
|
||||
strings.Contains(filePath, "..%2f") || // ../
|
||||
strings.Contains(filePath, "%2e%2e%5c") || // ..\
|
||||
strings.Contains(filePath, "..%5c") || // ..\
|
||||
strings.Contains(filePath, "\\\\") || // 双反斜杠
|
||||
strings.Contains(filePath, "//") // 双正斜杠
|
||||
}
|
||||
|
||||
// GetClickBasicCaptData 获取基础点击验证码数据
|
||||
func (s *CaptchaService) GetClickBasicCaptData(w http.ResponseWriter, r *http.Request) {
|
||||
var capt click.Captcha
|
||||
if r.URL.Query().Get("type") == "light" {
|
||||
capt = s.lightTextCapt
|
||||
} else {
|
||||
capt = s.textCapt
|
||||
}
|
||||
|
||||
captData, err := capt.Generate()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
dotData := captData.GetData()
|
||||
if dotData == nil {
|
||||
bt, _ := json.Marshal(map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "gen captcha data failed",
|
||||
})
|
||||
_, _ = fmt.Fprintf(w, string(bt))
|
||||
return
|
||||
}
|
||||
|
||||
var masterImageBase64, thumbImageBase64 string
|
||||
masterImageBase64, err = captData.GetMasterImage().ToBase64()
|
||||
if err != nil {
|
||||
bt, _ := json.Marshal(map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "base64 data failed",
|
||||
})
|
||||
_, _ = fmt.Fprintf(w, string(bt))
|
||||
return
|
||||
}
|
||||
|
||||
thumbImageBase64, err = captData.GetThumbImage().ToBase64()
|
||||
if err != nil {
|
||||
bt, _ := json.Marshal(map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "base64 data failed",
|
||||
})
|
||||
_, _ = fmt.Fprintf(w, string(bt))
|
||||
return
|
||||
}
|
||||
|
||||
dotsByte, _ := json.Marshal(dotData)
|
||||
key := uuid.NewV4().String()
|
||||
//key := helper.StringToMD5(string(dotsByte))
|
||||
s.cache.SetWithTTl(enums.CACHE_CAPTCHA_TRY+key, dotsByte, 1*time.Minute)
|
||||
|
||||
bt, _ := json.Marshal(map[string]interface{}{
|
||||
"code": 0,
|
||||
"captcha_key": key,
|
||||
"image_base64": masterImageBase64,
|
||||
"thumb_base64": thumbImageBase64,
|
||||
})
|
||||
|
||||
_, _ = fmt.Fprintf(w, string(bt))
|
||||
}
|
||||
|
||||
// VerifyCaptcha 验证验证码
|
||||
func (s *CaptchaService) VerifyCaptcha(w http.ResponseWriter, r *http.Request, captchaType string, expireTime int, webLog innerbean.WebLog) {
|
||||
code := 1
|
||||
_ = r.ParseForm()
|
||||
dots := r.Form.Get("dots")
|
||||
key := r.Form.Get("key")
|
||||
if dots == "" || key == "" {
|
||||
bt, _ := json.Marshal(map[string]interface{}{
|
||||
"code": code,
|
||||
"message": "dots or key param is empty",
|
||||
})
|
||||
_, _ = fmt.Fprintf(w, string(bt))
|
||||
return
|
||||
}
|
||||
|
||||
cacheDataByte, err := s.cache.GetBytes(enums.CACHE_CAPTCHA_TRY + key)
|
||||
if err != nil {
|
||||
bt, _ := json.Marshal(map[string]interface{}{
|
||||
"code": code,
|
||||
"message": "illegal key",
|
||||
})
|
||||
_, _ = fmt.Fprintf(w, string(bt))
|
||||
return
|
||||
}
|
||||
s.cache.Remove(enums.CACHE_CAPTCHA_TRY + key)
|
||||
if len(cacheDataByte) == 0 {
|
||||
bt, _ := json.Marshal(map[string]interface{}{
|
||||
"code": code,
|
||||
"message": "illegal key",
|
||||
})
|
||||
_, _ = fmt.Fprintf(w, string(bt))
|
||||
return
|
||||
}
|
||||
src := strings.Split(dots, ",")
|
||||
|
||||
var dct map[int]*click.Dot
|
||||
if err := json.Unmarshal(cacheDataByte, &dct); err != nil {
|
||||
bt, _ := json.Marshal(map[string]interface{}{
|
||||
"code": code,
|
||||
"message": "illegal key",
|
||||
})
|
||||
_, _ = fmt.Fprintf(w, string(bt))
|
||||
return
|
||||
}
|
||||
|
||||
chkRet := false
|
||||
if (len(dct) * 2) == len(src) {
|
||||
for i := 0; i < len(dct); i++ {
|
||||
dot := dct[i]
|
||||
j := i * 2
|
||||
k := i*2 + 1
|
||||
sx, _ := strconv.ParseFloat(fmt.Sprintf("%v", src[j]), 64)
|
||||
sy, _ := strconv.ParseFloat(fmt.Sprintf("%v", src[k]), 64)
|
||||
|
||||
chkRet = click.CheckPoint(int64(sx), int64(sy), int64(dot.X), int64(dot.Y), int64(dot.Width), int64(dot.Height), 0)
|
||||
if !chkRet {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if chkRet {
|
||||
code = 0
|
||||
// 生成验证通过的标识
|
||||
captchaPassToken := uuid.NewV4().String()
|
||||
// 将标识存入缓存
|
||||
s.cache.SetWithTTl(enums.CACHE_CAPTCHA_PASS+captchaPassToken, "ok", time.Duration(expireTime)*time.Hour)
|
||||
|
||||
// 设置Cookie
|
||||
cookie := &http.Cookie{
|
||||
Name: "samwaf_captcha_token",
|
||||
Value: captchaPassToken,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: r.TLS != nil, // 如果是HTTPS请求则设置Secure
|
||||
MaxAge: expireTime * 3600,
|
||||
}
|
||||
http.SetCookie(w, cookie)
|
||||
|
||||
// 同时在响应头中也设置验证标识
|
||||
w.Header().Set("X-SamWaf-Captcha-Token", captchaPassToken)
|
||||
webLog.ACTION = "放行"
|
||||
webLog.RULE = "图形验证码验证通过"
|
||||
global.GQEQUE_LOG_DB.Enqueue(webLog)
|
||||
} else {
|
||||
webLog.ACTION = "禁止"
|
||||
webLog.RULE = "图形验证码验证失败"
|
||||
global.GQEQUE_LOG_DB.Enqueue(webLog)
|
||||
}
|
||||
|
||||
bt, _ := json.Marshal(map[string]interface{}{
|
||||
"code": code,
|
||||
})
|
||||
_, _ = fmt.Fprintf(w, string(bt))
|
||||
return
|
||||
}
|
||||
|
||||
// ShowCaptchaHomePage 显示验证码首页
|
||||
func (s *CaptchaService) ShowCaptchaHomePage(w http.ResponseWriter, r *http.Request) {
|
||||
// 设置内容类型
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
|
||||
// 从指定目录加载index.html
|
||||
http.ServeFile(w, r, utils.GetCurrentDir()+"/data/captcha/index.html")
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
|
||||
// writeJSONResponse 写入JSON响应
|
||||
func writeJSONResponse(w http.ResponseWriter, data interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(data)
|
||||
}
|
||||
|
||||
// generateRandomKey 生成随机密钥
|
||||
func generateRandomKey() string {
|
||||
// 实际实现中应该使用更安全的随机数生成方法
|
||||
return "random_key_123456"
|
||||
}
|
||||
|
||||
// generateImageBase64 生成图片的Base64编码
|
||||
func generateImageBase64() string {
|
||||
// 实际实现中应该生成真实的验证码图片
|
||||
return "base64_encoded_image_data"
|
||||
}
|
||||
|
||||
// generateThumbBase64 生成缩略图的Base64编码
|
||||
func generateThumbBase64() string {
|
||||
// 实际实现中应该生成真实的缩略图
|
||||
return "base64_encoded_thumb_data"
|
||||
}
|
||||
@@ -356,6 +356,42 @@ func (waf *WafEngine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// 验证码检测
|
||||
captchaConfig := model.CaptchaConfig{
|
||||
IsEnableCaptcha: 0,
|
||||
ExcludeURLs: "",
|
||||
ExpireTime: 24,
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(hostTarget.Host.CaptchaJSON), &captchaConfig)
|
||||
if err != nil {
|
||||
zlog.Debug("解析captcha json失败")
|
||||
}
|
||||
|
||||
if captchaConfig.IsEnableCaptcha == 1 {
|
||||
if !waf.checkCaptchaToken(r, weblogbean) {
|
||||
// 检查当前URL是否在排除列表中
|
||||
currentURL := strings.ToLower(r.URL.Path)
|
||||
isExcluded := false
|
||||
|
||||
if len(captchaConfig.ExcludeURLs) > 0 {
|
||||
// 将换行分隔的URL列表拆分为数组
|
||||
excludeURLs := strings.Split(captchaConfig.ExcludeURLs, "\n")
|
||||
for _, excludeURL := range excludeURLs {
|
||||
// 去除可能的空白字符并转为小写
|
||||
excludeURL = strings.TrimSpace(strings.ToLower(excludeURL))
|
||||
if excludeURL != "" && strings.HasPrefix(currentURL, excludeURL) {
|
||||
isExcluded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !isExcluded {
|
||||
waf.handleCaptchaRequest(w, r, captchaConfig.ExpireTime, weblogbean)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -79,6 +79,9 @@ func setConfigIntValue(name string, value int64, change int) {
|
||||
case "force_bind_2fa":
|
||||
global.GCONFIG_RECORD_FORCE_BIND_2FA = value
|
||||
break
|
||||
case "fake_spider_captcha":
|
||||
global.GCONFIG_RECORD_FAKE_SPIDER_CAPTCHA = value
|
||||
break
|
||||
default:
|
||||
zlog.Warn("Unknown config item:", name)
|
||||
}
|
||||
@@ -218,4 +221,6 @@ func TaskLoadSetting(initLoad bool) {
|
||||
updateConfigStringItem(initLoad, "gpt", "gpt_model", global.GCONFIG_RECORD_GPT_MODEL, "GPT模型名称", "string", "")
|
||||
updateConfigIntItem(initLoad, "security", "hide_server_header", global.GCONFIG_RECORD_HIDE_SERVER_HEADER, "是否隐藏Server响应头(1隐藏 0不隐藏)", "int", "")
|
||||
updateConfigIntItem(initLoad, "security", "force_bind_2fa", global.GCONFIG_RECORD_FORCE_BIND_2FA, "是否强制绑定双因素认证(1强制 0不强制)", "options", "0|不强制,1|强制")
|
||||
updateConfigIntItem(initLoad, "system", "fake_spider_captcha", global.GCONFIG_RECORD_FAKE_SPIDER_CAPTCHA, "伪爬虫进行图形挑战开关 0 放过 1 显示图形验证码", "int", "")
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user