fix(acl): normalize trailing dot in domain matches (#1574)
* fix(acl): normalize trailing dot in domain matches
The ACL like
```yaml
- reject(example.com)
- reject(suffix:blocked.test)
- reject(*.wild.test)
```
can be easily bypass through by adding a dot to the domain like
example.com.:443
Signed-off-by: Cherrling <me@cherr.cc>
* fix(acl): trim all trailing dots and add tests
Use strings.TrimRight instead of strings.TrimSuffix so domains with
multiple trailing dots (e.g. example.com..) are also normalized and
cannot bypass ACL rules. Add test coverage for trailing-dot handling
on exact, wildcard and suffix domain rules, as well as for trailing
dots in rule patterns.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Signed-off-by: Cherrling <me@cherr.cc>
Co-authored-by: Toby <tobyxdd@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
0e2b37ad6c
commit
3991117d27
2 changed files with 53 additions and 2 deletions
|
|
@ -86,7 +86,7 @@ type matchResultCacheKey struct {
|
|||
}
|
||||
|
||||
func (s *compiledRuleSetImpl[O]) Match(host HostInfo, proto Protocol, port uint16) (O, net.IP) {
|
||||
host.Name = strings.ToLower(host.Name) // Normalize host name to lower case
|
||||
host.Name = strings.TrimRight(strings.ToLower(host.Name), ".") // Normalize host name (lower case, no trailing dots)
|
||||
key := matchResultCacheKey{
|
||||
Host: host.String(),
|
||||
Proto: proto,
|
||||
|
|
@ -233,7 +233,7 @@ func parseProtoPort(protoPort string) (Protocol, uint16, uint16, bool) {
|
|||
}
|
||||
|
||||
func compileHostMatcher(addr string, geoLoader GeoLoader) (hostMatcher, string) {
|
||||
addr = strings.ToLower(addr) // Normalize to lower case
|
||||
addr = strings.TrimRight(strings.ToLower(addr), ".") // Normalize host pattern (lower case, no trailing dots)
|
||||
if addr == "*" || addr == "all" {
|
||||
// Match all hosts
|
||||
return &allMatcher{}, ""
|
||||
|
|
|
|||
|
|
@ -97,6 +97,12 @@ func TestCompile(t *testing.T) {
|
|||
ProtoPort: "tcp/6881-6889",
|
||||
HijackAddress: "",
|
||||
},
|
||||
{
|
||||
Outbound: "ob1",
|
||||
Address: "dotpattern.test.", // trailing dot in pattern should be normalized away
|
||||
ProtoPort: "tcp/443",
|
||||
HijackAddress: "",
|
||||
},
|
||||
}
|
||||
comp, err := Compile[int](rules, map[string]int{
|
||||
"ob1": ob1,
|
||||
|
|
@ -283,6 +289,51 @@ func TestCompile(t *testing.T) {
|
|||
wantOutbound: ob6, // match range port rule 6881-6889
|
||||
wantIP: nil,
|
||||
},
|
||||
{
|
||||
host: HostInfo{
|
||||
Name: "crap.v2ex.com.", // trailing dot must not bypass exact domain rule
|
||||
},
|
||||
proto: ProtocolTCP,
|
||||
port: 80,
|
||||
wantOutbound: ob1,
|
||||
wantIP: net.ParseIP("2.2.2.2"),
|
||||
},
|
||||
{
|
||||
host: HostInfo{
|
||||
Name: "hoho.v2ex.com.", // trailing dot must not bypass wildcard domain rule
|
||||
},
|
||||
proto: ProtocolUDP,
|
||||
port: 9999,
|
||||
wantOutbound: ob3,
|
||||
wantIP: nil,
|
||||
},
|
||||
{
|
||||
host: HostInfo{
|
||||
Name: "real.microsoft.com.", // trailing dot must not bypass suffix domain rule
|
||||
},
|
||||
proto: ProtocolUDP,
|
||||
port: 5353,
|
||||
wantOutbound: ob5,
|
||||
wantIP: nil,
|
||||
},
|
||||
{
|
||||
host: HostInfo{
|
||||
Name: "microsoft.com...", // multiple trailing dots must also be normalized
|
||||
},
|
||||
proto: ProtocolTCP,
|
||||
port: 6000,
|
||||
wantOutbound: ob5,
|
||||
wantIP: nil,
|
||||
},
|
||||
{
|
||||
host: HostInfo{
|
||||
Name: "dotpattern.test", // host without dot must match rule whose pattern had a trailing dot
|
||||
},
|
||||
proto: ProtocolTCP,
|
||||
port: 443,
|
||||
wantOutbound: ob1,
|
||||
wantIP: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue