diff --git a/.Rbuildignore b/.Rbuildignore index c9a5c92..19cdbd8 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -19,3 +19,4 @@ ^CRAN-RELEASE$ ^appveyor\.yml$ ^tools$ +^LICENSE\.md$ diff --git a/DESCRIPTION b/DESCRIPTION index c10541d..87968b7 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: attackerkb Type: Package -Title: attackerkb title goes here otherwise CRAN checks fail +Title: Tools to Query the Rapid7 AttackerKB API Version: 0.1.0 Date: 2020-04-15 Authors@R: c( @@ -8,17 +8,20 @@ Authors@R: c( comment = c(ORCID = "0000-0001-5670-2640")) ) Maintainer: Bob Rudis -Description: A good description goes here otherwise CRAN checks fail. +Description: Rapid7 manages a service — - where experts can evaluate + various aspects of emergent or existing vulnerabilities and the community can query and + retrieve results. Tools are provides to query the AttackerKB API. URL: https://git.rud.is/hrbrmstr/attackerkb BugReports: https://git.rud.is/hrbrmstr/attackerkb/issues Encoding: UTF-8 -License: AGPL +License: MIT + file LICENSE Suggests: covr, tinytest Depends: R (>= 3.2.0) Imports: httr, - jsonlite + jsonlite, + data.table Roxygen: list(markdown = TRUE) RoxygenNote: 7.1.0 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..49d554e --- /dev/null +++ b/LICENSE @@ -0,0 +1,2 @@ +YEAR: 2020 +COPYRIGHT HOLDER: Bob Rudis diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..524f6aa --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2020 Bob Rudis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NAMESPACE b/NAMESPACE index 5b4b9ae..3ced161 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,4 +1,12 @@ # Generated by roxygen2: do not edit by hand +export(attackerkb_api_key) +export(kb_assessment) +export(kb_assessments) +export(kb_contributors) +export(kb_topic) +export(kb_topics) +export(kd_contributor) import(httr) +importFrom(data.table,rbindlist) importFrom(jsonlite,fromJSON) diff --git a/R/aaa.R b/R/aaa.R new file mode 100644 index 0000000..3fc9272 --- /dev/null +++ b/R/aaa.R @@ -0,0 +1,9 @@ +set_names <- function (object = nm, nm) { names(object) <- nm ; object } + +httr::user_agent( + sprintf( + "{attackerkb} package v%s: (<%s>)", + utils::packageVersion("attackerkb"), + utils::packageDescription("attackerkb")$URL + ) +) -> .ATTACKERKB_UA diff --git a/R/api-key.R b/R/api-key.R new file mode 100644 index 0000000..cb126b1 --- /dev/null +++ b/R/api-key.R @@ -0,0 +1,35 @@ +#' Get or set ATTACKERKB_API_KEY value +#' +#' The API wrapper functions in this package all rely on a AttackerKB API +#' key residing in the environment variable `ATTACKERKB_API_KEY`. +#' The easiest way to accomplish this is to set it +#' in the `.Renviron` file in your home directory. +#' +#' @md +#' @param force Force setting a new AttackerKB key for the current environment? +#' @return atomic character vector containing the AttackerKB api key +#' @export +attackerkb_api_key <- function(force = FALSE) { + + env <- Sys.getenv('ATTACKERKB_API_KEY') + if (!identical(env, "") && !force) return(env) + + if (!interactive()) { + stop("Please set env var ATTACKERKB_API_KEY to your AttackerKB key", + call. = FALSE) + } + + message("Couldn't find env var ATTACKERKB_API_KEY See ?ATTACKERKB_API_KEY for more details.") + message("Please enter your API key:") + pat <- readline(": ") + + if (identical(pat, "")) { + stop("AttackerKB key entry failed", call. = FALSE) + } + + message("Updating ATTACKERKB_API_KEY env var") + Sys.setenv(ATTACKERKB_API_KEY = pat) + + pat + +} diff --git a/R/assessments.R b/R/assessments.R new file mode 100644 index 0000000..b712813 --- /dev/null +++ b/R/assessments.R @@ -0,0 +1,117 @@ +#' Helpers to query AttackerKB assessments +#' +#' The main `kb_contributors` function maps 1:1 to the API. Leave values +#' `NULL` that you do not want included in the search parameters. +#' +#' @param assessment_id UUID of a specific assessment to return +#' @param editor_id UUID of a contributor. +#' @param topid_id UUID of the topic this assessment was based on. +#' @param created Return all assessments that were created on the given date. +#' @param revised Return all assessments that were revised on the given date. +#' @param document Text to query the document attribute. A substring match is performed +#' @param metadata Text to query the metadata attribute. A substring match is performed +#' @param score Return all assessments with this score. +#' @param q Return all assessments that have content that match the query string. +#' @param api_key See [attackerkb_api_key()] +#' @references +#' @export +kb_assessments <- function(assessment_id = NULL, + editor_id = NULL, + topid_id = NULL, + created = NULL, + revised = NULL, + document = NULL, + metadata = NULL, + score = NULL, + q = NULL, + api_key = attackerkb_api_key()) { + + assessment_id <- assessment_id[1] + editor_id <- editor_id[1] + topid_id <- topid_id[1] + created <- created[1] + revised <-revised[1] + document <- document[1] + metadata <- metadata[1] + score <-score[1] + q <- q[1] + + if (length(created)) created <- as.character(as.Date(created[1])) + if (length(revised)) created <- as.character(as.Date(created[1])) + + httr::GET( + url = "https://api.attackerkb.com/assessments", + .ATTACKERKB_UA, + query = list( + id = assessment_id, + editorId = editor_id, + topicId = topid_id, + created = created, + revisionDate = revised, + document = document, + metadata = metadata, + score = score, + q = q, + size = 500L + ), + httr::add_headers(`Authorization` = sprintf("basic %s", api_key)) + ) -> res + + httr::stop_for_status(res) + + out <- httr::content(res, as = "text", encoding = "UTF-8") + out <- jsonlite::fromJSON(out) + + out <- handle_response(out) + + out + +} + +#' @rdname kb_assessments +#' @export +kb_assessment <- function(assessment_id, api_key = attackerkb_api_key()) { + + assessment_id <- assessment_id[1] + + httr::GET( + url = sprintf("https://api.attackerkb.com/assessments/%s", assessment_id), + .ATTACKERKB_UA, + httr::add_headers(`Authorization` = sprintf("basic %s", api_key)) + ) -> res + + httr::stop_for_status(res) + + out <- httr::content(res, as = "text", encoding = "UTF-8") + out <- jsonlite::fromJSON(out) + + out <- out$data + + metadata <- out$metadata + out$metadata <- NULL + + out <- as.data.frame(out, stringsAsFactors = FALSE) + + out$tags <- list(metadata[["tags"]]) %l0% NA_character_ + out$attacker_value <- metadata[["attacker-value"]] %l0% NA_integer_ + out$exploitability <- metadata[["exploitability"]] %l0% NA_integer_ + out$stability <- metadata[["stability"]] %l0% NA_integer_ + out$reliability <- metadata[["reliability"]] %l0% NA_integer_ + out$urgent_to_patch <- metadata[["urgent-to-patch"]] %l0% NA_integer_ + out$used_successfully <- metadata[["used-successfully"]] %l0% NA_integer_ + out$mitigation_strength <- metadata[["mitigation-strength"]] %l0% NA_integer_ + out$confidence_in_ratings <- metadata[["confidence-in-ratings"]] %l0% NA_integer_ + out$effort_to_develop_exploit <- metadata[["effort-to-develop-exploit"]] %l0% NA_integer_ + out$versions <- list(metadata[["versions"]]) %||% list() + out$mitigation <- metadata[["mitigation"]] %l0% NA_character_ + out$forever_day_versions <- list(metadata[["forever_day_versions"]]) %l0% list() + out$offensive_application <- metadata[["offensive-application"]] %l0% NA_character_ + out$atacker_utility <- metadata[["atacker-utility"]] %l0% NA_integer_ + out$urgent_topatch <- metadata[["urgent-to patch"]] %l0% NA_integer_ + + class(out) <- c("tbl_df", "tbl", "data.frame") + + out + + +} \ No newline at end of file diff --git a/R/attackerkb-package.R b/R/attackerkb-package.R index 2669a8e..f046468 100644 --- a/R/attackerkb-package.R +++ b/R/attackerkb-package.R @@ -1,9 +1,14 @@ -#' ... -#' +#' Tools to Query the Rapid7 AttackerKB API +#' +#' Rapid7 manages a service — - where experts can evaluate +#' various aspects of emergent or existing vulnerabilities and the community can query and +#' retrieve results. Tools are provides to query the AttackerKB API. +#' #' @md #' @name attackerkb #' @keywords internal #' @author Bob Rudis (bob@@rud.is) #' @import httr #' @importFrom jsonlite fromJSON +#' @importFrom data.table rbindlist "_PACKAGE" diff --git a/R/contributor.R b/R/contributor.R new file mode 100644 index 0000000..4d5a062 --- /dev/null +++ b/R/contributor.R @@ -0,0 +1,81 @@ +#' Helpers to query AttackerKB contributors +#' +#' The main `kb_contributors` function maps 1:1 to the API. Leave values +#' `NULL` that you do not want included in the search parameters. +#' +#' @param contributor_id UUID of a specific contributror to return +#' @param username Return contributors with the matching username. +#' @param avatar Return all contributors where avatar matches the given value +#' @param created Return all contributors that were created on the given date. +#' @param score Return all contributors with this score. +#' @param q Return all contributors that have usernames that match the query string. +#' @param api_key See [attackerkb_api_key()] +#' @references +#' @export +kb_contributors <- function(contributor_id = NULL, + username = NULL, + avatar = NULL, + created = NULL, + score = NULL, + q = NULL, + api_key = attackerkb_api_key()) { + + contributor_id <- contributor_id[1] + username <- username[1] + avatar <- avatar[1] + created <- created[1] + score <- score[1] + q <- q[1] + + if (length(created)) created <- as.character(as.Date(created[1])) + + httr::GET( + url = "https://api.attackerkb.com/contributors", + .ATTACKERKB_UA, + query = list( + id = contributor_id, + username = username, + avatar = avatar, + created = created, + score = score, + q = q, + size = 500L + ), + httr::add_headers(`Authorization` = sprintf("basic %s", api_key)) + ) -> res + + httr::stop_for_status(res) + + out <- httr::content(res, as = "text", encoding = "UTF-8") + out <- jsonlite::fromJSON(out) + + out <- handle_response(out) + + out + +} + +#' @rdname kb_contributors +#' @export +kd_contributor <- function(contributor_id, api_key = attackerkb_api_key()) { + + contributor_id <- contributor_id[1] + + httr::GET( + url = sprintf("https://api.attackerkb.com/contributors/%s", contributor_id), + .ATTACKERKB_UA, + httr::add_headers(`Authorization` = sprintf("basic %s", api_key)) + ) -> res + + httr::stop_for_status(res) + + out <- httr::content(res, as = "text", encoding = "UTF-8") + out <- jsonlite::fromJSON(out) + + out <- as.data.frame(out$data, stringsAsFactors=FALSE) + + class(out) <- c("tbl_df", "tbl", "data.frame") + out + + +} \ No newline at end of file diff --git a/R/topics.R b/R/topics.R new file mode 100644 index 0000000..1b17a11 --- /dev/null +++ b/R/topics.R @@ -0,0 +1,305 @@ +#' Helpers to query AttackerKB topics +#' +#' The main `kb_topics` function maps 1:1 to the API. Leave values +#' `NULL` that you do not want included in the search parameters. +#' +#' @param topic_id UUID of a specific topic to return +#' @param editor_id UUID of a contributor +#' @param name Text to query the name attribute. A substring match is performed +#' @param created Return all topics that were created on the given date. +#' @param revised Return all topics that were revised on the given date. +#' @param disclosed Return all topics that were disclosed on the given date. +#' @param document Text to query the document attribute. A substring match is performed +#' @param metadata Text to query the metadata attribute. A substring match is performed +#' @param featured (lgl) `TRUE`/`FALSE`. Return all topics that are featured. +#' @param q Return all topics that have content that matches the query string. +#' @param api_key See [attackerkb_api_key()] +#' @references +#' @export +kb_topics <- function(topic_id = NULL, + editor_id = NULL, + name = NULL, + created = NULL, + revised = NULL, + disclosed = NULL, + document = NULL, + metadata = NULL, + featured = NULL, + q = NULL, + api_key = attackerkb_api_key()) { + + topic_id <- topic_id[1] + editor_id <-editor_id[1] + name <- name[1] + created <- created[1] + revised <-revised[1] + disclosed <- disclosed[1] + document <- document[1] + metadata <- metadata[1] + featured <- featured[1] + q <- q[1] + + if (length(featured)) featured <- tolower(as.character(as.logical(featured))) + if (length(created)) created <- as.character(as.Date(created[1])) + if (length(revised)) revised <- as.character(as.Date(revised[1])) + if (length(disclosed)) disclosed <- as.character(as.Date(disclosed[1])) + + httr::GET( + url = "https://api.attackerkb.com/topics", + .ATTACKERKB_UA, + query = list( + id = topic_id, + editorId = editor_id, + name = name, + created = created, + revisionDate = revised, + disclosureDate = disclosed, + document = document, + metadata = metadata, + featured = featured, + q = q, + size = 500L + ), + httr::add_headers(`Authorization` = sprintf("basic %s", api_key)) + ) -> res + + httr::stop_for_status(res) + + out <- httr::content(res, as = "text", encoding = "UTF-8") + out <- jsonlite::fromJSON(out) + + out <- handle_response(out) + + out + +} + +#' @rdname kb_topics +#' @export +kb_topic <- function(topic_id = "131226a6-a1e9-48a1-a5d0-ac94baf8dfd2", api_key = attackerkb_api_key()) { + + httr::GET( + url = sprintf("https://api.attackerkb.com/topics/%s", topic_id[1]), + .ATTACKERKB_UA, + httr::add_headers(`Authorization` = sprintf("basic %s", api_key)) + ) -> res + + httr::stop_for_status(res) + + out <- httr::content(res, as = "text", encoding = "UTF-8") + out <- jsonlite::fromJSON(out) + + out <- handle_response(out) + + out + +} +# +# #' @rdname kb_topics +# #' @export +# kb_topics_by_contributor <- function(editor_id = "7191a637-aa4e-4885-98a0-f4f2da285b99", api_key = attackerkb_api_key()) { +# +# httr::GET( +# url = "https://api.attackerkb.com/topics", +# .ATTACKERKB_UA, +# query = list( +# editorId = editor_id[1], +# size = 500L +# ), +# httr::add_headers(`Authorization` = sprintf("basic %s", api_key)) +# ) -> res +# +# httr::stop_for_status(res) +# +# out <- httr::content(res, as = "text", encoding = "UTF-8") +# out <- jsonlite::fromJSON(out) +# +# out <- handle_response(out) +# +# out +# +# } +# +# #' @rdname kb_topics +# #' @export +# kb_topics_by_name <- function(q = "bluekeep", api_key = attackerkb_api_key()) { +# +# httr::GET( +# url = "https://api.attackerkb.com/topics", +# .ATTACKERKB_UA, +# query = list( +# name = q[1], +# size = 500L +# ), +# httr::add_headers(`Authorization` = sprintf("basic %s", api_key)) +# ) -> res +# +# httr::stop_for_status(res) +# +# out <- httr::content(res, as = "text", encoding = "UTF-8") +# out <- jsonlite::fromJSON(out) +# +# out <- handle_response(out) +# +# out +# +# } +# +# #' @rdname kb_topics +# #' @export +# kb_topics_by_document <- function(q = "bluekeep", api_key = attackerkb_api_key()) { +# +# httr::GET( +# url = "https://api.attackerkb.com/topics", +# .ATTACKERKB_UA, +# query = list( +# document = q[1], +# size = 500L +# ), +# httr::add_headers(`Authorization` = sprintf("basic %s", api_key)) +# ) -> res +# +# httr::stop_for_status(res) +# +# out <- httr::content(res, as = "text", encoding = "UTF-8") +# out <- jsonlite::fromJSON(out) +# +# out <- handle_response(out) +# +# out +# +# } +# +# +# #' @rdname kb_topics +# #' @export +# kb_topics_by_content <- function(q = "bluekeep", api_key = attackerkb_api_key()) { +# +# httr::GET( +# url = "https://api.attackerkb.com/topics", +# .ATTACKERKB_UA, +# query = list( +# content = q[1], +# size = 500L +# ), +# httr::add_headers(`Authorization` = sprintf("basic %s", api_key)) +# ) -> res +# +# httr::stop_for_status(res) +# +# out <- httr::content(res, as = "text", encoding = "UTF-8") +# out <- jsonlite::fromJSON(out) +# +# out <- handle_response(out) +# +# out +# +# } +# +# #' @rdname kb_topics +# #' @export +# kb_topics_by_metadata <- function(lookup = "bluekeep", api_key = attackerkb_api_key()) { +# +# httr::GET( +# url = "https://api.attackerkb.com/topics", +# .ATTACKERKB_UA, +# query = list( +# metadata = lookup[1], +# size = 500L +# ), +# httr::add_headers(`Authorization` = sprintf("basic %s", api_key)) +# ) -> res +# +# httr::stop_for_status(res) +# +# out <- httr::content(res, as = "text", encoding = "UTF-8") +# out <- jsonlite::fromJSON(out) +# +# out <- handle_response(out) +# +# out +# +# } +# +# +# #' @rdname kb_topics +# #' @export +# kb_topics_by_date_created_date <- function(created = "2019-07-04", api_key = attackerkb_api_key()) { +# +# created <- as.Date(created[1]) +# +# httr::GET( +# url = "https://api.attackerkb.com/topics", +# .ATTACKERKB_UA, +# query = list( +# created = as.character(created[1]), +# size = 500L +# ), +# httr::add_headers(`Authorization` = sprintf("basic %s", api_key)) +# ) -> res +# +# httr::stop_for_status(res) +# +# out <- httr::content(res, as = "text", encoding = "UTF-8") +# out <- jsonlite::fromJSON(out) +# +# out <- handle_response(out) +# +# out +# +# } +# +# #' @rdname kb_topics +# #' @export +# kb_topics_by_date_revised_date <- function(revised = "2019-07-04", api_key = attackerkb_api_key()) { +# +# revised <- as.Date(revised[1]) +# +# httr::GET( +# url = "https://api.attackerkb.com/topics", +# .ATTACKERKB_UA, +# query = list( +# revisionDate = as.character(revised[1]), +# size = 500L +# ), +# httr::add_headers(`Authorization` = sprintf("basic %s", api_key)) +# ) -> res +# +# httr::stop_for_status(res) +# +# out <- httr::content(res, as = "text", encoding = "UTF-8") +# out <- jsonlite::fromJSON(out) +# +# out <- handle_response(out) +# +# out +# +# } +# +# #' @rdname kb_topics +# #' @export +# kb_topics_by_date_disclosed_date <- function(disclosed = "2019-07-04", api_key = attackerkb_api_key()) { +# +# disclosed <- as.Date(disclosed[1]) +# +# httr::GET( +# url = "https://api.attackerkb.com/topics", +# .ATTACKERKB_UA, +# query = list( +# disclosureDate = as.character(disclosed[1]), +# size = 500L +# ), +# httr::add_headers(`Authorization` = sprintf("basic %s", api_key)) +# ) -> res +# +# httr::stop_for_status(res) +# +# out <- httr::content(res, as = "text", encoding = "UTF-8") +# out <- jsonlite::fromJSON(out) +# +# out <- handle_response(out) +# +# out +# +# } +# diff --git a/R/utils-infix-helpers.R b/R/utils-infix-helpers.R new file mode 100644 index 0000000..11e792d --- /dev/null +++ b/R/utils-infix-helpers.R @@ -0,0 +1,4 @@ +`%l0%` <- function(x, y) if (length(x) == 0) y else x +`%||%` <- function(x, y) if (is.null(x)) y else x +`%@%` <- function(x, name) attr(x, name, exact = TRUE) +`%nin%` <- function(x, table) match(x, table, nomatch = 0) == 0 diff --git a/R/utils.R b/R/utils.R new file mode 100644 index 0000000..9f09c8e --- /dev/null +++ b/R/utils.R @@ -0,0 +1,116 @@ +.kb_reshape <- function(.x, path) { + + if (path == "/topics") { + + easy_cols <- .x[, c("id", "editorId", "name", "created", "revisionDate", "disclosureDate", "document")] + + easy_cols$references <- .x[["metadata"]][["references"]] + + cvss_v3 <- .x[["metadata"]][["baseMetricV3"]][["cvssV3"]] + + easy_cols$cvss_v3_scope <- cvss_v3[["scope"]] + easy_cols$cvss_v3_version <- cvss_v3[["version"]] + easy_cols$cvss_v3_baseScore <- cvss_v3[["baseScore"]] + easy_cols$cvss_v3_attackVector <- cvss_v3[["attackVector"]] + easy_cols$cvss_v3_baseSeverity <- cvss_v3[["baseSeverity"]] + easy_cols$cvss_v3_vectorString <- cvss_v3[["vectorString"]] + easy_cols$cvss_v3_integrityImpact <- cvss_v3[["integrityImpact"]] + easy_cols$cvss_v3_userInteraction <- cvss_v3[["userInteraction"]] + easy_cols$cvss_v3_attackComplexity <- cvss_v3[["attackComplexity"]] + easy_cols$cvss_v3_availabilityImpact <- cvss_v3[["availabilityImpact"]] + easy_cols$cvss_v3_privilegesRequired <- cvss_v3[["privilegesRequired"]] + easy_cols$cvss_v3_confidentialityImpact <- cvss_v3[["confidentialityImpact"]] + easy_cols$cvss_v3_impact <- .x[["metadata"]][["baseMetricV3"]][["impactScore"]] + easy_cols$cvss_v3_exploitability <- .x[["metadata"]][["baseMetricV3"]][["exploitabilityScore"]] + easy_cols$vulnerable_versions <- .x[["metadata"]][["vulnerable-versions"]] + easy_cols$kb_attacker_value <- .x[["score"]][["attackerValue"]] + easy_cols$kb_exploitability <- .x[["score"]][["exploitability"]] + easy_cols$tag_commonEnterprise <- .x$tags[["commonEnterprise"]] + easy_cols$tag_defaultConfiguration <- .x$tags[["defaultConfiguration"]] + easy_cols$tag_difficultToPatch <- .x$tags[["difficultToPatch"]] + easy_cols$tag_highPrivilegeAccess <- .x$tags[["highPrivilegeAccess"]] + easy_cols$tag_easyToDevelop <- .x$tags[["easyToDevelop"]] + easy_cols$tag_requiresInteraction <- .x$tags[["requiresInteraction"]] + easy_cols$tag_obscureConfiguration <- .x$tags[["obscureConfiguration"]] + easy_cols$tag_difficultToExploit <- .x$tags[["difficultToExploit"]] + easy_cols$tag_noUsefulData <- .x$tags[["noUsefulData"]] + easy_cols$tag_difficultToDevelop <- .x$tags[["difficultToDevelop"]] + easy_cols$tag_preAuth <- .x$tags[["preAuth"]] + easy_cols$tag_postAuth <- .x$tags[["postAuth"]] + + easy_cols + + rownames(easy_cols) <- NULL + + easy_cols + + } else if (path == "/contributors") { + + .x + + } else if (path == "/assessments") { + + easy_cols <- .x[,c("id", "editorId", "topicId", "created", "revisionDate", "document", "score")] + + metadata <- easy_cols[["metadata"]] + + easy_cols$tags <- metadata[["tags"]] + easy_cols$attacker_value <- metadata[["attacker-value"]] + easy_cols$exploitability <- metadata[["exploitability"]] + easy_cols$stability <- metadata[["stability"]] + easy_cols$reliability <- metadata[["reliability"]] + easy_cols$urgent_to_patch <- metadata[["urgent-to-patch"]] + easy_cols$used_successfully <- metadata[["used-successfully"]] + easy_cols$mitigation_strength <- metadata[["mitigation-strength"]] + easy_cols$confidence_in_ratings <- metadata[["confidence-in-ratings"]] + easy_cols$effort_to_develop_exploit <- metadata[["effort-to-develop-exploit"]] + easy_cols$versions <- metadata[["versions"]] + easy_cols$mitigation <- metadata[["mitigation"]] + easy_cols$forever_day_versions <- metadata[["forever_day_versions"]] + easy_cols$offensive_application <- metadata[["offensive-application"]] + easy_cols$atacker_utility <- metadata[["atacker-utility"]] + easy_cols$urgent_topatch <- metadata[["urgent-to patch"]] + + easy_cols + + } + +} + +handle_response <- function(.x, api_key = attackerkb_api_key()) { + + path <- .x[["links"]][["self"]][["href"]] + + ret <- .x[["data"]] + + ret <- .kb_reshape(ret, path) + + next_href <- .x[["links"]][["next"]][["href"]] + + while(length(next_href)) { + + cat(".") + + httr::GET( + url = sprintf("https://api.attackerkb.com%s", next_href), + .ATTACKERKB_UA, + httr::add_headers(`Authorization` = sprintf("basic %s", api_key)) + ) -> res + + httr::stop_for_status(res) + + out <- httr::content(res, as = "text", encoding = "UTF-8") + out <- jsonlite::fromJSON(out) + + next_href <- out[["links"]][["next"]][["href"]] + + ret <- data.table::rbindlist(list(ret, .kb_reshape(out[["data"]], path)), fill = TRUE) + + } + + cat("\n") + + class(ret) <- c("tbl_df", "tbl", "data.frame") + ret + +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..9393dc4 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ + +[![Project Status: Active – The project has reached a stable, usable +state and is being actively +developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) +[![Signed +by](https://img.shields.io/badge/Keybase-Verified-brightgreen.svg)](https://keybase.io/hrbrmstr) +![Signed commit +%](https://img.shields.io/badge/Signed_Commits-100%25-lightgrey.svg) +[![Linux build +Status](https://travis-ci.org/hrbrmstr/attackerkb.svg?branch=master)](https://travis-ci.org/hrbrmstr/attackerkb) +![Minimal R +Version](https://img.shields.io/badge/R%3E%3D-3.2.0-blue.svg) +![License](https://img.shields.io/badge/License-MIT-blue.svg) + +# attackerkb + +Tools to Query the Rapid7 AttackerKB API + +## Description + +Rapid7 manages a service — - where experts can +evaluate various aspects of emergent or existing vulnerabilities and the +community can query and retrieve results. Tools are provides to query +the AttackerKB API. + +## What’s Inside The Tin + +The following functions are implemented: + + - `attackerkb_api_key`: Get or set ATTACKERKB\_API\_KEY value + - `kb_assessments`: Helpers to query AttackerKB assessments + - `kb_contributors`: Helpers to query AttackerKB contributors + - `kb_topics`: Helpers to query AttackerKB topics + +## Installation + +``` r +remotes::install_git("https://git.rud.is/hrbrmstr/attackerkb.git") +# or +remotes::install_git("https://git.sr.ht/~hrbrmstr/attackerkb") +# or +remotes::install_gitlab("hrbrmstr/attackerkb") +# or +remotes::install_bitbucket("hrbrmstr/attackerkb") +``` + +NOTE: To use the ‘remotes’ install options you will need to have the +[{remotes} package](https://github.com/r-lib/remotes) installed. + +## Usage + +``` r +library(attackerkb) + +# current version +packageVersion("attackerkb") +## [1] '0.1.0' +``` + +## attackerkb Metrics + +| Lang | \# Files | (%) | LoC | (%) | Blank lines | (%) | \# Lines | (%) | +| :--- | -------: | --: | --: | ---: | ----------: | ---: | -------: | ---: | +| R | 9 | 0.9 | 302 | 0.97 | 96 | 0.86 | 288 | 0.91 | +| Rmd | 1 | 0.1 | 8 | 0.03 | 15 | 0.14 | 28 | 0.09 | + +## Code of Conduct + +Please note that this project is released with a Contributor Code of +Conduct. By participating in this project you agree to abide by its +terms. diff --git a/man/attackerkb.Rd b/man/attackerkb.Rd index 466fcc9..9791848 100644 --- a/man/attackerkb.Rd +++ b/man/attackerkb.Rd @@ -4,9 +4,11 @@ \name{attackerkb} \alias{attackerkb} \alias{attackerkb-package} -\title{...} +\title{Tools to Query the Rapid7 AttackerKB API} \description{ -A good description goes here otherwise CRAN checks fail. +Rapid7 manages a service — \url{https://attackerkb.com/} - where experts can evaluate +various aspects of emergent or existing vulnerabilities and the community can query and +retrieve results. Tools are provides to query the AttackerKB API. } \seealso{ Useful links: diff --git a/man/attackerkb_api_key.Rd b/man/attackerkb_api_key.Rd new file mode 100644 index 0000000..c338403 --- /dev/null +++ b/man/attackerkb_api_key.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/api-key.R +\name{attackerkb_api_key} +\alias{attackerkb_api_key} +\title{Get or set ATTACKERKB_API_KEY value} +\usage{ +attackerkb_api_key(force = FALSE) +} +\arguments{ +\item{force}{Force setting a new AttackerKB key for the current environment?} +} +\value{ +atomic character vector containing the AttackerKB api key +} +\description{ +The API wrapper functions in this package all rely on a AttackerKB API +key residing in the environment variable \code{ATTACKERKB_API_KEY}. +The easiest way to accomplish this is to set it +in the \code{.Renviron} file in your home directory. +} diff --git a/man/kb_assessments.Rd b/man/kb_assessments.Rd new file mode 100644 index 0000000..c8cf005 --- /dev/null +++ b/man/kb_assessments.Rd @@ -0,0 +1,50 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/assessments.R +\name{kb_assessments} +\alias{kb_assessments} +\alias{kb_assessment} +\title{Helpers to query AttackerKB assessments} +\usage{ +kb_assessments( + assessment_id = NULL, + editor_id = NULL, + topid_id = NULL, + created = NULL, + revised = NULL, + document = NULL, + metadata = NULL, + score = NULL, + q = NULL, + api_key = attackerkb_api_key() +) + +kb_assessment(assessment_id, api_key = attackerkb_api_key()) +} +\arguments{ +\item{assessment_id}{UUID of a specific assessment to return} + +\item{editor_id}{UUID of a contributor.} + +\item{topid_id}{UUID of the topic this assessment was based on.} + +\item{created}{Return all assessments that were created on the given date.} + +\item{revised}{Return all assessments that were revised on the given date.} + +\item{document}{Text to query the document attribute. A substring match is performed} + +\item{metadata}{Text to query the metadata attribute. A substring match is performed} + +\item{score}{Return all assessments with this score.} + +\item{q}{Return all assessments that have content that match the query string.} + +\item{api_key}{See \code{\link[=attackerkb_api_key]{attackerkb_api_key()}}} +} +\description{ +The main \code{kb_contributors} function maps 1:1 to the API. Leave values +\code{NULL} that you do not want included in the search parameters. +} +\references{ +\url{https://api.attackerkb.com/api-docs/docs} +} diff --git a/man/kb_contributors.Rd b/man/kb_contributors.Rd new file mode 100644 index 0000000..11235fc --- /dev/null +++ b/man/kb_contributors.Rd @@ -0,0 +1,41 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/contributor.R +\name{kb_contributors} +\alias{kb_contributors} +\alias{kd_contributor} +\title{Helpers to query AttackerKB contributors} +\usage{ +kb_contributors( + contributor_id = NULL, + username = NULL, + avatar = NULL, + created = NULL, + score = NULL, + q = NULL, + api_key = attackerkb_api_key() +) + +kd_contributor(contributor_id, api_key = attackerkb_api_key()) +} +\arguments{ +\item{contributor_id}{UUID of a specific contributror to return} + +\item{username}{Return contributors with the matching username.} + +\item{avatar}{Return all contributors where avatar matches the given value} + +\item{created}{Return all contributors that were created on the given date.} + +\item{score}{Return all contributors with this score.} + +\item{q}{Return all contributors that have usernames that match the query string.} + +\item{api_key}{See \code{\link[=attackerkb_api_key]{attackerkb_api_key()}}} +} +\description{ +The main \code{kb_contributors} function maps 1:1 to the API. Leave values +\code{NULL} that you do not want included in the search parameters. +} +\references{ +\url{https://api.attackerkb.com/api-docs/docs} +} diff --git a/man/kb_topics.Rd b/man/kb_topics.Rd new file mode 100644 index 0000000..0cf1fee --- /dev/null +++ b/man/kb_topics.Rd @@ -0,0 +1,56 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/topics.R +\name{kb_topics} +\alias{kb_topics} +\alias{kb_topic} +\title{Helpers to query AttackerKB topics} +\usage{ +kb_topics( + topic_id = NULL, + editor_id = NULL, + name = NULL, + created = NULL, + revised = NULL, + disclosed = NULL, + document = NULL, + metadata = NULL, + featured = NULL, + q = NULL, + api_key = attackerkb_api_key() +) + +kb_topic( + topic_id = "131226a6-a1e9-48a1-a5d0-ac94baf8dfd2", + api_key = attackerkb_api_key() +) +} +\arguments{ +\item{topic_id}{UUID of a specific topic to return} + +\item{editor_id}{UUID of a contributor} + +\item{name}{Text to query the name attribute. A substring match is performed} + +\item{created}{Return all topics that were created on the given date.} + +\item{revised}{Return all topics that were revised on the given date.} + +\item{disclosed}{Return all topics that were disclosed on the given date.} + +\item{document}{Text to query the document attribute. A substring match is performed} + +\item{metadata}{Text to query the metadata attribute. A substring match is performed} + +\item{featured}{(lgl) \code{TRUE}/\code{FALSE}. Return all topics that are featured.} + +\item{q}{Return all topics that have content that matches the query string.} + +\item{api_key}{See \code{\link[=attackerkb_api_key]{attackerkb_api_key()}}} +} +\description{ +The main \code{kb_topics} function maps 1:1 to the API. Leave values +\code{NULL} that you do not want included in the search parameters. +} +\references{ +\url{https://api.attackerkb.com/api-docs/docs} +}