From 28e249b671addbdba9b32a948ad711884f0137b0 Mon Sep 17 00:00:00 2001 From: Jose Toro Date: Wed, 13 Apr 2022 17:07:57 +0200 Subject: [PATCH] feat(Collections): Remove items / delete collection (#46) --- cmd/collection.go | 134 ++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 ++ 3 files changed, 139 insertions(+), 1 deletion(-) diff --git a/cmd/collection.go b/cmd/collection.go index cfbb5b3..ae39afd 100644 --- a/cmd/collection.go +++ b/cmd/collection.go @@ -3,6 +3,8 @@ package cmd import ( "fmt" "io" + "net" + "regexp" "strings" "github.com/VirusTotal/vt-go" @@ -12,6 +14,12 @@ import ( "github.com/spf13/viper" ) +type objectDescriptor struct { + Type string `json:"type"` + Id string `json:"id,omitempty"` + Url string `json:"url,omitempty"` +} + var collectionCmdHelp = `Get information about one or more collections. This command receives one or more collection IDs and returns information about @@ -49,6 +57,8 @@ func NewCollectionCmd() *cobra.Command { cmd.AddCommand(NewCollectionCreateCmd()) cmd.AddCommand(NewCollectionRenameCmd()) cmd.AddCommand(NewCollectionUpdateCmd()) + cmd.AddCommand(NewCollectionDeleteCmd()) + cmd.AddCommand(NewCollectionRemoveItemsCmd()) addRelationshipCmds(cmd, "collections", "collection", "[collection]") addThreadsFlag(cmd.Flags()) @@ -200,6 +210,82 @@ func NewCollectionUpdateCmd() *cobra.Command { } } +var removeCollectionItemsCmdHelp = `Remove items from a collection. + +This command receives a collection ID and one of more IoCs +(sha256 hashes, URLs, domains, IP addresses) and removes them from the collection. + +If the command receives a single hypen (-) the IoCs will be read from the +standard input.` + +var removeCollectionItemsExample = ` vt collection remove [collection id] www.example.com + vt collection remove [collection id] www.example.com 8.8.8.8 + cat list_of_iocs | vt collection remove [collection id] -` + +// NewCollectionRemoveItemsCmd returns a command for removing items from a collection. +func NewCollectionRemoveItemsCmd() *cobra.Command { + return &cobra.Command{ + Use: "remove [collection id] [ioc]...", + Short: "Remove items from a collection.", + Args: cobra.MinimumNArgs(2), + Long: removeCollectionItemsCmdHelp, + Example: removeCollectionItemsExample, + + RunE: func(cmd *cobra.Command, args []string) error { + c, err := NewAPIClient() + if err != nil { + return err + } + relationshipDescriptors := descriptorsFromReader( + utils.StringReaderFromCmdArgs(args[1:])) + for relationshipName, descriptors := range relationshipDescriptors { + url := vt.URL("collections/%s/%s", args[0], relationshipName) + response, err := c.DeleteData(url, descriptors) + if err != nil { + return err + } + if response.Error.Code != "" { + return response.Error + } + } + return nil + }, + } +} + +var deleteCollectionCmdHelp = `Delete a collection. + +This command receives a collection ID and deletes it.` + +var deleteCollectionExample = ` vt collection delete [collection id]` + +// NewCollectionDeleteCmd returns a command for deleting a collection. +func NewCollectionDeleteCmd() *cobra.Command { + return &cobra.Command{ + Use: "delete [collection id]", + Short: "Delete a collection.", + Args: cobra.MinimumNArgs(1), + Long: deleteCollectionCmdHelp, + Example: deleteCollectionExample, + + RunE: func(cmd *cobra.Command, args []string) error { + c, err := NewAPIClient() + if err != nil { + return err + } + url := vt.URL("collections/%s", args[0]) + response, err := c.Delete(url) + if err != nil { + return err + } + if response.Error.Code != "" { + return response.Error + } + return nil + }, + } +} + func rawFromReader(reader utils.StringReader) string { var lines []string for { @@ -211,3 +297,51 @@ func rawFromReader(reader utils.StringReader) string { } return strings.Join(lines, " ") } + +func descriptorsFromReader(reader utils.StringReader) map[string][]objectDescriptor { + descriptors := make(map[string][]objectDescriptor) + hashPattern := regexp.MustCompile("[0-9a-fA-F]{32,64}") + urlPattern := regexp.MustCompile("[hH][tTxX]{2}[pP][sS]?://.*") + // At least two domain parts. + domainPattern := regexp.MustCompile(".*[^.]+\\.[^.]+.*") + for { + next, err := reader.ReadString() + if err == io.EOF { + break + } + if match := hashPattern.MatchString(next); match { + files := descriptors["files"] + files = append(files, objectDescriptor{ + Type: "file", + Id: next, + }) + descriptors["files"] = files + } else if net.ParseIP(next) != nil { + if strings.Contains(next, ".") { + ipAddresses := descriptors["ip_addresses"] + ipAddresses = append(ipAddresses, objectDescriptor{ + Type: "ip_address", + Id: next, + }) + descriptors["ip_addresses"] = ipAddresses + } else { + // IPv6, skip. + } + } else if urlPattern.MatchString(next) { + urls := descriptors["urls"] + urls = append(urls, objectDescriptor{ + Type: "url", + Url: next, + }) + descriptors["urls"] = urls + } else if domainPattern.MatchString(next) { + domains := descriptors["domains"] + domains = append(domains, objectDescriptor{ + Type: "domain", + Id: next, + }) + descriptors["domains"] = domains + } + } + return descriptors +} diff --git a/go.mod b/go.mod index 2430d6e..3fb02f7 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/VirusTotal/vt-cli go 1.14 require ( - github.com/VirusTotal/vt-go v0.0.0-20211209151516-855a1e790678 + github.com/VirusTotal/vt-go v0.0.0-20220413144842-e010bf48aaee github.com/briandowns/spinner v1.7.0 github.com/cavaliercoder/grab v2.0.0+incompatible github.com/dustin/go-humanize v1.0.0 diff --git a/go.sum b/go.sum index e81d023..d86ce72 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,10 @@ github.com/VirusTotal/vt-go v0.0.0-20211116094520-07a92e6467b7 h1:1oIDITWcezvHQX github.com/VirusTotal/vt-go v0.0.0-20211116094520-07a92e6467b7/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE= github.com/VirusTotal/vt-go v0.0.0-20211209151516-855a1e790678 h1:IVvLDz0INo1rn7wG4OQub9MkyNNaBp0sQgU1apho/mk= github.com/VirusTotal/vt-go v0.0.0-20211209151516-855a1e790678/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE= +github.com/VirusTotal/vt-go v0.0.0-20220413141716-fce0077b709b h1:YdiHY6VO3InYpfasvfpF6EolaNqKNv6pmGJfBQCVuL4= +github.com/VirusTotal/vt-go v0.0.0-20220413141716-fce0077b709b/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE= +github.com/VirusTotal/vt-go v0.0.0-20220413144842-e010bf48aaee h1:JDhi0dS8y9QLMJZA7ezLyXHxYaMlyzX6MDkq0SSc304= +github.com/VirusTotal/vt-go v0.0.0-20220413144842-e010bf48aaee/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=