2020-05-27 12:05:53 +02:00
|
|
|
package matcher
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2020-06-02 10:58:31 +02:00
|
|
|
"fmt"
|
2020-05-27 12:05:53 +02:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
|
|
|
"runtime"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/dgraph-io/dgo/v2"
|
2020-05-29 11:32:55 +02:00
|
|
|
"github.com/dgraph-io/dgo/v2/protos/api"
|
2020-05-27 12:05:53 +02:00
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"github.com/spf13/viper"
|
|
|
|
"gitlab.dcso.lolcat/LABS/styx/models"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
_, b, _, _ = runtime.Caller(0)
|
|
|
|
basepath = filepath.Dir(b)
|
|
|
|
)
|
|
|
|
|
2020-05-29 11:32:55 +02:00
|
|
|
// Matcher is the structure for the matching logic within Styx.
|
2020-05-27 12:05:53 +02:00
|
|
|
type Matcher struct {
|
|
|
|
Running bool
|
|
|
|
StopChan chan bool
|
|
|
|
StoppedChan chan bool
|
|
|
|
}
|
|
|
|
|
2020-05-29 11:32:55 +02:00
|
|
|
// Initialize initialises the matcher based on the given configuration.
|
2020-05-27 12:05:53 +02:00
|
|
|
func (m *Matcher) Initialize() bool {
|
|
|
|
if !viper.GetBool("matcher.activated") {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Info("matching is activated")
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-05-29 11:32:55 +02:00
|
|
|
// Stop gracefully stops the matching logic.
|
2020-05-27 12:05:53 +02:00
|
|
|
func (m *Matcher) Stop(wg *sync.WaitGroup) {
|
|
|
|
if m.Running {
|
|
|
|
m.StopChan = make(chan bool)
|
|
|
|
close(m.StopChan)
|
|
|
|
<-m.StopChan
|
|
|
|
wg.Done()
|
|
|
|
m.Running = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-29 11:32:55 +02:00
|
|
|
// Result is the result from the matching query. Probably going to change.
|
2020-05-27 12:05:53 +02:00
|
|
|
type Result struct {
|
2020-06-08 10:49:19 +02:00
|
|
|
Result []models.Node `json:"Node,omitempty"`
|
2020-05-27 12:05:53 +02:00
|
|
|
}
|
|
|
|
|
2020-06-08 12:58:07 +02:00
|
|
|
func loadTargets(graphClient *dgo.Dgraph) ([]string, error) {
|
2020-05-29 11:32:55 +02:00
|
|
|
path := basepath + "/data/"
|
2020-06-08 12:58:07 +02:00
|
|
|
res := []string{}
|
2020-05-29 11:32:55 +02:00
|
|
|
|
|
|
|
sliceDomain, err := ioutil.ReadDir(path)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Warn("matcher#ReadDir#domains", err)
|
2020-06-08 12:58:07 +02:00
|
|
|
return nil, err
|
2020-05-29 11:32:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, file := range sliceDomain {
|
2020-06-03 16:20:40 +02:00
|
|
|
logrus.Info("loading: ", file.Name(), " please wait...")
|
2020-05-29 11:32:55 +02:00
|
|
|
f, err := os.OpenFile(path+file.Name(), 0, 0644)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Warn("matcher#OpenFile#", err)
|
2020-06-08 12:58:07 +02:00
|
|
|
return nil, err
|
2020-05-27 12:05:53 +02:00
|
|
|
}
|
2020-05-29 11:32:55 +02:00
|
|
|
scanner := bufio.NewScanner(f)
|
2020-05-27 12:05:53 +02:00
|
|
|
|
2020-05-29 11:32:55 +02:00
|
|
|
for scanner.Scan() {
|
|
|
|
uuid := uuid.New().String()
|
|
|
|
t := time.Now()
|
|
|
|
rfc3339time := t.Format(time.RFC3339)
|
|
|
|
matcher := models.Match{
|
|
|
|
ID: uuid,
|
|
|
|
Timestamp: rfc3339time,
|
|
|
|
Target: scanner.Text(),
|
|
|
|
Nodes: []models.Node{},
|
2020-08-28 13:34:08 +02:00
|
|
|
NodeType: "matcher",
|
2020-05-29 11:32:55 +02:00
|
|
|
}
|
2020-05-27 12:05:53 +02:00
|
|
|
ctx := context.Background()
|
2020-06-10 10:48:47 +02:00
|
|
|
query := `query eq($a: string){
|
|
|
|
Node(func: eq(target, $a)){
|
|
|
|
uid
|
|
|
|
}
|
|
|
|
}`
|
2020-05-29 11:32:55 +02:00
|
|
|
|
2020-06-10 10:48:47 +02:00
|
|
|
txn := graphClient.NewTxn()
|
|
|
|
ret, err := txn.QueryWithVars(ctx, query, map[string]string{"$a": scanner.Text()})
|
2020-05-27 12:05:53 +02:00
|
|
|
if err != nil {
|
2020-06-10 10:48:47 +02:00
|
|
|
logrus.Warn(err)
|
2020-05-27 12:05:53 +02:00
|
|
|
}
|
|
|
|
|
2020-06-10 10:48:47 +02:00
|
|
|
n := Result{}
|
|
|
|
json.Unmarshal([]byte(ret.Json), &n)
|
2020-05-27 12:05:53 +02:00
|
|
|
|
2020-06-10 10:48:47 +02:00
|
|
|
// Check if the target already exists, if so, skipping not inserting
|
|
|
|
// the data
|
|
|
|
if len(n.Result) == 0 {
|
|
|
|
logrus.Info("new matcher, charging...")
|
|
|
|
mu := &api.Mutation{
|
|
|
|
CommitNow: true,
|
|
|
|
}
|
2020-06-03 16:20:40 +02:00
|
|
|
|
2020-06-10 10:48:47 +02:00
|
|
|
pb, err := json.Marshal(matcher)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Error(err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
mu.SetJson = pb
|
|
|
|
|
|
|
|
txn = graphClient.NewTxn()
|
|
|
|
defer txn.Discard(ctx)
|
|
|
|
|
|
|
|
_, err = txn.Mutate(ctx, mu)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Error(err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
res = append(res, scanner.Text())
|
|
|
|
if err := scanner.Err(); err != nil {
|
2020-05-29 11:32:55 +02:00
|
|
|
logrus.Error(err)
|
2020-06-08 12:58:07 +02:00
|
|
|
return nil, err
|
2020-05-27 12:05:53 +02:00
|
|
|
}
|
2020-08-24 16:25:49 +02:00
|
|
|
|
2020-05-29 11:32:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-08 12:58:07 +02:00
|
|
|
return res, nil
|
2020-05-29 11:32:55 +02:00
|
|
|
}
|
|
|
|
|
2020-06-08 12:58:07 +02:00
|
|
|
// Run runs the routine trying to find matches in the ingested Pastebin data.
|
2020-05-29 11:32:55 +02:00
|
|
|
func (m *Matcher) Run(wg *sync.WaitGroup, graphClient *dgo.Dgraph) {
|
2020-06-10 10:48:47 +02:00
|
|
|
logrus.Info("loading matcher targets, this might take some time...")
|
2020-06-08 12:58:07 +02:00
|
|
|
targets, err := loadTargets(graphClient)
|
|
|
|
if err != nil {
|
2020-05-29 11:32:55 +02:00
|
|
|
logrus.Error(err)
|
|
|
|
}
|
2020-06-02 10:58:31 +02:00
|
|
|
logrus.Info("finished loading matcher targets")
|
2020-08-24 16:25:49 +02:00
|
|
|
fmt.Println(targets)
|
2020-06-03 16:20:40 +02:00
|
|
|
|
2020-05-29 11:32:55 +02:00
|
|
|
if !m.Running {
|
2020-06-02 10:58:31 +02:00
|
|
|
m.StoppedChan = make(chan bool)
|
|
|
|
wg.Add(1)
|
2020-06-10 10:48:47 +02:00
|
|
|
logrus.Info("Running matchers")
|
2020-06-03 16:20:40 +02:00
|
|
|
for _, target := range targets {
|
2020-06-10 11:32:56 +02:00
|
|
|
go runPasteMatcher(target, graphClient)
|
2020-06-08 12:58:07 +02:00
|
|
|
go runCertstreamMatcher(target, graphClient)
|
2020-06-10 10:48:47 +02:00
|
|
|
go runShodanMatcher(target, graphClient)
|
2020-06-03 16:20:40 +02:00
|
|
|
}
|
2020-06-08 10:49:19 +02:00
|
|
|
|
2020-06-03 16:20:40 +02:00
|
|
|
// TODO: probably not the best design here
|
|
|
|
wg.Add(len(targets))
|
|
|
|
m.Running = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-08 12:58:07 +02:00
|
|
|
func runPasteMatcher(target string, graphClient *dgo.Dgraph) {
|
2020-06-03 16:20:40 +02:00
|
|
|
for {
|
|
|
|
q := `query allofterms($a: string) {
|
|
|
|
Node(func: allofterms(full, $a)) {
|
|
|
|
uid
|
2020-08-28 13:34:08 +02:00
|
|
|
nodeType
|
2020-06-03 16:20:40 +02:00
|
|
|
full
|
|
|
|
}
|
2020-06-02 15:44:24 +02:00
|
|
|
|
|
|
|
}`
|
2020-06-02 10:58:31 +02:00
|
|
|
|
2020-06-03 16:20:40 +02:00
|
|
|
ctx := context.Background()
|
|
|
|
txn := graphClient.NewTxn()
|
|
|
|
res, err := txn.QueryWithVars(ctx, q, map[string]string{"$a": target})
|
|
|
|
if err != nil {
|
|
|
|
logrus.Warn(err)
|
|
|
|
}
|
2020-06-02 10:58:31 +02:00
|
|
|
|
2020-06-03 16:20:40 +02:00
|
|
|
n := Result{}
|
|
|
|
json.Unmarshal([]byte(res.Json), &n)
|
|
|
|
uuid := uuid.New().String()
|
|
|
|
t := time.Now()
|
|
|
|
rfc3339time := t.Format(time.RFC3339)
|
|
|
|
matcher := models.Match{
|
|
|
|
ID: uuid,
|
|
|
|
Timestamp: rfc3339time,
|
|
|
|
Target: target,
|
|
|
|
Nodes: []models.Node{},
|
2020-08-28 13:34:08 +02:00
|
|
|
NodeType: "matcher",
|
2020-06-03 16:20:40 +02:00
|
|
|
}
|
|
|
|
if len(n.Result) != 0 {
|
2020-06-08 10:49:19 +02:00
|
|
|
time.Sleep(time.Duration(2) * time.Second)
|
2020-06-10 12:31:07 +02:00
|
|
|
logrus.Info("Found paste match for ", target)
|
2020-06-03 16:20:40 +02:00
|
|
|
// TODO: review time and id to be updated on new resulsts
|
|
|
|
|
|
|
|
for _, res := range n.Result {
|
|
|
|
if len(matcher.Nodes) == 0 {
|
|
|
|
matcher.Nodes = append(matcher.Nodes, res)
|
|
|
|
continue
|
|
|
|
}
|
2020-06-02 10:58:31 +02:00
|
|
|
|
2020-06-03 16:20:40 +02:00
|
|
|
for _, node := range matcher.Nodes {
|
|
|
|
if res.UID != node.UID {
|
2020-06-02 10:58:31 +02:00
|
|
|
matcher.Nodes = append(matcher.Nodes, res)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
2020-06-03 16:20:40 +02:00
|
|
|
}
|
2020-06-02 10:58:31 +02:00
|
|
|
|
2020-06-03 16:20:40 +02:00
|
|
|
query := fmt.Sprintf(`query { match as var(func: eq(target, "%s")) }`, target)
|
2020-06-02 10:58:31 +02:00
|
|
|
|
2020-08-28 13:34:08 +02:00
|
|
|
pb, err := json.Marshal(models.Match{UID: "uid(match)", ID: matcher.ID, Target: target, Nodes: matcher.Nodes, NodeType: "matcher", Timestamp: rfc3339time})
|
2020-06-03 16:20:40 +02:00
|
|
|
if err != nil {
|
|
|
|
logrus.Fatal(err)
|
|
|
|
}
|
2020-06-02 10:58:31 +02:00
|
|
|
|
2020-06-03 16:20:40 +02:00
|
|
|
mu := &api.Mutation{
|
2020-06-08 10:49:19 +02:00
|
|
|
SetJson: pb,
|
2020-06-03 16:20:40 +02:00
|
|
|
}
|
2020-06-02 10:58:31 +02:00
|
|
|
|
2020-06-03 16:20:40 +02:00
|
|
|
req := &api.Request{
|
|
|
|
Query: query,
|
|
|
|
Mutations: []*api.Mutation{mu},
|
|
|
|
CommitNow: true,
|
2020-06-02 10:58:31 +02:00
|
|
|
}
|
|
|
|
|
2020-06-03 16:20:40 +02:00
|
|
|
txn := graphClient.NewTxn()
|
|
|
|
_, err = txn.Do(ctx, req)
|
|
|
|
if err != nil {
|
2020-06-08 10:49:19 +02:00
|
|
|
logrus.Warn(err)
|
2020-06-03 16:20:40 +02:00
|
|
|
}
|
2020-06-08 10:49:19 +02:00
|
|
|
// txn.Discard(ctx)
|
|
|
|
time.Sleep(time.Duration(2) * time.Second)
|
2020-06-02 10:58:31 +02:00
|
|
|
}
|
2020-06-08 10:49:19 +02:00
|
|
|
|
2020-05-27 12:05:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-08 12:58:07 +02:00
|
|
|
func runCertstreamMatcher(target string, graphClient *dgo.Dgraph) {
|
|
|
|
for {
|
|
|
|
q := `query allofterms($a: string){
|
|
|
|
Node(func: allofterms(cn, $a)){
|
|
|
|
fingerprint
|
|
|
|
notBefore
|
|
|
|
notAfter
|
|
|
|
cn
|
|
|
|
sourceName
|
|
|
|
basicConstraints
|
|
|
|
raw
|
|
|
|
}
|
|
|
|
}`
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
txn := graphClient.NewTxn()
|
|
|
|
res, err := txn.QueryWithVars(ctx, q, map[string]string{"$a": target})
|
|
|
|
if err != nil {
|
|
|
|
logrus.Warn(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
n := Result{}
|
|
|
|
json.Unmarshal([]byte(res.Json), &n)
|
|
|
|
uuid := uuid.New().String()
|
|
|
|
t := time.Now()
|
|
|
|
rfc3339time := t.Format(time.RFC3339)
|
|
|
|
matcher := models.Match{
|
|
|
|
ID: uuid,
|
|
|
|
Timestamp: rfc3339time,
|
|
|
|
Target: target,
|
|
|
|
Nodes: []models.Node{},
|
2020-08-28 13:34:08 +02:00
|
|
|
NodeType: "matcher",
|
2020-06-08 12:58:07 +02:00
|
|
|
}
|
|
|
|
if len(n.Result) != 0 {
|
|
|
|
time.Sleep(time.Duration(2) * time.Second)
|
|
|
|
logrus.Info("Found certstream match for ", target)
|
|
|
|
|
|
|
|
for _, res := range n.Result {
|
|
|
|
if len(matcher.Nodes) == 0 {
|
|
|
|
matcher.Nodes = append(matcher.Nodes, res)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, nodes := range matcher.Nodes {
|
|
|
|
if res.UID != nodes.UID {
|
|
|
|
matcher.Nodes = append(matcher.Nodes, res)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
query := fmt.Sprintf(`query { match as var(func: eq(target, "%s")) }`, target)
|
|
|
|
|
2020-08-28 13:34:08 +02:00
|
|
|
pb, err := json.Marshal(models.Match{UID: "uid(match)", ID: matcher.ID, Target: target, Nodes: matcher.Nodes, NodeType: "matcher"})
|
2020-06-08 12:58:07 +02:00
|
|
|
if err != nil {
|
|
|
|
logrus.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
mu := &api.Mutation{
|
|
|
|
SetJson: pb,
|
|
|
|
}
|
|
|
|
|
|
|
|
req := &api.Request{
|
|
|
|
Query: query,
|
|
|
|
Mutations: []*api.Mutation{mu},
|
|
|
|
CommitNow: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
txn := graphClient.NewTxn()
|
|
|
|
_, err = txn.Do(ctx, req)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Warn(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
time.Sleep(time.Duration(2) * time.Second)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-10 10:48:47 +02:00
|
|
|
func runShodanMatcher(target string, graphClient *dgo.Dgraph) {
|
|
|
|
for {
|
|
|
|
q := `query allofterms($a: string){
|
|
|
|
Node(func: eq(hostnames, $a)){
|
|
|
|
uid
|
|
|
|
type
|
|
|
|
hostnames
|
|
|
|
product
|
|
|
|
version
|
|
|
|
title
|
|
|
|
ip
|
|
|
|
os
|
|
|
|
organization
|
|
|
|
isp
|
|
|
|
cpe
|
|
|
|
asn
|
|
|
|
port
|
|
|
|
html
|
|
|
|
banner
|
|
|
|
link
|
|
|
|
transport
|
|
|
|
domains
|
|
|
|
timestamp
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`
|
|
|
|
ctx := context.Background()
|
|
|
|
txn := graphClient.NewTxn()
|
|
|
|
res, err := txn.QueryWithVars(ctx, q, map[string]string{"$a": target})
|
|
|
|
if err != nil {
|
|
|
|
logrus.Warn(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
n := Result{}
|
|
|
|
json.Unmarshal([]byte(res.Json), &n)
|
|
|
|
uuid := uuid.New().String()
|
|
|
|
t := time.Now()
|
|
|
|
rfc3339time := t.Format(time.RFC3339)
|
|
|
|
matcher := models.Match{
|
|
|
|
ID: uuid,
|
|
|
|
Timestamp: rfc3339time,
|
|
|
|
Target: target,
|
|
|
|
Nodes: []models.Node{},
|
2020-08-28 13:34:08 +02:00
|
|
|
NodeType: "matcher",
|
2020-06-10 10:48:47 +02:00
|
|
|
}
|
|
|
|
if len(n.Result) != 0 {
|
|
|
|
time.Sleep(time.Duration(2) * time.Second)
|
2020-06-10 12:31:07 +02:00
|
|
|
logrus.Info("Found shodan match for ", target)
|
2020-06-10 10:48:47 +02:00
|
|
|
// TODO: review time and id to be updated on new resulsts
|
|
|
|
|
|
|
|
for _, res := range n.Result {
|
|
|
|
if len(matcher.Nodes) == 0 {
|
|
|
|
matcher.Nodes = append(matcher.Nodes, res)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, node := range matcher.Nodes {
|
|
|
|
if res.UID != node.UID {
|
|
|
|
matcher.Nodes = append(matcher.Nodes, res)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
query := fmt.Sprintf(`query { match as var(func: eq(target, "%s")) }`, target)
|
|
|
|
|
2020-08-28 13:34:08 +02:00
|
|
|
pb, err := json.Marshal(models.Match{UID: "uid(match)", ID: matcher.ID, Target: target, Nodes: matcher.Nodes, NodeType: "matcher", Timestamp: rfc3339time})
|
2020-06-10 10:48:47 +02:00
|
|
|
if err != nil {
|
|
|
|
logrus.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
mu := &api.Mutation{
|
|
|
|
SetJson: pb,
|
|
|
|
}
|
|
|
|
|
|
|
|
req := &api.Request{
|
|
|
|
Query: query,
|
|
|
|
Mutations: []*api.Mutation{mu},
|
|
|
|
CommitNow: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
txn := graphClient.NewTxn()
|
|
|
|
_, err = txn.Do(ctx, req)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Warn(err)
|
|
|
|
}
|
|
|
|
// txn.Discard(ctx)
|
|
|
|
time.Sleep(time.Duration(2) * time.Second)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-27 12:05:53 +02:00
|
|
|
// RunDomainMatch looks for a target within the identified IOCs in /matcher/data.
|
|
|
|
// the function could be refactored with RunDomainFilters
|
|
|
|
func RunDomainMatch(domain string) bool {
|
|
|
|
path := basepath + "/data/domains.txt"
|
|
|
|
|
|
|
|
sliceDomain, err := ioutil.ReadDir(path)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Warn("matcher#ReadDir#domains", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, file := range sliceDomain {
|
|
|
|
f, err := os.OpenFile(path+file.Name(), 1, 0644)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Warn("matcher#OpenFile#", err)
|
|
|
|
}
|
|
|
|
scanner := bufio.NewScanner(f)
|
|
|
|
|
|
|
|
for scanner.Scan() {
|
|
|
|
r, err := regexp.Compile(scanner.Text())
|
|
|
|
if err != nil {
|
|
|
|
logrus.Warn("matcher#Compile#", err)
|
|
|
|
}
|
|
|
|
if r.MatchString(domain) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|