Skip to content

Commit e6866d7

Browse files
committed
Fixed #85: Add API endpoint to manage IOCs spread on endpoints for detection
1 parent dcacc6d commit e6866d7

20 files changed

+699
-461
lines changed

.github/coverage/coverage.txt

Lines changed: 103 additions & 87 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
* Built by an Incident Responder for all Incident Responders to make their job easier
4040
* Low footprint (no process injection)
4141
* Can co-exist with **any antivirus** product (advised to run it along with **MS Defender**)
42-
* Designed for high thoughput. It can easily enrich and analyse 4M events a day per endpoint without performance impact. Good luck to achieve that with a SIEM.
42+
* Designed for high throughput. It can easily enrich and analyze 4M events a day per endpoint without performance impact. Good luck to achieve that with a SIEM.
4343
* Easily integrable with other tools (Splunk, ELK, MISP ...)
4444
* Integrated with [ATT&CK framework](https://attack.mitre.org/)
4545

@@ -88,12 +88,12 @@ This section covers the installation of the agent on the endpoint.
8888
2. Run `manage.bat` as **administrator**
8989
3. Launch installation by selecting the appropriate option
9090
4. Verify that files have been created at the **installation directory**
91-
5. Edit configuration file by selecting the appropriate option in `manage.bat` or using your prefered text editor
91+
5. Edit configuration file by selecting the appropriate option in `manage.bat` or using your preferred text editor
9292
6. Skip this if running with a connection to a manager, because rules will be updated automatically. If there is nothing in the **rules directory** the tool will be useless, so make sure there are some **gene** rules in there. Some rules are packaged with WHIDS and you will be prompted to choose if you want to install those or not. If you want the last up to date rules, you can get those [here](https://raw.githubusercontent.com/0xrawsec/gene-rules/master/compiled.gen) (take the **compiled** ones)
9393
7. Start the **services** from appropriate option in `manage.bat` or just reboot (**preferred option** otherwise some enrichment fields will be incomplete leading to false alerts)
9494
8. If you configured a **manager** do not forget to run it in order to receive alerts and dumps
9595

96-
**NB:** At installation time the **Sysmon service** will be made *dependant* of **WHIDS service** so that we are sure the EDR runs before **Sysmon** starts generating some events.
96+
**NB:** At installation time the **Sysmon service** will be made *dependent* of **WHIDS service** so that we are sure the EDR runs before **Sysmon** starts generating some events.
9797

9898
## EDR Manager
9999

@@ -105,7 +105,7 @@ The EDR manager can be installed on several platforms, pre-built binaries are pr
105105

106106
# Configuration Examples
107107

108-
Please visit [doc/configuration.md](https://github.com/0xrawsec/whids/blob/master/doc/configuration.md)
108+
Please visit [doc/configuration.md](doc/configuration.md)
109109

110110
# Further Documentation
111111

@@ -141,7 +141,7 @@ Please visit [doc/configuration.md](https://github.com/0xrawsec/whids/blob/maste
141141
* Put the content of the clipboard data inside the event to allow creating rule on the content of the clipboard
142142
- Integrate ProcessTampering events
143143
* Enrich event with a diffing score between .text section on disk and in memory
144-
- Implemented certificate pinning on client to enhance security of the communiaction channel between endpoints and management server
144+
- Implemented certificate pinning on client to enhance security of the communication channel between endpoints and management server
145145
- Log filtering capabilities, allowing one to collect contextual events. Log filtering is achieved by creating Gene filtering rules (c.f. [Gene Documentation](https://github.com/0xrawsec/gene)).
146146
- Configuration files in TOML format for better readability
147147
- Better protection of the installation directory

api/adminapi_test.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,40 @@ var (
2626
}
2727
)
2828

29-
func doRequest(method, url string) (r AdminAPIResponse) {
30-
cl := http.Client{Transport: cconf.Transport()}
29+
func prepare(method, URL string, data []byte, params map[string]string) *http.Request {
3130
key := testAdminUser.Key
32-
uri := fmt.Sprintf("https://%s:%d%s", mconf.AdminAPI.Host, mconf.AdminAPI.Port, url)
33-
req, err := http.NewRequest(method, uri, new(bytes.Buffer))
31+
buf := new(bytes.Buffer)
32+
33+
// preparing request body
34+
if data != nil {
35+
if len(data) > 0 {
36+
buf.Write(data)
37+
}
38+
}
39+
40+
// preparing parameters to be passed to the query
41+
if params != nil {
42+
v := url.Values{}
43+
for param, value := range params {
44+
v.Set(param, value)
45+
}
46+
47+
if len(params) > 0 {
48+
URL = fmt.Sprintf("%s?%s", URL, v.Encode())
49+
}
50+
}
51+
52+
uri := fmt.Sprintf("https://%s:%d%s", mconf.AdminAPI.Host, mconf.AdminAPI.Port, URL)
53+
req, err := http.NewRequest(method, uri, buf)
3454
if err != nil {
3555
panic(err)
3656
}
3757
req.Header.Add(AuthKeyHeader, key)
58+
return req
59+
}
60+
61+
func do(req *http.Request) (r AdminAPIResponse) {
62+
cl := http.Client{Transport: cconf.Transport()}
3863
resp, err := cl.Do(req)
3964
if err != nil {
4065
panic(err)
@@ -54,11 +79,11 @@ func doRequest(method, url string) (r AdminAPIResponse) {
5479
}
5580

5681
func get(url string) (r AdminAPIResponse) {
57-
return doRequest("GET", url)
82+
return do(prepare("GET", url, nil, nil))
5883
}
5984

6085
func put(url string) (r AdminAPIResponse) {
61-
return doRequest("PUT", url)
86+
return do(prepare("PUT", url, nil, nil))
6287
}
6388

6489
func post(url string, data []byte) (r AdminAPIResponse) {
@@ -82,8 +107,10 @@ func post(url string, data []byte) (r AdminAPIResponse) {
82107
if err != nil {
83108
panic(err)
84109
}
85-
if err := json.Unmarshal(b, &r); err != nil {
86-
panic(err)
110+
if len(b) > 0 {
111+
if err := json.Unmarshal(b, &r); err != nil {
112+
panic(err)
113+
}
87114
}
88115
return
89116
}
@@ -323,7 +350,7 @@ func TestAdminAPIGetEndpointReport(t *testing.T) {
323350
failOnAdminAPIError(t, r)
324351
t.Logf("received: %s", prettyJSON(r))
325352

326-
r = doRequest("DELETE", AdmAPIEndpointsPath+"/"+euuid+"/report")
353+
r = do(prepare("DELETE", AdmAPIEndpointsPath+"/"+euuid+"/report", nil, nil))
327354
failOnAdminAPIError(t, r)
328355
t.Logf("received: %s", prettyJSON(r))
329356

api/api_client.go

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -305,12 +305,12 @@ func (m *ManagerClient) GetRulesSha256() (string, error) {
305305
return "", nil
306306
}
307307

308-
// GetContainer retrieves a given container from the manager
309-
func (m *ManagerClient) GetContainer(name string) ([]string, error) {
308+
// GetIoCs get IoCs from manager
309+
func (m *ManagerClient) GetIoCs() ([]string, error) {
310310
ctn := make([]string, 0)
311311

312312
if auth, _ := m.IsServerAuthenticated(); auth {
313-
req, err := m.Prepare("GET", strings.Replace(EptAPIContainerPath, "{name}", name, 1), nil)
313+
req, err := m.Prepare("GET", EptAPIIoCsPath, nil)
314314
if err != nil {
315315
return ctn, fmt.Errorf("GetContainer failed to prepare request: %s", err)
316316
}
@@ -334,40 +334,11 @@ func (m *ManagerClient) GetContainer(name string) ([]string, error) {
334334
return ctn, nil
335335
}
336336

337-
// GetContainersList retrieves the names of the containers available in the manager
338-
func (m *ManagerClient) GetContainersList() ([]string, error) {
339-
ctn := make([]string, 0)
340-
341-
if auth, _ := m.IsServerAuthenticated(); auth {
342-
req, err := m.Prepare("GET", EptAPIContainerListPath, nil)
343-
if err != nil {
344-
return ctn, fmt.Errorf("GetContainersList failed to prepare request: %s", err)
345-
}
346-
347-
resp, err := m.HTTPClient.Do(req)
348-
if err != nil {
349-
return ctn, fmt.Errorf("GetContainersList failed to issue HTTP request: %s", err)
350-
}
351-
352-
if resp != nil {
353-
defer resp.Body.Close()
354-
if resp.StatusCode != 200 {
355-
return ctn, fmt.Errorf("failed to retrieve containers list, unexpected HTTP status code %d", resp.StatusCode)
356-
}
357-
dec := json.NewDecoder(resp.Body)
358-
if err = dec.Decode(&ctn); err != nil {
359-
return ctn, fmt.Errorf("GetContainersList failed to decode container list")
360-
}
361-
}
362-
}
363-
return ctn, nil
364-
}
365-
366-
// GetContainerSha256 retrieves a given container from the manager
367-
func (m *ManagerClient) GetContainerSha256(name string) (string, error) {
337+
// GetIoCsSha256 retrieves a sha256 from the IoCs available in the manager
338+
func (m *ManagerClient) GetIoCsSha256() (string, error) {
368339

369340
if auth, _ := m.IsServerAuthenticated(); auth {
370-
req, err := m.Prepare("GET", strings.Replace(EptAPIContainerSha256Path, "{name}", name, 1), nil)
341+
req, err := m.Prepare("GET", EptAPIIoCsSha256Path, nil)
371342
if err != nil {
372343
return "", fmt.Errorf("GetContainerSha256 failed to prepare request: %s", err)
373344
}

api/api_client_test.go

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
package api
22

33
import (
4+
"fmt"
5+
"math/rand"
46
"path/filepath"
57
"strings"
68
"testing"
79
"time"
810

911
"github.com/0xrawsec/golang-utils/crypto/data"
1012
"github.com/0xrawsec/golang-utils/crypto/file"
11-
"github.com/0xrawsec/golang-utils/datastructs"
1213
"github.com/0xrawsec/golang-utils/fsutil/fswalker"
14+
"github.com/0xrawsec/whids/ioc"
1315
"github.com/0xrawsec/whids/utils"
1416
)
1517

1618
var (
17-
cconf = ClientConfig{
19+
cconf = ClientConfig{
1820
Proto: "https",
1921
Host: "localhost",
2022
Port: 8000,
@@ -100,47 +102,65 @@ func TestClientContainer(t *testing.T) {
100102

101103
key := KeyGen(DefaultKeySize)
102104

103-
r, err := NewManager(&mconf)
105+
m, err := NewManager(&mconf)
104106
if err != nil {
105107
panic(err)
106108
}
107-
r.AddEndpoint(cconf.UUID, key)
108-
r.Run()
109-
defer r.Shutdown()
109+
m.AddEndpoint(cconf.UUID, key)
110+
m.Run()
111+
defer m.Shutdown()
110112

111113
cconf.Key = key
112114
c, err := NewManagerClient(&cconf)
113115
if err != nil {
114116
panic(err)
115117
}
116118

117-
containers, err := c.GetContainersList()
118-
if err != nil {
119-
t.Error(err)
120-
t.FailNow()
121-
}
119+
niocs := 1000
120+
iocs := make([]ioc.IoC, 0, niocs)
121+
del := 0
122+
for i := 0; i < niocs; i++ {
123+
key := "test"
124+
if rand.Int()%3 == 0 {
125+
key = "to_delete"
126+
del++
127+
}
122128

123-
verif := datastructs.NewInitSyncedSet(datastructs.ToInterfaceSlice(containers)...)
124-
if !verif.Contains("blacklist") || !verif.Contains("whitelist") {
125-
t.Error("Missing containers")
126-
t.FailNow()
129+
iocs = append(iocs, ioc.IoC{
130+
Value: fmt.Sprintf("%d.random.com", i),
131+
Key: key})
127132
}
133+
post(AdmAPIIocsPath, JSON(iocs))
128134

129-
for _, cont := range containers {
130-
bl, err := c.GetContainer(cont)
131-
if err != nil {
132-
t.Error(err)
135+
if iocs, err := c.GetIoCs(); err != nil {
136+
t.Error(err)
137+
} else {
138+
if len(iocs) != niocs {
139+
t.Error("Unexpected IOC length")
140+
}
141+
if rsha256, _ := c.GetIoCsSha256(); rsha256 != utils.Sha256StringArray(m.iocs.StringSlice()) {
142+
t.Error("IOC container hash is not correct")
133143
}
144+
}
134145

135-
if sha256, err := c.GetContainerSha256(cont); sha256 != utils.Sha256StringArray(bl) || err != nil {
136-
if err != nil {
137-
t.Error(err)
138-
} else {
139-
t.Errorf("Failed to verify container integrity")
140-
}
146+
// deleting iocs from admin API
147+
r := prepare("DELETE",
148+
AdmAPIIocsPath,
149+
nil,
150+
map[string]string{"key": "to_delete"})
151+
do(r)
152+
153+
if iocs, err := c.GetIoCs(); err != nil {
154+
t.Error(err)
155+
} else {
156+
if len(iocs) != niocs-del {
157+
t.Errorf("Unexpected IOC length expected %d, got %d", niocs-del, len(iocs))
158+
}
159+
if rsha256, _ := c.GetIoCsSha256(); rsha256 != utils.Sha256StringArray(m.iocs.StringSlice()) {
160+
t.Error("IOC container hash is not correct")
141161
}
142-
t.Logf("%v", bl)
143162
}
163+
144164
}
145165

146166
func TestClientExecuteCommand(t *testing.T) {

api/forwarder_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,9 @@ var (
4646
Root: "./data/logs",
4747
LogBasename: "alerts",
4848
},
49-
Database: "./data/database",
50-
RulesDir: "./data",
51-
DumpDir: "./data/uploads/",
52-
ContainersDir: "./data/containers",
49+
Database: "./data/database",
50+
RulesDir: "./data",
51+
DumpDir: "./data/uploads/",
5352
TLS: TLSConfig{
5453
Cert: "./data/cert.pem",
5554
Key: "./data/key.pem",

0 commit comments

Comments
 (0)