XSS, GET and POST

There is recent work converting pages in the pfSense software webGUI to use POST rather than GET. This work is scheduled to appear in pfSense software version 2.4.

While this work was spurred by the recent security issue that caused the pending release of pfSense software version 2.3.3, it isn’t specifically about closing XSS bugs. There are situations when you should use POST rather than GET, but just avoiding XSS isn’t one of them.  Even if what we’re talking about is XSRF, requiring POST doesn’t really protect the application. REST advocates would actually say that you shouldn’t just use GET in a web application, but rather that you should use POST, PUT and DELETE for the corresponding “CRUD” operations, operations that change the state of the application.

To specifically avoid XSS, a web app needs to escape and/or scrub content from users as appropriate.  To avoid XSRF, a web app has to require secret tokens on any side-effect causing operation that is potentially dangerous.  Note that is is a good idea to avoid using GET requests when passing secret tokens as this could result in them leaking in referrers.  Still, switching to post does help avoid XSS attacks.  As Wikipedia explains:

In HTTP GET the CSRF exploitation is trivial. For example, a simple hyperlink containing manipulated parameters and automatically loaded by a IMG tag. By the HTTP specification however, GET should be used as a safe method, that is, not significantly changing user’s state in the application. Applications using GET for such operations should be rewritten to use HTTP POST and/or use anti-CSRF protection.

Simplifying the above:

  • Use GET for read-only requests whenever possible. (pretty much whenever the query can fit in a URL)
  • Use POST (or PUT or DELETE, if feasible and appropriate) for write requests.

The process of conversion from using GET to POST has previously required a comprehensive re-write of the page, converting anchors into buttons and adding Javascript to handle the click event.  Jim Pingle recently found some code where someone had attempted to automate this in Javascript. While it was not suitable for what we needed, it sparked an idea, and that idea has now been implemented for pfSense 2.4.

The file pfSenseHelpers.js now contains code that intercepts clicks on anchor tags with the attribute “usepost” set. The target URL and the GET arguments are extracted from the event href attribute, and these are used to compose a new, temporary form with the previous arguments inserted as POST parameters.

Converting a page from GET to POST now only requires four steps:

  1. Replace $_GET with $_POST where appropriate
  2. Add the “usepost” attribute to anchors that have the href attributes set
  3. Fix any “if ($_POST)” instances (or similar)
  4. Test

Not all GET calls need to be replaced, in fact where the action involved is not harmful, such as “edit”, or “view” it is better to leave the GET or REQUEST in place. That way the action can be bookmarked and using the browser “Back” button is less frustrating.

Here is a simple example of a conversion:

Before:

<?php
  if ($_GET['act'] == "delete") {
    deleteGateway($_GET['id']);
  }

  if ($_POST) {
    if ($_POST['apply'] {
      write_nvram();
    } else {
      if (!save_config($id)) {
        $input_errors[] = "Something broke";
      }
    }
  }
?>

<a type="button" class="btn btn-danger" href="system_something.php?act=delete&id=<?=htmlspecialchars($id)?>" >
  <i class="fa fa-trash></i>
  <?=gettext("Delete")?>
</a>

After:

<?php
  if ($_POST['act'] == "delete") {
    deleteGateway($_POST['id']);
  }

  if ($_POST['apply']) {
    write_nvram();
  }

  if ($_POST['save']) { // The generic if ($_POST) is now if ($_POST['save'] to detect when the form is being saved
    if (!save_config($id)) {
      $input_errors[] = "Something broke";
    }
  }
?>

<!-- The "usepost" attribute is added to the anchor -->
  <a type="button" class="btn btn-danger" href="system_something.php?act=delete&id=<?=htmlspecialchars($id)?>" usepost>
  <i class="fa fa-trash></i>
  <?=gettext("Delete")?>
</a>

Most of the main body of pfSense software version 2.4 has been converted to use this scheme.  Now we need the help of the pfSense Community, to test the whole of the pfSense 2.4 web GUI, and file bugs on https://redmine.pfsense.org if inconsistent behavior is observed. Additionally, authors and maintainers of pfSense packages should convert their packages when possible.

We thank you in advance for your assistance and continued participation in the community around pfSense software.