Web form testing primer

Input testing is one of the most important parts of testing web applications. Any opportunity to process user input is an opportunity for improper handling, validation, or output escaping. When constructing and processing web forms you must ensure that all operations are secure and robust, so you don't fall prey to leakage of secure data or presentational issues like double escaping.

The list below provides a prompt for both manual and automated test cases: it's by no means exhaustive, and you should always consider the particular operations of your application. I would generally advise following Postel's Law: be liberal with the input you accept, but conservative in how you process and output it.

Practices that will minimise your attack surface are:

  • Use input whitelisting when processing known values
  • Enforce consistent text encoding in input, storage, and output (I'd recommend UTF-8)
  • Accept and store un-escaped content, and enforce consistent output escaping (e.g. with a template engine like Twig, or by building then converting native data structures with functions like json_encode())
  • Use parameterised queries via a library like PDO, and never construct raw SQL or other queries with user-supplied content
  • Implement a layer to do operations like escaping or encoding once, rather than relying on separate user operations

A note on escaping

As a simple escaping example, imagine you've done this to construct some JSON in PHP:

$json = '{"foo": "' . $bar . '"}';

Hopefully you can see here that we have an immediate issue: values like 'cat' or 'dog' will work fine in this case, but if we passed in 'cat"' the extra double quote won't be escaped, and will break our JSON. This then creates security issues, as the user can inject arbitrary JSON into the output.

The simplistic way of fixing this would be to wrap $bar in json_encode() like so:

$json = '{"foo": "' . json_encode($bar) . '"}';

This is fine in this particular case, but is easily broken if you're consistently constructing JSON as strings. It's also very easy to introduce syntax errors:

$json = '{';
$json .= '"foo": "' . json_encode($bar),';
$json .= '"baz": "' . json_encode($qux),';
$json .= '}';

Oops—I've introduced an extra trailing comma with some copy-pasting, which will cause this JSON to be invalid.

A much more robust method is to pass the data to json_encode() as an array. This will be converted and escaped in one smooth operation, and any structural issues with the array will be picked up by the PHP parser:

$json = json_encode(array('foo' => $bar, 'baz' => $qux));

This also has the side-effect of making the data much easier to manipulate in transit. If I had constructed this as a string in multiple operations, it would be very hard to do anything other than append values to an existing structure. With an array, I can manipulate it as I would any other data structure. I can also rely on json_encode() to format the JSON output, should I wish to add indentation. Try managing that with a string!

Strings

One of my favourite string testing resources is minimaxir's Big List of Naughty Strings, which gives you values for a lot of the test cases below.

  • Long, short, empty
  • Multi-byte characters
  • Reserved words e.g. TRUE/FALSE, None, undefined
  • Diacritics decomposed/precomposed e.g. café/café
  • Whitespace (spaces, tabs, zero-width spaces, etc.)
  • Syntax-specific escaping e.g.:
    • For JSON, try [, {, or ".
    • For Solr, try AND, OR, field selection with field:value
    • HTML elements or special characters
    • SQL or other query injection
    • JavaScript execution with <script>alert('hello');</script>
    • URL generation, to ensure correct paths and query-strings

Numbers

  • Zero
  • Small int/float
  • Large int/float
  • Special values (NaN, Infinity, 10e10, 08)
  • Overflow when number exceeds language limits (e.g. PHP_INT_MIN, PHP_INT_MAX, PHP_FLOAT_MAX, Number.MAX_VALUE)

Forms

It is important to cross-browser test forms, since the return values may vary depending on the browser use. This includes testing empty fields, to ensure that the correct defaults are populated. The structure and processing of the form should also be tested using by editing the form using the browser inspector.

  • Remove required elements
  • Add new elements to the form
  • Add new elements to select elements
  • Alter disabled or hidden form elements
  • Check for URL enumeration vulnerabilities (e.g. accessing other submissions from a URL format like https://example.com/form?submission=10)
  • Check for CSRF protection if necessary