Shopify Scripts

Introduction

Shopify Scripts is an embedded Ruby interpreter based on MRuby. It is implemented as a Ruby gem, which provides a sandboxed, lightweight environment where we can safely run untrusted Ruby scripts in a way that isolates them from Shopify’s native Ruby environment. We are looking to reward any issue that affects this sandboxed environment that could put Shopify’s infrastructure or our merchants’ data at risk.

For more information about the bounty program please head to our HackerOne page at https://hackerone.com/shopify-scripts

Below is an overview of the technical limitations we’ve implemented to make the sandbox safe. We rely on a stripped down version of MRuby that is compiled without most of the dangerous methods normally available in a Ruby environment, a seccomp-bpf sandbox, and resource quotas (described below).

We use MRI to designate the Ruby environment running outside the sandbox, and MRuby to designate the embedded Ruby environment that runs inside the sandbox.

seccomp-bpf sandbox

Untrusted Ruby scripts are executed in a separate process. This process limits itself using a seccomb-bpf sandbox before untrusted Ruby code is executed. The sandbox only allows read from stdin, write to stdout and stderr, and exit. Bypassing the seccomp restrictions to execute dangerous system calls is in scope for this bug bounty program. You are allowed to assume that a vulnerability in MRuby would allow an untrusted Ruby script to achieve arbitrary code execution inside the seccomp-bpf sandbox.

Resource limits

Bypassing any of these restrictions is in scope for this bug bounty program.

Memory usage limit

You should not be able to allocate more than 8MB of memory. We are evaluating the memory consumption per page and the upper bound page will always get rejected. This always includes the size of the script itself, the size of MRuby virtual machine and the space needed for the scripts API and data.

Wall clock execution time limit

A script is scheduled for termination if it runs more than 100ms. This includes the time needed for initializing the virtual machine, the environment of the script and the deserialization of the input data.

Instruction count limit

We limit scripts to 200 000 virtual machine instructions, which includes the instructions that are executed when we initialize the environment and the virtual machine itself.

Passing data between MRI and MRuby

MRI is able to send and fetch data to MRuby. This is done using C code by converting basic MRI objects into equivalent MRuby objects, and vice versa.

The mechanism used to fetch data from MRuby prevents native objects, other than strings, fixnums, symbols, arrays or hashes from being extracted from the sandbox. Any bypass of this mechanism where unsafe data is extracted and processed by MRI is in scope for this program.

How to install

If you find it useful to run the sandbox on your computer, you can compile the sandbox with the following instructions (we’re assuming you have a functional Ruby environment).

  1. git clone --recursive https://github.com/Shopify/ess.git
  2. cd ess
  3. bundle install
  4. bin/rake

Running a script

Create a Ruby file and execute it inside of the sandbox:

  1. bin/sandbox <pathtoyourscript.rb>

References

You can find information about MRuby on their wiki page: https://github.com/mruby/mruby/wiki.

The implementation of the sandbox as a Ruby gem is available at https://github.com/shopify/ess.

Try our live sandbox

For a quick look at how the sandbox works, we’re hosting a complete sandboxed environment that you can try live in your browser.