Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for filtering middle-wildcard domains #1

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions traversal.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,17 @@ func RegisteredToFQDN(registered, fqdn string, callback func(domain string) bool
}
}
}

// SplitRegisteredToFQDN executes the provided callback routine for domain names, splitting
// the registered domain into a prefix and suffix part and omitting the part in between.
// The process stops if the callback routine returns true, indicating completion.
func SplitRegisteredToFQDN(registered, fqdn string, callback func(prefix, suffix string) bool) {
base := len(strings.Split(registered, "."))
labels := strings.Split(fqdn, ".")

for i := 1; i <= len(labels)-base-1; i++ {
if callback(strings.Join(labels[:i], "."), strings.Join(labels[i+1:], ".")) {
break
}
}
}
81 changes: 81 additions & 0 deletions traversal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,84 @@ func TestRegisteredToFQDN(t *testing.T) {
})
}
}

func TestSplitRegisteredToFQDN(t *testing.T) {
type Result struct {
prefix string
suffix string
}

var got []Result
tests := []struct {
name string
fqdn string
registered string
expected []Result
callback func(prefix, suffix string) bool
}{
{
name: "Full traversal",
fqdn: "www.accessphysiotherapy.com.ezproxy.utica.edu",
registered: "utica.edu",
expected: []Result{
{
"www",
"com.ezproxy.utica.edu",
},
{
"www.accessphysiotherapy",
"ezproxy.utica.edu",
},
{
"www.accessphysiotherapy.com",
"utica.edu",
},
},
callback: func(prefix, suffix string) bool {
got = append(got, Result{
prefix,
suffix,
})
return false
},
},
{
name: "Only TLD+1",
fqdn: "ezproxy.utica.edu",
registered: "utica.edu",
expected: []Result{},
callback: func(prefix, suffix string) bool {
got = append(got, Result{
prefix,
suffix,
})
return true
},
},

{
name: "Only subdomain",
fqdn: "utica.edu",
registered: "utica.edu",
expected: []Result{},
callback: func(prefix, suffix string) bool {
got = append(got, Result{
prefix,
suffix,
})
return true
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got = []Result{}

SplitRegisteredToFQDN(tt.registered, tt.fqdn, tt.callback)
if !reflect.DeepEqual(got, tt.expected) {
t.Errorf("Unexpected Result, expected %v, got %v", tt.expected, got)
}
})
}
}
51 changes: 40 additions & 11 deletions wildcards.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,19 @@ type wildcard struct {

// UnlikelyName takes a subdomain name and returns an unlikely DNS name within that subdomain.
func UnlikelyName(sub string) string {
return UnlikelyNameFromSplit("", sub)
}

// UnlikelyNameFromSplit takes a subdomain prefix and suffix and returns an unlikely DNS name in the middle of those.
func UnlikelyNameFromSplit(prefix, suffix string) string {
ldh := []rune(LDHChars)
ldhLen := len(ldh)

// Determine the max label length
l := MaxDNSNameLen - (len(sub) + 1)
l := MaxDNSNameLen - (len(suffix) + 1)
if len(prefix) > 0 {
l = l - (len(prefix) + 1)
}
if l > MaxLabelLen {
l = MaxLabelLen
} else if l < MinLabelLen {
Expand All @@ -69,7 +77,13 @@ func UnlikelyName(sub string) string {
if newlabel == "" {
return newlabel
}
return newlabel + "." + sub

sub := newlabel + "." + suffix
if len(prefix) > 0 {
sub = prefix + "." + sub
}

return sub
}

// WildcardDetected returns true when the provided DNS response could be a wildcard match.
Expand All @@ -78,7 +92,8 @@ func (r *Resolvers) WildcardDetected(ctx context.Context, resp *dns.Msg, domain
return false
}

name := strings.ToLower(RemoveLastDot(resp.Question[0].Name))
question := RemoveLastDot(resp.Question[0].Name)
name := strings.ToLower(question)
domain = strings.ToLower(RemoveLastDot(domain))
if labels := strings.Split(name, "."); len(labels) > len(strings.Split(domain, ".")) {
name = strings.Join(labels[1:], ".")
Expand All @@ -87,12 +102,26 @@ func (r *Resolvers) WildcardDetected(ctx context.Context, resp *dns.Msg, domain
var found bool
// Check for a DNS wildcard at each label starting with the registered domain
RegisteredToFQDN(domain, name, func(sub string) bool {
if w := r.getWildcard(ctx, sub); w.respMatchesWildcard(resp) {
if w := r.getWildcard(ctx, "", sub); w.respMatchesWildcard(resp) {
found = true
return true
}
return false
})

if found {
return true
}

// Check for a DNS wildcard between levels
SplitRegisteredToFQDN(domain, question, func(prefix, suffix string) bool {
if w := r.getWildcard(ctx, prefix, suffix); w.respMatchesWildcard(resp) {
found = true
return true
}
return false
})

return found
}

Expand Down Expand Up @@ -143,18 +172,18 @@ func (r *Resolvers) goodDetector() bool {
return success
}

func (r *Resolvers) getWildcard(ctx context.Context, sub string) *wildcard {
func (r *Resolvers) getWildcard(ctx context.Context, prefix, sub string) *wildcard {
r.Lock()
w, found := r.wildcards[sub]
w, found := r.wildcards[prefix+".."+sub]
if !found {
w = &wildcard{}
r.wildcards[sub] = w
r.wildcards[prefix+".."+sub] = w
}
r.Unlock()

if !found {
w.Lock()
w.Detected, w.Answers = r.wildcardTest(ctx, sub)
w.Detected, w.Answers = r.wildcardTest(ctx, prefix, sub)
w.Unlock()
}
return w
Expand Down Expand Up @@ -182,7 +211,7 @@ func (w *wildcard) respMatchesWildcard(resp *dns.Msg) bool {
}

// Determines if the provided subdomain has a DNS wildcard.
func (r *Resolvers) wildcardTest(ctx context.Context, sub string) (bool, []*ExtractedAnswer) {
func (r *Resolvers) wildcardTest(ctx context.Context, prefix, suffix string) (bool, []*ExtractedAnswer) {
var detected bool
var answers []*ExtractedAnswer

Expand All @@ -192,7 +221,7 @@ func (r *Resolvers) wildcardTest(ctx context.Context, sub string) (bool, []*Extr
for i := 0; i < numOfWildcardTests; i++ {
var name string
for {
name = UnlikelyName(sub)
name = UnlikelyNameFromSplit(prefix, suffix)
if name != "" {
break
}
Expand Down Expand Up @@ -228,7 +257,7 @@ func (r *Resolvers) wildcardTest(ctx context.Context, sub string) (bool, []*Extr
}
}
if detected {
r.log.Printf("DNS wildcard detected: Resolver %s: %s", r.detector.address, "*."+sub)
r.log.Printf("DNS wildcard detected: Resolver %s: %s", r.detector.address, "*."+suffix)
}
return detected, final
}
Expand Down
25 changes: 25 additions & 0 deletions wildcards_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ import (
"github.com/miekg/dns"
)

func TestUnlikelyNameFromSplit(t *testing.T) {
name := UnlikelyNameFromSplit("proxy", "example.com")
if !strings.HasPrefix(name, "proxy.") {
t.Fatalf("Unlikely name `%s` does not have `%s` prefix", name, "proxy.")
}
if !strings.HasSuffix(name, ".example.com") {
t.Fatalf("Unlikely name `%s` does not have `%s` suffix", name, ".example.com")
}
t.Log(name)
}

func TestSetDetectionResolver(t *testing.T) {
r := NewResolvers()
defer r.Stop()
Expand Down Expand Up @@ -58,6 +69,16 @@ func TestWildcardDetected(t *testing.T) {
input: "ns.wildcard.domain.com",
want: false,
},
{
label: "invalid name within a middle-wildcard",
input: "wildcard.jeff_foley.domain.com",
want: true,
},
{
label: "valid name within a middle-wildcard",
input: "wildcard.app.domain.com",
want: false,
},
}

for _, c := range cases {
Expand All @@ -83,6 +104,10 @@ func wildcardHandler(w dns.ResponseWriter, req *dns.Msg) {
addr = "192.168.1.2"
} else if strings.HasSuffix(name, ".wildcard.domain.com.") {
addr = "192.168.1.64"
} else if name == "wildcard.app.domain.com." {
addr = "192.168.1.65"
} else if strings.HasPrefix(name, "wildcard.") && strings.HasSuffix(name, ".domain.com.") {
addr = "192.168.1.66"
}

if addr == "" {
Expand Down