Skip to content

Commit

Permalink
added ability to configure excluded ips from config, and automaticall…
Browse files Browse the repository at this point in the history
…y excluding own ips
  • Loading branch information
Connor Carroll committed Aug 20, 2024
1 parent 7ea7331 commit 78d02ce
Show file tree
Hide file tree
Showing 12 changed files with 65 additions and 28 deletions.
Binary file modified build/chickadee.exe
Binary file not shown.
Binary file modified build/chickadee_installer_win-x64.exe
Binary file not shown.
8 changes: 7 additions & 1 deletion build/config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@

interface = ``

# A comma separated list of IPs you wish to ignore. (Note: all
# configured addresses on the chosen interface are already ignored)

excluded_ips = ""

# Defines how many SYN packets (threshold_count) over time
# (threshold_time) to trigger alerts, and how long to ignore that
# src IP after triggering an alert (ignore_time).
Expand Down Expand Up @@ -65,7 +70,8 @@
# Details
to = ""
from = ""
# Subjects will be prepended with the agent_name value. e.g. "chickadee: Network scan d"
# Subjects will be prepended with the agent_name value.
# e.g. "chickadee: Network scan detected!"
subject = ""

## Webhook (assumes all webhooks accept POST)
Expand Down
2 changes: 1 addition & 1 deletion src/alerts/alerts.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/basht0p/chickadee/models"
)

func TriggerAlert(alertOptions models.AlertOptions, alertMessage string, srcIp string) {
func TriggerAlert(alertOptions *models.AlertOptions, alertMessage string, srcIp string) {

if alertOptions.SmtpEnabled {
smtp.SendSmtpAlert(alertOptions, alertMessage, srcIp)
Expand Down
2 changes: 1 addition & 1 deletion src/alerts/smtp/smtp.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

// SendSmtpAlert sends an email using the go-smtp library
func SendSmtpAlert(alertOptions models.AlertOptions, alertMessage string, srcIp string) error {
func SendSmtpAlert(alertOptions *models.AlertOptions, alertMessage string, srcIp string) error {
if !alertOptions.SmtpEnabled {
logger.Log(true, 1, 503, "SMTP is not enabled, but the function was called. How'd you do that?")
return fmt.Errorf("SMTP is not enabled in the config")
Expand Down
2 changes: 1 addition & 1 deletion src/alerts/snmp/snmp.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/gosnmp/gosnmp"
)

func SendSnmpTrap(alertOptions models.AlertOptions, srcIp string) error {
func SendSnmpTrap(alertOptions *models.AlertOptions, srcIp string) error {
port, err := strconv.Atoi(alertOptions.SnmpPort)
if err != nil {
logger.Log(true, 1, 503, fmt.Sprintf("Invalid port number: %v", err))
Expand Down
2 changes: 1 addition & 1 deletion src/alerts/webhook/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/basht0p/chickadee/models"
)

func SendWebhookAlert(alertOptions models.AlertOptions, srcIp string) (err error) {
func SendWebhookAlert(alertOptions *models.AlertOptions, srcIp string) (err error) {

data := models.WebhookData{
SourceIP: srcIp,
Expand Down
12 changes: 9 additions & 3 deletions src/config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,19 @@
# Agent Name
# The agent name will be included as an identifier for any alerts
# Set this to whatever you'd like, usually the hostname
agent_name = "Chickadee_NSD"
agent_name = ""

# In Windows, you need to use the InterfaceDescription property to
# configure the correct interface.
#
# You can use the PowerShell Cmdlet "Get-NetAdapter" to find this.

interface = `Ethernet 1`
interface = ``

# A comma separated list of IPs you wish to ignore. (Note: all
# configured addresses on the chosen interface are already ignored)

excluded_ips = ""

# Defines how many SYN packets (threshold_count) over time
# (threshold_time) to trigger alerts, and how long to ignore that
Expand Down Expand Up @@ -65,7 +70,8 @@
# Details
to = ""
from = ""
# Subjects will be prepended with the agent_name value. e.g. "chickadee: Network scan d"
# Subjects will be prepended with the agent_name value.
# e.g. "chickadee: Network scan detected!"
subject = ""

## Webhook (assumes all webhooks accept POST)
Expand Down
11 changes: 8 additions & 3 deletions src/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package config

import (
"fmt"
"strings"

"github.com/basht0p/chickadee/logger"
"github.com/basht0p/chickadee/models"
Expand All @@ -15,7 +16,7 @@ func handleConfigErr(section string, err error) {
}
}

func ReadConfig() (models.DetectionOptions, models.AlertOptions) {
func ReadConfig() (*models.DetectionOptions, *models.AlertOptions) {

var detectionOptions models.DetectionOptions
var alertOptions models.AlertOptions
Expand All @@ -24,6 +25,8 @@ func ReadConfig() (models.DetectionOptions, models.AlertOptions) {
iniOptions := iniContent.Section("")
handleConfigErr("config file", err)

iniAgentName := iniOptions.Key("agent_name").String()

iniIface := iniOptions.Key("interface").String()
handleConfigErr("interface", err)

Expand All @@ -36,7 +39,8 @@ func ReadConfig() (models.DetectionOptions, models.AlertOptions) {
iniIgnoreTime, err := iniOptions.Key("ignore_time").Uint()
handleConfigErr("ignore_time", err)

iniAgentName := iniOptions.Key("agent_name").String()
excludedIpString := iniOptions.Key("excluded_ips").String()
iniExcludedIps := strings.Split(strings.ReplaceAll(excludedIpString, " ", ""), ",")

iniSmtpEnabled, err := iniOptions.Key("enable_smtp").Bool()
handleConfigErr("enable_smtp", err)
Expand Down Expand Up @@ -82,6 +86,7 @@ func ReadConfig() (models.DetectionOptions, models.AlertOptions) {
ThresholdTime: iniThresholdTime,
IgnoreTime: iniIgnoreTime,
AgentName: iniAgentName,
ExcludedIps: iniExcludedIps,
}

alertOptions = models.AlertOptions{
Expand All @@ -106,5 +111,5 @@ func ReadConfig() (models.DetectionOptions, models.AlertOptions) {
SnmpCommunity: iniSnmpCommunity,
}

return detectionOptions, alertOptions
return &detectionOptions, &alertOptions
}
51 changes: 35 additions & 16 deletions src/detector/detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
var scans = make(map[string][]models.PortScan)
var lastAlertTime = make(map[string]time.Time)

func FindIface(ifaceQuery string) (iface string, ifaceDesc string) {
func FindIface(detectionOptions *models.DetectionOptions) (iface string, ifaceDesc string) {

devices, err := pcap.FindAllDevs()
if err != nil {
Expand All @@ -26,24 +26,34 @@ func FindIface(ifaceQuery string) (iface string, ifaceDesc string) {
}

for _, device := range devices {
if device.Description == ifaceQuery {
if device.Description == detectionOptions.Iface {
if len(device.Addresses) > 0 {
iface = device.Name
ifaceDesc = device.Description

for _, address := range device.Addresses {
detectionOptions.ExcludedIps = append(detectionOptions.ExcludedIps, address.IP.String())
}

break
} else {
logger.Log(true, 2, 500, fmt.Sprintf("Device (%v) was found, but no usable addresses were configured. Exiting...", ifaceQuery))
logger.Log(true, 2, 500, fmt.Sprintf("Device (%v) was found, but no usable addresses were configured. Exiting...", detectionOptions.Iface))
log.Fatal(fmt.Errorf("device (%v) was found, but no usable addresses were configured. exiting", ifaceDesc))
}
}
}

if iface == "" {
logger.Log(true, 2, 500, fmt.Sprintf("Device (%v) was not found. Using first interface found with valid address...", ifaceQuery))
logger.Log(true, 2, 500, fmt.Sprintf("Device (%v) was not found. Using first interface found with valid address...", detectionOptions.Iface))
for _, device := range devices {
if len(device.Addresses) > 0 {
iface = device.Name
ifaceDesc = device.Description

for _, address := range device.Addresses {
detectionOptions.ExcludedIps = append(detectionOptions.ExcludedIps, address.IP.String())
}

break
}
}
Expand All @@ -54,7 +64,7 @@ func FindIface(ifaceQuery string) (iface string, ifaceDesc string) {
return
}

func OpenPcap(iface string, ifaceDesc string, detectionOptions models.DetectionOptions) *pcap.Handle {
func OpenPcap(iface string, ifaceDesc string, detectionOptions *models.DetectionOptions) *pcap.Handle {

handle, err := pcap.OpenLive(iface, 1600, true, pcap.BlockForever)
if err != nil {
Expand All @@ -72,7 +82,7 @@ func OpenPcap(iface string, ifaceDesc string, detectionOptions models.DetectionO
return handle
}

func InitDetector(pktSrc *gopacket.PacketSource, detectionOptions models.DetectionOptions, alertOptions models.AlertOptions) {
func InitDetector(pktSrc *gopacket.PacketSource, detectionOptions *models.DetectionOptions, alertOptions *models.AlertOptions) {
for packet := range pktSrc.Packets() {
tcpLayer := packet.Layer(layers.LayerTypeTCP)
if tcpLayer != nil {
Expand All @@ -83,20 +93,27 @@ func InitDetector(pktSrc *gopacket.PacketSource, detectionOptions models.Detecti
DetectPortScan(
srcIP,
uint16(tcp.DstPort),
uint16(detectionOptions.ThresholdCount),
uint16(detectionOptions.ThresholdTime),
uint16(detectionOptions.IgnoreTime),
detectionOptions,
alertOptions,
)
}
}
}
}

func DetectPortScan(ip string, port uint16, tCount uint16, tTime uint16, iTime uint16, alertOptions models.AlertOptions) {
func IsIpExcluded(srcIp string, detectionOptions *models.DetectionOptions) bool {
for _, ip := range detectionOptions.ExcludedIps {
if ip == srcIp {
return true
}
}
return false
}

func DetectPortScan(ip string, port uint16, detectionOptions *models.DetectionOptions, alertOptions *models.AlertOptions) {
now := time.Now()

if lastAlert, alerted := lastAlertTime[ip]; alerted && now.Sub(lastAlert) < time.Duration(iTime)*time.Second {
if lastAlert, alerted := lastAlertTime[ip]; alerted && now.Sub(lastAlert) < time.Duration(detectionOptions.IgnoreTime)*time.Second {
return
}

Expand All @@ -106,17 +123,19 @@ func DetectPortScan(ip string, port uint16, tCount uint16, tTime uint16, iTime u

var newScans []models.PortScan
for _, scan := range scans[ip] {
if now.Sub(scan.Timestamp) <= time.Duration(tTime)*time.Second {
if now.Sub(scan.Timestamp) <= time.Duration(detectionOptions.ThresholdTime)*time.Second {
newScans = append(newScans, scan)
}
}
scans[ip] = newScans

scans[ip] = append(scans[ip], models.PortScan{Port: port, Timestamp: now})

if len(scans[ip]) > int(tCount) {
logger.Log(true, 1, 515, ("Port scan detected from: " + ip))
alerts.TriggerAlert(alertOptions, ("Port scan detected from: " + ip), ip)
lastAlertTime[ip] = now
if len(scans[ip]) > int(detectionOptions.ThresholdCount) {
if !IsIpExcluded(ip, detectionOptions) {
logger.Log(true, 1, 515, ("Port scan detected from: " + ip))
alerts.TriggerAlert(alertOptions, ("Port scan detected from: " + ip), ip)
lastAlertTime[ip] = now
}
}
}
2 changes: 1 addition & 1 deletion src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (p *program) run() {
detectionOptions, alertOptions := config.ReadConfig()

// Attempt to find the interface defined in the config
iface, ifaceDesc := detector.FindIface(detectionOptions.Iface)
iface, ifaceDesc := detector.FindIface(detectionOptions)

// Open a pcap channel with that interface
handle := detector.OpenPcap(iface, ifaceDesc, detectionOptions)
Expand Down
1 change: 1 addition & 0 deletions src/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type DetectionOptions struct {
ThresholdTime uint
IgnoreTime uint
AgentName string
ExcludedIps []string
}

type AlertOptions struct {
Expand Down

0 comments on commit 78d02ce

Please sign in to comment.