From cd01dc7664f6240c7086a5aa8296dc177f30c01e Mon Sep 17 00:00:00 2001 From: maride Date: Wed, 8 Jan 2020 11:48:53 +0100 Subject: [PATCH] Add support for HTTP packets --- protocol/http/http.go | 52 +++++++++++++++++++++ protocol/http/httpRequestFactory.go | 67 ++++++++++++++++++++++++++++ protocol/http/httpResponseFactory.go | 59 ++++++++++++++++++++++++ protocol/index.go | 2 + 4 files changed, 180 insertions(+) create mode 100644 protocol/http/http.go create mode 100644 protocol/http/httpRequestFactory.go create mode 100644 protocol/http/httpResponseFactory.go diff --git a/protocol/http/http.go b/protocol/http/http.go new file mode 100644 index 0000000..606d14e --- /dev/null +++ b/protocol/http/http.go @@ -0,0 +1,52 @@ +package http + +import ( + "git.darknebu.la/maride/pancap/common" + "git.darknebu.la/maride/pancap/output" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/google/gopacket/tcpassembly" +) + +type Protocol struct { + initialized bool + requestFactory *httpRequestFactory + responseFactory *httpResponseFactory + requestPool *tcpassembly.StreamPool + responsePool *tcpassembly.StreamPool + requestAssembler *tcpassembly.Assembler + responseAssembler *tcpassembly.Assembler +} + +// Checks if the given packet is an HTTP packet we can process +func (p *Protocol) CanAnalyze(packet gopacket.Packet) bool { + return packet.Layer(layers.LayerTypeTCP) != nil && packet.Layer(layers.LayerTypeTLS) == nil +} + +// Analyzes the given HTTP packet +func (p *Protocol) Analyze(packet gopacket.Packet) error { + // Check if we need to init + if !p.initialized { + // Initialize + p.requestFactory = &httpRequestFactory{} + p.responseFactory = &httpResponseFactory{} + p.requestPool = tcpassembly.NewStreamPool(p.requestFactory) + p.responsePool = tcpassembly.NewStreamPool(p.responseFactory) + p.requestAssembler = tcpassembly.NewAssembler(p.requestPool) + p.responseAssembler = tcpassembly.NewAssembler(p.responsePool) + p.initialized = true + } + + // Try to cast packet and assemble HTTP stream + tcp := packet.TransportLayer().(*layers.TCP) + p.requestAssembler.AssembleWithTimestamp(packet.NetworkLayer().NetworkFlow(), tcp, packet.Metadata().Timestamp) + p.responseAssembler.AssembleWithTimestamp(packet.NetworkLayer().NetworkFlow(), tcp, packet.Metadata().Timestamp) + + return nil +} + +// Print a summary after all packets are processed +func (p *Protocol) PrintSummary() { + output.PrintBlock("HTTP Requests", common.GenerateTree(requestSummaryLines)) + output.PrintBlock("HTTP Responses", common.GenerateTree(responseSummaryLines)) +} diff --git a/protocol/http/httpRequestFactory.go b/protocol/http/httpRequestFactory.go new file mode 100644 index 0000000..ec0004e --- /dev/null +++ b/protocol/http/httpRequestFactory.go @@ -0,0 +1,67 @@ +package http + +import ( + "bufio" + "fmt" + "github.com/google/gopacket" + "github.com/google/gopacket/tcpassembly" + "github.com/google/gopacket/tcpassembly/tcpreader" + "io" + "log" + "net/http" +) + +var ( + requestSummaryLines []string +) + +type httpRequestFactory struct{} + +type httpRequestStream struct { + net, transport gopacket.Flow + r tcpreader.ReaderStream +} + +// Creates a new HTTPRequestStream for the given packet flow, and analyzes it in a separate thread +func (h *httpRequestFactory) New(net, transport gopacket.Flow) tcpassembly.Stream { + hstream := &httpRequestStream{ + net: net, + transport: transport, + r: tcpreader.NewReaderStream(), + } + + // Start analyzer as thread and return TCP reader stream + go hstream.run() + return &hstream.r +} + +// Analyzes the given request +func (h *httpRequestStream) run() { + iobuf := bufio.NewReader(&h.r) + + for { + req, reqErr := http.ReadRequest(iobuf) + + if reqErr == io.EOF { + // That's ok, we can ignore EOF errors + return + } else if reqErr != nil { + // Ignore, because it may be a response + } else { + // Try to process assembled request + tcpreader.DiscardBytesToEOF(req.Body) + req.Body.Close() + + // Build summary + line := fmt.Sprintf("Request %s http://%s%s", req.Method, req.Host, req.RequestURI) + requestSummaryLines = append(requestSummaryLines, line) + + // Check for file uploads + if req.MultipartForm != nil && req.MultipartForm.File != nil { + for k, v := range req.MultipartForm.File { + log.Println(k, v) + } + } + } + } +} diff --git a/protocol/http/httpResponseFactory.go b/protocol/http/httpResponseFactory.go new file mode 100644 index 0000000..95b0b90 --- /dev/null +++ b/protocol/http/httpResponseFactory.go @@ -0,0 +1,59 @@ +package http + +import ( + "bufio" + "fmt" + "github.com/google/gopacket" + "github.com/google/gopacket/tcpassembly" + "github.com/google/gopacket/tcpassembly/tcpreader" + "io" + "net/http" +) + +var ( + responseSummaryLines []string +) + +type httpResponseFactory struct{} + +type httpResponseStream struct { + net, transport gopacket.Flow + r tcpreader.ReaderStream +} + +// Creates a new HTTPResponseStream for the given packet flow, and analyzes it in a separate thread +func (h *httpResponseFactory) New(net, transport gopacket.Flow) tcpassembly.Stream { + hstream := &httpResponseStream{ + net: net, + transport: transport, + r: tcpreader.NewReaderStream(), + } + go hstream.run() // Important... we must guarantee that data from the reader stream is read. + + // ReaderStream implements tcpassembly.Stream, so we can return a pointer to it. + return &hstream.r +} + +// Analyzes the given response +func (h *httpResponseStream) run() { + iobuf := bufio.NewReader(&h.r) + + for { + resp, respErr := http.ReadResponse(iobuf, nil) + + if respErr == io.EOF { + // That's ok, we can ignore EOF errors + return + } else if respErr != nil { + // Ignore, because it may be a request + } else { + // Try to process assembled request + tcpreader.DiscardBytesToEOF(resp.Body) + resp.Body.Close() + + // Build summary + line := fmt.Sprintf("Response %s, Type %s, Size %d bytes", resp.Status, resp.Header.Get("Content-Type"), resp.ContentLength) + responseSummaryLines = append(responseSummaryLines, line) + } + } +} diff --git a/protocol/index.go b/protocol/index.go index 239d70b..2e0c0b7 100644 --- a/protocol/index.go +++ b/protocol/index.go @@ -4,6 +4,7 @@ import ( "git.darknebu.la/maride/pancap/protocol/arp" "git.darknebu.la/maride/pancap/protocol/dhcpv4" "git.darknebu.la/maride/pancap/protocol/dns" + "git.darknebu.la/maride/pancap/protocol/http" ) var ( @@ -11,5 +12,6 @@ var ( &arp.Protocol{}, &dhcpv4.Protocol{}, &dns.Protocol{}, + &http.Protocol{}, } )