├── 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 | --------------------------------------------------------------------------------