diff --git a/.gitignore b/.gitignore
index 900a83e..919c978 100644
--- a/.gitignore
+++ b/.gitignore
@@ -63,3 +63,4 @@ www/html/index.html
/src/plugins
.DS_Store
+/build
diff --git a/src/mod/dynamicproxy/Server.go b/src/mod/dynamicproxy/Server.go
index 17ec73b..fa9d400 100644
--- a/src/mod/dynamicproxy/Server.go
+++ b/src/mod/dynamicproxy/Server.go
@@ -75,6 +75,15 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
+ /* Exploit Detection */
+ if sep.detector != nil {
+ if sep.detector.CheckIsAttack(w, r) {
+ //Request was handled by exploit detector, log it
+ h.Parent.logRequest(r, false, 403, "exploit-blocked", domainOnly, "blocked", sep)
+ return
+ }
+ }
+
// Rate Limit
if sep.RequireRateLimit {
err := h.handleRateLimitRouting(w, r, sep)
diff --git a/src/mod/dynamicproxy/dynamicproxy.go b/src/mod/dynamicproxy/dynamicproxy.go
index f90f1d2..5fb873e 100644
--- a/src/mod/dynamicproxy/dynamicproxy.go
+++ b/src/mod/dynamicproxy/dynamicproxy.go
@@ -391,6 +391,8 @@ func CopyEndpoint(endpoint *ProxyEndpoint) *ProxyEndpoint {
if err != nil {
return nil
}
+ // Initialize the exploit detector for the copied endpoint
+ newProxyEndpoint.InitializeExploitDetector()
return &newProxyEndpoint
}
diff --git a/src/mod/dynamicproxy/exploits/exploits.go b/src/mod/dynamicproxy/exploits/exploits.go
index ff5e6dd..79b0055 100644
--- a/src/mod/dynamicproxy/exploits/exploits.go
+++ b/src/mod/dynamicproxy/exploits/exploits.go
@@ -4,6 +4,14 @@ package exploits
exploits.go
This file is used to define routing rules that blocks common exploits.
+ These include SQL injection, file injection, and other common attack patterns.
+ It can also detect requests made by AI crawlers or bots based on their user-agent strings.
+
+ Warning: This is not a complete security solution. Sometimes it might misfire and block legitimate requests.
+ Use with caution and always monitor the logs.
+
+ References:
+ https://github.com/NginxProxyManager/nginx-proxy-manager/blob/bb0f4bfa626bfa30e3ad2fa31b5d759c9de98559/docker/rootfs/etc/nginx/conf.d/include/block-exploits.conf
*/
@@ -15,11 +23,80 @@ import (
agents "github.com/monperrus/crawler-user-agents"
)
+type ExploitsRequestResponseType int
+
+const (
+ ExploitRequestResponseTypeNotFound ExploitsRequestResponseType = iota //Respond with 404 Not Found
+ ExploitRequestResponseTypeForbidden //Respond with 403 Forbidden
+ ExploitRequestResponseTypeBadRequest //Respond with 400 Bad Request
+ ExploitRequestResponseTypeDropConnection //Drop the connection without responding
+ ExploitRequestResponseTypeCaptcha //Present a captcha challenge
+)
+
type Detector struct {
+ CheckCommonExploits bool
+ CheckAiBots bool
+ ExploitRespType ExploitsRequestResponseType
}
-func NewExploitDetector() *Detector {
- return &Detector{}
+func NewExploitDetector(CheckCommonExploits bool, CheckAiBots bool, ExploitRespType ExploitsRequestResponseType) *Detector {
+ return &Detector{
+ CheckCommonExploits: CheckCommonExploits,
+ CheckAiBots: CheckAiBots,
+ ExploitRespType: ExploitRespType,
+ }
+}
+
+// CheckIsAttack checks if the request is an attack based on common exploits
+// return true if the request is handled
+func (d *Detector) CheckIsAttack(w http.ResponseWriter, r *http.Request) bool {
+ if d.CheckCommonExploits && d.RequestContainCommonExploits(r) {
+ return d.handleExploitResponse(w, r, d.ExploitRespType)
+ }
+ if d.CheckAiBots && d.RequestIsMadeByAiCrawlerOrBots(r) {
+ return d.handleExploitResponse(w, r, d.ExploitRespType)
+ }
+ return false
+}
+
+// GetResponseStatusCodeFromResponseType converts the response type to HTTP status code
+func (d *Detector) GetResponseStatusCode() int {
+ respType := d.ExploitRespType
+ switch respType {
+ case ExploitRequestResponseTypeNotFound:
+ return http.StatusNotFound
+ case ExploitRequestResponseTypeForbidden:
+ return http.StatusForbidden
+ case ExploitRequestResponseTypeBadRequest:
+ return http.StatusBadRequest
+ default:
+ return http.StatusForbidden
+ }
+}
+
+func (d *Detector) handleExploitResponse(w http.ResponseWriter, r *http.Request, respType ExploitsRequestResponseType) bool {
+ isHandled := true
+ switch respType {
+ case ExploitRequestResponseTypeNotFound:
+ http.NotFound(w, r)
+ case ExploitRequestResponseTypeForbidden:
+ http.Error(w, "Forbidden", http.StatusForbidden)
+ case ExploitRequestResponseTypeBadRequest:
+ http.Error(w, "Bad Request", http.StatusBadRequest)
+ case ExploitRequestResponseTypeDropConnection:
+ // Drop the connection without responding
+ hj, ok := w.(http.Hijacker)
+ if ok {
+ conn, _, err := hj.Hijack()
+ if err == nil {
+ conn.Close()
+ }
+ }
+
+ case ExploitRequestResponseTypeCaptcha:
+ // Present a captcha challenge
+ }
+ return isHandled
}
// RequestContainCommonExploits checks if the request contains common exploits
@@ -101,8 +178,31 @@ func (d *Detector) RequestContainCommonExploits(r *http.Request) bool {
return false
}
-// RequestIsMadeByBots checks if the request is made by bots or crawlers
-func (d *Detector) RequestIsMadeByBots(r *http.Request) bool {
+func (d *Detector) RequestIsMadeByAiCrawlerOrBots(r *http.Request) bool {
userAgent := r.UserAgent()
+ if userAgent == "" {
+ return false
+ }
+
+ aiBotPatterns := []string{
+ `(?i)openai`,
+ `(?i)chatgpt`,
+ `(?i)gpt-?`,
+ `(?i)claude`,
+ `(?i)anthropic`,
+ `(?i)perplexity`,
+ `(?i)perplexitybot`,
+ `(?i)bingbot`,
+ `(?i)bingpreview`,
+ `(?i)serpapi`,
+ }
+
+ for _, pattern := range aiBotPatterns {
+ if match, _ := regexp.MatchString(pattern, userAgent); match {
+ return true
+ }
+ }
+
+ // Fallback: treat known crawlers as bots too
return agents.IsCrawler(userAgent)
}
diff --git a/src/mod/dynamicproxy/redirection/redirection.go b/src/mod/dynamicproxy/redirection/redirection.go
index 15a3b14..a7de77c 100644
--- a/src/mod/dynamicproxy/redirection/redirection.go
+++ b/src/mod/dynamicproxy/redirection/redirection.go
@@ -2,7 +2,6 @@ package redirection
import (
"encoding/json"
- "fmt"
"log"
"os"
"path"
@@ -215,7 +214,6 @@ func (t *RuleTable) MatchRedirectRule(requestedURL string) *RedirectRules {
//Check matching based on exact match requirement
var matched bool
if rule.RequireExactMatch {
- fmt.Println(requestedURL, keyStr)
//Exact match required
if t.CaseSensitive {
matched = requestedURL == keyStr
diff --git a/src/mod/dynamicproxy/router.go b/src/mod/dynamicproxy/router.go
index 2e484d2..49500af 100644
--- a/src/mod/dynamicproxy/router.go
+++ b/src/mod/dynamicproxy/router.go
@@ -8,6 +8,7 @@ import (
"time"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
+ "imuslab.com/zoraxy/mod/dynamicproxy/exploits"
"imuslab.com/zoraxy/mod/utils"
)
@@ -66,6 +67,9 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
vdir.parent = endpoint
}
+ // Initialize the exploit detector for this endpoint
+ endpoint.InitializeExploitDetector()
+
return endpoint, nil
}
@@ -152,3 +156,13 @@ func (h *Router) GetProxyEndpointByAlias(alias string) (*ProxyEndpoint, error) {
}
return nil, errors.New("proxy rule with given alias not found")
}
+
+// InitializeExploitDetector initializes or updates the exploit detector for this proxy endpoint
+func (pe *ProxyEndpoint) InitializeExploitDetector() {
+ if pe.BlockCommonExploits || pe.BlockAICrawlers {
+ exploitRespType := exploits.ExploitsRequestResponseType(pe.MitigationAction)
+ pe.detector = exploits.NewExploitDetector(pe.BlockCommonExploits, pe.BlockAICrawlers, exploitRespType)
+ } else {
+ pe.detector = nil
+ }
+}
diff --git a/src/mod/dynamicproxy/typedef.go b/src/mod/dynamicproxy/typedef.go
index d207a86..403a367 100644
--- a/src/mod/dynamicproxy/typedef.go
+++ b/src/mod/dynamicproxy/typedef.go
@@ -18,6 +18,7 @@ import (
"imuslab.com/zoraxy/mod/access"
"imuslab.com/zoraxy/mod/auth/sso/forward"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
+ "imuslab.com/zoraxy/mod/dynamicproxy/exploits"
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
@@ -211,6 +212,11 @@ type ProxyEndpoint struct {
DisableLogging bool //Disable logging of reverse proxy requests
DisableStatisticCollection bool //Disable statistic collection for this endpoint
+ //Exploit Detection
+ BlockCommonExploits bool //Enable blocking of common exploits (SQLi, XSS, etc.)
+ BlockAICrawlers bool //Enable blocking of AI crawlers and bots
+ MitigationAction int //Action to take when exploit/crawler detected (0=404, 1=403, 2=400, 3=Drop, 4=Delay, 5=Captcha)
+
// Chunked Transfer Encoding
DisableChunkedTransferEncoding bool //Disable chunked transfer encoding for this endpoint
@@ -222,8 +228,9 @@ type ProxyEndpoint struct {
DefaultSiteValue string //Fallback routing target, optional
//Internal Logic Elements
- parent *Router `json:"-"`
- Tags []string // Tags for the proxy endpoint
+ parent *Router `json:"-"` //Parent router, excluded from JSON
+ detector *exploits.Detector `json:"-"` //Exploit detector instance, excluded from JSON
+ Tags []string // Tags for the proxy endpoint
}
/*
diff --git a/src/reverseproxy.go b/src/reverseproxy.go
index 8e9cf77..943837d 100644
--- a/src/reverseproxy.go
+++ b/src/reverseproxy.go
@@ -343,6 +343,15 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
}
tags = filteredTags
+ // Exploit prevention settings
+ blockCommonExploits, _ := utils.PostBool(r, "blockCommonExploits")
+ blockAICrawlers, _ := utils.PostBool(r, "blockAICrawlers")
+ mitigationActionStr, _ := utils.PostPara(r, "mitigationAction")
+ mitigationAction := 0
+ if mitigationActionStr != "" {
+ mitigationAction, _ = strconv.Atoi(mitigationActionStr)
+ }
+
var proxyEndpointCreated *dynamicproxy.ProxyEndpoint
switch eptype {
case "host":
@@ -420,6 +429,9 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
Tags: tags,
DisableUptimeMonitor: !enableUtm,
DisableLogging: disableLog,
+ BlockCommonExploits: blockCommonExploits,
+ BlockAICrawlers: blockAICrawlers,
+ MitigationAction: mitigationAction,
}
preparedEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisProxyEndpoint)
@@ -580,6 +592,15 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
// Disable statistic collection
disableStatisticCollection, _ := utils.PostBool(r, "dStatisticCollection")
+ // Exploit Detection
+ blockCommonExploits, _ := utils.PostBool(r, "blockCommonExploits")
+ blockAICrawlers, _ := utils.PostBool(r, "blockAICrawlers")
+ mitigationActionStr, _ := utils.PostPara(r, "mitigationAction")
+ mitigationAction := 0
+ if mitigationActionStr != "" {
+ mitigationAction, _ = strconv.Atoi(mitigationActionStr)
+ }
+
//Load the previous basic auth credentials from current proxy rules
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
if err != nil {
@@ -623,6 +644,9 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
newProxyEndpoint.DisableChunkedTransferEncoding = disableChunkedEncoding
newProxyEndpoint.DisableLogging = disableLogging
newProxyEndpoint.DisableStatisticCollection = disableStatisticCollection
+ newProxyEndpoint.BlockCommonExploits = blockCommonExploits
+ newProxyEndpoint.BlockAICrawlers = blockAICrawlers
+ newProxyEndpoint.MitigationAction = mitigationAction
newProxyEndpoint.Tags = tags
//Prepare to replace the current routing rule
diff --git a/src/router.go b/src/router.go
index 064ed27..92434ca 100644
--- a/src/router.go
+++ b/src/router.go
@@ -1,7 +1,6 @@
package main
import (
- "fmt"
"net/http"
"os"
"path/filepath"
@@ -68,13 +67,13 @@ func FSHandler(handler http.Handler) http.Handler {
if len(parts) > 2 {
//Extract the instance ID from the request path
instanceUUID := parts[2]
- fmt.Println(instanceUUID)
+ //fmt.Println(instanceUUID)
//Rewrite the url so the proxy knows how to serve stuffs
r.URL, _ = sshprox.RewriteURL("/web.ssh/"+instanceUUID, r.RequestURI)
webSshManager.HandleHttpByInstanceId(instanceUUID, w, r)
} else {
- fmt.Println(parts)
+ //fmt.Println(parts)
http.Error(w, "Invalid Usage", http.StatusInternalServerError)
}
return
diff --git a/src/web/components/httprp.html b/src/web/components/httprp.html
index 0e3e3d3..7c563bb 100644
--- a/src/web/components/httprp.html
+++ b/src/web/components/httprp.html
@@ -437,7 +437,38 @@
-
+