203 lines
7.6 KiB
Go
203 lines
7.6 KiB
Go
package services
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func NewMongoDBHandler(log LoggerFunc) Handler {
|
|
return func(conn net.Conn) {
|
|
defer conn.Close()
|
|
remote := conn.RemoteAddr().String()
|
|
conn.SetDeadline(time.Now().Add(30 * time.Second))
|
|
buf := make([]byte, 4096)
|
|
n, err := conn.Read(buf)
|
|
if err != nil { return }
|
|
details := map[string]string{"event":"protocol_attempt","bytes_received":strconv.Itoa(n)}
|
|
if n >= 16 {
|
|
msgLen := int32(binary.LittleEndian.Uint32(buf[0:4]))
|
|
// reqID := int32(binary.LittleEndian.Uint32(buf[4:8]))
|
|
// respTo := int32(binary.LittleEndian.Uint32(buf[8:12]))
|
|
opCode := int32(binary.LittleEndian.Uint32(buf[12:16]))
|
|
details["opcode"] = strconv.Itoa(int(opCode))
|
|
details["msg_len"] = strconv.Itoa(int(msgLen))
|
|
if opCode == 2004 { // OP_QUERY
|
|
// flags (4) + cstring ns starting at offset 20
|
|
// skip flags
|
|
i := 20
|
|
// extract cstring namespace
|
|
end := i
|
|
for end < n && buf[end] != 0 { end++ }
|
|
ns := string(buf[i:end])
|
|
details["namespace"] = ns
|
|
} else if opCode == 2013 { // OP_MSG (modern commands like hello)
|
|
// Structure: flags (4) + sections...
|
|
payload := buf[16:n]
|
|
if len(payload) >= 5 {
|
|
// first section
|
|
kind := payload[4]
|
|
if kind == 0 && len(payload) > 5 { // body BSON document
|
|
doc := payload[5:]
|
|
out := map[string]string{}
|
|
// Flatten with prefix, cap strings to 64, cap total fields to 32
|
|
parseBSONFlat(doc, 64, "", out, 32)
|
|
// Extract common command indicators
|
|
for _, k := range []string{"hello","isMaster","ismaster","saslStart","saslContinue","client","mechanism"} {
|
|
if v, ok := out[k]; ok {
|
|
details["op_msg_"+k] = v
|
|
}
|
|
}
|
|
if _, ok := out["hello"]; ok { details["op_msg_hint"] = "hello" }
|
|
if _, ok := out["isMaster"]; ok { details["op_msg_hint"] = "isMaster" }
|
|
if _, ok := out["ismaster"]; ok { details["op_msg_hint"] = "ismaster" }
|
|
if _, ok := out["saslStart"]; ok { details["op_msg_hint"] = "saslStart" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "mongodb", Details: details})
|
|
// Send a minimal isMaster/hello-like JSON to keep client talking
|
|
reply := `{"ok":1,"ismaster":true,"maxWireVersion":13,"minWireVersion":0,"helloOk":true}`
|
|
_, _ = conn.Write([]byte(reply))
|
|
}
|
|
}
|
|
|
|
// parseBSONFlat flattens a BSON document or array into out with key prefixing.
|
|
// - maxStr: maximum string length to record
|
|
// - prefix: current key prefix (e.g., "client.")
|
|
// - out: destination map
|
|
// - maxFields: stop after this many fields to avoid floods
|
|
func parseBSONFlat(b []byte, maxStr int, prefix string, out map[string]string, maxFields int) {
|
|
if len(out) >= maxFields || len(b) < 5 { return }
|
|
// First 4 bytes are int32 length; be lenient but keep bounds
|
|
i := 4
|
|
for i < len(b) && len(out) < maxFields {
|
|
t := b[i]
|
|
if t == 0x00 { // EOO
|
|
break
|
|
}
|
|
i++
|
|
// cstring key
|
|
ks := i
|
|
for i < len(b) && b[i] != 0 { i++ }
|
|
if i >= len(b) { return }
|
|
key := string(b[ks:i])
|
|
if prefix != "" { key = prefix + key }
|
|
i++ // skip NUL
|
|
switch t {
|
|
case 0x01: // double
|
|
if i+8 > len(b) { return }
|
|
out[key] = "<double>"
|
|
i += 8
|
|
case 0x02: // string
|
|
if i+4 > len(b) { return }
|
|
sl := int(int32(binary.LittleEndian.Uint32(b[i : i+4])))
|
|
i += 4
|
|
if sl <= 0 || i+sl > len(b) { return }
|
|
val := string(b[i : i+sl-1]) // exclude trailing NUL
|
|
if maxStr > 0 && len(val) > maxStr { val = val[:maxStr] }
|
|
out[key] = val
|
|
i += sl
|
|
case 0x03: // embedded document
|
|
if i+4 > len(b) { return }
|
|
// read declared length to bound sub-doc
|
|
subLen := int(int32(binary.LittleEndian.Uint32(b[i : i+4])))
|
|
end := i + subLen
|
|
if subLen <= 4 || end > len(b) { return }
|
|
parseBSONFlat(b[i:end], maxStr, key+".", out, maxFields)
|
|
i = end
|
|
case 0x04: // array
|
|
if i+4 > len(b) { return }
|
|
subLen := int(int32(binary.LittleEndian.Uint32(b[i : i+4])))
|
|
end := i + subLen
|
|
if subLen <= 4 || end > len(b) { return }
|
|
parseBSONFlat(b[i:end], maxStr, key+".", out, maxFields)
|
|
i = end
|
|
case 0x08: // bool
|
|
if i >= len(b) { return }
|
|
if b[i] == 0x01 { out[key] = "true" } else { out[key] = "false" }
|
|
i++
|
|
case 0x10: // int32
|
|
if i+4 > len(b) { return }
|
|
v := int32(binary.LittleEndian.Uint32(b[i : i+4]))
|
|
out[key] = strconv.FormatInt(int64(v), 10)
|
|
i += 4
|
|
case 0x12: // int64
|
|
if i+8 > len(b) { return }
|
|
out[key] = "<int64>"
|
|
i += 8
|
|
default:
|
|
// skip unknown types conservatively: stop parsing to avoid desync
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// containsAny reports whether s contains any of the substrings in list.
|
|
func containsAny(s string, list []string) bool {
|
|
ls := strings.ToLower(s)
|
|
for _, t := range list {
|
|
if t == "" { continue }
|
|
if strings.Contains(ls, strings.ToLower(t)) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// parseBSONStrings extracts top-level string/boolean/int32/double markers from a BSON document buffer.
|
|
// It is intentionally minimal and bounded for honeypot logging.
|
|
func parseBSONStrings(b []byte, maxStr int) map[string]string {
|
|
out := map[string]string{}
|
|
if len(b) < 5 {
|
|
return out
|
|
}
|
|
i := 4 // skip doc length
|
|
for i < len(b) {
|
|
t := b[i]
|
|
if t == 0x00 { // terminator
|
|
break
|
|
}
|
|
i++
|
|
// read cstring key
|
|
ks := i
|
|
for i < len(b) && b[i] != 0 { i++ }
|
|
if i >= len(b) {
|
|
break
|
|
}
|
|
key := string(b[ks:i])
|
|
i++ // skip NUL
|
|
switch t {
|
|
case 0x02: // string
|
|
if i+4 > len(b) { return out }
|
|
sl := int(int32(binary.LittleEndian.Uint32(b[i : i+4])))
|
|
i += 4
|
|
if sl <= 0 || i+sl > len(b) { return out }
|
|
val := string(b[i : i+sl-1]) // exclude trailing NUL
|
|
if maxStr > 0 && len(val) > maxStr { val = val[:maxStr] }
|
|
out[key] = val
|
|
i += sl
|
|
case 0x08: // boolean
|
|
if i >= len(b) { return out }
|
|
if b[i] == 0x01 { out[key] = "true" } else { out[key] = "false" }
|
|
i++
|
|
case 0x10: // int32
|
|
if i+4 > len(b) { return out }
|
|
v := int32(binary.LittleEndian.Uint32(b[i : i+4]))
|
|
out[key] = strconv.FormatInt(int64(v), 10)
|
|
i += 4
|
|
case 0x01: // double
|
|
if i+8 > len(b) { return out }
|
|
out[key] = "<double>"
|
|
i += 8
|
|
default:
|
|
// Stop on types we don't parse to avoid desync
|
|
return out
|
|
}
|
|
}
|
|
return out
|
|
}
|