├── LICENSE
├── README.md
├── docs
└── package.txt
├── go.mod
├── go.sum
└── misp
├── config.json.example
├── misp.go
├── misp_test.go
└── version.go
/LICENSE:
--------------------------------------------------------------------------------
1 | Golang MISP is a pure golang library to interact with the Malware Information
2 | Sharing Platform
3 |
4 | Copyright (C) 2016 Quentin JEROME
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with this program. If not, see .
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MISP Golang
2 |
3 | So far, only search is implemented but it might already be enough for several use
4 | cases.
5 |
6 | # Installation
7 |
8 | ```
9 | get -u github.com/0xrawsec/golang-misp
10 | ```
11 |
12 | # Testing
13 |
14 | Before testing, you need to put a valid `config.json` file under the testing
15 | directory `misp/test`. Copy the `config.json.example` found at the root of the
16 | project and modify it.
17 |
18 | ```
19 | # Go to misp/test directory
20 | go test -v
21 | ```
22 |
23 | # Documentation
24 |
25 | Install the golang-misp package into your go project and issue the following
26 | command.
27 |
28 | ```
29 | go doc misp
30 | ```
31 |
--------------------------------------------------------------------------------
/docs/package.txt:
--------------------------------------------------------------------------------
1 | use 'godoc cmd/misp' for documentation on the misp command
2 |
3 | PACKAGE DOCUMENTATION
4 |
5 | package misp
6 | import "misp"
7 |
8 |
9 | VARIABLES
10 |
11 | var (
12 | // ErrUnknownProtocol : raised when bad protocol specified
13 | ErrUnknownProtocol = errors.New("Unknown protocol")
14 | )
15 |
16 | TYPES
17 |
18 | type EmptyMispResponse struct{}
19 |
20 | func (emr EmptyMispResponse) Iter() chan MispObject
21 | Iter : MispResponse implementation
22 |
23 | type MispAttribute struct {
24 | ID string `json:"id"`
25 | EventID string `json:"event_id"`
26 | UUID string `json:"uuid"`
27 | SharingGroupID string `json:"sharing_group_id"`
28 | StrTimestamp string `json:"timestamp"`
29 | Distribution string `json:"distribution"`
30 | Category string `json:"category"`
31 | Type string `json:"type"`
32 | Value string `json:"value"`
33 | ToIDS bool `json:"to_ids"`
34 | Deleted bool `json:"deleted"`
35 | Comment string `json:"comment"`
36 | }
37 | MispAttribute : define structure of attribute object returned by API
38 |
39 | func (ma MispAttribute) Timestamp() time.Time
40 | Timestamp : return Time struct according to a string time
41 |
42 | type MispAttributeDict struct {
43 | Attribute []MispAttribute `json:"Attribute"`
44 | }
45 | MispAttributeDict : itermediate structure to handle MISP attribute
46 | search
47 |
48 | type MispAttributeQuery struct {
49 | Value string `json:"value"`
50 | Type string `json:"type"`
51 | Category string `json:"category"`
52 | Org string `json:"org"`
53 | Tags string `json:"tags"`
54 | From string `json:"from"`
55 | To string `json:"to"`
56 | Last string `json:"last"`
57 | EventID string `json:"eventid"`
58 | UUID string `json:"uuid"`
59 | }
60 |
61 | func (maq MispAttributeQuery) Prepare() (j []byte)
62 | Prepare : MispQuery Implementation
63 |
64 | type MispAttributeResponse struct {
65 | Response MispAttributeDict `json:"response"`
66 | }
67 | MispAttributeResponse : API response when attribute query is done
68 |
69 | func (mar MispAttributeResponse) Iter() (moc chan MispObject)
70 | Iter : MispResponse implementation
71 |
72 | type MispCon struct {
73 | Proto string
74 | Host string
75 | APIKey string
76 | RestAPIURL string
77 | Client *http.Client
78 | }
79 |
80 | func NewCon(proto, host, apiKey, restApiUrl string) MispCon
81 | NewCon : create a new MispCon struct return (MispcCon)
82 |
83 | func NewInsecureCon(proto, host, apiKey, restApiUrl string) MispCon
84 | NewInsecureCon : Return a new MispCon with insecured TLS connection
85 | settings return (MispCon)
86 |
87 | func (mc MispCon) Search(mq MispQuery) MispResponse
88 | Search : Issue a search and return a MispObject @mq : a struct
89 | implementing MispQuery interface return (MispObject)
90 |
91 | type MispConfig struct {
92 | Proto string
93 | Host string
94 | APIKey string
95 | APIURL string
96 | }
97 | MispConfig structure
98 |
99 | func LoadConfig(path string) (mc MispConfig)
100 | LoadConfig : load a configuration file from path return (MispConfig)
101 |
102 | type MispError struct {
103 | StatusCode int
104 | Message string
105 | }
106 |
107 | func (me MispError) Error() string
108 |
109 | type MispEvent struct {
110 | ID string `json:"id"`
111 | OrgcID string `json:"orgc_id"`
112 | OrgID string `json:"org_id"`
113 | Date string `json:"date"`
114 | ThreatLevelID string `json:"threat_level_id"`
115 | Info string `json:"info"`
116 | Published bool `json:"published"`
117 | UUID string `json:"uuid"`
118 | AttributeCount string `json:"attribute_count"`
119 | Analysis string `json:"analysis"`
120 | StrTimestamp string `json:"timestamp"`
121 | Distribution string `json:"distribution"`
122 | ProposalEmailLock bool `json:"proposal_email_lock"`
123 | Locked bool `json:"locked"`
124 | PublishedTimestamp string `json:"publish_timestamp"`
125 | SharingGroupID string `json:"sharing_group_id"`
126 | Org Org `json:"Org"`
127 | Orgc Org `json:"Orgc"`
128 | Attribute []MispAttribute `json:"Attribute"`
129 | ShadowAttribute []MispAttribute `json:"ShadowAttribute"`
130 | RelatedEvent []MispRelatedEvent `json:"RelatedEvent"`
131 | Galaxy []MispRelatedEvent `json:"Galaxy"`
132 | }
133 | MispEvent definition
134 |
135 | func (me MispEvent) Timestamp() time.Time
136 | Timestamp : return Time struct according to a string time
137 |
138 | type MispEventDict struct {
139 | Event MispEvent `json:"Event"`
140 | }
141 | MispEventDict : intermediate structure to handle properly MISP API
142 | results
143 |
144 | type MispEventQuery struct {
145 | Value string `json:"value"`
146 | Type string `json:"type"`
147 | Category string `json:"category"`
148 | Org string `json:"org"`
149 | Tags string `json:"tags"`
150 | QuickFilter string `json:"quickfilter"`
151 | From string `json:"from"`
152 | To string `json:"to"`
153 | Last string `json:"last"`
154 | EventID string `json:"eventid"`
155 | WithAttachments string `json:"withAttachments"`
156 | Metadata string `json:"metadata"`
157 | SearchAll int8 `json:"searchall"`
158 | }
159 | MispEventQuery : defines the structure of query to event search API
160 |
161 | func (meq MispEventQuery) Prepare() (j []byte)
162 | Prepare : MispQuery Implementation
163 |
164 | type MispEventResponse struct {
165 | Response []MispEventDict `json:"response"`
166 | }
167 | MispEventResponse : intermediate structure to handle properly MISP API
168 | results
169 |
170 | func (mer MispEventResponse) Iter() (moc chan MispObject)
171 | Iter : MispResponse implementation
172 |
173 | type MispObject interface{}
174 |
175 | type MispQuery interface {
176 | // Prepare the query and returns a JSON object in a form of a byte array
177 | Prepare() []byte
178 | }
179 |
180 | type MispRelatedEvent struct {
181 | ID string `json:"id"`
182 | Date string `json:"date"`
183 | ThreatLevelID string `json:"threat_level_id"`
184 | Info string `json:"info"`
185 | Published bool `json:"published"`
186 | UUID string `json:"uuid"`
187 | Analysis string `json:"analysis"`
188 | StrTimestamp string `json:"timestamp"`
189 | Distribution string `json:"distribution"`
190 | OrgID string `json:"org_id"`
191 | OrgcID string `json:"orgc_id"`
192 | Org Org `json:"Org"`
193 | Orgc Org `json:"Orgc"`
194 | }
195 | MispRelatedEvent definition
196 |
197 | func (mre *MispRelatedEvent) Timestamp() time.Time
198 | Timestamp : return Time struct according to a string time
199 |
200 | type MispRequest struct {
201 | Request MispQuery `json:"request"`
202 | }
203 |
204 | type MispResponse interface {
205 | Iter() chan MispObject
206 | }
207 |
208 | type Org struct {
209 | ID string `json:"id"`
210 | Name string `json:"name"`
211 | UUID string `json:"uuid"`
212 | }
213 | Org definition
214 |
215 | SUBDIRECTORIES
216 |
217 | test
218 |
219 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/0xrawsec/golang-misp
2 |
3 | go 1.12
4 |
5 | require (
6 | github.com/0xrawsec/golang-utils v1.1.8
7 | github.com/pkg/sftp v1.10.1 // indirect
8 | github.com/stretchr/objx v0.2.0 // indirect
9 | golang.org/x/crypto v0.0.0-20190909091759-094676da4a83 // indirect
10 | golang.org/x/net v0.0.0-20190909003024-a7b16738d86b // indirect
11 | golang.org/x/sys v0.0.0-20190909082730-f460065e899a // indirect
12 | golang.org/x/text v0.3.2 // indirect
13 | golang.org/x/tools v0.0.0-20190909194007-75be6cdcda07 // indirect
14 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
15 | )
16 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/0xrawsec/golang-utils v1.1.8 h1:9TzAKzC7+V2IXaV/3Y1aXiUFHeShL3BXcfL6BheAEH0=
2 | github.com/0xrawsec/golang-utils v1.1.8/go.mod h1:DADTtCFY10qXjWmUVhhJqQIZdSweaHH4soYUDEi8mj0=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
6 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
7 | github.com/pkg/sftp v1.10.0/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk=
8 | github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
9 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
10 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
11 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
12 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
13 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
14 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
15 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
16 | golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
17 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
18 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
19 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
20 | golang.org/x/net v0.0.0-20190909003024-a7b16738d86b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
21 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
22 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
23 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
24 | golang.org/x/sys v0.0.0-20190909082730-f460065e899a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
25 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
26 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
27 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
28 | golang.org/x/tools v0.0.0-20190320215829-36c10c0a621f/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
29 | golang.org/x/tools v0.0.0-20190909194007-75be6cdcda07/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
30 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
31 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
32 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
33 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
34 |
--------------------------------------------------------------------------------
/misp/config.json.example:
--------------------------------------------------------------------------------
1 | {
2 | "protocol" :"https or http",
3 | "host" : "misp.hostname",
4 | "api-key" :"your-api-key",
5 | "api-url" : "/restSearch/download/"
6 | }
7 |
--------------------------------------------------------------------------------
/misp/misp.go:
--------------------------------------------------------------------------------
1 | package misp
2 |
3 | import (
4 | "bytes"
5 | "crypto/tls"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "io"
10 | "io/ioutil"
11 | "net"
12 | "net/http"
13 | "sort"
14 | "strconv"
15 | "strings"
16 | "time"
17 |
18 | "github.com/0xrawsec/golang-utils/config"
19 | "github.com/0xrawsec/golang-utils/datastructs"
20 | "github.com/0xrawsec/golang-utils/log"
21 | "github.com/0xrawsec/golang-utils/readers"
22 | )
23 |
24 | type MispError struct {
25 | StatusCode int
26 | Message string
27 | }
28 |
29 | func (me MispError) Error() string {
30 | return fmt.Sprintf("MISP ERROR (HTTP %d) : %s", me.StatusCode, me.Message)
31 | }
32 |
33 | type MispCon struct {
34 | Proto string
35 | Host string
36 | APIKey string
37 | Client *http.Client
38 | }
39 |
40 | type MispRequest struct {
41 | Request MispQuery `json:"request"`
42 | }
43 |
44 | type MispQuery interface {
45 | // Prepare the query and returns a JSON object in a form of a byte array
46 | Prepare() []byte
47 | }
48 |
49 | type MispObject interface{}
50 |
51 | type MispResponse interface {
52 | Iter() chan MispObject
53 | }
54 |
55 | type EmptyMispResponse struct{}
56 |
57 | // Iter : MispResponse implementation
58 | func (emr EmptyMispResponse) Iter() chan MispObject {
59 | c := make(chan MispObject)
60 | close(c)
61 | return c
62 | }
63 |
64 | ////////////////////////////////////////////////////////////////////////////////
65 | ////////////////////////////////// Events //////////////////////////////////////
66 | ////////////////////////////////////////////////////////////////////////////////
67 |
68 | // MispEventQuery : defines the structure of query to event search API
69 | type MispEventQuery struct {
70 | Value string `json:"value,omitempty"`
71 | Type string `json:"type,omitempty"`
72 | Category string `json:"category,omitempty"`
73 | Org string `json:"org,omitempty"`
74 | Tags string `json:"tags,omitempty"`
75 | QuickFilter string `json:"quickfilter,omitempty"`
76 | From string `json:"from,omitempty"`
77 | To string `json:"to,omitempty"`
78 | Last string `json:"last,omitempty"`
79 | EventID string `json:"eventid,omitempty"`
80 | WithAttachments string `json:"withAttachments,omitempty"`
81 | Metadata string `json:"metadata,omitempty"`
82 | SearchAll int8 `json:"searchall,omitempty"`
83 | }
84 |
85 | // Prepare : MispQuery Implementation
86 | func (meq MispEventQuery) Prepare() (j []byte) {
87 | jsMeq, err := json.Marshal(MispRequest{meq})
88 | if err != nil {
89 | panic(err)
90 | }
91 | return jsMeq
92 | }
93 |
94 | // Org definition
95 | type Org struct {
96 | ID string `json:"id"`
97 | Name string `json:"name"`
98 | UUID string `json:"uuid"`
99 | }
100 |
101 | // MispRelatedEvent definition
102 | type MispRelatedEvent struct {
103 | ID string `json:"id"`
104 | Date string `json:"date"`
105 | ThreatLevelID string `json:"threat_level_id"`
106 | Info string `json:"info"`
107 | Published bool `json:"published"`
108 | UUID string `json:"uuid"`
109 | Analysis string `json:"analysis"`
110 | StrTimestamp string `json:"timestamp"`
111 | Distribution string `json:"distribution"`
112 | OrgID string `json:"org_id"`
113 | OrgcID string `json:"orgc_id"`
114 | Org Org `json:"Org"`
115 | Orgc Org `json:"Orgc"`
116 | }
117 |
118 | // Timestamp : return Time struct according to a string time
119 | func (mre *MispRelatedEvent) Timestamp() time.Time {
120 | sec, err := strconv.ParseInt(mre.StrTimestamp, 10, 64)
121 | if err != nil {
122 | panic(err)
123 | }
124 | return time.Unix(sec, 0)
125 | }
126 |
127 | // MispEvent definition
128 | type MispEvent struct {
129 | ID string `json:"id"`
130 | OrgcID string `json:"orgc_id"`
131 | OrgID string `json:"org_id"`
132 | Date string `json:"date"`
133 | ThreatLevelID string `json:"threat_level_id"`
134 | Info string `json:"info"`
135 | Published bool `json:"published"`
136 | UUID string `json:"uuid"`
137 | AttributeCount string `json:"attribute_count"`
138 | Analysis string `json:"analysis"`
139 | StrTimestamp string `json:"timestamp"`
140 | Distribution string `json:"distribution"`
141 | ProposalEmailLock bool `json:"proposal_email_lock"`
142 | Locked bool `json:"locked"`
143 | StrPublishedTimestamp string `json:"publish_timestamp"`
144 | SharingGroupID string `json:"sharing_group_id"`
145 | Org Org `json:"Org"`
146 | Orgc Org `json:"Orgc"`
147 | Attribute []MispAttribute `json:"Attribute"`
148 | ShadowAttribute []MispAttribute `json:"ShadowAttribute"`
149 | RelatedEvent []MispRelatedEvent `json:"RelatedEvent"`
150 | Galaxy []MispRelatedEvent `json:"Galaxy"`
151 | }
152 |
153 | // Timestamp : return Time struct according to a string time
154 | func (me MispEvent) Timestamp() time.Time {
155 | sec, err := strconv.ParseInt(me.StrTimestamp, 10, 64)
156 | if err != nil {
157 | panic(err)
158 | }
159 | return time.Unix(sec, 0)
160 | }
161 |
162 | // PublishedTimestamp : return Time struct according to a string time
163 | func (me MispEvent) PublishedTimestamp() time.Time {
164 | sec, err := strconv.ParseInt(me.StrPublishedTimestamp, 10, 64)
165 | if err != nil {
166 | panic(err)
167 | }
168 | return time.Unix(sec, 0)
169 | }
170 |
171 | // MispEventDict : intermediate structure to handle properly MISP API results
172 | type MispEventDict struct {
173 | Event MispEvent `json:"Event"`
174 | }
175 |
176 | // MispEventResponse : intermediate structure to handle properly MISP API results
177 | type MispEventResponse struct {
178 | Response []MispEventDict `json:"response"`
179 | }
180 |
181 | // Iter : MispResponse implementation
182 | func (mer MispEventResponse) Iter() (moc chan MispObject) {
183 | moc = make(chan MispObject)
184 | go func() {
185 | defer close(moc)
186 | for _, me := range mer.Response {
187 | moc <- me.Event
188 | }
189 | }()
190 | return
191 | }
192 |
193 | ////////////////////////////////////////////////////////////////////////////////
194 | //////////////////////////////// Attributes ////////////////////////////////////
195 | ////////////////////////////////////////////////////////////////////////////////
196 |
197 | type MispAttributeQuery struct {
198 | Value string `json:"value,omitempty"`
199 | Type string `json:"type,omitempty"`
200 | Category string `json:"category,omitempty"`
201 | Org string `json:"org,omitempty"`
202 | Tags string `json:"tags,omitempty"`
203 | From string `json:"from,omitempty"`
204 | To string `json:"to,omitempty"`
205 | Last string `json:"last,omitempty"`
206 | EventID string `json:"eventid,omitempty"`
207 | UUID string `json:"uuid,omitempty"`
208 | }
209 |
210 | // Prepare : MispQuery Implementation
211 | func (maq MispAttributeQuery) Prepare() (j []byte) {
212 | jsMaq, err := json.Marshal(MispRequest{maq})
213 | if err != nil {
214 | panic(err)
215 | }
216 | return jsMaq
217 | }
218 |
219 | // MispAttributeDict : itermediate structure to handle MISP attribute search
220 | type MispAttributeDict struct {
221 | Attribute []MispAttribute `json:"Attribute"`
222 | }
223 |
224 | // MispAttributeResponse : API response when attribute query is done
225 | type MispAttributeResponse struct {
226 | Response MispAttributeDict `json:"response"`
227 | }
228 |
229 | // Iter : MispResponse implementation
230 | func (mar MispAttributeResponse) Iter() (moc chan MispObject) {
231 | moc = make(chan MispObject)
232 | go func() {
233 | defer close(moc)
234 | for _, ma := range mar.Response.Attribute {
235 | moc <- ma
236 | }
237 | }()
238 | return
239 | }
240 |
241 | // MispAttribute : define structure of attribute object returned by API
242 | type MispAttribute struct {
243 | ID string `json:"id"`
244 | EventID string `json:"event_id"`
245 | UUID string `json:"uuid"`
246 | SharingGroupID string `json:"sharing_group_id"`
247 | StrTimestamp string `json:"timestamp"`
248 | Distribution string `json:"distribution"`
249 | Category string `json:"category"`
250 | Type string `json:"type"`
251 | Value string `json:"value"`
252 | ToIDS bool `json:"to_ids"`
253 | Deleted bool `json:"deleted"`
254 | Comment string `json:"comment"`
255 | }
256 |
257 | // Timestamp : return Time struct according to a string time
258 | func (ma MispAttribute) Timestamp() time.Time {
259 | sec, err := strconv.ParseInt(ma.StrTimestamp, 10, 64)
260 | if err != nil {
261 | panic(err)
262 | }
263 | return time.Unix(sec, 0)
264 | }
265 |
266 | ////////////////////////////////////////////////////////////////////////////////
267 | //////////////////////////////// Config ////////////////////////////////////////
268 | ////////////////////////////////////////////////////////////////////////////////
269 |
270 | // MispConfig structure
271 | type MispConfig struct {
272 | Proto string `json:"protocol"`
273 | Host string `json:"host"`
274 | APIKey string `json:"api-key"`
275 | }
276 |
277 | ////////////////////////////////////////////////////////////////////////////////
278 | ////////////////////////////// MISP Interface //////////////////////////////////
279 | ////////////////////////////////////////////////////////////////////////////////
280 |
281 | var (
282 | // ErrUnknownProtocol : raised when bad protocol specified
283 | ErrUnknownProtocol = errors.New("Unknown protocol")
284 | )
285 |
286 | func headerSortedKeys(d http.Header) (sk []string) {
287 | sk = make([]string, 0, len(d))
288 | for k := range d {
289 | sk = append(sk, k)
290 | }
291 | sort.Strings(sk)
292 | return
293 | }
294 |
295 | func logRequest(req *http.Request) {
296 | proxyURL, err := http.ProxyFromEnvironment(req)
297 | if err != nil {
298 | panic(err)
299 | }
300 | body, _ := req.GetBody()
301 | log.Debugf("Proxy: %s", proxyURL)
302 | log.Debugf("%s %s", req.Method, req.URL)
303 | log.Debug("Header:")
304 | for _, sk := range headerSortedKeys(req.Header) {
305 | for _, v := range req.Header[sk] {
306 | log.Debugf(" %s: %v", sk, v)
307 | }
308 | }
309 | log.Debugf("Body: %s", string(readAllOrPanic(body)))
310 | }
311 |
312 | func readAllOrPanic(r io.Reader) []byte {
313 | respBody, err := ioutil.ReadAll(r)
314 | if err != nil {
315 | panic(err)
316 | }
317 | return respBody
318 | }
319 |
320 | // LoadConfig : load a configuration file from path
321 | // return (MispConfig)
322 | func LoadConfig(path string) (mc MispConfig) {
323 | conf, err := config.Load(path)
324 | if err != nil {
325 | panic(err)
326 | }
327 | mc.Proto = conf.GetRequiredString("protocol")
328 | mc.Host = conf.GetRequiredString("host")
329 | mc.APIKey = conf.GetRequiredString("api-key")
330 | return
331 | }
332 |
333 | // NewInsecureCon : Return a new MispCon with insecured TLS connection settings
334 | // return (MispCon)
335 | func NewInsecureCon(proto, host, apiKey string) MispCon {
336 | if proto != "http" && proto != "https" {
337 | log.Errorf("%s : only http and https protocols are allowed", ErrUnknownProtocol.Error())
338 | panic(ErrUnknownProtocol)
339 | }
340 | var noCertTransport http.RoundTripper = &http.Transport{
341 | Proxy: http.ProxyFromEnvironment,
342 | DialContext: (&net.Dialer{
343 | Timeout: 30 * time.Second,
344 | KeepAlive: 30 * time.Second,
345 | }).DialContext,
346 | MaxIdleConns: 100,
347 | IdleConnTimeout: 90 * time.Second,
348 | TLSHandshakeTimeout: 10 * time.Second,
349 | ExpectContinueTimeout: 1 * time.Second,
350 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
351 | }
352 | c := http.Client{Transport: noCertTransport}
353 | return MispCon{proto, host, apiKey, &c}
354 | }
355 |
356 | // NewCon : create a new MispCon struct
357 | // return (MispcCon)
358 | func NewCon(proto, host, apiKey string) MispCon {
359 | if proto != "http" && proto != "https" {
360 | log.Errorf("%s : only http and https protocols are allowed", ErrUnknownProtocol.Error())
361 | panic(ErrUnknownProtocol)
362 | }
363 | return MispCon{proto, host, apiKey, &http.Client{}}
364 | }
365 |
366 | func (mc MispCon) buildURL(path ...string) string {
367 | for i := range path {
368 | path[i] = strings.TrimLeft(path[i], "/")
369 | }
370 | return fmt.Sprintf("%s://%s/%s", mc.Proto, mc.Host, strings.Join(path, "/"))
371 | }
372 |
373 | func (mc MispCon) prepareRequest(method, url string, body io.Reader) (*http.Request, error) {
374 | req, err := http.NewRequest(method, url, body)
375 | req.Header.Add("Authorization", mc.APIKey)
376 | req.Header.Add("Content-Type", "application/json")
377 | req.Header.Add("Accept", "application/json")
378 | req.Header.Add("User-Agent", fmt.Sprintf("GolangMisp/%s (https://github.com/0xrawsec/golang-misp)", version))
379 | return req, err
380 | }
381 |
382 | func (mc MispCon) postSearch(kind string, mq *MispQuery) ([]byte, error) {
383 | fullURL := mc.buildURL(kind, "restSearch", "download")
384 | pReq, err := mc.prepareRequest("POST", fullURL, bytes.NewReader((*mq).Prepare()))
385 | if err != nil {
386 | return []byte{}, err
387 | }
388 | if err != nil {
389 | return []byte{}, err
390 | }
391 | logRequest(pReq)
392 | pResp, err := mc.Client.Do(pReq)
393 | if err != nil {
394 | return []byte{}, err
395 | }
396 | defer pResp.Body.Close()
397 |
398 | respBody := readAllOrPanic(pResp.Body)
399 | switch pResp.StatusCode {
400 | case 200:
401 | return respBody, err
402 | default:
403 | return []byte{}, MispError{pResp.StatusCode, string(respBody)}
404 | }
405 | }
406 |
407 | // Search : Issue a search and return a MispObject
408 | // @mq : a struct implementing MispQuery interface
409 | // return (MispObject, error)
410 | func (mc MispCon) Search(mq MispQuery) (MispResponse, error) {
411 | switch mq.(type) {
412 | case MispAttributeQuery:
413 | mar := MispAttributeResponse{}
414 | bResp, err := mc.postSearch("attributes", &mq)
415 | if err != nil {
416 | log.Debugf("Error: %s", err)
417 | return EmptyMispResponse{}, err
418 | }
419 | err = json.Unmarshal(bResp, &mar)
420 | if err != nil {
421 | log.Debug(string(bResp))
422 | return mar, err
423 | }
424 | return mar, nil
425 |
426 | case MispEventQuery:
427 | mer := MispEventResponse{}
428 | bResp, err := mc.postSearch("events", &mq)
429 | if err != nil {
430 | log.Debugf("Error: %s", err)
431 | return EmptyMispResponse{}, err
432 | }
433 | err = json.Unmarshal(bResp, &mer)
434 | if err != nil {
435 | log.Debug(string(bResp))
436 | return mer, err
437 | }
438 | return mer, nil
439 | }
440 | return EmptyMispResponse{}, fmt.Errorf("Empty Response")
441 | }
442 |
443 | // TextExport text export API wrapper https:///attributes/text/download/
444 | // The wrapper takes care of removing the duplicated entries
445 | // @flags: the list of flags to use for the query
446 | func (mc MispCon) TextExport(flags ...string) (out []string, err error) {
447 | path := make([]string, 0)
448 | path = append(path, "attributes", "text", "download")
449 | path = append(path, flags...)
450 |
451 | url := mc.buildURL(path...)
452 |
453 | out = make([]string, 0)
454 |
455 | pReq, err := mc.prepareRequest("GET", url, new(bytes.Buffer))
456 | if err != nil {
457 | return
458 | }
459 | logRequest(pReq)
460 | pResp, err := mc.Client.Do(pReq)
461 | if err != nil {
462 | return
463 | }
464 | defer pResp.Body.Close()
465 | switch pResp.StatusCode {
466 | case 200:
467 | // used to remove duplicates
468 | marked := datastructs.NewSyncedSet()
469 | for line := range readers.Readlines(pResp.Body) {
470 | txt := string(line)
471 | if !marked.Contains(txt) {
472 | out = append(out, txt)
473 | }
474 | marked.Add(txt)
475 | }
476 | default:
477 | return out, MispError{pResp.StatusCode, string(readAllOrPanic(pResp.Body))}
478 | }
479 | return
480 | }
481 |
--------------------------------------------------------------------------------
/misp/misp_test.go:
--------------------------------------------------------------------------------
1 | package misp
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/0xrawsec/golang-utils/log"
7 | )
8 |
9 | var (
10 | proto = ""
11 | host = ""
12 | key = ""
13 | )
14 |
15 | func init() {
16 | log.InitLogger(log.LDebug)
17 | mc := LoadConfig("./test/config.json")
18 | proto = mc.Proto
19 | host = mc.Host
20 | key = mc.APIKey
21 | }
22 |
23 | func TestSimpleAttributeSearch(t *testing.T) {
24 | con := NewInsecureCon(proto, host, key)
25 | ma := MispAttributeQuery{Last: "1d"}
26 | mr, err := con.Search(ma)
27 | if err != nil {
28 | t.Errorf("Failed to search: %s", err)
29 | }
30 | for a := range mr.Iter() {
31 | t.Log(a.(MispAttribute).Timestamp())
32 | t.Log(a)
33 | }
34 | }
35 |
36 | func TestSimpleEventSearch(t *testing.T) {
37 | con := NewInsecureCon(proto, host, key)
38 | me := MispEventQuery{Last: "1d"}
39 | mr, err := con.Search(me)
40 | if err != nil {
41 | t.Errorf("Failed to search: %s", err)
42 | }
43 | for e := range mr.Iter() {
44 | t.Log(e.(MispEvent).Timestamp())
45 | t.Log(e)
46 | }
47 | }
48 |
49 | func TestTextExport(t *testing.T) {
50 | con := NewInsecureCon(proto, host, key)
51 | domains, err := con.TextExport("mutex")
52 | if err != nil {
53 | t.Errorf("Failed TextExport: %s", err)
54 | t.FailNow()
55 | }
56 | for _, domain := range domains {
57 | t.Log(domain)
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/misp/version.go:
--------------------------------------------------------------------------------
1 | package misp
2 |
3 | const (
4 | version = "1.0"
5 | )
6 |
--------------------------------------------------------------------------------