mirror of
https://github.com/Li4n0/revsuit.git
synced 2026-09-26 16:41:53 +08:00
feat: complete basic functions
Support http,dns and mysql connection. Support custom http, dns response. Support dns rebinding. Support mysql load local files and jdbc deserialize exploit.
This commit is contained in:
@@ -0,0 +1,445 @@
|
||||
package newdns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Config provides configuration for a DNS server.
|
||||
type Config struct {
|
||||
// The buffer size used if EDNS is enabled by a client.
|
||||
//
|
||||
// Default: 1220.
|
||||
BufferSize int
|
||||
|
||||
// The list of zones handled by this server.
|
||||
//
|
||||
// Default: ["."].
|
||||
Zones []string
|
||||
|
||||
// Handler is the callback that returns a zone for the specified name.
|
||||
// The returned zone must not be altered going forward.
|
||||
Handler func(name string) (*Zone, error)
|
||||
|
||||
// The fallback DNS server to be used if the zones is not matched. Exact
|
||||
// zones must be provided above for this to work.
|
||||
Fallback string
|
||||
|
||||
// Reporter is the callback called with request errors.
|
||||
Logger Logger
|
||||
}
|
||||
|
||||
// Server is a DNS server.
|
||||
type Server struct {
|
||||
config Config
|
||||
close chan struct{}
|
||||
}
|
||||
|
||||
// NewServer creates and returns a new DNS server.
|
||||
func NewServer(config Config) *Server {
|
||||
// set default buffer size
|
||||
if config.BufferSize <= 0 {
|
||||
config.BufferSize = 1220
|
||||
}
|
||||
|
||||
// set default zone
|
||||
if len(config.Zones) == 0 {
|
||||
config.Zones = []string{"."}
|
||||
}
|
||||
|
||||
// check zones if fallback
|
||||
if config.Fallback != "" {
|
||||
for _, zone := range config.Zones {
|
||||
if zone == "." {
|
||||
panic(`fallback conflicts with the match all pattern "." (default)`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &Server{
|
||||
config: config,
|
||||
close: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Run will run a udp and tcp server on the specified address. It will return
|
||||
// on the first accept error and close all servers.
|
||||
func (s *Server) Run(addr string) error {
|
||||
// prepare mux
|
||||
mux := dns.NewServeMux()
|
||||
|
||||
// register handler
|
||||
for _, zone := range s.config.Zones {
|
||||
mux.Handle(zone, s)
|
||||
}
|
||||
|
||||
// add fallback if available
|
||||
if s.config.Fallback != "" {
|
||||
mux.Handle(".", Proxy(s.config.Fallback, s.config.Logger))
|
||||
}
|
||||
|
||||
// run server
|
||||
err := Run(addr, mux, Accept(s.config.Logger), s.close)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServeDNS implements the dns.Handler interface.
|
||||
func (s *Server) ServeDNS(w dns.ResponseWriter, req *dns.Msg) {
|
||||
// get question
|
||||
question := req.Question[0]
|
||||
|
||||
// check class
|
||||
if question.Qclass != dns.ClassINET {
|
||||
log(s.config.Logger, Ignored, nil, nil, fmt.Sprintf("unsupported class: %s", dns.ClassToString[question.Qclass]))
|
||||
return
|
||||
}
|
||||
|
||||
// log request and finish
|
||||
log(s.config.Logger, Request, req, nil, "")
|
||||
defer log(s.config.Logger, Finish, nil, nil, "")
|
||||
|
||||
// prepare response
|
||||
res := new(dns.Msg)
|
||||
res.SetReply(req)
|
||||
|
||||
// always compress responses
|
||||
res.Compress = true
|
||||
|
||||
// set flag
|
||||
res.Authoritative = true
|
||||
|
||||
// check edns
|
||||
if req.IsEdns0() != nil {
|
||||
// use edns in reply
|
||||
res.SetEdns0(uint16(s.config.BufferSize), false)
|
||||
|
||||
// check version
|
||||
if req.IsEdns0().Version() != 0 {
|
||||
log(s.config.Logger, Refused, nil, nil, fmt.Sprintf("unsupported EDNS version: %d", req.IsEdns0().Version()))
|
||||
s.writeError(w, req, res, nil, dns.RcodeBadVers)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// check any type
|
||||
if question.Qtype == dns.TypeANY {
|
||||
log(s.config.Logger, Refused, nil, nil, "unsupported type: ANY")
|
||||
s.writeError(w, req, res, nil, dns.RcodeNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
// get name
|
||||
name := NormalizeDomain(question.Name, true, false, false)
|
||||
|
||||
// get zone
|
||||
zone, err := s.config.Handler(name)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "server handler error")
|
||||
log(s.config.Logger, BackendError, nil, err, "")
|
||||
s.writeError(w, req, res, nil, dns.RcodeServerFailure)
|
||||
return
|
||||
}
|
||||
|
||||
// check zone
|
||||
if zone == nil {
|
||||
log(s.config.Logger, Refused, nil, nil, "no zone")
|
||||
res.Authoritative = false
|
||||
s.writeError(w, req, res, nil, dns.RcodeRefused)
|
||||
return
|
||||
}
|
||||
|
||||
// validate zone
|
||||
err = zone.Validate()
|
||||
if err != nil {
|
||||
log(s.config.Logger, BackendError, nil, err, "")
|
||||
s.writeError(w, req, res, nil, dns.RcodeServerFailure)
|
||||
return
|
||||
}
|
||||
|
||||
// answer SOA directly
|
||||
if question.Qtype == dns.TypeSOA && name == zone.Name {
|
||||
s.writeSOAResponse(w, req, res, zone)
|
||||
return
|
||||
}
|
||||
|
||||
// answer NS directly
|
||||
if question.Qtype == dns.TypeNS && name == zone.Name {
|
||||
s.writeNSResponse(w, req, res, zone)
|
||||
return
|
||||
}
|
||||
|
||||
// check type
|
||||
typ := Type(question.Qtype)
|
||||
|
||||
// return error if type is not supported
|
||||
if !typ.valid() {
|
||||
log(s.config.Logger, Refused, nil, nil, fmt.Sprintf("unsupported type: %s", dns.TypeToString[question.Qtype]))
|
||||
s.writeError(w, req, res, zone, dns.RcodeNameError)
|
||||
return
|
||||
}
|
||||
|
||||
// lookup main answer
|
||||
answer, exists, err := zone.Lookup(name, w.RemoteAddr().String(), typ)
|
||||
if err != nil {
|
||||
log(s.config.Logger, BackendError, nil, err, "")
|
||||
s.writeError(w, req, res, nil, dns.RcodeServerFailure)
|
||||
return
|
||||
}
|
||||
|
||||
// check result
|
||||
if len(answer) == 0 {
|
||||
// write SOA with success code to indicate existence of other sets
|
||||
if exists {
|
||||
s.writeError(w, req, res, zone, dns.RcodeSuccess)
|
||||
return
|
||||
}
|
||||
|
||||
// otherwise return name error
|
||||
s.writeError(w, req, res, zone, dns.RcodeNameError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// prepare extra set
|
||||
var extra []Set
|
||||
|
||||
// TODO: Lookup glue records for NS records?
|
||||
|
||||
// lookup extra sets
|
||||
for _, set := range answer {
|
||||
for _, record := range set.Records {
|
||||
switch set.Type {
|
||||
case MX:
|
||||
// lookup internal MX target A and AAAA records
|
||||
if InZone(zone.Name, record.Address) {
|
||||
ret, _, err := zone.Lookup(record.Address, w.RemoteAddr().String(), A, AAAA)
|
||||
if err != nil {
|
||||
log(s.config.Logger, BackendError, nil, err, "")
|
||||
s.writeError(w, req, res, nil, dns.RcodeServerFailure)
|
||||
return
|
||||
}
|
||||
|
||||
// add to extra
|
||||
extra = append(extra, ret...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set answer
|
||||
for _, set := range answer {
|
||||
res.Answer = append(res.Answer, s.convert(question.Name, zone, set)...)
|
||||
}
|
||||
|
||||
// set extra
|
||||
for _, set := range extra {
|
||||
res.Extra = append(res.Extra, s.convert(question.Name, zone, set)...)
|
||||
}
|
||||
|
||||
// add ns records
|
||||
for _, ns := range zone.AllNameServers {
|
||||
res.Ns = append(res.Ns, &dns.NS{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: TransferCase(question.Name, zone.Name),
|
||||
Rrtype: dns.TypeNS,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: toSeconds(zone.NSTTL),
|
||||
},
|
||||
Ns: ns,
|
||||
})
|
||||
}
|
||||
|
||||
// check if NS query
|
||||
if typ == NS {
|
||||
// move answers
|
||||
res.Ns = res.Answer
|
||||
res.Answer = nil
|
||||
|
||||
// no authoritative response for other zone in NS queries
|
||||
res.Authoritative = false
|
||||
}
|
||||
|
||||
// write message
|
||||
s.writeMessage(w, req, res)
|
||||
}
|
||||
|
||||
// Close will close the server.
|
||||
func (s *Server) Close() {
|
||||
defer func() { recover() }()
|
||||
close(s.close)
|
||||
}
|
||||
|
||||
func (s *Server) writeSOAResponse(w dns.ResponseWriter, rq, rs *dns.Msg, zone *Zone) {
|
||||
// add soa record
|
||||
rs.Answer = append(rs.Answer, &dns.SOA{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: zone.Name,
|
||||
Rrtype: dns.TypeSOA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: toSeconds(zone.SOATTL),
|
||||
},
|
||||
Ns: zone.MasterNameServer,
|
||||
Mbox: emailToDomain(zone.AdminEmail),
|
||||
Serial: 1,
|
||||
Refresh: toSeconds(zone.Refresh),
|
||||
Retry: toSeconds(zone.Retry),
|
||||
Expire: toSeconds(zone.Expire),
|
||||
Minttl: toSeconds(zone.MinTTL),
|
||||
})
|
||||
|
||||
// add ns records
|
||||
for _, ns := range zone.AllNameServers {
|
||||
rs.Ns = append(rs.Ns, &dns.NS{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: zone.Name,
|
||||
Rrtype: dns.TypeNS,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: toSeconds(zone.NSTTL),
|
||||
},
|
||||
Ns: ns,
|
||||
})
|
||||
}
|
||||
|
||||
// write message
|
||||
s.writeMessage(w, rq, rs)
|
||||
}
|
||||
|
||||
func (s *Server) writeNSResponse(w dns.ResponseWriter, rq, rs *dns.Msg, zone *Zone) {
|
||||
// add ns records
|
||||
for _, ns := range zone.AllNameServers {
|
||||
rs.Answer = append(rs.Answer, &dns.NS{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: zone.Name,
|
||||
Rrtype: dns.TypeNS,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: toSeconds(zone.NSTTL),
|
||||
},
|
||||
Ns: ns,
|
||||
})
|
||||
}
|
||||
|
||||
// write message
|
||||
s.writeMessage(w, rq, rs)
|
||||
}
|
||||
|
||||
func (s *Server) writeError(w dns.ResponseWriter, rq, rs *dns.Msg, zone *Zone, code int) {
|
||||
// set code
|
||||
rs.Rcode = code
|
||||
|
||||
// add soa record
|
||||
if zone != nil {
|
||||
rs.Ns = append(rs.Ns, &dns.SOA{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: zone.Name,
|
||||
Rrtype: dns.TypeSOA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: toSeconds(zone.SOATTL),
|
||||
},
|
||||
Ns: zone.MasterNameServer,
|
||||
Mbox: emailToDomain(zone.AdminEmail),
|
||||
Serial: 1,
|
||||
Refresh: toSeconds(zone.Refresh),
|
||||
Retry: toSeconds(zone.Retry),
|
||||
Expire: toSeconds(zone.Expire),
|
||||
Minttl: toSeconds(zone.MinTTL),
|
||||
})
|
||||
}
|
||||
|
||||
// write message
|
||||
s.writeMessage(w, rq, rs)
|
||||
}
|
||||
|
||||
func (s *Server) writeMessage(w dns.ResponseWriter, rq, rs *dns.Msg) {
|
||||
// get buffer size
|
||||
var buffer = 512
|
||||
if rq.IsEdns0() != nil {
|
||||
buffer = int(rq.IsEdns0().UDPSize())
|
||||
}
|
||||
|
||||
// determine if client is using UDP
|
||||
isUDP := w.RemoteAddr().Network() == "udp"
|
||||
|
||||
// truncate message if client is using UDP and message is too long
|
||||
if isUDP && rs.Len() > buffer {
|
||||
rs.Truncated = true
|
||||
rs.Answer = nil
|
||||
rs.Ns = nil
|
||||
rs.Extra = nil
|
||||
}
|
||||
|
||||
// write message
|
||||
err := w.WriteMsg(rs)
|
||||
if err != nil {
|
||||
log(s.config.Logger, NetworkError, nil, err, "")
|
||||
_ = w.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// log response
|
||||
log(s.config.Logger, Response, rs, nil, "")
|
||||
}
|
||||
|
||||
func (s *Server) convert(query string, zone *Zone, set Set) []dns.RR {
|
||||
// prepare header
|
||||
header := dns.RR_Header{
|
||||
Name: TransferCase(query, set.Name),
|
||||
Rrtype: uint16(set.Type),
|
||||
Class: dns.ClassINET,
|
||||
Ttl: toSeconds(set.TTL),
|
||||
}
|
||||
|
||||
// ensure zone min TTL
|
||||
if set.TTL < zone.MinTTL {
|
||||
header.Ttl = toSeconds(zone.MinTTL)
|
||||
}
|
||||
|
||||
// prepare list
|
||||
var list []dns.RR
|
||||
|
||||
// add records
|
||||
for _, record := range set.Records {
|
||||
// construct record
|
||||
switch set.Type {
|
||||
case A:
|
||||
list = append(list, &dns.A{
|
||||
Hdr: header,
|
||||
A: net.ParseIP(record.Address),
|
||||
})
|
||||
case AAAA:
|
||||
list = append(list, &dns.AAAA{
|
||||
Hdr: header,
|
||||
AAAA: net.ParseIP(record.Address),
|
||||
})
|
||||
case CNAME:
|
||||
list = append(list, &dns.CNAME{
|
||||
Hdr: header,
|
||||
Target: dns.Fqdn(record.Address),
|
||||
})
|
||||
case MX:
|
||||
list = append(list, &dns.MX{
|
||||
Hdr: header,
|
||||
Preference: uint16(record.Priority),
|
||||
Mx: dns.Fqdn(record.Address),
|
||||
})
|
||||
case TXT:
|
||||
list = append(list, &dns.TXT{
|
||||
Hdr: header,
|
||||
Txt: record.Data,
|
||||
})
|
||||
case NS:
|
||||
list = append(list, &dns.NS{
|
||||
Hdr: header,
|
||||
Ns: dns.Fqdn(record.Address),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
Reference in New Issue
Block a user