Skip to content

Commit 0bebd73

Browse files
authored
Merge pull request #10 from discoverygarden/IT-692
IT-692: Stop false positives on substrings
2 parents da43f03 + 80078ae commit 0bebd73

File tree

2 files changed

+65
-13
lines changed

2 files changed

+65
-13
lines changed

botblocker.go

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"fmt"
77
"io"
8+
"regexp"
89

910
"net/http"
1011
"net/netip"
@@ -183,26 +184,35 @@ func (b *BotBlocker) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
183184
log.Debugf("Checking request: CIDR: \"%v\" user agent: \"%s\"", req.RemoteAddr, req.UserAgent())
184185
// Using an external plugin to avoid https://github.com/traefik/yaegi/issues/1697
185186
timer := getTimer(startTime)
186-
defer timer()
187187

188188
remoteAddrPort, err := netip.ParseAddrPort(req.RemoteAddr)
189189
if err != nil {
190+
timer()
190191
http.Error(rw, "internal error", http.StatusInternalServerError)
191192
return
192193
}
193194
if b.shouldBlockIp(remoteAddrPort.Addr()) {
194-
log.Infof("blocked request with from IP %v", remoteAddrPort.Addr())
195+
log.Infof("blocked request with from IP \"%v\"", remoteAddrPort.Addr())
196+
timer()
195197
http.Error(rw, "blocked", http.StatusForbidden)
196198
return
197199
}
198200

199201
agent := strings.ToLower(req.UserAgent())
200-
if b.shouldBlockAgent(agent) {
201-
log.Infof("blocked request with user agent %v because it contained %v", agent, agent)
202+
blocked, badAgent, err := b.shouldBlockAgent(agent)
203+
if err != nil {
204+
timer()
205+
http.Error(rw, "internal error", http.StatusInternalServerError)
206+
return
207+
}
208+
if blocked {
209+
log.Infof("blocked request with user agent \"%v\" because it contained \"%v\"", agent, badAgent)
210+
timer()
202211
http.Error(rw, "blocked", http.StatusForbidden)
203212
return
204213
}
205214

215+
timer()
206216
b.next.ServeHTTP(rw, req)
207217
}
208218

@@ -215,14 +225,23 @@ func (b *BotBlocker) shouldBlockIp(addr netip.Addr) bool {
215225
return false
216226
}
217227

218-
func (b *BotBlocker) shouldBlockAgent(userAgent string) bool {
228+
func (b *BotBlocker) shouldBlockAgent(userAgent string) (bool, string, error) {
219229
userAgent = strings.ToLower(strings.TrimSpace(userAgent))
220230
for _, badAgent := range b.userAgentBlockList {
231+
// fast check with contains
221232
if strings.Contains(userAgent, badAgent) {
222-
return true
233+
// verify with regex
234+
pattern := fmt.Sprintf(`(?:\b)%s(?:\b)`, badAgent)
235+
matched, err := regexp.Match(pattern, []byte(userAgent))
236+
if err != nil {
237+
return false, "", fmt.Errorf("failed to check user agent %s: %e", userAgent, err)
238+
}
239+
if matched {
240+
return true, badAgent, nil
241+
}
223242
}
224243
}
225-
return false
244+
return false, "", nil
226245
}
227246

228247
func getTimer(startTime time.Time) func() {

botblocker_test.go

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,29 +150,62 @@ func TestShouldAllowIpCidr(t *testing.T) {
150150
}
151151

152152
func TestShouldBlockUserAgent(t *testing.T) {
153+
badAgent := "nintendobrowser"
153154
botBlocker := BotBlocker{
154155
userAgentBlockList: []string{
155-
"nintendobrowser",
156+
badAgent,
156157
},
157158
}
158-
badUserAgent := "Mozilla/5.0 (Nintendo WiiU) AppleWebKit/536.30 (KHTML, like Gecko) NX/3.0.4.2.12 NintendoBrowser/4.3.1.11264.US"
159+
requestAgent := "Mozilla/5.0 (Nintendo WiiU) AppleWebKit/536.30 (KHTML, like Gecko) NX/3.0.4.2.12 NintendoBrowser/4.3.1.11264.US"
159160

160-
blocked := botBlocker.shouldBlockAgent(badUserAgent)
161+
blocked, blockedAgent, err := botBlocker.shouldBlockAgent(requestAgent)
162+
if err != nil {
163+
t.Fatalf("botBlocker.shouldBlockAgent(%s) returned a none nil error", requestAgent)
164+
}
161165
if !blocked {
162-
t.Fatalf("botBlocker.shouldBlockAgent(%s) = %t; want true", badUserAgent, blocked)
166+
t.Fatalf("botBlocker.shouldBlockAgent(%s) = %t; want true", requestAgent, blocked)
167+
}
168+
if blockedAgent != badAgent {
169+
t.Fatalf("botBlocker.shouldBlockAgent(%s) = %s; want \"\"", requestAgent, blockedAgent)
163170
}
164171
}
165172

166-
func TestShouldAlowUserAgent(t *testing.T) {
173+
func TestShouldAllowUserAgent(t *testing.T) {
167174
botBlocker := BotBlocker{
168175
userAgentBlockList: []string{
169176
"nintendobrowser",
170177
},
171178
}
172179
userAgent := "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
173180

174-
blocked := botBlocker.shouldBlockAgent(userAgent)
181+
blocked, badAgent, err := botBlocker.shouldBlockAgent(userAgent)
182+
if err != nil {
183+
t.Fatalf("botBlocker.shouldBlockAgent(%s) returned a non nil error", userAgent)
184+
}
185+
if blocked {
186+
t.Fatalf("botBlocker.shouldBlockAgent(%s) = %t; want false", userAgent, blocked)
187+
}
188+
if badAgent != "" {
189+
t.Fatalf("botBlocker.shouldBlockAgent(%s) = %s; want \"\"", userAgent, badAgent)
190+
}
191+
}
192+
193+
func TestShouldAllowUserAgentSubstring(t *testing.T) {
194+
botBlocker := BotBlocker{
195+
userAgentBlockList: []string{
196+
"obot",
197+
},
198+
}
199+
userAgent := "mozilla/5.0+(compatible; uptimerobot/2.0; http://www.uptimerobot.com/)"
200+
201+
blocked, badAgent, err := botBlocker.shouldBlockAgent(userAgent)
202+
if err != nil {
203+
t.Fatalf("botBlocker.shouldBlockAgent(%s) returned a non nil error", userAgent)
204+
}
175205
if blocked {
176206
t.Fatalf("botBlocker.shouldBlockAgent(%s) = %t; want false", userAgent, blocked)
177207
}
208+
if badAgent != "" {
209+
t.Fatalf("botBlocker.shouldBlockAgent(%s) = %s; want \"\"", userAgent, badAgent)
210+
}
178211
}

0 commit comments

Comments
 (0)