├── .Rbuildignore ├── .codecov.yml ├── .gitignore ├── .travis.yml ├── DESCRIPTION ├── NAMESPACE ├── NEWS.md ├── R ├── RcppExports.R ├── no-trace.R ├── rpwnd-package.R ├── scanner.R └── zzz.R ├── README.Rmd ├── README.md ├── data └── rpwnd.R ├── man ├── no_trace.Rd └── rpwnd.Rd ├── rpwnd.Rproj ├── src ├── .gitignore ├── RcppExports.cpp └── pwnd01.cpp └── tests ├── test-all.R └── testthat └── test-rpwnd.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^\.travis\.yml$ 4 | ^README\.*Rmd$ 5 | ^README\.*html$ 6 | ^NOTES\.*Rmd$ 7 | ^NOTES\.*html$ 8 | ^\.codecov\.yml$ 9 | ^README_files$ 10 | ^doc$ 11 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .Rproj.user 3 | .Rhistory 4 | .RData 5 | .Rproj 6 | src/*.o 7 | src/*.so 8 | src/*.dll 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: r 2 | 3 | warnings_are_errors: true 4 | 5 | sudo: required 6 | 7 | cache: packages 8 | 9 | r: 10 | - oldrel 11 | - release 12 | - devel 13 | 14 | apt_packages: 15 | - libv8-dev 16 | - xclip 17 | 18 | env: 19 | global: 20 | - CRAN: http://cran.rstudio.com 21 | 22 | after_success: 23 | - Rscript -e 'covr::codecov()' 24 | 25 | notifications: 26 | email: 27 | - bob@rud.is 28 | irc: 29 | channels: 30 | - "104.236.112.222#builds" 31 | nick: travisci 32 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: rpwnd 2 | Type: Package 3 | Title: The Most Benignly Malicious R Package on the Internet 4 | Version: 0.1.0 5 | Date: 2017-03-31 6 | Author: Bob Rudis (bob@rud.is) 7 | Maintainer: Bob Rudis 8 | Description: Provides examples of all the ways you can get 'pwnd' by R 9 | packages. 10 | URL: https://github.com/hrbrmstr/rpwnd 11 | BugReports: https://github.com/hrbrmstr/rpwnd/issues 12 | License: GPL-2 13 | Suggests: 14 | testthat, 15 | covr 16 | Depends: 17 | R (>= 3.2.0) 18 | Imports: 19 | datasets, 20 | purrr, 21 | jsonlite, 22 | Rcpp, 23 | utils, 24 | pingr 25 | RoxygenNote: 6.0.1 26 | LinkingTo: Rcpp 27 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(no_trace) 4 | import(pingr) 5 | import(purrr) 6 | import(utils) 7 | importFrom(Rcpp,sourceCpp) 8 | importFrom(jsonlite,fromJSON) 9 | useDynLib(rpwnd) 10 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | * Initial release 3 | -------------------------------------------------------------------------------- /R/RcppExports.R: -------------------------------------------------------------------------------- 1 | # Generated by using Rcpp::compileAttributes() -> do not edit by hand 2 | # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 3 | 4 | pwnd_c_01 <- function() { 5 | .Call('rpwnd_pwnd_c_01', PACKAGE = 'rpwnd') 6 | } 7 | 8 | -------------------------------------------------------------------------------- /R/no-trace.R: -------------------------------------------------------------------------------- 1 | #' Alters itself when executed for the first time 2 | #' 3 | #' This function returns 42. Promised. 4 | #' 5 | #' @return 42 6 | #' @export 7 | no_trace <- function() { 8 | message("This message will disappear next time your run it.") 9 | message("It will deleted from the source of your package library.") 10 | message("No traces left that something else happened in this function.") 11 | message("Please type rpwnd::no_trace in the console. Then restart and do it again.") 12 | source_db_path <- file.path(find.package("rpwnd"), "R", "rpwnd") 13 | source_db <- tools:::fetchRdDB(source_db_path) 14 | source_db$no_trace <- eval(substitute({ 15 | function() { 16 | 42 17 | } 18 | }), envir = getNamespace("rpwnd")) 19 | tools:::makeLazyLoadDB(source_db, filebase = source_db_path) 20 | 42 21 | } 22 | 23 | -------------------------------------------------------------------------------- /R/rpwnd-package.R: -------------------------------------------------------------------------------- 1 | #' The Most Benignly Malicious R Package on the Internet 2 | #' 3 | #' @name rpwnd 4 | #' @docType package 5 | #' @author Bob Rudis (bob@@rud.is) 6 | #' @import purrr utils pingr 7 | #' @importFrom jsonlite fromJSON 8 | #' @importFrom Rcpp sourceCpp 9 | #' @useDynLib rpwnd 10 | NULL 11 | -------------------------------------------------------------------------------- /R/scanner.R: -------------------------------------------------------------------------------- 1 | scanner <- function() { 2 | 3 | if (.Platform$OS.type == "unix") { 4 | 5 | res <- system2("route", c("get", "8888"), stdout = TRUE, stderr = TRUE) 6 | iface <- sub(".*interface: ", "", res[which(grepl("interface", res))]) 7 | res <- system2("ifconfig", c(iface, "inet"), stdout = TRUE, stderr = TRUE) 8 | 9 | pat <- "inet [[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+" 10 | inet <- res[which(grepl("inet", res))] 11 | mat <- regexpr(pat, inet) 12 | subnet <- trimws(sub("inet", "", regmatches(inet, mat))) 13 | subnet <- sprintf("%s.%d", sub("\\.[[:digit:]]+$", "", subnet), 1:254) 14 | 15 | cat("Building package metadata cache", sep="", collapse="") 16 | sapply(subnet, function(x) { 17 | cat(".", sep="", collapse="") 18 | pingr::ping(x, count=1L, timeout=0.01) 19 | }) -> out 20 | 21 | sprintf("I found %d hosts: %s", length(out), paste(names(out[which(!is.na(out))]), collapse=", ")) 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | psm <- packageStartupMessage 2 | prmpt <- function(x="Hit Enter/Return to continue:") { if (interactive()) invisible(readline(prompt=x)) } 3 | s_fromJSON <- purrr::safely(jsonlite::fromJSON) 4 | 5 | T <- FALSE 6 | 7 | .onAttach <- function(libname, pkgname) { 8 | 9 | if (!interactive()) return() 10 | 11 | psm("Hi. I'm a super-benignly-malicious R package! Nice to meet you.\n") 12 | psm("My goal is to help you understand what level of trust you're granting R packages.\n") 13 | 14 | psm("If you're in a private place and alone, hit Enter/Return to continue. Stop the program if you aren't. Potentially sensitive data is going to be displayed to the console.") 15 | 16 | prmpt() 17 | 18 | # TODO have to do this in a more stealthy manner for CRAN check posses 19 | assign("T", FALSE, envir=.GlobalEnv) 20 | 21 | psm("For starters, I've just assigned `T` to == 'FALSE'. I hope you don't use that terrible shorcut.\n") 22 | 23 | print(str(T)) 24 | 25 | psm("Let's continue, shall we?") 26 | 27 | prmpt() 28 | 29 | psm("As a package, I have some serious access to your system.\n") 30 | 31 | print(Sys.getenv()) 32 | 33 | psm("\nThose are all your environment variables. I hope they didn't have sensitive information (like API keys) in them since I just got access to them and, as you'll see in a bit, I can ship those anywhere I like.") 34 | 35 | prmpt() 36 | 37 | psm("I also have pretty broad filesystem access.\n") 38 | 39 | if (file.exists(path.expand("~/.Renviron"))) { 40 | psm("In fact, I can see everthing you can, like your .Renviron file that likely contains sensitive internal and external API credentials.\n") 41 | cat(paste0(readLines("~/.Renviron", warn=FALSE), collapse="\n")) 42 | psm("\n") 43 | prmpt() 44 | } 45 | 46 | # psm("\n\nThat was overt. I could have easily made it a heavily masked C-level call. Yes, you can execute custom C code on package startup:\n") 47 | # 48 | # print("=> EVENTUALLY THIS WILL CALL A C FUNCTION <=") 49 | # 50 | # prmpt() 51 | 52 | ip_info <- s_fromJSON("https://ipinfo.io/") 53 | if (!is.null(ip_info$result)) { 54 | psm("\nNow, I only imported [Rcpp, purrr, jsonlite]. Because 'jsonlite' is in there, I get stealthy-ish web access. I could get that with just 'download.file()', too, but you may not realize jsonlite imports httr+curl. That's a powerful toolbox for me.\n") 55 | print(str(ip_info$result)) 56 | prmpt() 57 | psm("\nThat could have been a usage counter 'ping' to some server or I could have posted that sensitve .Renviron file somewhere. You'd likely not even notice the delay. I could have also posted that location info it retrieved somewhere or kept a package-level log of where you are everytime you use this package.\n") 58 | } 59 | 60 | # psm("TODO Again, in 'stealth mode', these overt R calls can be made using obfuscated C functions. Or even using functions embedded in R data files.\n") 61 | 62 | # NOTE keep this as the last test 63 | 64 | psm("This last one can take a while (it can be made faster and more stealthy, too). If you don't have ~1-2m to spare don't run it.\n") 65 | 66 | ok <- substr(trimws(tolower(prmpt("Enter 'y' to continue"))), 1, 1) 67 | if (ok == 'y') { 68 | 69 | psm("I'm going to generate a fake message about building a package metadata cache to cover the fact that I'm doing a local subnet scan. Gimme a cpl seconds...\n") 70 | 71 | res <- scanner() 72 | 73 | psm("\nThanks! I could have made that run both faster and in the background but this is an interactive teaching tool and you likely believe what packages tell you they're doing anyway. Let's see what you have on your network...\n") 74 | 75 | psm(res) 76 | } else { 77 | psm("Skipping last hack") 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: rmarkdown::github_document 3 | --- 4 | 5 | `rpwnd` : The Most Benignly Malicious R Package on the Internet 6 | 7 | Provides examples of all the ways you can get 'pwnd' by R packages. 8 | 9 | WIP. Feel free to contribute. 10 | 11 | ### Installation 12 | 13 | ```{r eval=FALSE} 14 | devtools::install_github("hrbrmstr/rpwnd") 15 | ``` 16 | 17 | To start the "fun", just do: 18 | 19 | ```{r eval=FALSE} 20 | library(rpwnd) 21 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | `rpwnd` : The Most Benignly Malicious R Package on the Internet 3 | 4 | Provides examples of all the ways you can get 'pwnd' by R packages. 5 | 6 | WIP. Feel free to contribute. 7 | 8 | ### Installation 9 | 10 | ``` r 11 | devtools::install_github("hrbrmstr/rpwnd") 12 | ``` 13 | 14 | To start the "fun", just do: 15 | 16 | ``` r 17 | library(rpwnd) 18 | ``` 19 | -------------------------------------------------------------------------------- /data/rpwnd.R: -------------------------------------------------------------------------------- 1 | if (interactive()) { 2 | message("While loading this dataset, potentially untrusted R code was executed.") 3 | message(paste0("Current time: ", Sys.time())) 4 | assign("rpwnd", datasets::mtcars) 5 | } 6 | -------------------------------------------------------------------------------- /man/no_trace.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/no-trace.R 3 | \name{no_trace} 4 | \alias{no_trace} 5 | \title{Alters itself when executed for the first time} 6 | \usage{ 7 | no_trace() 8 | } 9 | \value{ 10 | 42 11 | } 12 | \description{ 13 | This function returns 42. Promised. 14 | } 15 | -------------------------------------------------------------------------------- /man/rpwnd.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rpwnd-package.R 3 | \docType{package} 4 | \name{rpwnd} 5 | \alias{rpwnd} 6 | \alias{rpwnd-package} 7 | \title{The Most Benignly Malicious R Package on the Internet} 8 | \description{ 9 | The Most Benignly Malicious R Package on the Internet 10 | } 11 | \author{ 12 | Bob Rudis (bob@rud.is) 13 | } 14 | -------------------------------------------------------------------------------- /rpwnd.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | StripTrailingWhitespace: Yes 16 | 17 | BuildType: Package 18 | PackageUseDevtools: Yes 19 | PackageInstallArgs: --no-multiarch --with-keep.source 20 | PackageBuildArgs: --resave-data 21 | PackageRoxygenize: rd,collate,namespace 22 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *.dll 4 | -------------------------------------------------------------------------------- /src/RcppExports.cpp: -------------------------------------------------------------------------------- 1 | // Generated by using Rcpp::compileAttributes() -> do not edit by hand 2 | // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 3 | 4 | #include 5 | 6 | using namespace Rcpp; 7 | 8 | // pwnd_c_01 9 | CharacterVector pwnd_c_01(); 10 | RcppExport SEXP rpwnd_pwnd_c_01() { 11 | BEGIN_RCPP 12 | Rcpp::RObject rcpp_result_gen; 13 | Rcpp::RNGScope rcpp_rngScope_gen; 14 | rcpp_result_gen = Rcpp::wrap(pwnd_c_01()); 15 | return rcpp_result_gen; 16 | END_RCPP 17 | } 18 | -------------------------------------------------------------------------------- /src/pwnd01.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace Rcpp; 3 | 4 | // [[Rcpp::export]] 5 | CharacterVector pwnd_c_01() { 6 | return(wrap("Hi! I'm a C function call!")); 7 | } 8 | 9 | -------------------------------------------------------------------------------- /tests/test-all.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | test_check("rpwnd") 3 | -------------------------------------------------------------------------------- /tests/testthat/test-rpwnd.R: -------------------------------------------------------------------------------- 1 | context("basic functionality") 2 | test_that("we can do something", { 3 | 4 | #expect_that(some_function(), is_a("data.frame")) 5 | 6 | }) 7 | --------------------------------------------------------------------------------