How To Automate Security Analysis with the RIPS API


RIPS API

RIPS exposes a powerful REST-API, an interface specifically designed for developers and their applications. It is used to provide the web interface with analysis results, to start scans through plugins, to manage users, and much more. In short, the API enables easy automation of all RIPS features through other programs.

In this blog post the architecture of the RIPS API is explained and the advantages of a RESTful API are shown. To demonstrate the simplistic nature of our interface, a small CI integration example is given that will reject code commits with security issues and hence protects the production server from new vulnerabilities.

RIPS API

This post uses the RIPS API version 2.7.1. Newer versions may contain more functionality.

Specification

For a convenient API usage it is important to understand what input is expected and what output is returned. There are multiple API specification standards available that allow developers to explicitly define an API in a machine-readable format. This definition can be used to generate documentations, libraries, tests, and other useful components.

RIPS uses OpenAPI 2.0 to define its API 1, a common standard that is maintained by the Open API Initiative 2. The specification is written in YAML, so it is human-readable as well. Instead of looking at the raw content though it is advised to use a client like Swagger to view the specification. Figure 1 shows the specification for our API call to initiate a new scan. The values can be modified on-the-fly in our Swagger interface on https://api.ripstech.com/ for testing. Please note that the specification defines all input and output but not all conditions, for example it does not consider user permissions. It is up to the user to look this information up.

Figure 1: Creating a scan in the Swagger user interface.

REST

RIPS has a RESTful API, but what does that mean? REST is the acronym for Representational State Transfer, an architecture designed for interoperability between computer systems on the Internet 3. The API is based on the Hypertext Transfer Protocol (HTTP) and provides stateless operations to get and manipulate data in a machine-readable format from RIPS.

There are many reasons why REST is a good choice 4:

  • Easy to integrate in any system since HTTP libraries and tools are available everywhere
  • Easy to scale and route through load balancers, reverse proxies, caches, …
  • Relatively easy to maintain in the long term since
    • changes are backwards compatible
    • it has a clean and easy to understand interface
  • Small attack surface
  • Well suited for web applications and existing web technologies

Methods

In this API, the following HTTP methods are used to specify the type of action that should be performed.

MethodDescriptionReturn
GETGet one or more objectsJSON-encoded object(s)
DELETEDelete one or more objectsempty
POSTCreate (usually) one objectJSON-encoded object(s)
PATCHUpdate one objectJSON-encoded object(s)

Resources

Uniform Resource Identifiers (URI) are used to specify resources. Each resource has a tree-like structure, e.g., an application resource has scans, scans have issues, issues have comments, etc. An URI can refer to a single object or to a collection of objects. With the help of the previously introduced methods a get, delete, create, or update of resources can be initiated. The following table provides some URI examples for the RIPS API.

DescriptionExample
Single application/applications/1
All scans of single application/applications/1/scans
Single scan of a single application/applications/1/scans/7
All issues of a single scan/applications/1/scans/7/issues
Single issue of a single scan/applications/1/scans/7/issues/19
All comments/applications/scans/issues/comments/all

Status Codes

HTTP status codes are used to communicate the success or failure of a request. In general all codes that are defined by the HTTP/1.1 standard can occur but there are 5 codes that occur predominantly.

CodeDescription
200Everything worked as expected
400Invalid user input
401Invalid or missing credentials
403Missing permissions
404Item not found

Additionally to the HTTP code a JSON object is generated and returned. Next to the status code it contains a more detailed human-readable error message for easy debugging. If the status code is not 200, the request should be considered failed and throwing an exception is recommended.


{
  "code": 401,
  "message": "Credentials required."
}

Authentication

The authentication system of the RIPS API 2.7 expects a username and a password in the custom HTTP headers X-API-Username and X-API-Password for every request. If no credentials are given or if the given credentials are not correct a 401 error is thrown.

Messages

Output

If there are no errors the requested resource is returned in JSON format. For a GET request this can either be an object or a collection of objects. Both POST and PATCH requests return a single object that was created or modified by the request, representing its state after the action.

For convenience and to save API requests most objects contain sub objects. Sub objects are included or excluded based on their depth. For example, if you directly read out an application it also contains the user object of its creator. If you directly read out a scan it contains the application object but this application object does not contain the user object anymore.

Input

Input always consists of a JSON object containing one or more additional objects. For a list of all possible parameters please refer to our detailed API specification.

For example, to create or update an application the following JSON message can be used:


{
  "application": {
    "name": "Project 1"
  }
}

To create a new scan the following JSON message can be used. Besides the main object scan it also contains the object php that defines additional settings for the scan, in this case the PHP environment that should be simulated by the RIPS engine.


{
  "scan": {
    "version": "1.2.3",
    "upload": 1
  },
  "php": {
    "majorVersion": 7,
    "minorVersion": 0,
    "releaseVersion": 0
  }
}

Filter

Most endpoints that work with collections allow to filter the results based on certain properties. This is a powerful tool to save bandwidth and time by avoiding manual filter processes on the full result set.

Different filters can be combined but each specific filter has to be unique. All filters have to be fulfilled, so technically speaking the conditions are AND-connected.

OperatorExample
limit/applications?limit=10
offset/applications?offset=20
orderBy/applications?orderBy[name]=desc&orderBy[id]=asc
equal/applications?equal[name]=DVWA
notEqual/applications?notEqual[name]=Wordpress
null/applications/scans/stats?null[upload]
notNull/applications/scans/stats?notNull[path]
like/applications?like[name]=D_WA
notLike/applications?notLike[name]=W%
lessThan/applications?lessThan[id]=101
greaterThan/applications?greaterThan[id]=9
lessThanEqual/applications?lessThanEqual[id]=100
greaterThanEqual/applications?greaterThanEqual[id]=10

Practical Example

We now have a good understanding of the basic concepts of the rich RIPS API. With a proper HTTP library it is a matter of minutes to talk to the API and to start controlling RIPS through own programs. In the following practical example we will write a simple CI plugin in Python that can be used to automatically check a code base for security issues with RIPS.

We want to implement the following basic functionality:

  1. Create a zip archive that contains the source code
  2. Upload the archive to RIPS (SaaS or On-Premises)
  3. Start a new scan
  4. Wait for the scan to finish
  5. Search for unreviewed issues

Unless specified otherwise, RIPS will automatically compare the analysis results of a new scan to its previous scan. Review flags as well as comments are automatically inherited. Hence if we review all reported security issues in our first analysis, perform a rescan of our code and there are unreviewed issues reported, we know that there are new issues we have not reviewed before.

We start by creating a class API for our API communication. This class is merely a wrapper around an HTTP library. As recommended previously, our class raises an exception if a request is not successful. Also, the JSON format is used for everything but the file upload. The following class is sufficient to perform get and post actions of the RIPS API with credentials and to upload file archives.


import requests
import shutil

class API:
    def __init__(self, base_url, username, password):
        self.base_url = base_url
        # Default (authentication) headers.
        self.headers = {
            'X-API-Username': username,
            'X-API-Password': password
        }

    def get(self, uri):
        # Send GET request.
        r = requests.get(self.base_url + uri, headers=self.headers)
        # Stop on failure.
        if r.status_code >= 300:
            raise Exception(r.text)
        # Decode output as JSON.
        return r.json()

    def post(self, uri, payload):
        # Send POST request. Include payload as JSON.
        r = requests.post(self.base_url + uri, headers=self.headers, json=payload)
        # Stop on failure.
        if r.status_code >= 300:
            raise Exception(r.text)
        # Decode output as JSON.
        return r.json()

    def upload(self, uri, path):
        # Create zip archive.
        archive = '/tmp/source' # XXX: should be unique to avoid collisions
        shutil.make_archive(archive, 'zip', path)
        # Send POST request. Include payload as form data.
        payload = {"upload[file]": open(archive + '.zip', 'rb')}
        r = requests.post(self.base_url + uri, headers=self.headers, files=payload)
        # Stop on failure.
        if r.status_code >= 300:
            raise Exception(r.text)
        # Decode output as JSON.
        return r.json()

Now we can utilize the methods of our API class to fulfill the tasks. For this example we are using the SaaS version of RIPS but by modifying the base URL in the constructor (line 17) the code can also be used in the On-Premises version.


import time
import sys

# Use command line arguments as parameters.
if len(sys.argv) < 4:
    print sys.argv[0] + ' [username] [password] [app_id] [path]'
    sys.exit(1)

username = sys.argv[1]
password = sys.argv[2]
app_id = sys.argv[3]
path = sys.argv[4]

# Use current date and time as a version identifier.
version = time.strftime("%x %X")

# Create API object, upload the source code, and start a scan.
api = API('https://api-2.ripstech.com', username, password)
upload = api.upload('/applications/' + app_id + '/uploads', path)
scan = api.post('/applications/' + app_id + '/scans',
                {'scan': {'version': version, 'upload': upload['id']}})

# Check status of scan until it finishes. We do this by polling the status every
# 5 seconds. For debugging purposes the current percentage is printed every loop.
while (True):
    status = api.get('/applications/' + app_id + '/scans/' + str(scan['id']))
    sys.stdout.write('\r' + str(status['percent']) + '%')
    sys.stdout.flush()
    if status['percent'] == 100:
        print # New line
        break
    time.sleep(5)

# Get 1 or 0 unreviewed issues.
issues = api.get('/applications/' + app_id + '/scans/' + str(scan['id']) +
                 '/issues?equal[reviewed]=0&limit=1')

if len(issues) > 0:
    print 'Unreviewed issue(s)'
    sys.exit(1)
else:
    sys.exit(0)

Our script expects the four parameters username, password, app_id, and path. The credentials are used for the RIPS API connection. The app_id is the ID of your existing RIPS application. The script will then pack the code in your local file path, upload it to RIPS and initiate the rescan. It then checks the scan status every 5 seconds and waits for completion. Finally, it checks if unreviewed security issues were found and warns the user.

Running the script may look like this.

Figure 2: Demonstration of the program with different versions of DVWA.

In this example, the second directory dvwa_x contains new security issues. Since these do not have any review flags, our script exits with status code 1 and informs the user. At this point the developer can either review the new issues with a flag in the RIPS interface or fix the code according to RIPS patch instructions.

Summary

In this blog post we provided a simplistic example script that demonstrates the usage of the RIPS API. Although we provide ready-to-use plugins for many popular systems, our RESTful API is a powerful tool for developers. It enables to fully automate nearly any task and thus to seamlessly integrate the process of automated code analysis in highly specialized development environments. The code sample given in this blog post can be used as an initial prototype and can be extended to perform more complex tasks.


  Request a free trial

Tags: hendrik buchwald, ci, api,

Author: Hendrik Buchwald

CISO, Co-Founder

Hendrik graduated in computer science at the Ruhr-University Bochum and is a professional software engineer. In addition to designing and building complex systems, he particularly enjoys breaking and securing web applications. He is an experienced security consultant and the lead developer of the open source web application firewall Shadow Daemon.

Comments

comments powered by Disqus