mirror of
https://github.com/bettercap/bettercap.git
synced 2025-01-06 03:00:15 -08:00
Add TLS support for DNS proxy, implement backwards compatible DNS record conversion.
This commit is contained in:
parent
a49d561dce
commit
43f1013f0d
@ -2,6 +2,10 @@ package dns_proxy
|
||||
|
||||
import (
|
||||
"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 {
|
||||
@ -22,6 +26,10 @@ func (mod *DnsProxy) Configure() error {
|
||||
var netProtocol string
|
||||
var proxyPort int
|
||||
var scriptPath string
|
||||
var certFile string
|
||||
var keyFile string
|
||||
var whitelist string
|
||||
var blacklist string
|
||||
|
||||
if mod.Running() {
|
||||
return session.ErrAlreadyStarted(mod.Name())
|
||||
@ -29,21 +37,56 @@ func (mod *DnsProxy) Configure() error {
|
||||
return err
|
||||
} else if err, address = mod.StringParam("dns.proxy.address"); err != nil {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
return err
|
||||
} else if err, scriptPath = mod.StringParam("dns.proxy.script"); err != nil {
|
||||
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 {
|
||||
@ -69,24 +112,42 @@ func NewDnsProxy(s *session.Session) *DnsProxy {
|
||||
session.IPv4Validator,
|
||||
"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",
|
||||
"1.1.1.1",
|
||||
session.IPv4Validator,
|
||||
"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",
|
||||
"8053",
|
||||
"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",
|
||||
"true",
|
||||
"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",
|
||||
"",
|
||||
"",
|
||||
|
@ -2,7 +2,10 @@ package dns_proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -20,13 +23,17 @@ const (
|
||||
)
|
||||
|
||||
type DNSProxy struct {
|
||||
Address string
|
||||
Name string
|
||||
Address string
|
||||
Server *dns.Server
|
||||
Redirection *firewall.Redirection
|
||||
Nameserver string
|
||||
NetProtocol string
|
||||
Redirection *firewall.Redirection
|
||||
Script *DnsProxyScript
|
||||
Server *dns.Server
|
||||
CertFile string
|
||||
KeyFile string
|
||||
Blacklist []string
|
||||
Whitelist []string
|
||||
Sess *session.Session
|
||||
|
||||
doRedirect bool
|
||||
@ -34,11 +41,13 @@ type DNSProxy struct {
|
||||
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
|
||||
|
||||
p.Address = address
|
||||
p.doRedirect = doRedirect
|
||||
p.CertFile = certFile
|
||||
p.KeyFile = keyFile
|
||||
|
||||
if scriptPath != "" {
|
||||
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)
|
||||
if res == nil {
|
||||
p.Error("response filter returned nil")
|
||||
p.Debug("response is nil")
|
||||
m.SetRcode(req, dns.RcodeServerFailure)
|
||||
w.WriteMsg(m)
|
||||
return
|
||||
} else {
|
||||
if err := w.WriteMsg(res); err != nil {
|
||||
p.Error("Error writing response: %s", err)
|
||||
@ -98,14 +108,35 @@ func (p *DNSProxy) Configure(address string, dnsPort int, doRedirect bool, names
|
||||
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.Sess.Firewall.IsForwardingEnabled() {
|
||||
p.Info("enabling forwarding.")
|
||||
p.Sess.Firewall.EnableForwarding(true)
|
||||
}
|
||||
|
||||
redirectProtocol := netProtocol
|
||||
if redirectProtocol == "tcp-tls" {
|
||||
redirectProtocol = "tcp"
|
||||
}
|
||||
p.Redirection = firewall.NewRedirection(p.Sess.Interface.Name(),
|
||||
netProtocol,
|
||||
redirectProtocol,
|
||||
dnsPort,
|
||||
p.Address,
|
||||
proxyPort)
|
||||
|
@ -6,43 +6,60 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func shortenResourceRecords(records []string) []string {
|
||||
shorterRecords := []string{}
|
||||
for _, record := range records {
|
||||
shorterRecord := strings.ReplaceAll(record, "\t", " ")
|
||||
shorterRecords = append(shorterRecords, shorterRecord)
|
||||
func questionsToStrings(qs []dns.Question) []string {
|
||||
questions := []string{}
|
||||
for _, q := range qs {
|
||||
questions = append(questions, tabsToSpaces(q.String()))
|
||||
}
|
||||
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 {
|
||||
Client string
|
||||
Questions []string
|
||||
}{
|
||||
j.Client["IP"],
|
||||
shortenResourceRecords(j.Questions),
|
||||
clientIP,
|
||||
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 {
|
||||
client string
|
||||
Extras []string
|
||||
Answers []string
|
||||
Extras []string
|
||||
Nameservers []string
|
||||
Questions []string
|
||||
}{
|
||||
j.Client["IP"],
|
||||
shortenResourceRecords(j.Extras),
|
||||
shortenResourceRecords(j.Answers),
|
||||
shortenResourceRecords(j.Nameservers),
|
||||
shortenResourceRecords(j.Questions),
|
||||
clientIP,
|
||||
recordsToStrings(m.Answer),
|
||||
recordsToStrings(m.Extra),
|
||||
recordsToStrings(m.Ns),
|
||||
questionsToStrings(m.Question),
|
||||
})
|
||||
}
|
||||
|
||||
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?
|
||||
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)
|
||||
if jsreq != nil {
|
||||
// the request has been changed by the script
|
||||
p.logRequestAction(jsreq)
|
||||
return jsreq.ToQuery(), nil
|
||||
req := jsreq.ToQuery()
|
||||
p.logRequestAction(req, clientIP)
|
||||
return req, nil
|
||||
} else if jsres != nil {
|
||||
// a fake response has been returned by the script
|
||||
p.logResponseAction(jsres)
|
||||
return query, jsres.ToQuery()
|
||||
res := jsres.ToQuery()
|
||||
p.logResponseAction(res, clientIP)
|
||||
return query, res
|
||||
}
|
||||
|
||||
return query, nil
|
||||
@ -70,15 +89,21 @@ func (p *DNSProxy) onResponseFilter(req, res *dns.Msg, clientIP string) *dns.Msg
|
||||
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?
|
||||
if p.Script != nil {
|
||||
_, jsres := p.Script.OnResponse(req, res, clientIP)
|
||||
if jsres != nil {
|
||||
// the response has been changed by the script
|
||||
p.logResponseAction(jsres)
|
||||
return jsres.ToQuery()
|
||||
res := jsres.ToQuery()
|
||||
p.logResponseAction(res, clientIP)
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
package dns_proxy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/log"
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
@ -11,17 +10,14 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
var whiteSpaceRegexp = regexp.MustCompile(`\s+`)
|
||||
var stripWhiteSpaceRegexp = regexp.MustCompile(`^\s*(.*?)\s*$`)
|
||||
|
||||
type JSQuery struct {
|
||||
Answers []string
|
||||
Answers []map[string]interface{}
|
||||
Client map[string]string
|
||||
Compress bool `json:"-"`
|
||||
Extras []string
|
||||
Header *JSQueryHeader
|
||||
Nameservers []string
|
||||
Questions []string
|
||||
Compress bool
|
||||
Extras []map[string]interface{}
|
||||
Header JSQueryHeader
|
||||
Nameservers []map[string]interface{}
|
||||
Questions []map[string]interface{}
|
||||
|
||||
refHash string
|
||||
}
|
||||
@ -41,6 +37,11 @@ type JSQueryHeader struct {
|
||||
}
|
||||
|
||||
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",
|
||||
j.Header.AuthenticatedData,
|
||||
j.Header.Authoritative,
|
||||
@ -53,50 +54,58 @@ func (j *JSQuery) NewHash() string {
|
||||
j.Header.Response,
|
||||
j.Header.Truncated,
|
||||
j.Header.Zero)
|
||||
|
||||
hash := fmt.Sprintf("%s.%s.%t.%s.%s.%s.%s",
|
||||
strings.Join(j.Answers, ""),
|
||||
answers,
|
||||
j.Client["IP"],
|
||||
j.Compress,
|
||||
strings.Join(j.Extras, ""),
|
||||
extras,
|
||||
headerHash,
|
||||
strings.Join(j.Nameservers, ""),
|
||||
strings.Join(j.Questions, ""))
|
||||
nameservers,
|
||||
questions)
|
||||
|
||||
return hash
|
||||
}
|
||||
|
||||
func NewJSQuery(query *dns.Msg, clientIP string) *JSQuery {
|
||||
answers := []string{}
|
||||
extras := []string{}
|
||||
nameservers := []string{}
|
||||
questions := []string{}
|
||||
func NewJSQuery(query *dns.Msg, clientIP string) (jsQuery *JSQuery) {
|
||||
answers := make([]map[string]interface{}, len(query.Answer))
|
||||
extras := make([]map[string]interface{}, len(query.Extra))
|
||||
nameservers := make([]map[string]interface{}, len(query.Ns))
|
||||
questions := make([]map[string]interface{}, len(query.Question))
|
||||
|
||||
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,
|
||||
for i, rr := range query.Answer {
|
||||
jsRecord, err := NewJSResourceRecord(rr)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
answers[i] = jsRecord
|
||||
}
|
||||
|
||||
for _, rr := range query.Answer {
|
||||
answers = append(answers, rr.String())
|
||||
for i, rr := range query.Extra {
|
||||
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 _, q := range query.Question {
|
||||
qType := dns.Type(q.Qtype).String()
|
||||
qClass := dns.Class(q.Qclass).String()
|
||||
questions = append(questions, fmt.Sprintf("%s\t%s\t%s", q.Name, qClass, qType))
|
||||
|
||||
for i, question := range query.Question {
|
||||
questions[i] = map[string]interface{}{
|
||||
"Name": question.Name,
|
||||
"Qtype": question.Qtype,
|
||||
"Qclass": question.Qclass,
|
||||
}
|
||||
}
|
||||
|
||||
clientMAC := ""
|
||||
@ -108,11 +117,23 @@ func NewJSQuery(query *dns.Msg, clientIP string) *JSQuery {
|
||||
client := map[string]string{"IP": clientIP, "MAC": clientMAC, "Alias": clientAlias}
|
||||
|
||||
jsquery := &JSQuery{
|
||||
Answers: answers,
|
||||
Client: client,
|
||||
Compress: query.Compress,
|
||||
Extras: extras,
|
||||
Header: header,
|
||||
Answers: answers,
|
||||
Client: client,
|
||||
Compress: query.Compress,
|
||||
Extras: extras,
|
||||
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,
|
||||
Questions: questions,
|
||||
}
|
||||
@ -121,80 +142,43 @@ func NewJSQuery(query *dns.Msg, clientIP string) *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 {
|
||||
var answers []dns.RR
|
||||
var extras []dns.RR
|
||||
var nameservers []dns.RR
|
||||
var questions []dns.Question
|
||||
|
||||
for _, s := range j.Answers {
|
||||
rr, err := dns.NewRR(s)
|
||||
for _, jsRR := range j.Answers {
|
||||
rr, err := ToRR(jsRR)
|
||||
if err != nil {
|
||||
log.Error("error parsing DNS answer resource record: %s", err.Error())
|
||||
return nil
|
||||
} else {
|
||||
answers = append(answers, rr)
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
answers = append(answers, rr)
|
||||
}
|
||||
for _, s := range j.Extras {
|
||||
rr, err := dns.NewRR(s)
|
||||
for _, jsRR := range j.Extras {
|
||||
rr, err := ToRR(jsRR)
|
||||
if err != nil {
|
||||
log.Error("error parsing DNS extra resource record: %s", err.Error())
|
||||
return nil
|
||||
} else {
|
||||
extras = append(extras, rr)
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
extras = append(extras, rr)
|
||||
}
|
||||
for _, s := range j.Nameservers {
|
||||
rr, err := dns.NewRR(s)
|
||||
for _, jsRR := range j.Nameservers {
|
||||
rr, err := ToRR(jsRR)
|
||||
if err != nil {
|
||||
log.Error("error parsing DNS nameserver resource record: %s", err.Error())
|
||||
return nil
|
||||
} else {
|
||||
nameservers = append(nameservers, rr)
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
nameservers = append(nameservers, rr)
|
||||
}
|
||||
|
||||
for _, s := range j.Questions {
|
||||
qStripped := stripWhiteSpaceRegexp.FindStringSubmatch(s)
|
||||
qParts := whiteSpaceRegexp.Split(qStripped[1], -1)
|
||||
|
||||
if len(qParts) != 3 {
|
||||
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})
|
||||
for _, jsQ := range j.Questions {
|
||||
questions = append(questions, dns.Question{
|
||||
Name: jsPropToString(jsQ, "Name"),
|
||||
Qtype: jsPropToUint16(jsQ, "Qtype"),
|
||||
Qclass: jsPropToUint16(jsQ, "Qclass"),
|
||||
})
|
||||
}
|
||||
|
||||
query := &dns.Msg{
|
||||
|
1014
modules/dns_proxy/dns_proxy_js_record.go
Normal file
1014
modules/dns_proxy/dns_proxy_js_record.go
Normal file
File diff suppressed because it is too large
Load Diff
208
modules/dns_proxy/dns_proxy_js_record_edns0.go
Normal file
208
modules/dns_proxy/dns_proxy_js_record_edns0.go
Normal 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
|
||||
}
|
127
modules/dns_proxy/dns_proxy_js_record_svcb.go
Normal file
127
modules/dns_proxy/dns_proxy_js_record_svcb.go
Normal 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
|
||||
}
|
@ -40,6 +40,14 @@ var (
|
||||
OrganizationalUnit: "https://certs.godaddy.com/repository/",
|
||||
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) {
|
||||
|
Loading…
Reference in New Issue
Block a user