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>
705 lines
25 KiB
Go
705 lines
25 KiB
Go
package integration_test
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"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/progressbar"
|
|
"github.com/activecm/rita/v5/util"
|
|
|
|
"github.com/ClickHouse/clickhouse-go/v2"
|
|
"github.com/charmbracelet/bubbles/progress"
|
|
"github.com/spf13/afero"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/suite"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
type FilterTestSuite struct {
|
|
suite.Suite
|
|
cfg *config.Config
|
|
}
|
|
|
|
func TestFilters(t *testing.T) {
|
|
suite.Run(t, new(FilterTestSuite))
|
|
}
|
|
|
|
// Reset config after each test since these tests load the config from a file
|
|
func (it *FilterTestSuite) SetupSuite() {
|
|
t := it.T()
|
|
afs := afero.NewOsFs()
|
|
cfg, err := config.ReadFileConfig(afs, ConfigPath)
|
|
require.NoError(t, err)
|
|
it.cfg = cfg
|
|
}
|
|
|
|
func (it *FilterTestSuite) SetupTest() {
|
|
t := it.T()
|
|
err := it.cfg.Reset()
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func (it *FilterTestSuite) TearDownSuite() {
|
|
t := it.T()
|
|
err := it.cfg.Reset()
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func (it *FilterTestSuite) TestNeverIncludeSubnets() {
|
|
t := it.T()
|
|
// set up file system interface
|
|
afs := afero.NewMemMapFs()
|
|
afs2 := afero.NewOsFs()
|
|
err := afero.WriteFile(afs, "testsuite_config.hjson", []byte(`
|
|
{
|
|
filtering: {
|
|
filter_external_to_internal: true,
|
|
internal_subnets: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fd00::/8"],
|
|
never_included_subnets: ["10.55.100.0/24"],
|
|
},
|
|
threat_intel: {
|
|
online_feeds: ["https://feodotracker.abuse.ch/downloads/ipblocklist.txt"],
|
|
custom_feeds_directory: "./deployment/threat_intel_feeds"
|
|
},
|
|
http_extensions_file_path: "../deployment/http_extensions_list.csv"
|
|
}
|
|
`), 0755)
|
|
require.NoError(t, err)
|
|
|
|
cfg, err := config.ReadFileConfig(afs, "testsuite_config.hjson")
|
|
require.NoError(t, err)
|
|
cfg.Env.DBConnection = dockerInfo.clickhouseConnection
|
|
it.cfg = cfg
|
|
require.Contains(t, cfg.Filtering.NeverIncludedSubnets, util.NewSubnet(&net.IPNet{IP: net.IP{10, 55, 100, 0}, Mask: net.IPMask{255, 255, 255, 0}}))
|
|
|
|
// // import data
|
|
_, err = cmd.RunImportCmd(time.Now(), cfg, afs2, "../test_data/valid_tsv", "never_include_subnet", false, true)
|
|
require.NoError(t, err)
|
|
|
|
// connect to database
|
|
db, err := database.ConnectToDB(context.Background(), "never_include_subnet", cfg, nil)
|
|
require.NoError(t, err)
|
|
|
|
var count uint64
|
|
|
|
// verify that not all connections in 10.0.0.0/8 were filtered
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count(DISTINCT hash) FROM conn
|
|
WHERE isIPAddressInRange(IPv6NumToString(src), '::ffff:10.0.0.0/104') OR isIPAddressInRange(IPv6NumToString(dst), '::ffff:10.0.0.0/104')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 14452-12584, count, "conn table should contain 1868 entries in 10.0.0.0/8, got: %d", count)
|
|
|
|
// 5531 in 10.55.100.0/24
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count(DISTINCT hash) FROM ssl
|
|
WHERE isIPAddressInRange(IPv6NumToString(src), '::ffff:10.0.0.0/104') OR isIPAddressInRange(IPv6NumToString(dst), '::ffff:10.0.0.0/104')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 5615-(5531), count, "ssl table should contain 63 entries in 10.0.0.0/8, got: %d", count)
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count(DISTINCT hash) FROM http
|
|
WHERE isIPAddressInRange(IPv6NumToString(src), '::ffff:10.0.0.0/104') OR isIPAddressInRange(IPv6NumToString(dst), '::ffff:10.0.0.0/104')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 2004-(1982), count, "http table should contain 22 entries in 10.0.0.0/8, got: %d", count)
|
|
|
|
// verify that all connections in 10.55.100.0/24 were filtered
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM conn
|
|
WHERE isIPAddressInRange(IPv6NumToString(src), '::ffff:10.55.100.0/120') OR isIPAddressInRange(IPv6NumToString(dst), '::ffff:10.55.100.0/120')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 0, count, "conn table shouldn't contain any entries in 10.55.100.0/24")
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM http
|
|
WHERE isIPAddressInRange(IPv6NumToString(src), '::ffff:10.55.100.0/120') OR isIPAddressInRange(IPv6NumToString(dst), '::ffff:10.55.100.0/120')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 0, count, "http table shouldn't contain any entries in 10.55.100.0/24")
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM ssl
|
|
WHERE isIPAddressInRange(IPv6NumToString(src), '::ffff:10.55.100.0/120') OR isIPAddressInRange(IPv6NumToString(dst), '::ffff:10.55.100.0/120')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 0, count, "ssl table shouldn't contain any entries in 10.55.100.0/24")
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM dns
|
|
WHERE isIPAddressInRange(IPv6NumToString(src), '::ffff:10.55.100.0/120') OR isIPAddressInRange(IPv6NumToString(dst), '::ffff:10.55.100.0/120')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 0, count, "dns table shouldn't contain any entries in 10.55.100.0/24")
|
|
|
|
}
|
|
|
|
func (it *FilterTestSuite) TestNeverIncludeDomains() {
|
|
t := it.T()
|
|
// set up file system interface
|
|
afs := afero.NewMemMapFs()
|
|
afs2 := afero.NewOsFs()
|
|
err := afero.WriteFile(afs, "testsuite_config2.hjson", []byte(`
|
|
{
|
|
filtering: {
|
|
filter_external_to_internal: true,
|
|
internal_subnets: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fd00::/8"],
|
|
never_included_domains: ["*.microsoft.com", "businessinsider.com"]
|
|
},
|
|
threat_intel: {
|
|
online_feeds: ["https://feodotracker.abuse.ch/downloads/ipblocklist.txt"],
|
|
custom_feeds_directory: "./deployment/threat_intel_feeds"
|
|
},
|
|
http_extensions_file_path: "../deployment/http_extensions_list.csv"
|
|
}
|
|
`), 0755)
|
|
require.NoError(t, err)
|
|
|
|
cfg, err := config.ReadFileConfig(afs, "testsuite_config2.hjson")
|
|
require.NoError(t, err)
|
|
cfg.Env.DBConnection = dockerInfo.clickhouseConnection
|
|
it.cfg = cfg
|
|
|
|
require.Contains(t, cfg.Filtering.NeverIncludedDomains, "*.microsoft.com")
|
|
require.Contains(t, cfg.Filtering.NeverIncludedDomains, "businessinsider.com")
|
|
|
|
// // import data
|
|
_, err = cmd.RunImportCmd(time.Now(), cfg, afs2, "../test_data/valid_tsv", "never_include_domain", false, true)
|
|
require.NoError(t, err)
|
|
|
|
// connect to database
|
|
db, err := database.ConnectToDB(context.Background(), "never_include_domain", cfg, nil)
|
|
require.NoError(t, err)
|
|
|
|
var count uint64
|
|
|
|
// verify that all connections w/ fqdns ending in .microsoft.com are filtered
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM http
|
|
WHERE cutToFirstSignificantSubdomain(host) = 'microsoft.com'
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 0, count, "http table shouldn't contain any entries with host ending in .microsoft.com")
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM ssl
|
|
WHERE cutToFirstSignificantSubdomain(server_name) = 'microsoft.com'
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 0, count, "ssl table shouldn't contain any entries with server_name ending in .microsoft.com")
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM dns
|
|
WHERE cutToFirstSignificantSubdomain(query) = 'microsoft.com'
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 0, count, "dns table shouldn't contain any entries with query ending in .microsoft.com")
|
|
|
|
// verify that not all domains in .businessinsider.com are filtered
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM dns
|
|
WHERE cutToFirstSignificantSubdomain(query) = 'businessinsider.com'
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.Greater(t, count, uint64(0), "dns table should contain at least one entry with a query ending in .businessinsider.com")
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM http
|
|
WHERE cutToFirstSignificantSubdomain(host) = 'businessinsider.com'
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.Greater(t, count, uint64(0), "http table should contain at least one entry with a host ending in .businessinsider.com")
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM ssl
|
|
WHERE cutToFirstSignificantSubdomain(server_name) = 'businessinsider.com'
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.Greater(t, count, uint64(0), "ssl table should contain at least one entry with a server_name ending in .businessinsider.com")
|
|
|
|
// verify that businessinsider.com is filtered
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM dns
|
|
WHERE query = 'businessinsider.com'
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 0, count, "dns table shouldn't contain any entries with a query of .businessinsider.com")
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM http
|
|
WHERE host = 'businessinsider.com'
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 0, count, "http table shouldn't contain any entries with a query of .businessinsider.com")
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM ssl
|
|
WHERE server_name = 'businessinsider.com'
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 0, count, "ssl table shouldn't contain any entries with a query of .businessinsider.com")
|
|
|
|
}
|
|
|
|
func (it *FilterTestSuite) TestAlwaysIncludeSubnets() {
|
|
t := it.T()
|
|
// set up file system interface
|
|
afs := afero.NewMemMapFs()
|
|
afs2 := afero.NewOsFs()
|
|
err := afero.WriteFile(afs, "testsuite_config3.hjson", []byte(`
|
|
{
|
|
filtering: {
|
|
filter_external_to_internal: true,
|
|
internal_subnets: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fd00::/8"],
|
|
never_included_subnets: ["10.0.0.0/8"],
|
|
always_included_subnets: ["10.55.100.0/24"]
|
|
},
|
|
threat_intel: {
|
|
online_feeds: ["https://feodotracker.abuse.ch/downloads/ipblocklist.txt"],
|
|
custom_feeds_directory: "./deployment/threat_intel_feeds"
|
|
},
|
|
http_extensions_file_path: "../deployment/http_extensions_list.csv"
|
|
}
|
|
`), 0755)
|
|
require.NoError(t, err)
|
|
|
|
cfg, err := config.ReadFileConfig(afs, "testsuite_config3.hjson")
|
|
require.NoError(t, err)
|
|
cfg.Env.DBConnection = dockerInfo.clickhouseConnection
|
|
it.cfg = cfg
|
|
|
|
require.Contains(t, cfg.Filtering.NeverIncludedSubnets, util.NewSubnet(&net.IPNet{IP: net.IP{10, 0, 0, 0}, Mask: net.IPMask{255, 0, 0, 0}}), "never included subnets should contain 10.0.0.0/8")
|
|
require.Contains(t, cfg.Filtering.AlwaysIncludedSubnets, util.NewSubnet(&net.IPNet{IP: net.IP{10, 55, 100, 0}, Mask: net.IPMask{255, 255, 255, 0}}), "always included subnets should contain 10.55.100.0/24")
|
|
|
|
// // import data
|
|
_, err = cmd.RunImportCmd(time.Now(), cfg, afs2, "../test_data/valid_tsv", "always_include_subnet", false, true)
|
|
require.NoError(t, err)
|
|
|
|
// connect to database
|
|
db, err := database.ConnectToDB(context.Background(), "always_include_subnet", cfg, nil)
|
|
require.NoError(t, err)
|
|
|
|
var count uint64
|
|
|
|
// verify that not all connections in 10.0.0.0/8 were filtered
|
|
conn := 12591
|
|
http := 1982 + 176 // regular http + http missing host
|
|
ssl := 5531
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count(DISTINCT hash) FROM conn
|
|
WHERE isIPAddressInRange(IPv6NumToString(src), '::ffff:10.0.0.0/104') OR isIPAddressInRange(IPv6NumToString(dst), '::ffff:10.0.0.0/104')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, conn, count, "conn table should contain %d entries in 10.0.0.0/8, got: %d", conn, count)
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count(DISTINCT hash) FROM ssl
|
|
WHERE isIPAddressInRange(IPv6NumToString(src), '::ffff:10.0.0.0/104') OR isIPAddressInRange(IPv6NumToString(dst), '::ffff:10.0.0.0/104')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, ssl, count, "ssl table should contain %d entries in 10.0.0.0/8, got: %d", ssl, count)
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count(DISTINCT hash) FROM http
|
|
WHERE isIPAddressInRange(IPv6NumToString(src), '::ffff:10.0.0.0/104') OR isIPAddressInRange(IPv6NumToString(dst), '::ffff:10.0.0.0/104')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, http, count, "http table should contain %d entries in 10.0.0.0/8, got: %d", http, count)
|
|
|
|
// verify that all connections in 10.55.100.0/24 were filtered
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count(DISTINCT hash) FROM conn
|
|
WHERE isIPAddressInRange(IPv6NumToString(src), '::ffff:10.55.100.0/120') OR isIPAddressInRange(IPv6NumToString(dst), '::ffff:10.55.100.0/120')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, conn, count, "conn table shouldn contain %d any entries in 10.55.100.0/24, got: %d", conn, count)
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count(DISTINCT hash) FROM http
|
|
WHERE isIPAddressInRange(IPv6NumToString(src), '::ffff:10.55.100.0/120') OR isIPAddressInRange(IPv6NumToString(dst), '::ffff:10.55.100.0/120')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, http, count, "http table shouldn contain %d entries in 10.55.100.0/24, got: %d", http, count)
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count(DISTINCT hash) FROM ssl
|
|
WHERE isIPAddressInRange(IPv6NumToString(src), '::ffff:10.55.100.0/120') OR isIPAddressInRange(IPv6NumToString(dst), '::ffff:10.55.100.0/120')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, ssl, count, "ssl table should contain %d entries in 10.55.100.0/24", ssl, count)
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count(DISTINCT hash) FROM dns
|
|
WHERE isIPAddressInRange(IPv6NumToString(src), '::ffff:10.55.100.0/120') OR isIPAddressInRange(IPv6NumToString(dst), '::ffff:10.55.100.0/120')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 1, count, "dns table should contain 1 entry in 10.55.100.0/24")
|
|
|
|
}
|
|
|
|
func (it *FilterTestSuite) TestAlwaysIncludeDomains() {
|
|
t := it.T()
|
|
// set up file system interface
|
|
afs := afero.NewMemMapFs()
|
|
afs2 := afero.NewOsFs()
|
|
err := afero.WriteFile(afs, "testsuite_config4.hjson", []byte(`
|
|
{
|
|
filtering: {
|
|
filter_external_to_internal: true,
|
|
internal_subnets: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fd00::/8"],
|
|
never_included_domains: ["*.microsoft.com", "businessinsider.com"],
|
|
always_included_domains: ["*.mp.microsoft.com", "analytics.businessinsider.com"]
|
|
},
|
|
threat_intel: {
|
|
online_feeds: ["https://feodotracker.abuse.ch/downloads/ipblocklist.txt"],
|
|
custom_feeds_directory: "./deployment/threat_intel_feeds"
|
|
},
|
|
http_extensions_file_path: "../deployment/http_extensions_list.csv"
|
|
}
|
|
`), 0755)
|
|
require.NoError(t, err)
|
|
|
|
cfg, err := config.ReadFileConfig(afs, "testsuite_config4.hjson")
|
|
require.NoError(t, err)
|
|
cfg.Env.DBConnection = dockerInfo.clickhouseConnection
|
|
it.cfg = cfg
|
|
|
|
require.Contains(t, cfg.Filtering.NeverIncludedDomains, "*.microsoft.com")
|
|
require.Contains(t, cfg.Filtering.NeverIncludedDomains, "businessinsider.com")
|
|
|
|
require.Contains(t, cfg.Filtering.AlwaysIncludedDomains, "*.mp.microsoft.com")
|
|
require.Contains(t, cfg.Filtering.AlwaysIncludedDomains, "analytics.businessinsider.com")
|
|
|
|
// // import data
|
|
_, err = cmd.RunImportCmd(time.Now(), cfg, afs2, "../test_data/valid_tsv", "always_include_domain", false, true)
|
|
require.NoError(t, err)
|
|
|
|
// connect to database
|
|
db, err := database.ConnectToDB(context.Background(), "always_include_domain", cfg, nil)
|
|
require.NoError(t, err)
|
|
|
|
var count uint64
|
|
|
|
// verify that all connections w/ fqdns ending in .microsoft.com (but not *.mp.microsoft.com) are filtered
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM http
|
|
WHERE cutToFirstSignificantSubdomain(host) = 'microsoft.com' AND NOT endsWith(host, '.mp.microsoft.com')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 0, count, "http table shouldn't contain any entries with host ending in .microsoft.com (but not *.mp.microsoft.com)")
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM ssl
|
|
WHERE cutToFirstSignificantSubdomain(server_name) = 'microsoft.com' AND NOT endsWith(server_name, '.mp.microsoft.com')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 0, count, "ssl table shouldn't contain any entries with server_name ending in .microsoft.com (but not *.mp.microsoft.com)")
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM dns
|
|
WHERE cutToFirstSignificantSubdomain(query) = 'microsoft.com' AND NOT endsWith(query, '.mp.microsoft.com')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 0, count, "dns table shouldn't contain any entries with query ending in .microsoft.com (but not *.mp.microsoft.com)")
|
|
|
|
// verify that connections w/ fqdns ending in *.mp.microsoft.com are not filtered
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM http
|
|
WHERE endsWith(host, '.mp.microsoft.com')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.Greater(t, count, uint64(0), "http table should contain at least 1 entry with host ending in .mp.microsoft.com")
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM ssl
|
|
WHERE endsWith(server_name, '.mp.microsoft.com')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.Greater(t, count, uint64(0), "ssl table should contain at least 1 entry with server name ending in .microsoft.com")
|
|
|
|
err = db.Conn.QueryRow(db.GetContext(), `
|
|
SELECT count() FROM dns
|
|
WHERE endsWith(query, '.mp.microsoft.com')
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.Greater(t, count, uint64(0), "dns table should contain at least 1 entry with query ending in .microsoft.com")
|
|
|
|
tables := []struct {
|
|
name string
|
|
field string
|
|
}{{name: "dns", field: "query"}, {name: "http", field: "host"}, {name: "ssl", field: "server_name"}}
|
|
|
|
domains := []struct {
|
|
d string
|
|
shouldBeFiltered bool
|
|
}{{d: "businessinsider.com", shouldBeFiltered: true}, {d: "static2.businessinsider.com", shouldBeFiltered: false}}
|
|
|
|
for _, domain := range domains {
|
|
for _, table := range tables {
|
|
chCtx := db.QueryParameters(clickhouse.Parameters{
|
|
"table": table.name,
|
|
"field": table.field,
|
|
"domain": domain.d,
|
|
})
|
|
|
|
// verify that not all subdomains in this domain are filtered (wildcard shouldn't apply)
|
|
err = db.Conn.QueryRow(chCtx, `
|
|
SELECT count() FROM {table:Identifier}
|
|
WHERE endsWith({field:Identifier}, {domain:String})
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
require.Greater(t, count, uint64(0), "%s table should contain at least one entry with a domain that ends in .%s", table.name, domain.d)
|
|
|
|
// verify that domain is filtered (if it should be)
|
|
err = db.Conn.QueryRow(chCtx, `
|
|
SELECT count() FROM {table:Identifier}
|
|
WHERE {field:Identifier} = {domain:String}
|
|
`).Scan(&count)
|
|
require.NoError(t, err)
|
|
if domain.shouldBeFiltered {
|
|
require.EqualValues(t, 0, count, "%s table shouldn't contain any entries with a domain of %s", table, domain.d)
|
|
} else {
|
|
require.Greater(t, count, uint64(0), "%s table should contain at least one entry with a domain of %s", table.name, domain.d)
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestFilterExternalToInternal also tests ICMP
|
|
func (it *FilterTestSuite) TestFilterExternalToInternal() {
|
|
t := it.T()
|
|
// set up file system interface
|
|
afs := afero.NewOsFs()
|
|
|
|
cfg, err := config.ReadFileConfig(afs, ConfigPath)
|
|
require.NoError(t, err)
|
|
cfg.Env.DBConnection = dockerInfo.clickhouseConnection
|
|
cfg.Filtering.FilterExternalToInternal = false
|
|
it.cfg = cfg
|
|
require.NoError(t, err, "updating config should not return an error")
|
|
|
|
require.False(t, cfg.Filtering.FilterExternalToInternal)
|
|
|
|
// // import data
|
|
importResults, err := cmd.RunImportCmd(time.Now(), cfg, afs, "../test_data/valid_tsv", "filter_ext_to_int", false, true)
|
|
require.NoError(t, err)
|
|
|
|
// connect to database
|
|
db, err := database.ConnectToDB(context.Background(), "filter_ext_to_int", cfg, nil)
|
|
require.NoError(t, err)
|
|
|
|
// // there are ICMP connections that are only on connections that are external to internal
|
|
|
|
type protoInfo struct {
|
|
PortProtoService string `ch:"port_proto_service"`
|
|
ConnCount uint64 `ch:"conn_count"`
|
|
BytesSent uint64 `ch:"bytes_sent"`
|
|
BytesReceived uint64 `ch:"bytes_received"`
|
|
}
|
|
|
|
type testData struct {
|
|
src string
|
|
dst string
|
|
portInfoList []protoInfo
|
|
}
|
|
|
|
testCases := []testData{
|
|
{
|
|
src: "165.227.88.15",
|
|
dst: "192.168.88.2",
|
|
portInfoList: []protoInfo{
|
|
{"icmp:3/3", 2, 4858, 0},
|
|
},
|
|
},
|
|
{
|
|
src: "66.218.84.141",
|
|
dst: "10.55.100.107",
|
|
portInfoList: []protoInfo{
|
|
{"icmp:3/3", 1, 240, 0},
|
|
},
|
|
},
|
|
{
|
|
src: "66.218.84.141",
|
|
dst: "10.55.100.104",
|
|
portInfoList: []protoInfo{
|
|
{"icmp:3/3", 1, 240, 0},
|
|
},
|
|
},
|
|
{
|
|
src: "66.218.84.141",
|
|
dst: "10.55.100.108",
|
|
portInfoList: []protoInfo{
|
|
{"icmp:3/3", 1, 240, 0},
|
|
},
|
|
},
|
|
{
|
|
src: "67.226.210.13",
|
|
dst: "10.55.100.106",
|
|
portInfoList: []protoInfo{
|
|
{"icmp:3/10", 1, 136, 0},
|
|
},
|
|
},
|
|
{
|
|
src: "67.226.210.15",
|
|
dst: "10.55.100.107",
|
|
portInfoList: []protoInfo{
|
|
{"icmp:3/10", 1, 136, 0},
|
|
},
|
|
},
|
|
{
|
|
src: "67.226.210.14",
|
|
dst: "10.55.100.108",
|
|
portInfoList: []protoInfo{
|
|
{"icmp:3/10", 1, 136, 0},
|
|
},
|
|
},
|
|
{
|
|
src: "24.220.6.168",
|
|
dst: "10.55.200.11",
|
|
portInfoList: []protoInfo{
|
|
{"icmp:3/13", 1, 96, 0},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
ctx := clickhouse.Context(context.Background(), clickhouse.WithParameters(clickhouse.Parameters{
|
|
"src": test.src,
|
|
"dst": test.dst,
|
|
}))
|
|
|
|
var res []protoInfo
|
|
err = db.Conn.Select(ctx, &res, `
|
|
SELECT concat(proto, ':', icmp_type, '/', icmp_code) AS port_proto_service,
|
|
countMerge(count) AS conn_count,
|
|
sumMerge(bytes_sent) AS bytes_sent,
|
|
sumMerge(bytes_received) AS bytes_received
|
|
FROM port_info
|
|
WHERE src={src:String} AND dst={dst:String}
|
|
GROUP BY src, dst, dst_port, proto, service, icmp_type, icmp_code
|
|
`)
|
|
require.NoError(t, err, "querying proto table should not produce an error")
|
|
|
|
// ensure that the length of the result list matches the expected value
|
|
require.Len(t, res, len(test.portInfoList), "length of result list should match expected value")
|
|
|
|
// ensure that the result list matches the expected value
|
|
require.ElementsMatch(t, test.portInfoList, res, "result list should match expected value")
|
|
}
|
|
|
|
/* ******* Mixtape Propagation *******
|
|
The entries that use ICMP don't have enough connections to qualify as beacons, so they don't appear in the mixtape.
|
|
In order to test the spagooper query that grabs the ICMP entries within port:proto:service, we have to go through
|
|
the results of the IP spagooper.
|
|
*/
|
|
|
|
// set up new analyzer
|
|
minTSBeacon, maxTSBeacon, notFromConn, err := db.GetBeaconMinMaxTimestamps()
|
|
require.NoError(t, err)
|
|
require.False(t, notFromConn, "min and max timestamps should be from conn table")
|
|
|
|
minTS, maxTS, notFromConn, useCurrentTime, err := db.GetTrueMinMaxTimestamps()
|
|
require.NoError(t, err)
|
|
require.False(t, notFromConn, "min and max timestamps should be from conn table")
|
|
require.False(t, useCurrentTime, "first seen analysis should not use the current time")
|
|
|
|
analyzer, err := analysis.NewAnalyzer(db, cfg, importResults.ImportID[0], minTS, maxTS, minTSBeacon, maxTSBeacon, useCurrentTime, false)
|
|
require.NoError(t, err)
|
|
|
|
ctx := context.Background()
|
|
queryGroup, ctx := errgroup.WithContext(ctx)
|
|
|
|
// create progress bars
|
|
bars := progressbar.New(ctx, []*progressbar.ProgressBar{
|
|
progressbar.NewBar("IP Connection Analysis ", 2, progress.New(progress.WithDefaultGradient())),
|
|
}, []progressbar.Spinner{})
|
|
|
|
type foundEntry struct {
|
|
PortProtoService string
|
|
ConnCount uint64
|
|
TotalBytes uint64
|
|
}
|
|
type resData struct {
|
|
src string
|
|
dst string
|
|
portInfoList []foundEntry
|
|
}
|
|
var foundIPs []resData
|
|
|
|
var expectedResData []resData
|
|
|
|
for _, test := range testCases {
|
|
d := resData{
|
|
src: test.src,
|
|
dst: test.dst,
|
|
}
|
|
var p []foundEntry
|
|
for _, dd := range test.portInfoList {
|
|
p = append(p, foundEntry{
|
|
PortProtoService: dd.PortProtoService,
|
|
ConnCount: dd.ConnCount,
|
|
TotalBytes: (dd.BytesSent + dd.BytesReceived) * 2, // multiply by 2 for openconns
|
|
})
|
|
}
|
|
d.portInfoList = p
|
|
expectedResData = append(expectedResData, d)
|
|
}
|
|
|
|
queryGroup.Go(func() error {
|
|
for entry := range analyzer.UconnChan {
|
|
|
|
for _, test := range testCases {
|
|
if entry.Src.String() == test.src && entry.Dst.String() == test.dst {
|
|
f := resData{
|
|
src: entry.Src.String(),
|
|
dst: entry.Dst.String(),
|
|
}
|
|
var portProto []foundEntry
|
|
for _, p := range entry.PortProtoService {
|
|
port := foundEntry{
|
|
PortProtoService: p,
|
|
ConnCount: entry.Count,
|
|
TotalBytes: entry.TotalBytes,
|
|
}
|
|
portProto = append(portProto, port)
|
|
}
|
|
f.portInfoList = portProto
|
|
foundIPs = append(foundIPs, f)
|
|
}
|
|
}
|
|
|
|
}
|
|
return nil
|
|
})
|
|
|
|
queryGroup.Go(func() error {
|
|
err := analyzer.ScoopIPConns(ctx, bars)
|
|
require.NoError(t, err)
|
|
close(analyzer.UconnChan)
|
|
return err
|
|
})
|
|
|
|
queryGroup.Go(func() error {
|
|
_, err := bars.Run()
|
|
require.NoError(t, err)
|
|
return err
|
|
})
|
|
|
|
if err := queryGroup.Wait(); err != nil {
|
|
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
require.ElementsMatch(t, expectedResData, foundIPs)
|
|
}
|