818 lines
19 KiB
Go
818 lines
19 KiB
Go
// Licensed to Elasticsearch B.V. under one or more contributor
|
|
// license agreements. See the NOTICE file distributed with
|
|
// this work for additional information regarding copyright
|
|
// ownership. Elasticsearch B.V. licenses this file to you under
|
|
// the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package http
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/elastic/beats/libbeat/beat"
|
|
"github.com/elastic/beats/libbeat/common"
|
|
"github.com/elastic/beats/libbeat/logp"
|
|
"github.com/elastic/beats/libbeat/monitoring"
|
|
|
|
"github.com/elastic/beats/packetbeat/procs"
|
|
"github.com/elastic/beats/packetbeat/protos"
|
|
)
|
|
|
|
var debugf = logp.MakeDebug("http")
|
|
var detailedf = logp.MakeDebug("httpdetailed")
|
|
|
|
type parserState uint8
|
|
|
|
const (
|
|
stateStart parserState = iota
|
|
stateHeaders
|
|
stateBody
|
|
stateBodyChunkedStart
|
|
stateBodyChunked
|
|
stateBodyChunkedWaitFinalCRLF
|
|
)
|
|
|
|
var (
|
|
unmatchedResponses = monitoring.NewInt(nil, "http.unmatched_responses")
|
|
unmatchedRequests = monitoring.NewInt(nil, "http.unmatched_requests")
|
|
)
|
|
|
|
type stream struct {
|
|
tcptuple *common.TCPTuple
|
|
|
|
data []byte
|
|
|
|
parseOffset int
|
|
parseState parserState
|
|
bodyReceived int
|
|
|
|
message *message
|
|
}
|
|
|
|
type httpConnectionData struct {
|
|
streams [2]*stream
|
|
requests messageList
|
|
responses messageList
|
|
}
|
|
|
|
type messageList struct {
|
|
head, tail *message
|
|
}
|
|
|
|
// HTTP application level protocol analyser plugin.
|
|
type httpPlugin struct {
|
|
// config
|
|
ports []int
|
|
sendRequest bool
|
|
sendResponse bool
|
|
splitCookie bool
|
|
hideKeywords []string
|
|
redactAuthorization bool
|
|
maxMessageSize int
|
|
|
|
parserConfig parserConfig
|
|
|
|
transactionTimeout time.Duration
|
|
|
|
results protos.Reporter
|
|
}
|
|
|
|
var (
|
|
isDebug = false
|
|
isDetailed = false
|
|
)
|
|
|
|
func init() {
|
|
protos.Register("http", New)
|
|
}
|
|
|
|
func New(
|
|
testMode bool,
|
|
results protos.Reporter,
|
|
cfg *common.Config,
|
|
) (protos.Plugin, error) {
|
|
p := &httpPlugin{}
|
|
config := defaultConfig
|
|
if !testMode {
|
|
if err := cfg.Unpack(&config); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if err := p.init(results, &config); err != nil {
|
|
return nil, err
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// Init initializes the HTTP protocol analyser.
|
|
func (http *httpPlugin) init(results protos.Reporter, config *httpConfig) error {
|
|
http.setFromConfig(config)
|
|
|
|
isDebug = logp.IsDebug("http")
|
|
isDetailed = logp.IsDebug("httpdetailed")
|
|
http.results = results
|
|
return nil
|
|
}
|
|
|
|
func (http *httpPlugin) setFromConfig(config *httpConfig) {
|
|
http.ports = config.Ports
|
|
http.sendRequest = config.SendRequest
|
|
http.sendResponse = config.SendResponse
|
|
http.hideKeywords = config.HideKeywords
|
|
http.redactAuthorization = config.RedactAuthorization
|
|
http.splitCookie = config.SplitCookie
|
|
http.parserConfig.realIPHeader = strings.ToLower(config.RealIPHeader)
|
|
http.transactionTimeout = config.TransactionTimeout
|
|
for _, list := range [][]string{config.IncludeBodyFor, config.IncludeRequestBodyFor} {
|
|
http.parserConfig.includeRequestBodyFor = append(http.parserConfig.includeRequestBodyFor, list...)
|
|
}
|
|
for _, list := range [][]string{config.IncludeBodyFor, config.IncludeResponseBodyFor} {
|
|
http.parserConfig.includeResponseBodyFor = append(http.parserConfig.includeResponseBodyFor, list...)
|
|
}
|
|
http.maxMessageSize = config.MaxMessageSize
|
|
|
|
if config.SendAllHeaders {
|
|
http.parserConfig.sendHeaders = true
|
|
http.parserConfig.sendAllHeaders = true
|
|
} else {
|
|
if len(config.SendHeaders) > 0 {
|
|
http.parserConfig.sendHeaders = true
|
|
|
|
http.parserConfig.headersWhitelist = map[string]bool{}
|
|
for _, hdr := range config.SendHeaders {
|
|
http.parserConfig.headersWhitelist[strings.ToLower(hdr)] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetPorts lists the port numbers the HTTP protocol analyser will handle.
|
|
func (http *httpPlugin) GetPorts() []int {
|
|
return http.ports
|
|
}
|
|
|
|
// messageGap is called when a gap of size `nbytes` is found in the
|
|
// tcp stream. Decides if we can ignore the gap or it's a parser error
|
|
// and we need to drop the stream.
|
|
func (http *httpPlugin) messageGap(s *stream, nbytes int) (ok bool, complete bool) {
|
|
m := s.message
|
|
switch s.parseState {
|
|
case stateStart, stateHeaders:
|
|
// we know we cannot recover from these
|
|
return false, false
|
|
case stateBody:
|
|
if isDebug {
|
|
debugf("gap in body: %d", nbytes)
|
|
}
|
|
|
|
if m.isRequest {
|
|
m.notes = append(m.notes, "Packet loss while capturing the request")
|
|
} else {
|
|
m.notes = append(m.notes, "Packet loss while capturing the response")
|
|
}
|
|
if !m.hasContentLength && (bytes.Equal(m.connection, constClose) ||
|
|
(isVersion(m.version, 1, 0) && !bytes.Equal(m.connection, constKeepAlive))) {
|
|
s.bodyReceived += nbytes
|
|
m.contentLength += nbytes
|
|
return true, false
|
|
} else if len(s.data)+nbytes >= m.contentLength-s.bodyReceived {
|
|
// we're done, but the last portion of the data is gone
|
|
return true, true
|
|
} else {
|
|
s.bodyReceived += nbytes
|
|
return true, false
|
|
}
|
|
}
|
|
// assume we cannot recover
|
|
return false, false
|
|
}
|
|
|
|
func (st *stream) PrepareForNewMessage() {
|
|
st.parseState = stateStart
|
|
st.parseOffset = 0
|
|
st.bodyReceived = 0
|
|
st.message = nil
|
|
}
|
|
|
|
// Called when the parser has identified the boundary
|
|
// of a message.
|
|
func (http *httpPlugin) messageComplete(
|
|
conn *httpConnectionData,
|
|
tcptuple *common.TCPTuple,
|
|
dir uint8,
|
|
st *stream,
|
|
) {
|
|
http.handleHTTP(conn, st.message, tcptuple, dir)
|
|
}
|
|
|
|
// ConnectionTimeout returns the configured HTTP transaction timeout.
|
|
func (http *httpPlugin) ConnectionTimeout() time.Duration {
|
|
return http.transactionTimeout
|
|
}
|
|
|
|
// Parse function is used to process TCP payloads.
|
|
func (http *httpPlugin) Parse(
|
|
pkt *protos.Packet,
|
|
tcptuple *common.TCPTuple,
|
|
dir uint8,
|
|
private protos.ProtocolData,
|
|
) protos.ProtocolData {
|
|
defer logp.Recover("ParseHttp exception")
|
|
|
|
conn := ensureHTTPConnection(private)
|
|
conn = http.doParse(conn, pkt, tcptuple, dir)
|
|
if conn == nil {
|
|
return nil
|
|
}
|
|
return conn
|
|
}
|
|
|
|
func ensureHTTPConnection(private protos.ProtocolData) *httpConnectionData {
|
|
conn := getHTTPConnection(private)
|
|
if conn == nil {
|
|
conn = &httpConnectionData{}
|
|
}
|
|
return conn
|
|
}
|
|
|
|
func getHTTPConnection(private protos.ProtocolData) *httpConnectionData {
|
|
if private == nil {
|
|
return nil
|
|
}
|
|
|
|
priv, ok := private.(*httpConnectionData)
|
|
if !ok {
|
|
logp.Warn("http connection data type error")
|
|
return nil
|
|
}
|
|
if priv == nil {
|
|
logp.Warn("Unexpected: http connection data not set")
|
|
return nil
|
|
}
|
|
|
|
return priv
|
|
}
|
|
|
|
// Parse function is used to process TCP payloads.
|
|
func (http *httpPlugin) doParse(
|
|
conn *httpConnectionData,
|
|
pkt *protos.Packet,
|
|
tcptuple *common.TCPTuple,
|
|
dir uint8,
|
|
) *httpConnectionData {
|
|
|
|
if isDetailed {
|
|
detailedf("Payload received: [%s]", pkt.Payload)
|
|
}
|
|
|
|
extraMsgSize := 0 // size of a "seen" packet for which we don't store the actual bytes
|
|
|
|
st := conn.streams[dir]
|
|
if st == nil {
|
|
st = newStream(pkt, tcptuple)
|
|
conn.streams[dir] = st
|
|
} else {
|
|
// concatenate bytes
|
|
totalLength := len(st.data) + len(pkt.Payload)
|
|
msg := st.message
|
|
if msg != nil {
|
|
totalLength += len(msg.body)
|
|
}
|
|
if totalLength > http.maxMessageSize {
|
|
if isDebug {
|
|
debugf("Stream data too large, ignoring message")
|
|
}
|
|
extraMsgSize = len(pkt.Payload)
|
|
} else {
|
|
st.data = append(st.data, pkt.Payload...)
|
|
}
|
|
}
|
|
|
|
for len(st.data) > 0 || extraMsgSize > 0 {
|
|
if st.message == nil {
|
|
st.message = &message{ts: pkt.Ts}
|
|
}
|
|
|
|
parser := newParser(&http.parserConfig)
|
|
ok, complete := parser.parse(st, extraMsgSize)
|
|
extraMsgSize = 0
|
|
if !ok {
|
|
// drop this tcp stream. Will retry parsing with the next
|
|
// segment in it
|
|
conn.streams[dir] = nil
|
|
return conn
|
|
}
|
|
|
|
if !complete {
|
|
// wait for more data
|
|
break
|
|
}
|
|
|
|
// all ok, ship it
|
|
http.messageComplete(conn, tcptuple, dir, st)
|
|
|
|
// and reset stream for next message
|
|
st.PrepareForNewMessage()
|
|
}
|
|
|
|
return conn
|
|
}
|
|
|
|
func newStream(pkt *protos.Packet, tcptuple *common.TCPTuple) *stream {
|
|
return &stream{
|
|
tcptuple: tcptuple,
|
|
data: pkt.Payload,
|
|
message: &message{ts: pkt.Ts},
|
|
}
|
|
}
|
|
|
|
// ReceivedFin will be called when TCP transaction is terminating.
|
|
func (http *httpPlugin) ReceivedFin(tcptuple *common.TCPTuple, dir uint8,
|
|
private protos.ProtocolData) protos.ProtocolData {
|
|
|
|
debugf("Received FIN")
|
|
conn := getHTTPConnection(private)
|
|
if conn == nil {
|
|
return private
|
|
}
|
|
|
|
stream := conn.streams[dir]
|
|
if stream == nil {
|
|
return conn
|
|
}
|
|
|
|
// send whatever data we got so far as complete. This
|
|
// is needed for the HTTP/1.0 without Content-Length situation.
|
|
if stream.message != nil {
|
|
http.handleHTTP(conn, stream.message, tcptuple, dir)
|
|
|
|
// and reset message. Probably not needed, just to be sure.
|
|
stream.PrepareForNewMessage()
|
|
}
|
|
|
|
return conn
|
|
}
|
|
|
|
// GapInStream is called when a gap of nbytes bytes is found in the stream (due
|
|
// to packet loss).
|
|
func (http *httpPlugin) GapInStream(tcptuple *common.TCPTuple, dir uint8,
|
|
nbytes int, private protos.ProtocolData) (priv protos.ProtocolData, drop bool) {
|
|
|
|
defer logp.Recover("GapInStream(http) exception")
|
|
|
|
conn := getHTTPConnection(private)
|
|
if conn == nil {
|
|
return private, false
|
|
}
|
|
|
|
stream := conn.streams[dir]
|
|
if stream == nil || stream.message == nil {
|
|
// nothing to do
|
|
return private, false
|
|
}
|
|
|
|
ok, complete := http.messageGap(stream, nbytes)
|
|
if isDetailed {
|
|
detailedf("messageGap returned ok=%v complete=%v", ok, complete)
|
|
}
|
|
if !ok {
|
|
// on errors, drop stream
|
|
conn.streams[dir] = nil
|
|
return conn, true
|
|
}
|
|
|
|
if complete {
|
|
// Current message is complete, we need to publish from here
|
|
http.messageComplete(conn, tcptuple, dir, stream)
|
|
}
|
|
|
|
// don't drop the stream, we can ignore the gap
|
|
return private, false
|
|
}
|
|
|
|
func (http *httpPlugin) handleHTTP(
|
|
conn *httpConnectionData,
|
|
m *message,
|
|
tcptuple *common.TCPTuple,
|
|
dir uint8,
|
|
) {
|
|
|
|
m.tcpTuple = *tcptuple
|
|
m.direction = dir
|
|
m.cmdlineTuple = procs.ProcWatcher.FindProcessesTupleTCP(tcptuple.IPPort())
|
|
http.hideHeaders(m)
|
|
|
|
if m.isRequest {
|
|
if isDebug {
|
|
debugf("Received request with tuple: %s", m.tcpTuple)
|
|
}
|
|
conn.requests.append(m)
|
|
} else {
|
|
if isDebug {
|
|
debugf("Received response with tuple: %s", m.tcpTuple)
|
|
}
|
|
conn.responses.append(m)
|
|
http.correlate(conn)
|
|
}
|
|
}
|
|
|
|
func (http *httpPlugin) flushResponses(conn *httpConnectionData) {
|
|
for !conn.responses.empty() {
|
|
unmatchedResponses.Add(1)
|
|
resp := conn.responses.pop()
|
|
debugf("Response from unknown transaction: %s. Reporting error.", resp.tcpTuple)
|
|
event := http.newTransaction(nil, resp)
|
|
http.publishTransaction(event)
|
|
}
|
|
}
|
|
|
|
func (http *httpPlugin) flushRequests(conn *httpConnectionData) {
|
|
for !conn.requests.empty() {
|
|
unmatchedRequests.Add(1)
|
|
requ := conn.requests.pop()
|
|
debugf("Request from unknown transaction %s. Reporting error.", requ.tcpTuple)
|
|
event := http.newTransaction(requ, nil)
|
|
http.publishTransaction(event)
|
|
}
|
|
}
|
|
|
|
func (http *httpPlugin) correlate(conn *httpConnectionData) {
|
|
|
|
// drop responses with missing requests
|
|
if conn.requests.empty() {
|
|
http.flushResponses(conn)
|
|
return
|
|
}
|
|
|
|
// merge requests with responses into transactions
|
|
for !conn.responses.empty() && !conn.requests.empty() {
|
|
requ := conn.requests.pop()
|
|
resp := conn.responses.pop()
|
|
event := http.newTransaction(requ, resp)
|
|
|
|
if isDebug {
|
|
debugf("HTTP transaction completed")
|
|
}
|
|
http.publishTransaction(event)
|
|
}
|
|
}
|
|
|
|
func (http *httpPlugin) newTransaction(requ, resp *message) beat.Event {
|
|
status := common.OK_STATUS
|
|
if resp == nil {
|
|
status = common.ERROR_STATUS
|
|
if requ != nil {
|
|
requ.notes = append(requ.notes, "Unmatched request")
|
|
}
|
|
} else if resp.statusCode >= 400 {
|
|
status = common.ERROR_STATUS
|
|
}
|
|
if requ == nil {
|
|
status = common.ERROR_STATUS
|
|
if resp != nil {
|
|
resp.notes = append(resp.notes, "Unmatched response")
|
|
}
|
|
}
|
|
|
|
httpDetails := common.MapStr{}
|
|
fields := common.MapStr{
|
|
"type": "http",
|
|
"status": status,
|
|
"http": httpDetails,
|
|
}
|
|
|
|
var timestamp time.Time
|
|
|
|
if requ != nil {
|
|
path, params, err := http.extractParameters(requ)
|
|
if err != nil {
|
|
logp.Warn("Fail to parse HTTP parameters: %v", err)
|
|
}
|
|
httpDetails["request"] = common.MapStr{
|
|
"params": params,
|
|
"headers": http.collectHeaders(requ),
|
|
}
|
|
fields["method"] = requ.method
|
|
fields["path"] = path
|
|
fields["query"] = fmt.Sprintf("%s %s", requ.method, path)
|
|
fields["bytes_in"] = requ.size
|
|
|
|
fields["src"], fields["dst"] = requ.getEndpoints()
|
|
|
|
http.setBody(httpDetails["request"].(common.MapStr), requ)
|
|
|
|
timestamp = requ.ts
|
|
|
|
if len(requ.notes) > 0 {
|
|
fields["notes"] = requ.notes
|
|
}
|
|
|
|
if len(requ.realIP) > 0 {
|
|
fields["real_ip"] = requ.realIP
|
|
}
|
|
|
|
if http.sendRequest {
|
|
fields["request"] = string(http.makeRawMessage(requ))
|
|
}
|
|
}
|
|
|
|
if resp != nil {
|
|
httpDetails["response"] = common.MapStr{
|
|
"code": resp.statusCode,
|
|
"phrase": resp.statusPhrase,
|
|
"headers": http.collectHeaders(resp),
|
|
}
|
|
http.setBody(httpDetails["response"].(common.MapStr), resp)
|
|
fields["bytes_out"] = resp.size
|
|
|
|
if http.sendResponse {
|
|
fields["response"] = string(http.makeRawMessage(resp))
|
|
}
|
|
|
|
if len(resp.notes) > 0 {
|
|
if fields["notes"] != nil {
|
|
fields["notes"] = append(fields["notes"].([]string), resp.notes...)
|
|
} else {
|
|
fields["notes"] = resp.notes
|
|
}
|
|
}
|
|
if requ == nil {
|
|
timestamp = resp.ts
|
|
fields["src"], fields["dst"] = resp.getEndpoints()
|
|
}
|
|
}
|
|
|
|
// resp_time in milliseconds
|
|
if requ != nil && resp != nil {
|
|
fields["responsetime"] = int32(resp.ts.Sub(requ.ts).Nanoseconds() / 1e6)
|
|
}
|
|
|
|
return beat.Event{
|
|
Timestamp: timestamp,
|
|
Fields: fields,
|
|
}
|
|
}
|
|
|
|
func (http *httpPlugin) makeRawMessage(m *message) string {
|
|
if m.sendBody {
|
|
var b strings.Builder
|
|
b.Grow(len(m.rawHeaders) + len(m.body))
|
|
b.Write(m.rawHeaders)
|
|
b.Write(m.body)
|
|
return b.String()
|
|
}
|
|
return string(m.rawHeaders)
|
|
}
|
|
|
|
func (http *httpPlugin) publishTransaction(event beat.Event) {
|
|
if http.results == nil {
|
|
return
|
|
}
|
|
http.results(event)
|
|
}
|
|
|
|
func (http *httpPlugin) collectHeaders(m *message) interface{} {
|
|
hdrs := map[string]interface{}{}
|
|
|
|
hdrs["content-length"] = m.contentLength
|
|
if len(m.contentType) > 0 {
|
|
hdrs["content-type"] = m.contentType
|
|
}
|
|
|
|
if http.parserConfig.sendHeaders {
|
|
|
|
cookie := "cookie"
|
|
if !m.isRequest {
|
|
cookie = "set-cookie"
|
|
}
|
|
|
|
for name, value := range m.headers {
|
|
if strings.ToLower(name) == "content-type" {
|
|
continue
|
|
}
|
|
if strings.ToLower(name) == "content-length" {
|
|
continue
|
|
}
|
|
if http.splitCookie && name == cookie {
|
|
hdrs[name] = splitCookiesHeader(string(value))
|
|
} else {
|
|
hdrs[name] = value
|
|
}
|
|
}
|
|
}
|
|
return hdrs
|
|
}
|
|
|
|
func (http *httpPlugin) setBody(result common.MapStr, m *message) {
|
|
if m.sendBody && len(m.body) > 0 {
|
|
result["body"] = string(m.body)
|
|
}
|
|
}
|
|
|
|
func splitCookiesHeader(headerVal string) map[string]string {
|
|
cookies := map[string]string{}
|
|
|
|
cstring := strings.Split(headerVal, ";")
|
|
for _, cval := range cstring {
|
|
cookie := strings.SplitN(cval, "=", 2)
|
|
if len(cookie) == 2 {
|
|
cookies[strings.ToLower(strings.TrimSpace(cookie[0]))] =
|
|
parseCookieValue(strings.TrimSpace(cookie[1]))
|
|
}
|
|
}
|
|
|
|
return cookies
|
|
}
|
|
|
|
func parseCookieValue(raw string) string {
|
|
// Strip the quotes, if present.
|
|
if len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' {
|
|
raw = raw[1 : len(raw)-1]
|
|
}
|
|
return raw
|
|
}
|
|
|
|
func (http *httpPlugin) hideHeaders(m *message) {
|
|
if !m.isRequest || !http.redactAuthorization {
|
|
return
|
|
}
|
|
|
|
msg := m.rawHeaders
|
|
limit := len(msg)
|
|
|
|
// byte64 != encryption, so obscure it in headers in case of Basic Authentication
|
|
|
|
redactHeaders := []string{"authorization", "proxy-authorization"}
|
|
authText := []byte("uthorization:") // [aA] case insensitive, also catches Proxy-Authorization:
|
|
|
|
authHeaderStartX := m.headerOffset
|
|
authHeaderEndX := limit
|
|
|
|
for authHeaderStartX < limit {
|
|
if isDebug {
|
|
debugf("looking for authorization from %d to %d",
|
|
authHeaderStartX, authHeaderEndX)
|
|
}
|
|
|
|
startOfHeader := bytes.Index(msg[authHeaderStartX:], authText)
|
|
if startOfHeader >= 0 {
|
|
authHeaderStartX = authHeaderStartX + startOfHeader
|
|
|
|
endOfHeader := bytes.Index(msg[authHeaderStartX:], constCRLF)
|
|
if endOfHeader >= 0 {
|
|
authHeaderEndX = authHeaderStartX + endOfHeader
|
|
|
|
if authHeaderEndX > limit {
|
|
authHeaderEndX = limit
|
|
}
|
|
|
|
if isDebug {
|
|
debugf("Redact authorization from %d to %d", authHeaderStartX, authHeaderEndX)
|
|
}
|
|
|
|
for i := authHeaderStartX + len(authText); i < authHeaderEndX; i++ {
|
|
msg[i] = byte('*')
|
|
}
|
|
}
|
|
}
|
|
authHeaderStartX = authHeaderEndX + len(constCRLF)
|
|
authHeaderEndX = len(m.rawHeaders)
|
|
}
|
|
|
|
for _, header := range redactHeaders {
|
|
if len(m.headers[header]) > 0 {
|
|
m.headers[header] = []byte("*")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (http *httpPlugin) hideSecrets(values url.Values) url.Values {
|
|
params := url.Values{}
|
|
for key, array := range values {
|
|
for _, value := range array {
|
|
if http.isSecretParameter(key) {
|
|
params.Add(key, "xxxxx")
|
|
} else {
|
|
params.Add(key, value)
|
|
}
|
|
}
|
|
}
|
|
return params
|
|
}
|
|
|
|
// extractParameters parses the URL and the form parameters and replaces the secrets
|
|
// with the string xxxxx. The parameters containing secrets are defined in http.Hide_secrets.
|
|
// Returns the Request URI path and the (adjusted) parameters.
|
|
func (http *httpPlugin) extractParameters(m *message) (path string, params string, err error) {
|
|
var values url.Values
|
|
|
|
u, err := url.Parse(string(m.requestURI))
|
|
if err != nil {
|
|
return
|
|
}
|
|
values = u.Query()
|
|
path = u.Path
|
|
|
|
paramsMap := http.hideSecrets(values)
|
|
|
|
if m.contentLength > 0 && m.saveBody && bytes.Contains(m.contentType, []byte("urlencoded")) {
|
|
|
|
values, err = url.ParseQuery(string(m.body))
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for key, value := range http.hideSecrets(values) {
|
|
paramsMap[key] = value
|
|
}
|
|
}
|
|
|
|
params = paramsMap.Encode()
|
|
if isDetailed {
|
|
detailedf("Form parameters: %s", params)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (http *httpPlugin) isSecretParameter(key string) bool {
|
|
for _, keyword := range http.hideKeywords {
|
|
if strings.ToLower(key) == keyword {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (http *httpPlugin) Expired(tuple *common.TCPTuple, private protos.ProtocolData) {
|
|
conn := getHTTPConnection(private)
|
|
if conn == nil {
|
|
return
|
|
}
|
|
if isDebug {
|
|
debugf("expired connection %s", tuple)
|
|
}
|
|
// terminate streams
|
|
for dir, s := range conn.streams {
|
|
// Do not send incomplete or empty messages
|
|
if s != nil && s.message != nil && s.message.headersReceived() {
|
|
if isDebug {
|
|
debugf("got message %+v", s.message)
|
|
}
|
|
http.handleHTTP(conn, s.message, tuple, uint8(dir))
|
|
s.PrepareForNewMessage()
|
|
}
|
|
}
|
|
// correlate transactions
|
|
http.correlate(conn)
|
|
|
|
// flush uncorrelated requests and responses
|
|
http.flushRequests(conn)
|
|
http.flushResponses(conn)
|
|
}
|
|
|
|
func (ml *messageList) append(msg *message) {
|
|
if ml.tail == nil {
|
|
ml.head = msg
|
|
} else {
|
|
ml.tail.next = msg
|
|
}
|
|
msg.next = nil
|
|
ml.tail = msg
|
|
}
|
|
|
|
func (ml *messageList) empty() bool {
|
|
return ml.head == nil
|
|
}
|
|
|
|
func (ml *messageList) pop() *message {
|
|
if ml.head == nil {
|
|
return nil
|
|
}
|
|
|
|
msg := ml.head
|
|
ml.head = ml.head.next
|
|
if ml.head == nil {
|
|
ml.tail = nil
|
|
}
|
|
return msg
|
|
}
|
|
|
|
func (ml *messageList) last() *message {
|
|
return ml.tail
|
|
}
|