Add TLS support for DNS proxy, implement backwards compatible DNS record conversion.

This commit is contained in:
buffermet 2024-10-09 13:47:21 +02:00
parent a49d561dce
commit 43f1013f0d
8 changed files with 1603 additions and 145 deletions

View file

@ -2,6 +2,10 @@ package dns_proxy
import ( import (
"github.com/bettercap/bettercap/v2/session" "github.com/bettercap/bettercap/v2/session"
"github.com/bettercap/bettercap/v2/tls"
"github.com/evilsocket/islazy/fs"
"github.com/evilsocket/islazy/str"
) )
type DnsProxy struct { type DnsProxy struct {
@ -22,6 +26,10 @@ func (mod *DnsProxy) Configure() error {
var netProtocol string var netProtocol string
var proxyPort int var proxyPort int
var scriptPath string var scriptPath string
var certFile string
var keyFile string
var whitelist string
var blacklist string
if mod.Running() { if mod.Running() {
return session.ErrAlreadyStarted(mod.Name()) return session.ErrAlreadyStarted(mod.Name())
@ -29,21 +37,56 @@ func (mod *DnsProxy) Configure() error {
return err return err
} else if err, address = mod.StringParam("dns.proxy.address"); err != nil { } else if err, address = mod.StringParam("dns.proxy.address"); err != nil {
return err return err
} else if err, certFile = mod.StringParam("dns.proxy.certificate"); err != nil {
return err
} else if certFile, err = fs.Expand(certFile); err != nil {
return err
} else if err, keyFile = mod.StringParam("dns.proxy.key"); err != nil {
return err
} else if keyFile, err = fs.Expand(keyFile); err != nil {
return err
} else if err, nameserver = mod.StringParam("dns.proxy.nameserver"); err != nil { } else if err, nameserver = mod.StringParam("dns.proxy.nameserver"); err != nil {
return err return err
} else if err, netProtocol = mod.StringParam("dns.proxy.networkprotocol"); err != nil {
return err
} else if err, proxyPort = mod.IntParam("dns.proxy.port"); err != nil { } else if err, proxyPort = mod.IntParam("dns.proxy.port"); err != nil {
return err return err
} else if err, netProtocol = mod.StringParam("dns.proxy.protocol"); err != nil {
return err
} else if err, doRedirect = mod.BoolParam("dns.proxy.redirect"); err != nil { } else if err, doRedirect = mod.BoolParam("dns.proxy.redirect"); err != nil {
return err return err
} else if err, scriptPath = mod.StringParam("dns.proxy.script"); err != nil { } else if err, scriptPath = mod.StringParam("dns.proxy.script"); err != nil {
return err return err
} else if err, blacklist = mod.StringParam("dns.proxy.blacklist"); err != nil {
return err
} else if err, whitelist = mod.StringParam("dns.proxy.whitelist"); err != nil {
return err
} }
error := mod.proxy.Configure(address, dnsPort, doRedirect, nameserver, netProtocol, proxyPort, scriptPath) mod.proxy.Blacklist = str.Comma(blacklist)
mod.proxy.Whitelist = str.Comma(whitelist)
return error if netProtocol == "tcp-tls" {
if !fs.Exists(certFile) || !fs.Exists(keyFile) {
cfg, err := tls.CertConfigFromModule("dns.proxy", mod.SessionModule)
if err != nil {
return err
}
mod.Debug("%+v", cfg)
mod.Info("generating proxy certification authority TLS key to %s", keyFile)
mod.Info("generating proxy certification authority TLS certificate to %s", certFile)
if err := tls.Generate(cfg, certFile, keyFile, true); err != nil {
return err
}
} else {
mod.Info("loading proxy certification authority TLS key from %s", keyFile)
mod.Info("loading proxy certification authority TLS certificate from %s", certFile)
}
}
err = mod.proxy.Configure(address, dnsPort, doRedirect, nameserver, netProtocol,
proxyPort, scriptPath, certFile, keyFile)
return err
} }
func (mod *DnsProxy) Description() string { func (mod *DnsProxy) Description() string {
@ -69,24 +112,42 @@ func NewDnsProxy(s *session.Session) *DnsProxy {
session.IPv4Validator, session.IPv4Validator,
"Address to bind the DNS proxy to.")) "Address to bind the DNS proxy to."))
mod.AddParam(session.NewStringParameter("dns.proxy.blacklist", "", "",
"Comma separated list of hostnames to skip while proxying (wildcard expressions can be used)."))
mod.AddParam(session.NewStringParameter("dns.proxy.whitelist", "", "",
"Comma separated list of hostnames to proxy if the blacklist is used (wildcard expressions can be used)."))
mod.AddParam(session.NewStringParameter("dns.proxy.nameserver", mod.AddParam(session.NewStringParameter("dns.proxy.nameserver",
"1.1.1.1", "1.1.1.1",
session.IPv4Validator, session.IPv4Validator,
"DNS resolver address.")) "DNS resolver address."))
mod.AddParam(session.NewStringParameter("dns.proxy.networkprotocol",
"udp",
"^(udp|tcp|tcp-tls)$",
"Network protocol for the DNS proxy server to use. Accepted values: udp, tcp, tcp-tls"))
mod.AddParam(session.NewIntParameter("dns.proxy.port", mod.AddParam(session.NewIntParameter("dns.proxy.port",
"8053", "8053",
"Port to bind the DNS proxy to.")) "Port to bind the DNS proxy to."))
mod.AddParam(session.NewStringParameter("dns.proxy.protocol",
"udp",
"^(udp|tcp|tcp-tls)$",
"Network protocol for the DNS proxy server to use. Accepted values: udp, tcp, tcp-tls"))
mod.AddParam(session.NewBoolParameter("dns.proxy.redirect", mod.AddParam(session.NewBoolParameter("dns.proxy.redirect",
"true", "true",
"Enable or disable port redirection with iptables.")) "Enable or disable port redirection with iptables."))
mod.AddParam(session.NewStringParameter("dns.proxy.certificate",
"~/.bettercap-ca.cert.pem",
"",
"DNS proxy certification authority TLS certificate file."))
mod.AddParam(session.NewStringParameter("dns.proxy.key",
"~/.bettercap-ca.key.pem",
"",
"DNS proxy certification authority TLS key file."))
tls.CertConfigToModule("dns.proxy", &mod.SessionModule, tls.DefaultCloudflareDNSConfig)
mod.AddParam(session.NewStringParameter("dns.proxy.script", mod.AddParam(session.NewStringParameter("dns.proxy.script",
"", "",
"", "",

View file

@ -2,7 +2,10 @@ package dns_proxy
import ( import (
"context" "context"
"crypto/tls"
"crypto/x509"
"fmt" "fmt"
"io/ioutil"
"strings" "strings"
"time" "time"
@ -20,13 +23,17 @@ const (
) )
type DNSProxy struct { type DNSProxy struct {
Address string
Name string Name string
Address string
Server *dns.Server
Redirection *firewall.Redirection
Nameserver string Nameserver string
NetProtocol string NetProtocol string
Redirection *firewall.Redirection
Script *DnsProxyScript Script *DnsProxyScript
Server *dns.Server CertFile string
KeyFile string
Blacklist []string
Whitelist []string
Sess *session.Session Sess *session.Session
doRedirect bool doRedirect bool
@ -34,11 +41,13 @@ type DNSProxy struct {
tag string tag string
} }
func (p *DNSProxy) Configure(address string, dnsPort int, doRedirect bool, nameserver string, netProtocol string, proxyPort int, scriptPath string) error { func (p *DNSProxy) Configure(address string, dnsPort int, doRedirect bool, nameserver string, netProtocol string, proxyPort int, scriptPath string, certFile string, keyFile string) error {
var err error var err error
p.Address = address p.Address = address
p.doRedirect = doRedirect p.doRedirect = doRedirect
p.CertFile = certFile
p.KeyFile = keyFile
if scriptPath != "" { if scriptPath != "" {
if err, p.Script = LoadDnsProxyScript(scriptPath, p.Sess); err != nil { if err, p.Script = LoadDnsProxyScript(scriptPath, p.Sess); err != nil {
@ -77,9 +86,10 @@ func (p *DNSProxy) Configure(address string, dnsPort int, doRedirect bool, names
} }
res = p.onResponseFilter(req, res, clientIP) res = p.onResponseFilter(req, res, clientIP)
if res == nil { if res == nil {
p.Error("response filter returned nil") p.Debug("response is nil")
m.SetRcode(req, dns.RcodeServerFailure) m.SetRcode(req, dns.RcodeServerFailure)
w.WriteMsg(m) w.WriteMsg(m)
return
} else { } else {
if err := w.WriteMsg(res); err != nil { if err := w.WriteMsg(res); err != nil {
p.Error("Error writing response: %s", err) p.Error("Error writing response: %s", err)
@ -98,14 +108,35 @@ func (p *DNSProxy) Configure(address string, dnsPort int, doRedirect bool, names
Handler: handler, Handler: handler,
} }
if netProtocol == "tcp-tls" && p.CertFile != "" && p.KeyFile != "" {
rawCert, _ := ioutil.ReadFile(p.CertFile)
rawKey, _ := ioutil.ReadFile(p.KeyFile)
ourCa, err := tls.X509KeyPair(rawCert, rawKey)
if err != nil {
return err
}
if ourCa.Leaf, err = x509.ParseCertificate(ourCa.Certificate[0]); err != nil {
return err
}
p.Server.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{ourCa},
}
}
if p.doRedirect { if p.doRedirect {
if !p.Sess.Firewall.IsForwardingEnabled() { if !p.Sess.Firewall.IsForwardingEnabled() {
p.Info("enabling forwarding.") p.Info("enabling forwarding.")
p.Sess.Firewall.EnableForwarding(true) p.Sess.Firewall.EnableForwarding(true)
} }
redirectProtocol := netProtocol
if redirectProtocol == "tcp-tls" {
redirectProtocol = "tcp"
}
p.Redirection = firewall.NewRedirection(p.Sess.Interface.Name(), p.Redirection = firewall.NewRedirection(p.Sess.Interface.Name(),
netProtocol, redirectProtocol,
dnsPort, dnsPort,
p.Address, p.Address,
proxyPort) proxyPort)

View file

@ -6,43 +6,60 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
) )
func shortenResourceRecords(records []string) []string { func questionsToStrings(qs []dns.Question) []string {
shorterRecords := []string{} questions := []string{}
for _, record := range records { for _, q := range qs {
shorterRecord := strings.ReplaceAll(record, "\t", " ") questions = append(questions, tabsToSpaces(q.String()))
shorterRecords = append(shorterRecords, shorterRecord)
} }
return shorterRecords return questions
} }
func (p *DNSProxy) logRequestAction(j *JSQuery) { func recordsToStrings(rrs []dns.RR) []string {
records := []string{}
for _, rr := range rrs {
records = append(records, tabsToSpaces(rr.String()))
}
return records
}
func tabsToSpaces(s string) string {
return strings.ReplaceAll(s, "\t", " ")
}
func (p *DNSProxy) logRequestAction(m *dns.Msg, clientIP string) {
var questions []string
for _, q := range m.Question {
questions = append(questions, tabsToSpaces(q.String()))
}
p.Sess.Events.Add(p.Name+".spoofed-request", struct { p.Sess.Events.Add(p.Name+".spoofed-request", struct {
Client string Client string
Questions []string Questions []string
}{ }{
j.Client["IP"], clientIP,
shortenResourceRecords(j.Questions), questions,
}) })
} }
func (p *DNSProxy) logResponseAction(j *JSQuery) { func (p *DNSProxy) logResponseAction(m *dns.Msg, clientIP string) {
p.Sess.Events.Add(p.Name+".spoofed-response", struct { p.Sess.Events.Add(p.Name+".spoofed-response", struct {
client string client string
Extras []string
Answers []string Answers []string
Extras []string
Nameservers []string Nameservers []string
Questions []string Questions []string
}{ }{
j.Client["IP"], clientIP,
shortenResourceRecords(j.Extras), recordsToStrings(m.Answer),
shortenResourceRecords(j.Answers), recordsToStrings(m.Extra),
shortenResourceRecords(j.Nameservers), recordsToStrings(m.Ns),
shortenResourceRecords(j.Questions), questionsToStrings(m.Question),
}) })
} }
func (p *DNSProxy) onRequestFilter(query *dns.Msg, clientIP string) (req, res *dns.Msg) { func (p *DNSProxy) onRequestFilter(query *dns.Msg, clientIP string) (req, res *dns.Msg) {
p.Debug("< %s %s", clientIP, query.Question) p.Debug("< %s %s",
clientIP,
strings.Join(questionsToStrings(query.Question), ","))
// do we have a proxy script? // do we have a proxy script?
if p.Script == nil { if p.Script == nil {
@ -53,12 +70,14 @@ func (p *DNSProxy) onRequestFilter(query *dns.Msg, clientIP string) (req, res *d
jsreq, jsres := p.Script.OnRequest(query, clientIP) jsreq, jsres := p.Script.OnRequest(query, clientIP)
if jsreq != nil { if jsreq != nil {
// the request has been changed by the script // the request has been changed by the script
p.logRequestAction(jsreq) req := jsreq.ToQuery()
return jsreq.ToQuery(), nil p.logRequestAction(req, clientIP)
return req, nil
} else if jsres != nil { } else if jsres != nil {
// a fake response has been returned by the script // a fake response has been returned by the script
p.logResponseAction(jsres) res := jsres.ToQuery()
return query, jsres.ToQuery() p.logResponseAction(res, clientIP)
return query, res
} }
return query, nil return query, nil
@ -70,15 +89,21 @@ func (p *DNSProxy) onResponseFilter(req, res *dns.Msg, clientIP string) *dns.Msg
return nil return nil
} }
p.Debug("> %s %s", clientIP, res.Answer) p.Debug("> %s %s [%s] [%s] [%s]",
clientIP,
strings.Join(questionsToStrings(res.Question), ","),
strings.Join(recordsToStrings(res.Answer), ","),
strings.Join(recordsToStrings(res.Extra), ","),
strings.Join(recordsToStrings(res.Ns), ","))
// do we have a proxy script? // do we have a proxy script?
if p.Script != nil { if p.Script != nil {
_, jsres := p.Script.OnResponse(req, res, clientIP) _, jsres := p.Script.OnResponse(req, res, clientIP)
if jsres != nil { if jsres != nil {
// the response has been changed by the script // the response has been changed by the script
p.logResponseAction(jsres) res := jsres.ToQuery()
return jsres.ToQuery() p.logResponseAction(res, clientIP)
return res
} }
} }

View file

@ -1,9 +1,8 @@
package dns_proxy package dns_proxy
import ( import (
"encoding/json"
"fmt" "fmt"
"regexp"
"strings"
"github.com/bettercap/bettercap/v2/log" "github.com/bettercap/bettercap/v2/log"
"github.com/bettercap/bettercap/v2/session" "github.com/bettercap/bettercap/v2/session"
@ -11,17 +10,14 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
) )
var whiteSpaceRegexp = regexp.MustCompile(`\s+`)
var stripWhiteSpaceRegexp = regexp.MustCompile(`^\s*(.*?)\s*$`)
type JSQuery struct { type JSQuery struct {
Answers []string Answers []map[string]interface{}
Client map[string]string Client map[string]string
Compress bool `json:"-"` Compress bool
Extras []string Extras []map[string]interface{}
Header *JSQueryHeader Header JSQueryHeader
Nameservers []string Nameservers []map[string]interface{}
Questions []string Questions []map[string]interface{}
refHash string refHash string
} }
@ -41,6 +37,11 @@ type JSQueryHeader struct {
} }
func (j *JSQuery) NewHash() string { func (j *JSQuery) NewHash() string {
answers, _ := json.Marshal(j.Answers)
extras, _ := json.Marshal(j.Extras)
nameservers, _ := json.Marshal(j.Nameservers)
questions, _ := json.Marshal(j.Questions)
headerHash := fmt.Sprintf("%t.%t.%t.%d.%d.%d.%t.%t.%t.%t.%t", headerHash := fmt.Sprintf("%t.%t.%t.%d.%d.%d.%t.%t.%t.%t.%t",
j.Header.AuthenticatedData, j.Header.AuthenticatedData,
j.Header.Authoritative, j.Header.Authoritative,
@ -53,50 +54,58 @@ func (j *JSQuery) NewHash() string {
j.Header.Response, j.Header.Response,
j.Header.Truncated, j.Header.Truncated,
j.Header.Zero) j.Header.Zero)
hash := fmt.Sprintf("%s.%s.%t.%s.%s.%s.%s", hash := fmt.Sprintf("%s.%s.%t.%s.%s.%s.%s",
strings.Join(j.Answers, ""), answers,
j.Client["IP"], j.Client["IP"],
j.Compress, j.Compress,
strings.Join(j.Extras, ""), extras,
headerHash, headerHash,
strings.Join(j.Nameservers, ""), nameservers,
strings.Join(j.Questions, "")) questions)
return hash return hash
} }
func NewJSQuery(query *dns.Msg, clientIP string) *JSQuery { func NewJSQuery(query *dns.Msg, clientIP string) (jsQuery *JSQuery) {
answers := []string{} answers := make([]map[string]interface{}, len(query.Answer))
extras := []string{} extras := make([]map[string]interface{}, len(query.Extra))
nameservers := []string{} nameservers := make([]map[string]interface{}, len(query.Ns))
questions := []string{} questions := make([]map[string]interface{}, len(query.Question))
header := &JSQueryHeader{ for i, rr := range query.Answer {
AuthenticatedData: query.MsgHdr.AuthenticatedData, jsRecord, err := NewJSResourceRecord(rr)
Authoritative: query.MsgHdr.Authoritative, if err != nil {
CheckingDisabled: query.MsgHdr.CheckingDisabled, log.Error(err.Error())
Id: query.MsgHdr.Id, continue
Opcode: query.MsgHdr.Opcode, }
Rcode: query.MsgHdr.Rcode, answers[i] = jsRecord
RecursionAvailable: query.MsgHdr.RecursionAvailable,
RecursionDesired: query.MsgHdr.RecursionDesired,
Response: query.MsgHdr.Response,
Truncated: query.MsgHdr.Truncated,
Zero: query.MsgHdr.Zero,
} }
for _, rr := range query.Answer { for i, rr := range query.Extra {
answers = append(answers, rr.String()) jsRecord, err := NewJSResourceRecord(rr)
if err != nil {
log.Error(err.Error())
continue
}
extras[i] = jsRecord
} }
for _, rr := range query.Extra {
extras = append(extras, rr.String()) for i, rr := range query.Ns {
jsRecord, err := NewJSResourceRecord(rr)
if err != nil {
log.Error(err.Error())
continue
}
nameservers[i] = jsRecord
} }
for _, rr := range query.Ns {
nameservers = append(nameservers, rr.String()) for i, question := range query.Question {
} questions[i] = map[string]interface{}{
for _, q := range query.Question { "Name": question.Name,
qType := dns.Type(q.Qtype).String() "Qtype": question.Qtype,
qClass := dns.Class(q.Qclass).String() "Qclass": question.Qclass,
questions = append(questions, fmt.Sprintf("%s\t%s\t%s", q.Name, qClass, qType)) }
} }
clientMAC := "" clientMAC := ""
@ -108,11 +117,23 @@ func NewJSQuery(query *dns.Msg, clientIP string) *JSQuery {
client := map[string]string{"IP": clientIP, "MAC": clientMAC, "Alias": clientAlias} client := map[string]string{"IP": clientIP, "MAC": clientMAC, "Alias": clientAlias}
jsquery := &JSQuery{ jsquery := &JSQuery{
Answers: answers, Answers: answers,
Client: client, Client: client,
Compress: query.Compress, Compress: query.Compress,
Extras: extras, Extras: extras,
Header: header, Header: JSQueryHeader{
AuthenticatedData: query.MsgHdr.AuthenticatedData,
Authoritative: query.MsgHdr.Authoritative,
CheckingDisabled: query.MsgHdr.CheckingDisabled,
Id: query.MsgHdr.Id,
Opcode: query.MsgHdr.Opcode,
Rcode: query.MsgHdr.Rcode,
RecursionAvailable: query.MsgHdr.RecursionAvailable,
RecursionDesired: query.MsgHdr.RecursionDesired,
Response: query.MsgHdr.Response,
Truncated: query.MsgHdr.Truncated,
Zero: query.MsgHdr.Zero,
},
Nameservers: nameservers, Nameservers: nameservers,
Questions: questions, Questions: questions,
} }
@ -121,80 +142,43 @@ func NewJSQuery(query *dns.Msg, clientIP string) *JSQuery {
return jsquery return jsquery
} }
func stringToClass(s string) (uint16, error) {
for i, dnsClass := range dns.ClassToString {
if s == dnsClass {
return i, nil
}
}
return 0, fmt.Errorf("unkown DNS class (got %s)", s)
}
func stringToType(s string) (uint16, error) {
for i, dnsType := range dns.TypeToString {
if s == dnsType {
return i, nil
}
}
return 0, fmt.Errorf("unkown DNS type (got %s)", s)
}
func (j *JSQuery) ToQuery() *dns.Msg { func (j *JSQuery) ToQuery() *dns.Msg {
var answers []dns.RR var answers []dns.RR
var extras []dns.RR var extras []dns.RR
var nameservers []dns.RR var nameservers []dns.RR
var questions []dns.Question var questions []dns.Question
for _, s := range j.Answers { for _, jsRR := range j.Answers {
rr, err := dns.NewRR(s) rr, err := ToRR(jsRR)
if err != nil { if err != nil {
log.Error("error parsing DNS answer resource record: %s", err.Error()) log.Error(err.Error())
return nil continue
} else {
answers = append(answers, rr)
} }
answers = append(answers, rr)
} }
for _, s := range j.Extras { for _, jsRR := range j.Extras {
rr, err := dns.NewRR(s) rr, err := ToRR(jsRR)
if err != nil { if err != nil {
log.Error("error parsing DNS extra resource record: %s", err.Error()) log.Error(err.Error())
return nil continue
} else {
extras = append(extras, rr)
} }
extras = append(extras, rr)
} }
for _, s := range j.Nameservers { for _, jsRR := range j.Nameservers {
rr, err := dns.NewRR(s) rr, err := ToRR(jsRR)
if err != nil { if err != nil {
log.Error("error parsing DNS nameserver resource record: %s", err.Error()) log.Error(err.Error())
return nil continue
} else {
nameservers = append(nameservers, rr)
} }
nameservers = append(nameservers, rr)
} }
for _, s := range j.Questions { for _, jsQ := range j.Questions {
qStripped := stripWhiteSpaceRegexp.FindStringSubmatch(s) questions = append(questions, dns.Question{
qParts := whiteSpaceRegexp.Split(qStripped[1], -1) Name: jsPropToString(jsQ, "Name"),
Qtype: jsPropToUint16(jsQ, "Qtype"),
if len(qParts) != 3 { Qclass: jsPropToUint16(jsQ, "Qclass"),
log.Error("invalid DNS question format: (got %s)", s) })
return nil
}
qName := dns.Fqdn(qParts[0])
qClass, err := stringToClass(qParts[1])
if err != nil {
log.Error("error parsing DNS question class: %s", err.Error())
return nil
}
qType, err := stringToType(qParts[2])
if err != nil {
log.Error("error parsing DNS question type: %s", err.Error())
return nil
}
questions = append(questions, dns.Question{qName, qType, qClass})
} }
query := &dns.Msg{ query := &dns.Msg{

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,208 @@
package dns_proxy
import (
"fmt"
"net"
"github.com/bettercap/bettercap/v2/log"
"github.com/miekg/dns"
)
func NewJSEDNS0(e dns.EDNS0) (jsEDNS0 map[string]interface{}, err error) {
option := e.Option()
jsEDNS0 = map[string]interface{}{
"Option": option,
}
var jsVal map[string]interface{}
switch opt := e.(type) {
case *dns.EDNS0_LLQ:
jsVal = map[string]interface{}{
"Code": opt.Code,
"Error": opt.Error,
"Id": opt.Id,
"LeaseLife": opt.LeaseLife,
"Opcode": opt.Opcode,
"Version": opt.Version,
}
case *dns.EDNS0_UL:
jsVal = map[string]interface{}{
"Code": opt.Code,
"Lease": opt.Lease,
"KeyLease": opt.KeyLease,
}
case *dns.EDNS0_NSID:
jsVal = map[string]interface{}{
"Code": opt.Code,
"Nsid": opt.Nsid,
}
case *dns.EDNS0_ESU:
jsVal = map[string]interface{}{
"Code": opt.Code,
"Uri": opt.Uri,
}
case *dns.EDNS0_DAU:
jsVal = map[string]interface{}{
"AlgCode": opt.AlgCode,
"Code": opt.Code,
}
case *dns.EDNS0_DHU:
jsVal = map[string]interface{}{
"AlgCode": opt.AlgCode,
"Code": opt.Code,
}
case *dns.EDNS0_N3U:
jsVal = map[string]interface{}{
"AlgCode": opt.AlgCode,
"Code": opt.Code,
}
case *dns.EDNS0_SUBNET:
jsVal = map[string]interface{}{
"Address": opt.Address.String(),
"Code": opt.Code,
"Family": opt.Family,
"SourceNetmask": opt.SourceNetmask,
"SourceScope": opt.SourceScope,
}
case *dns.EDNS0_EXPIRE:
jsVal = map[string]interface{}{
"Code": opt.Code,
"Empty": opt.Empty,
"Expire": opt.Expire,
}
case *dns.EDNS0_COOKIE:
jsVal = map[string]interface{}{
"Code": opt.Code,
"Cookie": opt.Cookie,
}
case *dns.EDNS0_TCP_KEEPALIVE:
jsVal = map[string]interface{}{
"Code": opt.Code,
"Length": opt.Length,
"Timeout": opt.Timeout,
}
case *dns.EDNS0_PADDING:
jsVal = map[string]interface{}{
"Padding": string(opt.Padding),
}
case *dns.EDNS0_EDE:
jsVal = map[string]interface{}{
"ExtraText": opt.ExtraText,
"InfoCode": opt.InfoCode,
}
case *dns.EDNS0_LOCAL:
jsVal = map[string]interface{}{
"Code": opt.Code,
"Data": string(opt.Data),
}
default:
return nil, fmt.Errorf("unsupported EDNS0 option: %d", option)
}
jsEDNS0["Value"] = jsVal
return jsEDNS0, nil
}
func ToEDNS0(jsEDNS0 map[string]interface{}) (e dns.EDNS0, err error) {
option := jsPropToUint16(jsEDNS0, "Option")
jsVal := jsPropToMap(jsEDNS0, "Value")
switch option {
case dns.EDNS0LLQ:
e = &dns.EDNS0_LLQ{
Code: jsPropToUint16(jsVal, "Code"),
Error: jsPropToUint16(jsVal, "Error"),
Id: jsPropToUint64(jsVal, "Id"),
LeaseLife: jsPropToUint32(jsVal, "LeaseLife"),
Opcode: jsPropToUint16(jsVal, "Opcode"),
Version: jsPropToUint16(jsVal, "Version"),
}
case dns.EDNS0UL:
e = &dns.EDNS0_UL{
Code: jsPropToUint16(jsVal, "Code"),
Lease: jsPropToUint32(jsVal, "Lease"),
KeyLease: jsPropToUint32(jsVal, "KeyLease"),
}
case dns.EDNS0NSID:
e = &dns.EDNS0_NSID{
Code: jsPropToUint16(jsVal, "Code"),
Nsid: jsPropToString(jsVal, "Nsid"),
}
case dns.EDNS0ESU:
e = &dns.EDNS0_ESU{
Code: jsPropToUint16(jsVal, "Code"),
Uri: jsPropToString(jsVal, "Uri"),
}
case dns.EDNS0DAU:
e = &dns.EDNS0_DAU{
AlgCode: jsPropToUint8Array(jsVal, "AlgCode"),
Code: jsPropToUint16(jsVal, "Code"),
}
case dns.EDNS0DHU:
e = &dns.EDNS0_DHU{
AlgCode: jsPropToUint8Array(jsVal, "AlgCode"),
Code: jsPropToUint16(jsVal, "Code"),
}
case dns.EDNS0N3U:
e = &dns.EDNS0_N3U{
AlgCode: jsPropToUint8Array(jsVal, "AlgCode"),
Code: jsPropToUint16(jsVal, "Code"),
}
case dns.EDNS0SUBNET:
e = &dns.EDNS0_SUBNET{
Address: net.ParseIP(jsPropToString(jsVal, "Address")),
Code: jsPropToUint16(jsVal, "Code"),
Family: jsPropToUint16(jsVal, "Family"),
SourceNetmask: jsPropToUint8(jsVal, "SourceNetmask"),
SourceScope: jsPropToUint8(jsVal, "SourceScope"),
}
case dns.EDNS0EXPIRE:
if empty, ok := jsVal["Empty"].(bool); !ok {
log.Error("invalid or missing EDNS0_EXPIRE.Empty bool value, skipping field.")
e = &dns.EDNS0_EXPIRE{
Code: jsPropToUint16(jsVal, "Code"),
Expire: jsPropToUint32(jsVal, "Expire"),
}
} else {
e = &dns.EDNS0_EXPIRE{
Code: jsPropToUint16(jsVal, "Code"),
Expire: jsPropToUint32(jsVal, "Expire"),
Empty: empty,
}
}
case dns.EDNS0COOKIE:
e = &dns.EDNS0_COOKIE{
Code: jsPropToUint16(jsVal, "Code"),
Cookie: jsPropToString(jsVal, "Cookie"),
}
case dns.EDNS0TCPKEEPALIVE:
e = &dns.EDNS0_TCP_KEEPALIVE{
Code: jsPropToUint16(jsVal, "Code"),
Length: jsPropToUint16(jsVal, "Length"),
Timeout: jsPropToUint16(jsVal, "Timeout"),
}
case dns.EDNS0PADDING:
e = &dns.EDNS0_PADDING{
Padding: []byte(jsPropToString(jsVal, "Padding")),
}
case dns.EDNS0EDE:
e = &dns.EDNS0_EDE{
ExtraText: jsPropToString(jsVal, "ExtraText"),
InfoCode: jsPropToUint16(jsVal, "InfoCode"),
}
case dns.EDNS0LOCALSTART, dns.EDNS0LOCALEND, 0x8000:
// _DO = 0x8000
e = &dns.EDNS0_LOCAL{
Code: jsPropToUint16(jsVal, "Code"),
Data: []byte(jsPropToString(jsVal, "Data")),
}
default:
return nil, fmt.Errorf("unsupported EDNS0 option: %d", option)
}
return e, nil
}

View file

@ -0,0 +1,127 @@
package dns_proxy
import (
"fmt"
"net"
"github.com/bettercap/bettercap/v2/log"
"github.com/miekg/dns"
)
func NewJSSVCBKeyValue(kv dns.SVCBKeyValue) (map[string]interface{}, error) {
key := kv.Key()
jsKv := map[string]interface{}{
"Key": uint16(key),
}
switch v := kv.(type) {
case *dns.SVCBAlpn:
jsKv["Alpn"] = v.Alpn
case *dns.SVCBNoDefaultAlpn:
break
case *dns.SVCBECHConfig:
jsKv["ECH"] = string(v.ECH)
case *dns.SVCBPort:
jsKv["Port"] = v.Port
case *dns.SVCBIPv4Hint:
ips := v.Hint
jsIps := make([]string, len(ips))
for i, ip := range ips {
jsIps[i] = ip.String()
}
jsKv["Hint"] = jsIps
case *dns.SVCBIPv6Hint:
ips := v.Hint
jsIps := make([]string, len(ips))
for i, ip := range ips {
jsIps[i] = ip.String()
}
jsKv["Hint"] = jsIps
case *dns.SVCBDoHPath:
jsKv["Template"] = v.Template
case *dns.SVCBOhttp:
break
case *dns.SVCBMandatory:
keys := v.Code
jsKeys := make([]uint16, len(keys))
for i, _key := range keys {
jsKeys[i] = uint16(_key)
}
jsKv["Code"] = jsKeys
default:
return nil, fmt.Errorf("error creating JSSVCBKeyValue: unknown key: %d", key)
}
return jsKv, nil
}
func ToSVCBKeyValue(jsKv map[string]interface{}) (dns.SVCBKeyValue, error) {
var kv dns.SVCBKeyValue
key := dns.SVCBKey(jsPropToUint16(jsKv, "Key"))
switch key {
case dns.SVCB_ALPN:
kv = &dns.SVCBAlpn{
Alpn: jsPropToStringArray(jsKv, "Value"),
}
case dns.SVCB_NO_DEFAULT_ALPN:
kv = &dns.SVCBNoDefaultAlpn{}
case dns.SVCB_ECHCONFIG:
kv = &dns.SVCBECHConfig{
ECH: []byte(jsPropToString(jsKv, "Value")),
}
case dns.SVCB_PORT:
kv = &dns.SVCBPort{
Port: jsPropToUint16(jsKv, "Value"),
}
case dns.SVCB_IPV4HINT:
jsIps := jsPropToStringArray(jsKv, "Value")
var ips []net.IP
for _, jsIp := range jsIps {
ip := net.ParseIP(jsIp)
if ip == nil {
log.Error("error converting to SVCBKeyValue: invalid IPv4Hint IP: %s", jsIp)
continue
}
ips = append(ips, ip)
}
kv = &dns.SVCBIPv4Hint{
Hint: ips,
}
case dns.SVCB_IPV6HINT:
jsIps := jsPropToStringArray(jsKv, "Value")
var ips []net.IP
for _, jsIp := range jsIps {
ip := net.ParseIP(jsIp)
if ip == nil {
log.Error("error converting to SVCBKeyValue: invalid IPv6Hint IP: %s", jsIp)
continue
}
ips = append(ips, ip)
}
kv = &dns.SVCBIPv6Hint{
Hint: ips,
}
case dns.SVCB_DOHPATH:
kv = &dns.SVCBDoHPath{
Template: jsPropToString(jsKv, "Value"),
}
case dns.SVCB_OHTTP:
kv = &dns.SVCBOhttp{}
case dns.SVCB_MANDATORY:
v := jsPropToUint16Array(jsKv, "Value")
keys := make([]dns.SVCBKey, len(v))
for i, jsKey := range v {
keys[i] = dns.SVCBKey(jsKey)
}
kv = &dns.SVCBMandatory{
Code: keys,
}
default:
return nil, fmt.Errorf("error converting to dns.SVCBKeyValue: unknown key: %d", key)
}
return kv, nil
}

View file

@ -40,6 +40,14 @@ var (
OrganizationalUnit: "https://certs.godaddy.com/repository/", OrganizationalUnit: "https://certs.godaddy.com/repository/",
CommonName: "Go Daddy Secure Certificate Authority - G2", CommonName: "Go Daddy Secure Certificate Authority - G2",
} }
DefaultCloudflareDNSConfig = CertConfig{
Bits: 4096,
Country: "US",
Locality: "San Francisco",
Organization: "Cloudflare, Inc.",
OrganizationalUnit: "",
CommonName: "cloudflare-dns.com",
}
) )
func CertConfigToModule(prefix string, m *session.SessionModule, defaults CertConfig) { func CertConfigToModule(prefix string, m *session.SessionModule, defaults CertConfig) {