зеркало из https://github.com/mozilla/mig.git
Merge pull request #411 from ameihm0912/remove-additional-render
remove geo-ip and compliance report modes
This commit is contained in:
Коммит
c5a5dc38a1
|
@ -1532,11 +1532,7 @@ func (cli Client) FetchActionResults(a mig.Action) (ret []mig.Command, err error
|
|||
//
|
||||
// show can either be found, notfound, or all and can be used to control which results
|
||||
// are fetched and displayed for a given action.
|
||||
//
|
||||
// The render parameter can be used to indicate the results should be rendered in a
|
||||
// certain way (as opposed to just printing the agent results). If this value is set to
|
||||
// map, results will be rendered into a map (geo-located).
|
||||
func (cli Client) PrintActionResults(a mig.Action, show, render string) (err error) {
|
||||
func (cli Client) PrintActionResults(a mig.Action, show string) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("PrintActionResults() -> %v", e)
|
||||
|
@ -1545,12 +1541,8 @@ func (cli Client) PrintActionResults(a mig.Action, show, render string) (err err
|
|||
var (
|
||||
found bool
|
||||
report, foundQ string
|
||||
locs []CommandLocation
|
||||
limit, offset, agtCount int = 37, 0, 0
|
||||
)
|
||||
if render == "map" {
|
||||
report = "&report=geolocations"
|
||||
}
|
||||
switch show {
|
||||
case "found":
|
||||
found = true
|
||||
|
@ -1573,38 +1565,24 @@ func (cli Client) PrintActionResults(a mig.Action, show, render string) (err err
|
|||
switch resource.Collection.Error.Message {
|
||||
case "", "no results found":
|
||||
err = nil
|
||||
case "maxmind database not initialized":
|
||||
panic("Maxmind database not configured in the API, geolocations cannot be displayed")
|
||||
default:
|
||||
panic(err)
|
||||
}
|
||||
count := 0
|
||||
for _, item := range resource.Collection.Items {
|
||||
for _, data := range item.Data {
|
||||
switch render {
|
||||
case "map":
|
||||
if data.Name != "geolocation" {
|
||||
continue
|
||||
}
|
||||
loc, err := ValueToLocation(data.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
locs = append(locs, loc)
|
||||
default:
|
||||
if data.Name != "command" {
|
||||
continue
|
||||
}
|
||||
cmd, err := ValueToCommand(data.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = PrintCommandResults(cmd, found, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
count++
|
||||
if data.Name != "command" {
|
||||
continue
|
||||
}
|
||||
cmd, err := ValueToCommand(data.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = PrintCommandResults(cmd, found, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
count++
|
||||
}
|
||||
}
|
||||
// if count is still at zero, we didn't get any results from the query and exit the loop
|
||||
|
@ -1615,23 +1593,11 @@ func (cli Client) PrintActionResults(a mig.Action, show, render string) (err err
|
|||
offset += limit
|
||||
agtCount += count
|
||||
}
|
||||
switch render {
|
||||
case "map":
|
||||
if len(locs) < 1 {
|
||||
break
|
||||
}
|
||||
title := fmt.Sprintf("Geolocation of %s results for action ID %.0f %s", show, a.ID, a.Name)
|
||||
err = PrintMap(locs, title)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
default:
|
||||
s := "agent has"
|
||||
if agtCount > 1 {
|
||||
s = "agents have"
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\x1b[31m%d %s %s results\x1b[0m\n", agtCount, s, show)
|
||||
s := "agent has"
|
||||
if agtCount > 1 {
|
||||
s = "agents have"
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\x1b[31m%d %s %s results\x1b[0m\n", agtCount, s, show)
|
||||
if show != "all" {
|
||||
var unsuccessful map[string][]string
|
||||
unsuccessful = make(map[string][]string)
|
||||
|
@ -1650,9 +1616,6 @@ func (cli Client) PrintActionResults(a mig.Action, show, render string) (err err
|
|||
// 404, move one
|
||||
err = nil
|
||||
goto nextunsuccessful
|
||||
case "maxmind database not initialized":
|
||||
// can't make the map, exit with error
|
||||
panic("Maxmind database not configured in the API, geolocations cannot be displayed")
|
||||
default:
|
||||
// something else happened, exit with error
|
||||
panic(err)
|
||||
|
|
234
client/map.go
234
client/map.go
|
@ -1,234 +0,0 @@
|
|||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]
|
||||
|
||||
package client /* import "mig.ninja/mig/client" */
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type CommandLocation struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
CommandID float64 `json:"commandid"`
|
||||
ActionID float64 `json:"actionid"`
|
||||
FoundAnything bool `json:"foundanything"`
|
||||
ConnectionsTo []string `json:"connections_to"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
City string `json:"city"`
|
||||
Country string `json:"country"`
|
||||
}
|
||||
|
||||
func ValueToLocation(v interface{}) (cl CommandLocation, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("ValueToLocation) -> %v", e)
|
||||
}
|
||||
}()
|
||||
bData, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = json.Unmarshal(bData, &cl)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func PrintMap(locs []CommandLocation, title string) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("PrintMap() -> %v", e)
|
||||
}
|
||||
}()
|
||||
gmap := makeMapHeader(title)
|
||||
locs = singularizeLocations(locs)
|
||||
data, err := json.Marshal(locs)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
gmap += fmt.Sprintf(` <script type="text/javascript"> var endpoints = %s </script>`, data)
|
||||
var details []string
|
||||
details = append(details, " <ol>\n")
|
||||
for _, cl := range locs {
|
||||
detail := fmt.Sprintf(" <li>%s: found=%t</li>", cl.Endpoint, cl.FoundAnything)
|
||||
details = append(details, detail)
|
||||
}
|
||||
details = append(details, " </ol>\n")
|
||||
gmap += makeMapFooter(title, details)
|
||||
|
||||
// write map data to temp file
|
||||
fd, err := ioutil.TempFile("", "migmap_")
|
||||
defer fd.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err = fd.Write([]byte(gmap))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fi, err := fd.Stat()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
filepath := fmt.Sprintf("%s/%s", os.TempDir(), fi.Name())
|
||||
fmt.Fprintf(os.Stderr, "map written to %s\n", filepath)
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
err = exec.Command("firefox", filepath).Start()
|
||||
case "darwin":
|
||||
err = exec.Command("open", "-b", "org.mozilla.firefox", filepath).Start()
|
||||
default:
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// singularizeLocations prevent multiple point from using the same coordinates, and therefore show as one point on the map
|
||||
func singularizeLocations(orig_locs []CommandLocation) (locs []CommandLocation) {
|
||||
locs = orig_locs
|
||||
for i, _ := range locs {
|
||||
for j := 0; j < i; j++ {
|
||||
if locs[i].Latitude == locs[j].Latitude && locs[i].Longitude == locs[j].Longitude {
|
||||
switch i % 8 {
|
||||
case 0:
|
||||
locs[i].Latitude += 0.005
|
||||
case 1:
|
||||
locs[i].Longitude += 0.005
|
||||
case 2:
|
||||
locs[i].Latitude -= 0.005
|
||||
case 3:
|
||||
locs[i].Longitude -= 0.005
|
||||
case 4:
|
||||
locs[i].Latitude += 0.005
|
||||
locs[i].Longitude += 0.005
|
||||
case 5:
|
||||
locs[i].Latitude -= 0.005
|
||||
locs[i].Longitude -= 0.005
|
||||
case 6:
|
||||
locs[i].Latitude += 0.005
|
||||
locs[i].Longitude -= 0.005
|
||||
case 7:
|
||||
locs[i].Latitude -= 0.005
|
||||
locs[i].Longitude += 0.005
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func makeMapHeader(title string) string {
|
||||
return fmt.Sprintf(`
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||
<title>%s</title>
|
||||
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?v=3.exp&signed_in=true"></script>
|
||||
<script type="text/javascript" src="https://raw.githubusercontent.com/googlemaps/js-marker-clusterer/gh-pages/src/markerclusterer_compiled.js"></script>
|
||||
`, title)
|
||||
}
|
||||
func makeMapFooter(title string, body []string) (footer string) {
|
||||
footer = `
|
||||
<script type="text/javascript">
|
||||
var locs = new Array();
|
||||
var marker = new Array();
|
||||
var connections = new Array();
|
||||
var cluster = new Array();
|
||||
var arrowSymbol = {
|
||||
path: google.maps.SymbolPath.CIRCLE,
|
||||
scale: 2,
|
||||
strokeColor: 'blue'
|
||||
};
|
||||
function initialize() {
|
||||
var center = new google.maps.LatLng(0,0);
|
||||
var mapOptions = {
|
||||
zoom: 2,
|
||||
center: center,
|
||||
mapTypeId: google.maps.MapTypeId.TERRAIN
|
||||
};
|
||||
var map = new google.maps.Map(
|
||||
document.getElementById('map'),
|
||||
mapOptions
|
||||
);
|
||||
endpointscount = endpoints.length;
|
||||
for (var i=0; i<endpointscount; i++) {
|
||||
locs[endpoints[i].endpoint] = new google.maps.LatLng(endpoints[i].latitude, endpoints[i].longitude);
|
||||
marker[endpoints[i].endpoint] = new google.maps.Marker({
|
||||
position: locs[endpoints[i].endpoint],
|
||||
map: map,
|
||||
title: endpoints[i].endpoint
|
||||
});
|
||||
cluster.push(marker[endpoints[i].endpoint]);
|
||||
}
|
||||
for (var i=0; i<endpointscount; i++) {
|
||||
if ( endpoints[i].connections_to == null ) {
|
||||
continue
|
||||
}
|
||||
for (var j=0; j<endpoints[i].connections_to.length; j++) {
|
||||
connections[i+j] = new google.maps.Polyline({
|
||||
path: [locs[endpoints[i].endpoint], locs[endpoints[i].connections_to[j]]],
|
||||
geodesic: true,
|
||||
strokeColor: 'blue',
|
||||
strokeOpacity: 1.0,
|
||||
strokeWeight: 1,
|
||||
icons: [{
|
||||
icon: arrowSymbol,
|
||||
offset: '100%'
|
||||
}],
|
||||
map: map
|
||||
});
|
||||
}
|
||||
}
|
||||
animateCircle();
|
||||
var markerCluster = new MarkerClusterer(map, cluster);
|
||||
}
|
||||
// Use the DOM setInterval() function to change the offset of the symbol
|
||||
// at fixed intervals.
|
||||
function animateCircle() {
|
||||
var count = 0;
|
||||
window.setInterval(function() {
|
||||
count = (count + 1) % 200;
|
||||
conncount = connections.length;
|
||||
for (var i=0; i<conncount; i++) {
|
||||
var icons = connections[i].get('icons');
|
||||
icons[0].offset = (count / 2) + '%';
|
||||
connections[i].set('icons', icons);
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
|
||||
google.maps.event.addDomListener(window, 'load', initialize);
|
||||
</script>
|
||||
<style type="text/css">
|
||||
#map {
|
||||
width:100%;
|
||||
height:600px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
`
|
||||
footer += fmt.Sprintf(" <p><b>%s</b></p>\n", title)
|
||||
footer += `<div id="map"></div>`
|
||||
for _, p := range body {
|
||||
footer += p + "\n"
|
||||
}
|
||||
footer += `
|
||||
</body>
|
||||
</html>`
|
||||
return
|
||||
}
|
|
@ -163,16 +163,7 @@ times show the various timestamps of the action
|
|||
panic("invalid show '" + orders[2] + "'")
|
||||
}
|
||||
}
|
||||
render := "text"
|
||||
if len(orders) > 2 {
|
||||
switch orders[2] {
|
||||
case "map", "text":
|
||||
render = orders[2]
|
||||
default:
|
||||
panic("invalid rendering '" + orders[2] + "'")
|
||||
}
|
||||
}
|
||||
err = cli.PrintActionResults(a, show, render)
|
||||
err = cli.PrintActionResults(a, show)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
@ -51,11 +51,6 @@ usage: %s <module> <global options> <module parameters>
|
|||
* notfound: Only print negative results
|
||||
* all: Print all results
|
||||
|
||||
-render <mode> Defines how results should be rendered.
|
||||
|
||||
* text (default): Results are printed to the console
|
||||
* map: Results are geolocated and a google map is generated
|
||||
|
||||
-t <target> Target to launch the action on. If no target is specified, the value will
|
||||
default to all online agents (status='online')
|
||||
|
||||
|
@ -103,7 +98,7 @@ func main() {
|
|||
err error
|
||||
op mig.Operation
|
||||
a mig.Action
|
||||
migrc, show, render, target, expiration string
|
||||
migrc, show, target, expiration string
|
||||
afile, aname, targetfound, targetnotfound string
|
||||
signAndOutput bool
|
||||
printAndExit bool
|
||||
|
@ -127,7 +122,6 @@ func main() {
|
|||
fs.BoolVar(&printAndExit, "p", false, "display action json that would be used and exit")
|
||||
fs.StringVar(&migrc, "c", homedir+"/.migrc", "alternative configuration file")
|
||||
fs.StringVar(&show, "show", "found", "type of results to show")
|
||||
fs.StringVar(&render, "render", "text", "results rendering mode")
|
||||
fs.StringVar(&target, "t", "", "action target")
|
||||
fs.StringVar(&targetfound, "target-found", "", "targets agents that have found results in a previous action.")
|
||||
fs.StringVar(&targetnotfound, "target-notfound", "", "targets agents that haven't found results in a previous action.")
|
||||
|
@ -425,7 +419,7 @@ readytolaunch:
|
|||
fmt.Fprintf(os.Stderr, "[notice] stopped following action, but agents may still be running.\n")
|
||||
fmt.Fprintf(os.Stderr, "fetching available results:\n")
|
||||
}
|
||||
err = cli.PrintActionResults(a, show, render)
|
||||
err = cli.PrintActionResults(a, show)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
100
doc/api.rst
100
doc/api.rst
|
@ -857,10 +857,6 @@ GET /api/v1/search
|
|||
with `limit`, offset can be used to paginate search results.
|
||||
ex: **&limit=10&offset=50** will grab 10 results discarding the first 50.
|
||||
|
||||
- `report`: if set, return results in the given report format:
|
||||
- `complianceitems` returns command results as compliance items
|
||||
- `geolocations` returns command results as geolocation endpoints
|
||||
|
||||
- `status`: filter on internal status, accept `ILIKE` pattern.
|
||||
Status depends on the type. Below are the available statuses per type:
|
||||
|
||||
|
@ -885,15 +881,6 @@ Note: URL encoding transform the **%** character into **%25**, its ASCII value.
|
|||
|
||||
* Examples:
|
||||
|
||||
Generate a compliance report from `compliance` action ran over the last 24
|
||||
hours. For more information on the `compliance` format, see section 2.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
/api/v1/search?type=command&threatfamily=compliance&status=done
|
||||
&report=complianceitems&limit=100000
|
||||
&after=2014-05-30T00:00:00-04:00&before=2014-05-30T23:59:59-04:00
|
||||
|
||||
List the agents that have sent a heartbeat in the last hour.
|
||||
|
||||
.. code:: bash
|
||||
|
@ -1207,93 +1194,6 @@ POST /api/v1/manifest/fetch/
|
|||
}
|
||||
}
|
||||
|
||||
Data transformation
|
||||
-------------------
|
||||
|
||||
The API implements several data transformation functions between the base
|
||||
format of `action` and `command`, and reporting formats.
|
||||
|
||||
Compliance Items
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The compliance item format is used to measure the compliance of a target with
|
||||
particular requirement. A single compliance item represent the compliance of
|
||||
one target (host) with one check (test + value).
|
||||
|
||||
In MIG, an `action` can contain compliance checks. An `action` creates one
|
||||
`command` per `agent`. Upon completion, the agent stores the results in the
|
||||
`command.results`. To visualize the results of an action, an investigator must
|
||||
look at the results of each command generated by that action.
|
||||
|
||||
To generate compliance items, the API takes the results from commands, and
|
||||
creates one item per result. Therefore, a single action that creates hundreds of
|
||||
commands could, in turn, generate thousands of compliance items.
|
||||
|
||||
The format for compliance items is simple, to be easily graphed and aggregated.
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"target": "server1.mydomain.example.net",
|
||||
"utctimestamp": "2015-02-19T02:59:30.203004Z",
|
||||
"tags": {
|
||||
"operator": "IT"
|
||||
},
|
||||
"compliance": true,
|
||||
"link": "https://api.mig.example.net/api/v1/command?commandid=1424314751392165120",
|
||||
"policy": {
|
||||
"url": "https://wiki.example.net/ComplianceDoc/IT+System+security+guidelines",
|
||||
"name": "system",
|
||||
"level": "low"
|
||||
},
|
||||
"check": {
|
||||
"test": {
|
||||
"type": "file",
|
||||
"value": "content='^-w /var/spool/cron/root -p wa'"
|
||||
},
|
||||
"location": "/etc/audit/audit.rules",
|
||||
"ref": "syslowaudit1",
|
||||
"description": "compliance check for auditd",
|
||||
"name": "attemptstoaltercrontab_user_config"
|
||||
}
|
||||
}
|
||||
|
||||
When using the parameter `&report=complianceitems`, the `search` endpoint of the API
|
||||
will generate a list of compliance items from the results of the search.
|
||||
|
||||
Geolocations
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The geolocations format transforms command results into an array of geolocated
|
||||
endpoints for consumption by a map, like Google Maps. The format discards
|
||||
results details, and only stores the value of FoundAnything.
|
||||
|
||||
This feature requires using **MaxMind's GeoIP2-City** database. The database
|
||||
must be configured in the API as follow:
|
||||
|
||||
.. code::
|
||||
|
||||
[maxmind]
|
||||
path = "/etc/mig/GeoIP2-City.mmdb"
|
||||
|
||||
Geolocations are returned as CLJS items in this format:
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"actionid": 1.4271242660295127e+18,
|
||||
"city": "Absecon",
|
||||
"commandid": 1.427124243673173e+18,
|
||||
"country": "United States",
|
||||
"endpoint": "somehost.example.net",
|
||||
"foundanything": true,
|
||||
"latitude": 39.4284,
|
||||
"longitude": -74.4957
|
||||
}
|
||||
|
||||
When using the parameter `&report=geolocations`, the `search` endpoint of the
|
||||
API will generate a list of geolocations from the results of the search.
|
||||
|
||||
Authentication with X-PGPAUTHORIZATION version 1
|
||||
------------------------------------------------
|
||||
|
||||
|
|
|
@ -1,159 +0,0 @@
|
|||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"mig.ninja/mig"
|
||||
"mig.ninja/mig/modules"
|
||||
"mig.ninja/mig/modules/file"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ComplianceItem struct {
|
||||
Utctimestamp string `json:"utctimestamp"`
|
||||
Target string `json:"target"`
|
||||
Policy CompliancePolicy `json:"policy"`
|
||||
Check ComplianceCheck `json:"check"`
|
||||
Compliance bool `json:"compliance"`
|
||||
Link string `json:"link"`
|
||||
Tags map[string]string `json:"tags"`
|
||||
}
|
||||
|
||||
type CompliancePolicy struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Level string `json:"level"`
|
||||
}
|
||||
|
||||
type ComplianceCheck struct {
|
||||
Ref string `json:"ref"`
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
Location string `json:"location"`
|
||||
Test ComplianceTest `json:"test"`
|
||||
}
|
||||
|
||||
type ComplianceTest struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func commandsToComplianceItems(commands []mig.Command) (items []ComplianceItem, err error) {
|
||||
for _, cmd := range commands {
|
||||
var bitem ComplianceItem
|
||||
bitem.Utctimestamp = cmd.FinishTime.UTC().Format(time.RFC3339Nano)
|
||||
bitem.Target = cmd.Agent.Name
|
||||
bitem.Policy.Name = cmd.Action.Threat.Type
|
||||
bitem.Policy.URL = cmd.Action.Description.URL
|
||||
bitem.Policy.Level = cmd.Action.Threat.Level
|
||||
bitem.Check.Ref = cmd.Action.Threat.Ref
|
||||
bitem.Check.Description = cmd.Action.Name
|
||||
bitem.Link = fmt.Sprintf("%s/command?commandid=%.0f", ctx.Server.BaseURL, cmd.ID)
|
||||
if _, ok := cmd.Agent.Tags["operator"]; ok {
|
||||
bitem.Tags["operator"] = cmd.Agent.Tags["operator"]
|
||||
}
|
||||
for i, result := range cmd.Results {
|
||||
buf, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return items, err
|
||||
}
|
||||
if i > (len(cmd.Action.Operations) - 1) {
|
||||
// skip this entry if the lookup fails
|
||||
continue
|
||||
}
|
||||
switch cmd.Action.Operations[i].Module {
|
||||
case "file":
|
||||
var el file.SearchResults
|
||||
var r modules.Result
|
||||
err = json.Unmarshal(buf, &r)
|
||||
if err != nil {
|
||||
return items, err
|
||||
}
|
||||
err = r.GetElements(&el)
|
||||
if err != nil {
|
||||
return items, err
|
||||
}
|
||||
for label, sr := range el {
|
||||
for _, mf := range sr {
|
||||
bitem.Check.Location = mf.File
|
||||
bitem.Check.Name = label
|
||||
bitem.Check.Test.Type = "file"
|
||||
bitem.Check.Test.Value = ""
|
||||
for _, v := range mf.Search.Names {
|
||||
if len(bitem.Check.Test.Value) > 0 {
|
||||
bitem.Check.Test.Value += " and "
|
||||
}
|
||||
bitem.Check.Test.Value += fmt.Sprintf("name='%s'", v)
|
||||
}
|
||||
for _, v := range mf.Search.Sizes {
|
||||
if len(bitem.Check.Test.Value) > 0 {
|
||||
bitem.Check.Test.Value += " and "
|
||||
}
|
||||
bitem.Check.Test.Value += fmt.Sprintf("size='%s'", v)
|
||||
}
|
||||
for _, v := range mf.Search.Modes {
|
||||
if len(bitem.Check.Test.Value) > 0 {
|
||||
bitem.Check.Test.Value += " and "
|
||||
}
|
||||
bitem.Check.Test.Value += fmt.Sprintf("mode='%s'", v)
|
||||
}
|
||||
for _, v := range mf.Search.Mtimes {
|
||||
if len(bitem.Check.Test.Value) > 0 {
|
||||
bitem.Check.Test.Value += " and "
|
||||
}
|
||||
bitem.Check.Test.Value += fmt.Sprintf("mtime='%s'", v)
|
||||
}
|
||||
for _, v := range mf.Search.Contents {
|
||||
if len(bitem.Check.Test.Value) > 0 {
|
||||
bitem.Check.Test.Value += " and "
|
||||
}
|
||||
bitem.Check.Test.Value += fmt.Sprintf("content='%s'", v)
|
||||
}
|
||||
for _, v := range mf.Search.MD5 {
|
||||
if len(bitem.Check.Test.Value) > 0 {
|
||||
bitem.Check.Test.Value += " and "
|
||||
}
|
||||
bitem.Check.Test.Value += fmt.Sprintf("md5='%s'", v)
|
||||
}
|
||||
for _, v := range mf.Search.SHA1 {
|
||||
if len(bitem.Check.Test.Value) > 0 {
|
||||
bitem.Check.Test.Value += " and "
|
||||
}
|
||||
bitem.Check.Test.Value += fmt.Sprintf("sha1='%s'", v)
|
||||
}
|
||||
for _, v := range mf.Search.SHA2 {
|
||||
if len(bitem.Check.Test.Value) > 0 {
|
||||
bitem.Check.Test.Value += " and "
|
||||
}
|
||||
bitem.Check.Test.Value += fmt.Sprintf("sha2='%s'", v)
|
||||
}
|
||||
for _, v := range mf.Search.SHA3 {
|
||||
if len(bitem.Check.Test.Value) > 0 {
|
||||
bitem.Check.Test.Value += " and "
|
||||
}
|
||||
bitem.Check.Test.Value += fmt.Sprintf("sha3='%s'", v)
|
||||
}
|
||||
if mf.File == "" {
|
||||
for i, p := range mf.Search.Paths {
|
||||
if i > 0 {
|
||||
bitem.Check.Location += ", "
|
||||
}
|
||||
bitem.Check.Location += p
|
||||
}
|
||||
bitem.Compliance = false
|
||||
} else {
|
||||
bitem.Compliance = true
|
||||
}
|
||||
items = append(items, bitem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
|
@ -7,7 +7,6 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
geo "github.com/oschwald/geoip2-golang"
|
||||
"gopkg.in/gcfg.v1"
|
||||
"io"
|
||||
"mig.ninja/mig"
|
||||
|
@ -50,10 +49,6 @@ type Context struct {
|
|||
ClientPublicIP string
|
||||
ClientPublicIPOffset int
|
||||
}
|
||||
MaxMind struct {
|
||||
Path string
|
||||
r *geo.Reader
|
||||
}
|
||||
Logging mig.Logging
|
||||
}
|
||||
|
||||
|
@ -106,12 +101,6 @@ func Init(path string, debug bool) (ctx Context, err error) {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
if ctx.MaxMind.Path != "" {
|
||||
ctx.MaxMind.r, err = geo.Open(ctx.MaxMind.Path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mig.ninja/mig"
|
||||
"net"
|
||||
)
|
||||
|
||||
type CommandLocation struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
CommandID float64 `json:"commandid"`
|
||||
ActionID float64 `json:"actionid"`
|
||||
FoundAnything bool `json:"foundanything"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
City string `json:"city"`
|
||||
Country string `json:"country"`
|
||||
}
|
||||
|
||||
func commandsToGeolocations(commands []mig.Command) (items []CommandLocation, err error) {
|
||||
if ctx.MaxMind.r == nil {
|
||||
return items, fmt.Errorf("maxmind database not initialized")
|
||||
}
|
||||
var cl CommandLocation
|
||||
for _, cmd := range commands {
|
||||
if cmd.Agent.Env.PublicIP == "" {
|
||||
continue
|
||||
}
|
||||
record, err := ctx.MaxMind.r.City(net.ParseIP(cmd.Agent.Env.PublicIP))
|
||||
if err != nil {
|
||||
return items, err
|
||||
}
|
||||
cl.Latitude = record.Location.Latitude
|
||||
cl.Longitude = record.Location.Longitude
|
||||
cl.City = record.City.Names["en"]
|
||||
cl.Country = record.Country.Names["en"]
|
||||
cl.Endpoint = cmd.Agent.Name
|
||||
cl.CommandID = cmd.ID
|
||||
cl.ActionID = cmd.Action.ID
|
||||
for _, r := range cmd.Results {
|
||||
if r.FoundAnything {
|
||||
cl.FoundAnything = true
|
||||
}
|
||||
}
|
||||
items = append(items, cl)
|
||||
}
|
||||
return
|
||||
}
|
|
@ -84,21 +84,17 @@ func search(respWriter http.ResponseWriter, request *http.Request) {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
// prepare the output in the requested format
|
||||
switch p.Report {
|
||||
case "complianceitems":
|
||||
if p.Type != "command" {
|
||||
panic("compliance items reporting is only available for the 'command' type")
|
||||
switch p.Type {
|
||||
case "action":
|
||||
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d actions", len(results.([]mig.Action)))}
|
||||
if len(results.([]mig.Action)) == 0 {
|
||||
panic("no results found")
|
||||
}
|
||||
items, err := commandsToComplianceItems(results.([]mig.Command))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for i, item := range items {
|
||||
for i, r := range results.([]mig.Action) {
|
||||
err = resource.AddItem(cljs.Item{
|
||||
Href: fmt.Sprintf("%s%s/search?type=command?agentname=%s&commandid=%s&actionid=%s&threatfamily=compliance&report=complianceitems",
|
||||
ctx.Server.Host, ctx.Server.BaseRoute, item.Target, p.CommandID, p.ActionID),
|
||||
Data: []cljs.Data{{Name: "compliance item", Value: item}},
|
||||
Href: fmt.Sprintf("%s%s/action?actionid=%.0f",
|
||||
ctx.Server.Host, ctx.Server.BaseRoute, r.ID),
|
||||
Data: []cljs.Data{{Name: p.Type, Value: r}},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -107,19 +103,16 @@ func search(respWriter http.ResponseWriter, request *http.Request) {
|
|||
break
|
||||
}
|
||||
}
|
||||
case "geolocations":
|
||||
if p.Type != "command" {
|
||||
panic("geolocations reporting is only available for the 'command' type")
|
||||
case "agent":
|
||||
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d agents", len(results.([]mig.Agent)))}
|
||||
if len(results.([]mig.Agent)) == 0 {
|
||||
panic("no results found")
|
||||
}
|
||||
items, err := commandsToGeolocations(results.([]mig.Command))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for i, item := range items {
|
||||
for i, r := range results.([]mig.Agent) {
|
||||
err = resource.AddItem(cljs.Item{
|
||||
Href: fmt.Sprintf("%s%s/search?type=command?agentname=%s&commandid=%s&actionid=%s&report=geolocations",
|
||||
ctx.Server.Host, ctx.Server.BaseRoute, item.Endpoint, p.CommandID, p.ActionID),
|
||||
Data: []cljs.Data{{Name: "geolocation", Value: item}},
|
||||
Href: fmt.Sprintf("%s%s/agent?agentid=%.0f",
|
||||
ctx.Server.Host, ctx.Server.BaseRoute, r.ID),
|
||||
Data: []cljs.Data{{Name: p.Type, Value: r}},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -128,116 +121,77 @@ func search(respWriter http.ResponseWriter, request *http.Request) {
|
|||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
switch p.Type {
|
||||
case "action":
|
||||
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d actions", len(results.([]mig.Action)))}
|
||||
if len(results.([]mig.Action)) == 0 {
|
||||
panic("no results found")
|
||||
case "command":
|
||||
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d commands", len(results.([]mig.Command)))}
|
||||
if len(results.([]mig.Command)) == 0 {
|
||||
panic("no results found")
|
||||
}
|
||||
for i, r := range results.([]mig.Command) {
|
||||
err = resource.AddItem(cljs.Item{
|
||||
Href: fmt.Sprintf("%s%s/command?commandid=%.0f",
|
||||
ctx.Server.Host, ctx.Server.BaseRoute, r.ID),
|
||||
Data: []cljs.Data{{Name: p.Type, Value: r}},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for i, r := range results.([]mig.Action) {
|
||||
err = resource.AddItem(cljs.Item{
|
||||
Href: fmt.Sprintf("%s%s/action?actionid=%.0f",
|
||||
ctx.Server.Host, ctx.Server.BaseRoute, r.ID),
|
||||
Data: []cljs.Data{{Name: p.Type, Value: r}},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if float64(i) > p.Limit {
|
||||
break
|
||||
}
|
||||
if float64(i) > p.Limit {
|
||||
break
|
||||
}
|
||||
case "agent":
|
||||
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d agents", len(results.([]mig.Agent)))}
|
||||
if len(results.([]mig.Agent)) == 0 {
|
||||
panic("no results found")
|
||||
}
|
||||
case "investigator":
|
||||
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d investigators", len(results.([]mig.Investigator)))}
|
||||
if len(results.([]mig.Investigator)) == 0 {
|
||||
panic("no results found")
|
||||
}
|
||||
for i, r := range results.([]mig.Investigator) {
|
||||
err = resource.AddItem(cljs.Item{
|
||||
Href: fmt.Sprintf("%s%s/investigator?investigatorid=%.0f",
|
||||
ctx.Server.Host, ctx.Server.BaseRoute, r.ID),
|
||||
Data: []cljs.Data{{Name: p.Type, Value: r}},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for i, r := range results.([]mig.Agent) {
|
||||
err = resource.AddItem(cljs.Item{
|
||||
Href: fmt.Sprintf("%s%s/agent?agentid=%.0f",
|
||||
ctx.Server.Host, ctx.Server.BaseRoute, r.ID),
|
||||
Data: []cljs.Data{{Name: p.Type, Value: r}},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if float64(i) > p.Limit {
|
||||
break
|
||||
}
|
||||
if float64(i) > p.Limit {
|
||||
break
|
||||
}
|
||||
case "command":
|
||||
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d commands", len(results.([]mig.Command)))}
|
||||
if len(results.([]mig.Command)) == 0 {
|
||||
panic("no results found")
|
||||
}
|
||||
case "manifest":
|
||||
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d manifests", len(results.([]mig.ManifestRecord)))}
|
||||
if len(results.([]mig.ManifestRecord)) == 0 {
|
||||
panic("no results found")
|
||||
}
|
||||
for i, r := range results.([]mig.ManifestRecord) {
|
||||
err = resource.AddItem(cljs.Item{
|
||||
Href: fmt.Sprintf("%s%s/manifest?manifestid=%.0f",
|
||||
ctx.Server.Host, ctx.Server.BaseRoute, r.ID),
|
||||
Data: []cljs.Data{{Name: p.Type, Value: r}},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for i, r := range results.([]mig.Command) {
|
||||
err = resource.AddItem(cljs.Item{
|
||||
Href: fmt.Sprintf("%s%s/command?commandid=%.0f",
|
||||
ctx.Server.Host, ctx.Server.BaseRoute, r.ID),
|
||||
Data: []cljs.Data{{Name: p.Type, Value: r}},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if float64(i) > p.Limit {
|
||||
break
|
||||
}
|
||||
if float64(i) > p.Limit {
|
||||
break
|
||||
}
|
||||
case "investigator":
|
||||
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d investigators", len(results.([]mig.Investigator)))}
|
||||
if len(results.([]mig.Investigator)) == 0 {
|
||||
panic("no results found")
|
||||
}
|
||||
case "loader":
|
||||
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d loaders", len(results.([]mig.LoaderEntry)))}
|
||||
if len(results.([]mig.LoaderEntry)) == 0 {
|
||||
panic("no results found")
|
||||
}
|
||||
for i, r := range results.([]mig.LoaderEntry) {
|
||||
err = resource.AddItem(cljs.Item{
|
||||
// XXX This should be an Href to fetch the entry
|
||||
Href: fmt.Sprintf("%s%s", ctx.Server.Host,
|
||||
ctx.Server.BaseRoute),
|
||||
Data: []cljs.Data{{Name: p.Type, Value: r}},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for i, r := range results.([]mig.Investigator) {
|
||||
err = resource.AddItem(cljs.Item{
|
||||
Href: fmt.Sprintf("%s%s/investigator?investigatorid=%.0f",
|
||||
ctx.Server.Host, ctx.Server.BaseRoute, r.ID),
|
||||
Data: []cljs.Data{{Name: p.Type, Value: r}},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if float64(i) > p.Limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
case "manifest":
|
||||
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d manifests", len(results.([]mig.ManifestRecord)))}
|
||||
if len(results.([]mig.ManifestRecord)) == 0 {
|
||||
panic("no results found")
|
||||
}
|
||||
for i, r := range results.([]mig.ManifestRecord) {
|
||||
err = resource.AddItem(cljs.Item{
|
||||
Href: fmt.Sprintf("%s%s/manifest?manifestid=%.0f",
|
||||
ctx.Server.Host, ctx.Server.BaseRoute, r.ID),
|
||||
Data: []cljs.Data{{Name: p.Type, Value: r}},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if float64(i) > p.Limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
case "loader":
|
||||
ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d loaders", len(results.([]mig.LoaderEntry)))}
|
||||
if len(results.([]mig.LoaderEntry)) == 0 {
|
||||
panic("no results found")
|
||||
}
|
||||
for i, r := range results.([]mig.LoaderEntry) {
|
||||
err = resource.AddItem(cljs.Item{
|
||||
// XXX This should be an Href to fetch the entry
|
||||
Href: fmt.Sprintf("%s%s", ctx.Server.Host,
|
||||
ctx.Server.BaseRoute),
|
||||
Data: []cljs.Data{{Name: p.Type, Value: r}},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if float64(i) > p.Limit {
|
||||
break
|
||||
}
|
||||
if float64(i) > p.Limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -338,15 +292,6 @@ func parseSearchParameters(qp url.Values) (p migdbsearch.Parameters, filterFound
|
|||
if err != nil {
|
||||
panic("invalid offset parameter")
|
||||
}
|
||||
case "report":
|
||||
switch qp["report"][0] {
|
||||
case "complianceitems":
|
||||
p.Report = qp["report"][0]
|
||||
case "geolocations":
|
||||
p.Report = qp["report"][0]
|
||||
default:
|
||||
panic("report not implemented")
|
||||
}
|
||||
case "status":
|
||||
p.Status = qp["status"][0]
|
||||
case "target":
|
||||
|
|
Загрузка…
Ссылка в новой задаче