mirror of
https://github.com/activecm/rita
synced 2026-06-08 13:02:45 +00:00
b923c39e2d
* Update on config structure, functionality, and tests Co-Authored-By: Naomi Kramer <naomiagoddard@gmail.com> * Extend subnet type to read/write from db, update tests Co-Authored-By: Liza Tsibur <liza@activecountermeasures.com> * updated read file config test and subnet tests * fixed config and util tests, updated subnet related functions Co-Authored-By: Naomi Kramer <naomiagoddard@gmail.com> * Remove error return from GetDefaultConfig Co-Authored-By: Liza Tsibur <liza@activecountermeasures.com> * added json tags to database struct * Updated beacon weights validation for config * updates to score thresholds validation tags * changes to config subnet validation and testing * Update subnet.go * Write missing host entries to http to populate http_proto * Updating some fields to uint64 * WIP update some field types * Update zeek count types and fix tests * Add clickhouse credentials * Misc fixes * Update pointer * Add ability to mark datasets as sample datasets * fix column name * Fix datasets exiting import if hour is empty * Fix zeek count parsing from TSV files * Remove storing dns conns in arrays, Fix historical first seen dns lag * Remove unused columns * Update config.hjson * Update config.hjson * updated impact category score functions to use float64 Co-Authored-By: Naomi Kramer <naomiagoddard@gmail.com> * Update subnet.go * Store import version in imports table * Fix duplicated SNI/IP long connections * Update subnet_test.go * Cleanup output * Rolling files updates (#39) * Limit number of days to import for rolling datasets * Fix breaking imports when import was interrupted * Remove debug output --------- Co-authored-by: Naomi Kramer <naomi@activecountermeasures.com> * Omit parts of env from output * Set max for threat intel datasize * Remove SELinux neutering for QA * Add network size column * Fix http_proto for missing host, update tests for missing host fixes * Add online feeds to default config * Update sshprep (#45) * Update sshprep Co-Authored-By: William Stearns <3538265+william-stearns@users.noreply.github.com> * Update sshprep Add Bradley's suggestion of using head -1 to limit to a single address. --------- Co-authored-by: Naomi Kramer <naomi@activecountermeasures.com> Co-authored-by: William Stearns <3538265+william-stearns@users.noreply.github.com> * Installer Behavior Tweaks (#41) * Add --yes flag to add-apt-repository command * Add missing sudo flags, make sure we're using the SUDO variable instead * Add ability to perform zone transfers (#48) * Store zone transfer records Co-Authored-By: moth <25512187+0x6d6f7468@users.noreply.github.com> * Update config * Add tests * Tests, connectivity test * Update tests --------- Co-authored-by: moth <25512187+0x6d6f7468@users.noreply.github.com> * Support RedHat/RHEL as a valid target (#47) * Update sshprep Co-Authored-By: William Stearns <3538265+william-stearns@users.noreply.github.com> * Supporrt RedHat/RHEL as a valid target --------- Co-authored-by: Naomi Kramer <naomi@activecountermeasures.com> Co-authored-by: William Stearns <3538265+william-stearns@users.noreply.github.com> Co-authored-by: moth <moth@blackhillsinfosec.com> * Fix tests (#49) * Fix tests * Update WalkFiles to use UTC * fixed issue with rolling datasets over 24hours old not getting historical first seen timestamp set (#52) * Change values from float32 to float64 (#50) * Switch float32 to float64 * Update threat category calculation to match CalculateBucketedScore (#51) --------- Co-authored-by: Liza Tsibur <liza@activecountermeasures.com> * Bump max query execution time default value * Use string instead of error for ZoneTransferConnectivityErrors struct fields (#61) * Upgrade Golang to version 1.24 (#59) (#60) * Replace get_url with shell and curl (#58) * Update sshprep Co-Authored-By: William Stearns <3538265+william-stearns@users.noreply.github.com> * Replace get_url with shell and curl * Use get_url by default, fall back to curl if it fails --------- Co-authored-by: Naomi Kramer <naomi@activecountermeasures.com> Co-authored-by: William Stearns <3538265+william-stearns@users.noreply.github.com> Co-authored-by: moth <moth@blackhillsinfosec.com> * add automated log transfer, AC-Hunter issue 135 (#62) * Update sshprep Co-Authored-By: William Stearns <3538265+william-stearns@users.noreply.github.com> * add automated log transfer, PR135 * cron requires non-executable permission * Specify suggested YAML plugin and config in VSCode workspace * Linting and light cleanup * Update generate_installer.sh Download zeek_log_transport.sh to send to the sensor. * Create cron file if remote zeek installation * Only run zeek log import steps for remote sensor installations --------- Co-authored-by: Naomi Kramer <naomi@activecountermeasures.com> Co-authored-by: William Stearns <3538265+william-stearns@users.noreply.github.com> Co-authored-by: moth <moth@blackhillsinfosec.com> * Temporarily disable RITA/Zeek log transport until installer is modular (#66) * Uniform -y flag usage for repo management/package installation; Uniform SUDO variable usage (#68) * Resolve Installer Side Effects and Formalize RHEL Support (#73) * Add missing necessary wildcards for RHEL versions * Remove Ansible task replacing python3-requests to avoid RHEL distro installation side effects * Update supported distros in README * Update scoring defaults * Resolve Ansible Reboot Errors (#75) * Clean up conditionals; Fix reboot step for Ubuntu * Suppress erroneous error output on RPM systems, ignore errors on reboot necessity checks * Ignore missing host rows for openhttp (#76) * Fix integration tests due to prevalence (#77) --------- Co-authored-by: Liza Tsibur <liza@activecountermeasures.com> Co-authored-by: moth <moth@blackhillsinfosec.com> Co-authored-by: William Stearns <william.l.stearns@gmail.com> Co-authored-by: William Stearns <3538265+william-stearns@users.noreply.github.com> Co-authored-by: moth <25512187+0x6d6f7468@users.noreply.github.com>
328 lines
12 KiB
Go
328 lines
12 KiB
Go
package integration_test
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/activecm/rita/v5/analysis"
|
|
"github.com/activecm/rita/v5/cmd"
|
|
"github.com/activecm/rita/v5/config"
|
|
"github.com/activecm/rita/v5/database"
|
|
"github.com/activecm/rita/v5/util"
|
|
|
|
"github.com/ClickHouse/clickhouse-go/v2"
|
|
"github.com/spf13/afero"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// **** Beacon Integration Tests ****
|
|
// These tests verify the main beacon score and the four sub-scores for a handful of known beacons.
|
|
// They validate that the output of the beacon scoring functions are properly working together
|
|
// to produce the desired score.
|
|
func (it *ValidDatasetTestSuite) TestBeacons() { // used by valid dataset test suite
|
|
|
|
it.Run("Beacon Type Counts", func() {
|
|
t := it.T()
|
|
// check the total count for each beacon type
|
|
type countRes struct {
|
|
BeaconType string `ch:"beacon_type"`
|
|
Count uint64 `ch:"count"`
|
|
}
|
|
|
|
cases := []countRes{
|
|
{BeaconType: "sni", Count: 3383},
|
|
{BeaconType: "ip", Count: 1252},
|
|
}
|
|
|
|
var res []countRes
|
|
// check total beacon count by beacon type
|
|
err := it.db.Conn.Select(it.db.GetContext(), &res, `
|
|
SELECT beacon_type, count() as count FROM threat_mixtape
|
|
WHERE beacon_score > 0
|
|
GROUP BY beacon_type
|
|
ORDER BY count DESC
|
|
`)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, res, cases)
|
|
})
|
|
|
|
// verify known beacon scores
|
|
it.Run("Verify Known Scores", func() {
|
|
t := it.T()
|
|
|
|
// these values can be validated by using ./get_beacon_info.py
|
|
beaconCases := []struct {
|
|
name string
|
|
mixtapeResult analysis.ThreatMixtape
|
|
}{
|
|
{
|
|
name: "10.55.100.111 -> 165.227.216.194",
|
|
mixtapeResult: analysis.ThreatMixtape{
|
|
AnalysisResult: analysis.AnalysisResult{
|
|
Src: net.ParseIP("10.55.100.111"),
|
|
SrcNUID: util.UnknownPrivateNetworkUUID,
|
|
Dst: net.ParseIP("165.227.216.194"),
|
|
DstNUID: util.PublicNetworkUUID,
|
|
Count: 20054,
|
|
BeaconType: "ip",
|
|
}, Beacon: analysis.Beacon{
|
|
Score: 1,
|
|
TimestampScore: 1,
|
|
DataSizeScore: 1,
|
|
DurationScore: 1,
|
|
HistogramScore: 1,
|
|
}},
|
|
},
|
|
{
|
|
name: "10.55.100.111 -> tile-service.weather.microsoft.com",
|
|
mixtapeResult: analysis.ThreatMixtape{
|
|
AnalysisResult: analysis.AnalysisResult{
|
|
Src: net.ParseIP("10.55.100.111"),
|
|
SrcNUID: util.UnknownPrivateNetworkUUID,
|
|
FQDN: "tile-service.weather.microsoft.com",
|
|
DstNUID: util.PublicNetworkUUID,
|
|
Count: 48,
|
|
}, Beacon: analysis.Beacon{
|
|
BeaconType: "sni",
|
|
Score: 1,
|
|
TimestampScore: 1,
|
|
DataSizeScore: 0.998,
|
|
DurationScore: 1,
|
|
HistogramScore: 1,
|
|
}},
|
|
},
|
|
{
|
|
name: "10.55.100.109 -> www.alexa.com",
|
|
mixtapeResult: analysis.ThreatMixtape{
|
|
AnalysisResult: analysis.AnalysisResult{
|
|
Src: net.ParseIP("10.55.100.109"),
|
|
SrcNUID: util.UnknownPrivateNetworkUUID,
|
|
FQDN: "www.alexa.com",
|
|
Count: 607,
|
|
}, Beacon: analysis.Beacon{
|
|
BeaconType: "sni",
|
|
Score: 0.896,
|
|
TimestampScore: 0.999,
|
|
DataSizeScore: 0.47,
|
|
DurationScore: 1,
|
|
HistogramScore: 0.937,
|
|
}},
|
|
},
|
|
{
|
|
name: "10.55.100.103 -> www.bankofamerica.com",
|
|
mixtapeResult: analysis.ThreatMixtape{
|
|
AnalysisResult: analysis.AnalysisResult{
|
|
Src: net.ParseIP("10.55.100.103"),
|
|
SrcNUID: util.UnknownPrivateNetworkUUID,
|
|
FQDN: "www.bankofamerica.com",
|
|
Count: 24,
|
|
}, Beacon: analysis.Beacon{
|
|
BeaconType: "sni",
|
|
Score: 0.465,
|
|
TimestampScore: 0.407,
|
|
DataSizeScore: 0.632,
|
|
DurationScore: 0.823,
|
|
HistogramScore: 0,
|
|
}},
|
|
},
|
|
}
|
|
|
|
for i := range beaconCases {
|
|
test := beaconCases[i]
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var hash util.FixedString
|
|
var err error
|
|
|
|
// set beacon value
|
|
beacon := test.mixtapeResult
|
|
|
|
// get the hash
|
|
if beacon.FQDN != "" {
|
|
hash, err = util.NewFixedStringHash(beacon.Src.String(), beacon.SrcNUID.String(), beacon.FQDN)
|
|
require.NoError(t, err)
|
|
} else {
|
|
hash, err = util.NewFixedStringHash(beacon.Src.String(), beacon.SrcNUID.String(), beacon.Dst.String(), beacon.DstNUID.String())
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// create a context with the hash parameter
|
|
ctx := clickhouse.Context(it.db.GetContext(), clickhouse.WithParameters(clickhouse.Parameters{
|
|
"hash": hash.Hex(),
|
|
}))
|
|
var res analysis.ThreatMixtape
|
|
err = it.db.Conn.QueryRow(ctx, `
|
|
SELECT src, src_nuid, dst, dst_nuid, fqdn, sum(count) as count, sum(beacon_score) as beacon_score, sum(ts_score) as ts_score, sum(ds_score) as ds_score, sum(dur_score) as dur_score, sum(hist_score) as hist_score FROM threat_mixtape
|
|
WHERE hash = unhex({hash:String})
|
|
GROUP BY src, src_nuid, dst, dst_nuid, fqdn
|
|
`).ScanStruct(&res)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, beacon.Count, res.Count, "beacon connection count must match %s -> %s -> %s", beacon.Src.String(), beacon.Dst.String(), beacon.FQDN)
|
|
require.InDelta(t, beacon.Score, res.Score, 0.05, "beacon score must match %s -> %s -> %s", beacon.Src.String(), beacon.Dst.String(), beacon.FQDN)
|
|
require.InDelta(t, beacon.TimestampScore, res.TimestampScore, 0.05, "beacon timestamp score must match %s -> %s -> %s", beacon.Src.String(), beacon.Dst.String(), beacon.FQDN)
|
|
require.InDelta(t, beacon.DataSizeScore, res.DataSizeScore, 0.05, "beacon data size score must match %s -> %s -> %s", beacon.Src.String(), beacon.Dst.String(), beacon.FQDN)
|
|
require.InDelta(t, beacon.DurationScore, res.DurationScore, 0.05, "beacon duration score must match %s -> %s -> %s", beacon.Src.String(), beacon.Dst.String(), beacon.FQDN)
|
|
require.InDelta(t, beacon.HistogramScore, res.HistogramScore, 0.05, "beacon histogram score must match %s -> %s -> %s", beacon.Src.String(), beacon.Dst.String(), beacon.FQDN)
|
|
})
|
|
}
|
|
})
|
|
|
|
// verify that the strobe in this dataset is not reported as a beacon in the threat_mixtape table
|
|
it.Run("Strobe Not Reported As Beacon", func() {
|
|
t := it.T()
|
|
var count uint64
|
|
err := it.db.Conn.QueryRow(it.db.GetContext(), `
|
|
SELECT sum(count) FROM threat_mixtape
|
|
WHERE src = '192.168.88.2' AND dst = '165.227.88.15'
|
|
AND beacon_score > 0
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 0, count, "known strobe 192.168.88.2 -> 165.227.88.15 should not exist in beacons")
|
|
})
|
|
|
|
// verify that there are no beacons with a connection count lower than the connection threshold
|
|
it.Run("Beacons Follow Connection Threshold", func() {
|
|
t := it.T()
|
|
var count uint64
|
|
|
|
ctx := clickhouse.Context(it.db.GetContext(), clickhouse.WithParameters(clickhouse.Parameters{
|
|
"threshold": strconv.Itoa(int(it.cfg.Scoring.Beacon.UniqueConnectionThreshold)),
|
|
}))
|
|
err := it.db.Conn.QueryRow(ctx, `
|
|
SELECT count() FROM threat_mixtape
|
|
WHERE beacon_score > 0 AND ts_unique < {threshold:Int}
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 0, count, "there should be no beacons with a unique timestamp count that is less than or equal to the connection threshold")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
// Verify proxy beacons
|
|
func TestProxyBeacons(t *testing.T) {
|
|
// set up file system interface
|
|
afs := afero.NewOsFs()
|
|
|
|
cfg, err := config.ReadFileConfig(afs, ConfigPath)
|
|
require.NoError(t, err, "loading config should not return an error")
|
|
|
|
cfg.Env.DBConnection = dockerInfo.clickhouseConnection
|
|
|
|
// import data
|
|
_, err = cmd.RunImportCmd(time.Now(), cfg, afs, "../test_data/proxy", "test_proxy_beacons", false, false)
|
|
require.NoError(t, err)
|
|
|
|
// connect to database
|
|
db, err := database.ConnectToDB(context.Background(), "test_proxy_beacons", cfg, nil)
|
|
require.NoError(t, err)
|
|
|
|
// check the total count for each beacon type
|
|
t.Run("Proxy Beacon Count", func(t *testing.T) {
|
|
var countRes uint64
|
|
|
|
// check total beacon count by beacon type
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count(DISTINCT hash) as count FROM threat_mixtape
|
|
HAVING sum(proxy_count) > 0 AND sum(beacon_score) > 0
|
|
`).Scan(&countRes)
|
|
require.NoError(t, err)
|
|
|
|
require.EqualValues(t, 2, countRes, "there should be two beacons with proxy connections")
|
|
})
|
|
|
|
// verify known beacon scores
|
|
t.Run("Verify Known Scores", func(t *testing.T) {
|
|
|
|
// These values can be validated by using ./get_beacon_info.py
|
|
beaconCases := []struct {
|
|
name string
|
|
mixtapeResult analysis.ThreatMixtape
|
|
}{
|
|
{
|
|
name: "10.136.0.18 -> www.honestimnotevil.com",
|
|
mixtapeResult: analysis.ThreatMixtape{
|
|
AnalysisResult: analysis.AnalysisResult{
|
|
Src: net.ParseIP("10.136.0.18"),
|
|
SrcNUID: util.UnknownPrivateNetworkUUID,
|
|
FQDN: "www.honestimnotevil.com",
|
|
Count: 357,
|
|
ProxyCount: 357,
|
|
TotalBytes: 963595,
|
|
TotalDuration: 12.454628000000003,
|
|
PortProtoService: []string{"3128:tcp:http,ssl"},
|
|
}, Beacon: analysis.Beacon{
|
|
BeaconType: "sni",
|
|
Score: 0.979,
|
|
TimestampScore: 0.921,
|
|
DataSizeScore: 0.995,
|
|
DurationScore: 1,
|
|
HistogramScore: 1,
|
|
}},
|
|
},
|
|
{
|
|
name: "10.136.0.18 -> www.google.com",
|
|
mixtapeResult: analysis.ThreatMixtape{
|
|
AnalysisResult: analysis.AnalysisResult{
|
|
Src: net.ParseIP("10.136.0.18"),
|
|
SrcNUID: util.UnknownPrivateNetworkUUID,
|
|
FQDN: "www.google.com",
|
|
Count: 6,
|
|
ProxyCount: 6,
|
|
TotalBytes: 139385,
|
|
TotalDuration: 0.725752,
|
|
PortProtoService: []string{"3128:tcp:http,ssl"},
|
|
}, Beacon: analysis.Beacon{
|
|
BeaconType: "sni",
|
|
Score: 0.586,
|
|
TimestampScore: 0.48,
|
|
DataSizeScore: 0.865,
|
|
DurationScore: 1,
|
|
HistogramScore: 0,
|
|
}},
|
|
},
|
|
}
|
|
|
|
for _, test := range beaconCases {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
// set beacon value
|
|
beacon := test.mixtapeResult
|
|
|
|
// get the hash
|
|
hash, err := util.NewFixedStringHash(beacon.Src.String(), beacon.SrcNUID.String(), beacon.FQDN)
|
|
require.NoError(t, err)
|
|
|
|
// create a context with the hash parameter
|
|
ctx := clickhouse.Context(db.GetContext(), clickhouse.WithParameters(clickhouse.Parameters{
|
|
"hash": hash.Hex(),
|
|
}))
|
|
|
|
var res analysis.ThreatMixtape
|
|
err = db.Conn.QueryRow(ctx, `
|
|
SELECT src, src_nuid, dst, dst_nuid, fqdn, count, proxy_count, beacon_score, ts_score, ds_score, dur_score, hist_score FROM threat_mixtape
|
|
WHERE hash = unhex({hash:String}) AND count > 0
|
|
`).ScanStruct(&res)
|
|
require.NoError(t, err)
|
|
|
|
// verfiy basic proxy beacon requirements
|
|
require.Greater(t, res.ProxyCount, uint64(0), "proxy count must be greater than 0")
|
|
require.GreaterOrEqual(t, int64(res.Count), cfg.Scoring.Beacon.UniqueConnectionThreshold, "connection count must be greater than or equal to the connection threshold")
|
|
|
|
// verify scores
|
|
require.EqualValues(t, beacon.Count, res.AnalysisResult.Count, "connection count must match expected value")
|
|
require.EqualValues(t, beacon.ProxyCount, res.ProxyCount, "proxy count must match expected value")
|
|
require.InDelta(t, beacon.Score, res.Score, 0.05, "beacon score must match expected value")
|
|
require.InDelta(t, beacon.TimestampScore, res.TimestampScore, 0.05, "timestamp score must match expected value")
|
|
require.InDelta(t, beacon.DataSizeScore, res.DataSizeScore, 0.05, "data size score must match expected value")
|
|
require.InDelta(t, beacon.DurationScore, res.DurationScore, 0.05, "duration score must match expected value")
|
|
require.InDelta(t, beacon.HistogramScore, res.HistogramScore, 0.05, "histogram score must match expected value")
|
|
|
|
})
|
|
}
|
|
|
|
})
|
|
|
|
}
|