This is a post about injecting carriage return and line feed characters into a internal API call. I wrote this up a year ago as a Gist on GitHub, but that’s not really the best platform for blog posts, is it? I’ve added more detail here so it’s not just a straight copy and paste.

I like to do white-box testing when I can. I’m not a very good black-box tester, but I’ve spent more than a decade reading and writing PHP — and made my fair share of mistakes along the way — so I tend to know what to look out for.

I was trawling through some source code and came across a function that looked a little bit like this:

<?php

// common.php



function getTrialGroups(){

$trialGroups = 'default';



if (isset($_COOKIE['trialGroups'])){

$trialGroups = $_COOKIE['trialGroups'];

}



return explode(",", $trialGroups);

}

The system I was looking at had a concept of ‘Trial Groups’. Every user session had a set of groups associated with it, stored as a comma-separated list in a cookie. The idea was that when new features were launched they could be enabled for a small percentage of customers at first to de-risk the feature launch, or allow comparison of different variations on a feature (an approach known as A/B Testing). The getTrialGroups() function simply read the cookie value, split the list apart and returned an array of trial groups for that user.

The lack of whitelisting in this function immediately caught my attention. I grepped the rest of the codebase to find where the function was called so I could see if there was any unsafe use of its return value.

I can’t share the exact code, but I’ve written a rough approximation of one of the things I found:

<?php

// server.php



// Include common functions

require __DIR__.'/common.php';



// Using the awesome httpbin.org here to just reflect

// our whole request back at us as JSON :)

$ch = curl_init("http://httpbin.org/post");



// Make curl_exec return the response body

curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);



// Set the content type and pass through any trial groups

curl_setopt($ch, CURLOPT_HTTPHEADER, [

"Content-Type: application/json",

"X-Trial-Groups: " . implode(",", getTrialGroups())

]);



// Call the 'getPublicData' RPC method on the internal API

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([

"method" => "getPublicData",

"params" => []

]));



// Return the response to the user

echo curl_exec($ch);



curl_close($ch);

This code was calling the getPublicData method on an internal JSON API using the cURL library. That API needed to know about the user’s trial groups so it could change its behaviour accordingly, and so the trial groups were being passed to the API in an X-Trial-Groups header.