Skip to content

Commit

Permalink
Merge pull request #333 from Security-Onion-Solutions/cogburn/detecti…
Browse files Browse the repository at this point in the history
…ons_playbooks

Cogburn/detections playbooks
  • Loading branch information
coreyogburn authored Feb 1, 2024
2 parents 82845b9 + 17f3d30 commit 529a15f
Show file tree
Hide file tree
Showing 90 changed files with 10,243 additions and 577 deletions.
11 changes: 9 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

FROM ghcr.io/security-onion-solutions/golang:1.21.5-alpine as builder
ARG VERSION=0.0.0
RUN apk update && apk add libpcap-dev bash git musl-dev gcc npm python3 py3-pip py3-virtualenv
RUN apk update && apk add libpcap-dev bash git musl-dev gcc npm python3 py3-pip py3-virtualenv python3-dev openssl-dev linux-headers
COPY . /build
WORKDIR /build
RUN if [ "$VERSION" != "0.0.0" ]; then mkdir gitdocs && cd gitdocs && \
Expand All @@ -22,6 +22,9 @@ RUN if [ "$VERSION" != "0.0.0" ]; then mkdir gitdocs && cd gitdocs && \
RUN npm install jest jest-environment-jsdom --global
RUN ./build.sh "$VERSION"

RUN pip3 install sigma-cli pysigma-backend-elasticsearch pysigma-pipeline-windows yara-python --break-system-packages
RUN sed -i 's/#!\/usr\/bin\/python3/#!\/usr\/bin\/env python/g' /usr/bin/sigma

FROM ghcr.io/security-onion-solutions/python:3-slim

ARG UID=939
Expand All @@ -30,7 +33,9 @@ ARG VERSION=0.0.0
ARG ELASTIC_VERSION=0.0.0
ARG WAZUH_VERSION=0.0.0

RUN apt update -y && apt install -y bash tzdata ca-certificates wget curl tcpdump unzip && update-ca-certificates
RUN apt update -y
RUN apt install -y bash tzdata ca-certificates wget curl tcpdump unzip tshark
RUN update-ca-certificates
RUN addgroup --gid "$GID" socore
RUN adduser --disabled-password --uid "$UID" --ingroup socore --gecos '' socore
RUN mkdir -p /opt/sensoroni/jobs && chown socore:socore /opt/sensoroni/jobs
Expand All @@ -44,6 +49,8 @@ COPY --from=builder /build/LICENSE .
COPY --from=builder /build/README.md .
COPY --from=builder /build/sensoroni.json .
COPY --from=builder /build/gitdocs/_build/html ./html/docs
COPY --from=builder /usr/lib/python3.11/site-packages /usr/local/lib/python3.9/site-packages
COPY --from=builder /usr/bin/sigma /usr/bin/sigma
RUN find html/js -name "*test*.js" -delete
RUN chmod u+x scripts/*
RUN chown 939:939 scripts/*
Expand Down
5 changes: 5 additions & 0 deletions agent/jobmanager_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// Copyright 2020-2023 Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
// or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
// https://securityonion.net/license; you may not use this file except in compliance with the
// Elastic License 2.0.

package agent

import (
Expand Down
2 changes: 2 additions & 0 deletions agent/modules/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/security-onion-solutions/securityonion-soc/agent/modules/importer"
"github.com/security-onion-solutions/securityonion-soc/agent/modules/statickeyauth"
"github.com/security-onion-solutions/securityonion-soc/agent/modules/stenoquery"
"github.com/security-onion-solutions/securityonion-soc/agent/modules/suriquery"
"github.com/security-onion-solutions/securityonion-soc/module"
)

Expand All @@ -21,5 +22,6 @@ func BuildModuleMap(agt *agent.Agent) map[string]module.Module {
moduleMap["importer"] = importer.NewImporter(agt)
moduleMap["statickeyauth"] = statickeyauth.NewStaticKeyAuth(agt)
moduleMap["stenoquery"] = stenoquery.NewStenoQuery(agt)
moduleMap["suriquery"] = suriquery.NewSuriQuery(agt)
return moduleMap
}
1 change: 1 addition & 0 deletions agent/modules/modules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func TestBuildModuleMap(tester *testing.T) {
findModule(tester, mm, "importer")
findModule(tester, mm, "statickeyauth")
findModule(tester, mm, "stenoquery")
findModule(tester, mm, "suriquery")
}

func findModule(tester *testing.T, mm map[string]module.Module, module string) {
Expand Down
209 changes: 209 additions & 0 deletions agent/modules/suriquery/suriquery.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// Copyright 2019 Jason Ertel (github.com/jertel).
// Copyright 2020-2023 Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
// or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
// https://securityonion.net/license; you may not use this file except in compliance with the
// Elastic License 2.0.

package suriquery

import (
"context"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"time"

"github.com/apex/log"
"github.com/kennygrant/sanitize"
"github.com/security-onion-solutions/securityonion-soc/agent"
"github.com/security-onion-solutions/securityonion-soc/model"
"github.com/security-onion-solutions/securityonion-soc/module"
)

const DEFAULT_EXECUTABLE_PATH = "suriquery.sh"
const DEFAULT_PCAP_OUTPUT_PATH = "/nsm/pcapout"
const DEFAULT_PCAP_INPUT_PATH = "/nsm/pcap"
const DEFAULT_EPOCH_REFRESH_MS = 120000
const DEFAULT_TIMEOUT_MS = 1200000
const DEFAULT_DATA_LAG_MS = 120000

type SuriQuery struct {
config module.ModuleConfig
executablePath string
pcapOutputPath string
pcapInputPath string
agent *agent.Agent
epochTimeTmp time.Time
epochTime time.Time
epochRefreshTime time.Time
epochRefreshMs int
timeoutMs int
dataLagMs int
}

func NewSuriQuery(agt *agent.Agent) *SuriQuery {
return &SuriQuery{
agent: agt,
}
}

func (lag *SuriQuery) PrerequisiteModules() []string {
return nil
}

func (suri *SuriQuery) Init(cfg module.ModuleConfig) error {
var err error
suri.config = cfg
suri.executablePath = module.GetStringDefault(cfg, "executablePath", DEFAULT_EXECUTABLE_PATH)
suri.pcapOutputPath = module.GetStringDefault(cfg, "pcapOutputPath", DEFAULT_PCAP_OUTPUT_PATH)
suri.pcapInputPath = module.GetStringDefault(cfg, "pcapInputPath", DEFAULT_PCAP_INPUT_PATH)
suri.epochRefreshMs = module.GetIntDefault(cfg, "epochRefreshMs", DEFAULT_EPOCH_REFRESH_MS)
suri.timeoutMs = module.GetIntDefault(cfg, "timeoutMs", DEFAULT_TIMEOUT_MS)
suri.dataLagMs = module.GetIntDefault(cfg, "dataLagMs", DEFAULT_DATA_LAG_MS)
if suri.agent == nil {
err = errors.New("Unable to invoke JobMgr.AddJobProcessor due to nil agent")
} else {
suri.agent.JobMgr.AddJobProcessor(suri)
}
return err
}

func (suri *SuriQuery) Start() error {
return nil
}

func (suri *SuriQuery) Stop() error {
return nil
}

func (suri *SuriQuery) IsRunning() bool {
return false
}

func (suri *SuriQuery) getDataLagDate() time.Time {
return time.Now().Add(time.Duration(-suri.dataLagMs) * time.Millisecond)
}

func (suri *SuriQuery) ProcessJob(job *model.Job, reader io.ReadCloser) (io.ReadCloser, error) {
var err error
if job.GetKind() != "pcap" {
log.WithFields(log.Fields{
"jobId": job.Id,
"kind": job.GetKind(),
}).Debug("Skipping suri processor due to unsupported job")
return reader, nil
}
if len(job.Filter.ImportId) > 0 {
log.WithFields(log.Fields{
"jobId": job.Id,
"importId": job.Filter.ImportId,
}).Debug("Skipping suri processor due to presence of importId")
return reader, nil
} else if job.Filter == nil || job.Filter.EndTime.Before(suri.GetDataEpoch()) || job.Filter.BeginTime.After(suri.getDataLagDate()) {
log.WithFields(log.Fields{
"jobId": job.Id,
"availableDataBeginDate": suri.GetDataEpoch(),
"availableDataEndDate": suri.getDataLagDate(),
"jobBeginDate": job.Filter.BeginTime,
"jobEndDate": job.Filter.EndTime,
}).Info("Skipping suri processor due to date range conflict")
err = errors.New("No data available for the requested dates")
} else {
job.FileExtension = "pcap"

query := suri.CreateQuery(job)

pcapFilepath := fmt.Sprintf("%s/%d.%s", suri.pcapOutputPath, job.Id, job.FileExtension)

log.WithField("jobId", job.Id).Info("Processing pcap export for job")

ctx, cancel := context.WithTimeout(context.Background(), time.Duration(suri.timeoutMs)*time.Millisecond)
defer cancel()
beginTime := job.Filter.BeginTime.Format(time.RFC3339)
endTime := job.Filter.EndTime.Format(time.RFC3339)

cmd := exec.CommandContext(ctx, suri.executablePath, pcapFilepath, beginTime, endTime, query)
var output []byte
output, err = cmd.CombinedOutput()
log.WithFields(log.Fields{
"executablePath": suri.executablePath,
"query": query,
"output": string(output),
"pcapFilepath": pcapFilepath,
"err": err,
}).Debug("Executed suriread")
if err == nil {
var file *os.File
file, err = os.Open(pcapFilepath)
if err == nil {
reader = file
}
}
}
return reader, err
}

func (suri *SuriQuery) CleanupJob(job *model.Job) {
pcapOutputFilepath := fmt.Sprintf("%s/%d.%s", suri.pcapOutputPath, job.Id, sanitize.Name(job.FileExtension))
os.Remove(pcapOutputFilepath)
}

func add(query string, added string) string {
if len(query) > 0 {
query = query + " and "
}
return query + added
}

func (suri *SuriQuery) CreateQuery(job *model.Job) string {

query := ""

if len(job.Filter.SrcIp) > 0 {
query = add(query, fmt.Sprintf("host %s", job.Filter.SrcIp))
}

if len(job.Filter.DstIp) > 0 {
query = add(query, fmt.Sprintf("host %s", job.Filter.DstIp))
}

if job.Filter.SrcPort > 0 {
query = add(query, fmt.Sprintf("port %d", job.Filter.SrcPort))
}

if job.Filter.DstPort > 0 {
query = add(query, fmt.Sprintf("port %d", job.Filter.DstPort))
}

return query
}

func (suri *SuriQuery) GetDataEpoch() time.Time {
now := time.Now()
refreshDuration := time.Duration(suri.epochRefreshMs) * time.Millisecond
if now.Sub(suri.epochRefreshTime) > refreshDuration {
suri.epochTimeTmp = now
err := filepath.Walk(suri.pcapInputPath, suri.updateEpochTimeTmp)
if err != nil {
log.WithError(err).WithField("pcapInputPath", suri.pcapInputPath)
} else {
suri.epochTime = suri.epochTimeTmp
}
suri.epochRefreshTime = now
}
return suri.epochTime
}

func (suri *SuriQuery) updateEpochTimeTmp(path string, info os.FileInfo, err error) error {
if err != nil {
log.WithError(err).WithField("path", path).Error("Unable to access path while updating epoch")
return err
}
if !info.IsDir() && info.Size() > 0 && info.ModTime().Before(suri.epochTimeTmp) {
suri.epochTimeTmp = info.ModTime()
}
return nil
}
66 changes: 66 additions & 0 deletions agent/modules/suriquery/suriquery_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2019 Jason Ertel (github.com/jertel).
// Copyright 2020-2023 Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
// or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
// https://securityonion.net/license; you may not use this file except in compliance with the
// Elastic License 2.0.

package suriquery

import (
"strconv"
"testing"
"time"

"github.com/security-onion-solutions/securityonion-soc/model"
"github.com/stretchr/testify/assert"
)

func TestInitSuriQuery(tester *testing.T) {
cfg := make(map[string]interface{})
sq := NewSuriQuery(nil)
err := sq.Init(cfg)
assert.Error(tester, err)
assert.Equal(tester, DEFAULT_EXECUTABLE_PATH, sq.executablePath)
assert.Equal(tester, DEFAULT_PCAP_OUTPUT_PATH, sq.pcapOutputPath)
assert.Equal(tester, DEFAULT_PCAP_INPUT_PATH, sq.pcapInputPath)
assert.Equal(tester, DEFAULT_TIMEOUT_MS, sq.timeoutMs)
assert.Equal(tester, DEFAULT_EPOCH_REFRESH_MS, sq.epochRefreshMs)
assert.Equal(tester, DEFAULT_DATA_LAG_MS, sq.dataLagMs)
}

func TestDataLag(tester *testing.T) {
cfg := make(map[string]interface{})
sq := NewSuriQuery(nil)
sq.Init(cfg)
lagDate := sq.getDataLagDate()
assert.False(tester, lagDate.After(time.Now()), "expected data lag datetime to be before current datetime")
}

func TestCreateQuery(tester *testing.T) {
sq := NewSuriQuery(nil)

job := model.NewJob()
expectedQuery := ""
query := sq.CreateQuery(job)
assert.Equal(tester, expectedQuery, query)

job.Filter.SrcIp = "1.2.3.4"
query = sq.CreateQuery(job)
expectedQuery = expectedQuery + "host " + job.Filter.SrcIp
assert.Equal(tester, expectedQuery, query)

job.Filter.DstIp = "1.2.1.2"
query = sq.CreateQuery(job)
expectedQuery = expectedQuery + " and host " + job.Filter.DstIp
assert.Equal(tester, expectedQuery, query)

job.Filter.SrcPort = 123
query = sq.CreateQuery(job)
expectedQuery = expectedQuery + " and port " + strconv.Itoa(job.Filter.SrcPort)
assert.Equal(tester, expectedQuery, query)

job.Filter.DstPort = 123
query = sq.CreateQuery(job)
expectedQuery = expectedQuery + " and port " + strconv.Itoa(job.Filter.DstPort)
assert.Equal(tester, expectedQuery, query)
}
Loading

0 comments on commit 529a15f

Please sign in to comment.