diff options
| author | Xe Iaso <me@xeiaso.net> | 2025-02-14 13:39:34 -0500 |
|---|---|---|
| committer | Xe Iaso <me@xeiaso.net> | 2025-02-14 13:39:34 -0500 |
| commit | 1acc1602f2ff678c0e4bf49fa62345505ec0aa35 (patch) | |
| tree | d2eec95941c9cd0a53f6de89cfde44222aa55940 /cmd | |
| parent | 17c44496aaa77e8d0a499db8c044cb42ab00086b (diff) | |
| download | x-1acc1602f2ff678c0e4bf49fa62345505ec0aa35.tar.xz x-1acc1602f2ff678c0e4bf49fa62345505ec0aa35.zip | |
cmd/anubis: enable DNSBL checking via dronebl
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/anubis/botPolicies.json | 3 | ||||
| -rw-r--r-- | cmd/anubis/internal/config/config.go | 3 | ||||
| -rw-r--r-- | cmd/anubis/internal/dnsbl/dnsbl.go | 95 | ||||
| -rw-r--r-- | cmd/anubis/internal/dnsbl/dnsbl_test.go | 55 | ||||
| -rw-r--r-- | cmd/anubis/internal/dnsbl/droneblresponse_string.go | 54 | ||||
| -rw-r--r-- | cmd/anubis/main.go | 14 | ||||
| -rw-r--r-- | cmd/anubis/policy.go | 5 |
7 files changed, 226 insertions, 3 deletions
diff --git a/cmd/anubis/botPolicies.json b/cmd/anubis/botPolicies.json index 8ca88e1..b90eba0 100644 --- a/cmd/anubis/botPolicies.json +++ b/cmd/anubis/botPolicies.json @@ -60,5 +60,6 @@ "user_agent_regex": "Mozilla", "action": "CHALLENGE" } - ] + ], + "dnsbl": true }
\ No newline at end of file diff --git a/cmd/anubis/internal/config/config.go b/cmd/anubis/internal/config/config.go index 7f986c1..b67db77 100644 --- a/cmd/anubis/internal/config/config.go +++ b/cmd/anubis/internal/config/config.go @@ -53,5 +53,6 @@ func (b Bot) Valid() error { } type Config struct { - Bots []Bot `json:"bots"` + Bots []Bot `json:"bots"` + DNSBL bool `json:"dnsbl"` } diff --git a/cmd/anubis/internal/dnsbl/dnsbl.go b/cmd/anubis/internal/dnsbl/dnsbl.go new file mode 100644 index 0000000..60edd5c --- /dev/null +++ b/cmd/anubis/internal/dnsbl/dnsbl.go @@ -0,0 +1,95 @@ +package dnsbl + +import ( + "errors" + "fmt" + "net" + "strings" +) + +//go:generate go tool golang.org/x/tools/cmd/stringer -type=DroneBLResponse + +type DroneBLResponse byte + +const ( + AllGood DroneBLResponse = 0 + IRCDrone DroneBLResponse = 3 + Bottler DroneBLResponse = 5 + UnknownSpambotOrDrone DroneBLResponse = 6 + DDOSDrone DroneBLResponse = 7 + SOCKSProxy DroneBLResponse = 8 + HTTPProxy DroneBLResponse = 9 + ProxyChain DroneBLResponse = 10 + OpenProxy DroneBLResponse = 11 + OpenDNSResolver DroneBLResponse = 12 + BruteForceAttackers DroneBLResponse = 13 + OpenWingateProxy DroneBLResponse = 14 + CompromisedRouter DroneBLResponse = 15 + AutoRootingWorms DroneBLResponse = 16 + AutoDetectedBotIP DroneBLResponse = 17 + Unknown DroneBLResponse = 255 +) + +func Reverse(ip net.IP) string { + if ip.To4() != nil { + return reverse4(ip) + } + + return reverse6(ip) +} + +func reverse4(ip net.IP) string { + splitAddress := strings.Split(ip.String(), ".") + + // swap first and last octet + splitAddress[0], splitAddress[3] = splitAddress[3], splitAddress[0] + // swap middle octets + splitAddress[1], splitAddress[2] = splitAddress[2], splitAddress[1] + + return strings.Join(splitAddress, ".") +} + +func reverse6(ip net.IP) string { + ipBytes := []byte(ip) + var sb strings.Builder + + for i := len(ipBytes) - 1; i >= 0; i-- { + // Split the byte into two nibbles + highNibble := ipBytes[i] >> 4 + lowNibble := ipBytes[i] & 0x0F + + // Append the nibbles in reversed order + sb.WriteString(fmt.Sprintf("%x.%x.", lowNibble, highNibble)) + } + + return sb.String()[:len(sb.String())-1] +} + +func Lookup(ipStr string) (DroneBLResponse, error) { + ip := net.ParseIP(ipStr) + if ip == nil { + return Unknown, errors.New("dnsbl: input is not an IP address") + } + + revIP := Reverse(ip) + ".dnsbl.dronebl.org" + + ips, err := net.LookupIP(revIP) + if err != nil { + var dnserr *net.DNSError + if errors.As(err, &dnserr) { + if dnserr.IsNotFound { + return AllGood, nil + } + } + + return Unknown, err + } + + if len(ips) != 0 { + for _, ip := range ips { + return DroneBLResponse(ip.To4()[3]), nil + } + } + + return UnknownSpambotOrDrone, nil +} diff --git a/cmd/anubis/internal/dnsbl/dnsbl_test.go b/cmd/anubis/internal/dnsbl/dnsbl_test.go new file mode 100644 index 0000000..9bcf0e7 --- /dev/null +++ b/cmd/anubis/internal/dnsbl/dnsbl_test.go @@ -0,0 +1,55 @@ +package dnsbl + +import ( + "fmt" + "net" + "testing" +) + +func TestReverse4(t *testing.T) { + cases := []struct { + inp, out string + }{ + {"1.2.3.4", "4.3.2.1"}, + } + + for _, cs := range cases { + t.Run(fmt.Sprintf("%s->%s", cs.inp, cs.out), func(t *testing.T) { + out := reverse4(net.ParseIP(cs.inp)) + + if out != cs.out { + t.Errorf("wanted %s\ngot: %s", cs.out, out) + } + }) + } +} + +func TestReverse6(t *testing.T) { + cases := []struct { + inp, out string + }{ + { + inp: "1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0", + out: "0.f.e.d.c.b.a.9.8.7.6.5.4.3.2.1.0.f.e.d.c.b.a.9.8.7.6.5.4.3.2.1", + }, + } + + for _, cs := range cases { + t.Run(fmt.Sprintf("%s->%s", cs.inp, cs.out), func(t *testing.T) { + out := reverse6(net.ParseIP(cs.inp)) + + if out != cs.out { + t.Errorf("wanted %s, got: %s", cs.out, out) + } + }) + } +} + +func TestLookup(t *testing.T) { + resp, err := Lookup("27.65.243.194") + if err != nil { + t.Fatalf("it broked: %v", err) + } + + t.Logf("response: %x", resp) +}
\ No newline at end of file diff --git a/cmd/anubis/internal/dnsbl/droneblresponse_string.go b/cmd/anubis/internal/dnsbl/droneblresponse_string.go new file mode 100644 index 0000000..5104dda --- /dev/null +++ b/cmd/anubis/internal/dnsbl/droneblresponse_string.go @@ -0,0 +1,54 @@ +// Code generated by "stringer -type=DroneBLResponse"; DO NOT EDIT. + +package dnsbl + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[AllGood-0] + _ = x[IRCDrone-3] + _ = x[Bottler-5] + _ = x[UnknownSpambotOrDrone-6] + _ = x[DDOSDrone-7] + _ = x[SOCKSProxy-8] + _ = x[HTTPProxy-9] + _ = x[ProxyChain-10] + _ = x[OpenProxy-11] + _ = x[OpenDNSResolver-12] + _ = x[BruteForceAttackers-13] + _ = x[OpenWingateProxy-14] + _ = x[CompromisedRouter-15] + _ = x[AutoRootingWorms-16] + _ = x[AutoDetectedBotIP-17] + _ = x[Unknown-255] +} + +const ( + _DroneBLResponse_name_0 = "AllGood" + _DroneBLResponse_name_1 = "IRCDrone" + _DroneBLResponse_name_2 = "BottlerUnknownSpambotOrDroneDDOSDroneSOCKSProxyHTTPProxyProxyChainOpenProxyOpenDNSResolverBruteForceAttackersOpenWingateProxyCompromisedRouterAutoRootingWormsAutoDetectedBotIP" + _DroneBLResponse_name_3 = "Unknown" +) + +var ( + _DroneBLResponse_index_2 = [...]uint8{0, 7, 28, 37, 47, 56, 66, 75, 90, 109, 125, 142, 158, 175} +) + +func (i DroneBLResponse) String() string { + switch { + case i == 0: + return _DroneBLResponse_name_0 + case i == 3: + return _DroneBLResponse_name_1 + case 5 <= i && i <= 17: + i -= 5 + return _DroneBLResponse_name_2[_DroneBLResponse_index_2[i]:_DroneBLResponse_index_2[i+1]] + case i == 255: + return _DroneBLResponse_name_3 + default: + return "DroneBLResponse(" + strconv.FormatInt(int64(i), 10) + ")" + } +} diff --git a/cmd/anubis/main.go b/cmd/anubis/main.go index a15dba8..477fe50 100644 --- a/cmd/anubis/main.go +++ b/cmd/anubis/main.go @@ -30,6 +30,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" "within.website/x" "within.website/x/cmd/anubis/internal/config" + "within.website/x/cmd/anubis/internal/dnsbl" "within.website/x/internal" "within.website/x/xess" ) @@ -213,6 +214,19 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request) { ) policyApplications.WithLabelValues(cr.Name, string(cr.Rule)).Add(1) + ip := r.Header.Get("X-Real-Ip") + + if s.policy.DNSBL && ip != "" { + resp, err := dnsbl.Lookup(ip) + if err != nil { + lg.Error("can't look up ip in dnsbl", "err", err) + } else { + lg.Info("DNSBL hit", "status", resp.String()) + templ.Handler(base("Oh noes!", errorPage(fmt.Sprintf("DroneBL reported an entry: %s, see https://dronebl.org/lookup?ip=%s", resp.String(), ip))), templ.WithStatus(http.StatusOK)).ServeHTTP(w, r) + return + } + } + switch cr.Rule { case config.RuleAllow: lg.Debug("allowing traffic to origin (explicit)") diff --git a/cmd/anubis/policy.go b/cmd/anubis/policy.go index 7d778c7..6e2241e 100644 --- a/cmd/anubis/policy.go +++ b/cmd/anubis/policy.go @@ -24,7 +24,8 @@ var ( type ParsedConfig struct { orig config.Config - Bots []Bot + Bots []Bot + DNSBL bool } type Bot struct { @@ -85,6 +86,8 @@ func parseConfig(fin io.Reader, fname string) (*ParsedConfig, error) { return nil, fmt.Errorf("errors validating policy config JSON %s: %w", fname, err) } + result.DNSBL = c.DNSBL + return result, nil } |
