bettercap/modules/events_stream/events_view_http.go
2024-08-09 18:19:21 +02:00

213 lines
5.4 KiB
Go

package events_stream
import (
"bytes"
"compress/gzip"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/url"
"regexp"
"strings"
"github.com/bettercap/bettercap/v2/modules/net_sniff"
"github.com/bettercap/bettercap/v2/session"
"github.com/evilsocket/islazy/tui"
)
var (
reJsonKey = regexp.MustCompile(`("[^"]+"):`)
)
func (mod *EventsStream) shouldDumpHttpRequest(req net_sniff.HTTPRequest) bool {
if mod.dumpHttpReqs {
// dump all
return true
} else if req.Method != "GET" {
// dump if it's not just a GET
return true
}
// search for interesting headers and cookies
for name := range req.Headers {
headerName := strings.ToLower(name)
if strings.Contains(headerName, "auth") || strings.Contains(headerName, "token") {
return true
}
}
return false
}
func (mod *EventsStream) shouldDumpHttpResponse(res net_sniff.HTTPResponse) bool {
if mod.dumpHttpResp {
return true
} else if strings.Contains(res.ContentType, "text/plain") {
return true
} else if strings.Contains(res.ContentType, "application/json") {
return true
} else if strings.Contains(res.ContentType, "text/xml") {
return true
}
// search for interesting headers
for name := range res.Headers {
headerName := strings.ToLower(name)
if strings.Contains(headerName, "auth") || strings.Contains(headerName, "token") || strings.Contains(headerName, "cookie") {
return true
}
}
return false
}
func (mod *EventsStream) dumpForm(body []byte) string {
form := []string{}
for _, v := range strings.Split(string(body), "&") {
if strings.Contains(v, "=") {
parts := strings.SplitN(v, "=", 2)
name := parts[0]
value, err := url.QueryUnescape(parts[1])
if err != nil {
value = parts[1]
}
form = append(form, fmt.Sprintf("%s=%s", tui.Green(name), tui.Bold(tui.Red(value))))
} else {
value, err := url.QueryUnescape(v)
if err != nil {
value = v
}
form = append(form, tui.Bold(tui.Red(value)))
}
}
return "\n" + strings.Join(form, "&") + "\n"
}
func (mod *EventsStream) dumpText(body []byte) string {
return "\n" + tui.Bold(tui.Red(string(body))) + "\n"
}
func (mod *EventsStream) dumpGZIP(body []byte) string {
buffer := bytes.NewBuffer(body)
uncompressed := bytes.Buffer{}
reader, err := gzip.NewReader(buffer)
if mod.dumpFormatHex {
if err != nil {
return mod.dumpRaw(body)
} else if _, err = uncompressed.ReadFrom(reader); err != nil {
return mod.dumpRaw(body)
}
return mod.dumpRaw(uncompressed.Bytes())
} else {
if err != nil {
return mod.dumpText(body)
} else if _, err = uncompressed.ReadFrom(reader); err != nil {
return mod.dumpText(body)
}
return mod.dumpText(uncompressed.Bytes())
}
}
func (mod *EventsStream) dumpJSON(body []byte) string {
var buf bytes.Buffer
var pretty string
if err := json.Indent(&buf, body, "", " "); err != nil {
pretty = string(body)
} else {
pretty = buf.String()
}
return "\n" + reJsonKey.ReplaceAllString(pretty, tui.Green(`$1:`)) + "\n"
}
func (mod *EventsStream) dumpXML(body []byte) string {
// TODO: indent xml
return "\n" + string(body) + "\n"
}
func (mod *EventsStream) dumpRaw(body []byte) string {
return "\n" + hex.Dump(body) + "\n"
}
func (mod *EventsStream) viewHttpRequest(output io.Writer, e session.Event) {
se := e.Data.(net_sniff.SnifferEvent)
req := se.Data.(net_sniff.HTTPRequest)
fmt.Fprintf(output, "[%s] [%s] %s\n",
e.Time.Format(mod.timeFormat),
tui.Green(e.Tag),
se.Message)
if mod.shouldDumpHttpRequest(req) {
dump := fmt.Sprintf("%s %s %s\n", tui.Bold(req.Method), req.URL, tui.Dim(req.Proto))
dump += fmt.Sprintf("%s: %s\n", tui.Blue("Host"), tui.Yellow(req.Host))
for name, values := range req.Headers {
for _, value := range values {
dump += fmt.Sprintf("%s: %s\n", tui.Blue(name), tui.Yellow(value))
}
}
if req.Body != nil {
if req.IsType("application/x-www-form-urlencoded") {
dump += mod.dumpForm(req.Body)
} else if req.IsType("text/plain") {
dump += mod.dumpText(req.Body)
} else if req.IsType("text/xml") {
dump += mod.dumpXML(req.Body)
} else if req.IsType("gzip") {
dump += mod.dumpGZIP(req.Body)
} else if req.IsType("application/json") {
dump += mod.dumpJSON(req.Body)
} else {
if mod.dumpFormatHex {
dump += mod.dumpRaw(req.Body)
} else {
dump += mod.dumpText(req.Body)
}
}
}
fmt.Fprintf(output, "\n%s\n", dump)
}
}
func (mod *EventsStream) viewHttpResponse(output io.Writer, e session.Event) {
se := e.Data.(net_sniff.SnifferEvent)
res := se.Data.(net_sniff.HTTPResponse)
fmt.Fprintf(output, "[%s] [%s] %s\n",
e.Time.Format(mod.timeFormat),
tui.Green(e.Tag),
se.Message)
if mod.shouldDumpHttpResponse(res) {
dump := fmt.Sprintf("%s %s\n", tui.Dim(res.Protocol), res.Status)
for name, values := range res.Headers {
for _, value := range values {
dump += fmt.Sprintf("%s: %s\n", tui.Blue(name), tui.Yellow(value))
}
}
if res.Body != nil {
// TODO: add more interesting response types
if res.IsType("text/plain") {
dump += mod.dumpText(res.Body)
} else if res.IsType("application/json") {
dump += mod.dumpJSON(res.Body)
} else if res.IsType("text/xml") {
dump += mod.dumpXML(res.Body)
}
}
fmt.Fprintf(output, "\n%s\n", dump)
}
}
func (mod *EventsStream) viewHttpEvent(output io.Writer, e session.Event) {
if e.Tag == "net.sniff.http.request" {
mod.viewHttpRequest(output, e)
} else if e.Tag == "net.sniff.http.response" {
mod.viewHttpResponse(output, e)
}
}