Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Proposed PHP function: is_literal()

Avatar for Craig Francis Craig Francis
September 09, 2020

Proposed PHP function: is_literal()

A way to identify trusted developer strings (aka "literals", which have been defined within the PHP script) which need to be used for HTML templates, SQL strings, CLI strings; and keep those completely separate from user controlled (attacker tainted) strings.

Avatar for Craig Francis

Craig Francis

September 09, 2020

Other Decks in Technology

Transcript

  1. is_literal() To distinguish between safe strings, which have been de

    fi ned in your PHP script. VS unsafe strings, which have been tainted by external sources.
  2. Google engineer Christoph Kern, 2015: "Bugs are hard to fi

    nd after the fact" "Manual Testing, Automated Testing, Static Analysis, Human Code Reviews; ... will fi nd some of these bugs..." @04:02 Background
  3. $users = $queryBuilder ->select('u') ->from('User', 'u') ->where('u.type_id = ' .

    $_GET['type_id']) ->getQuery() ->getResult(); Examples
  4. $users = $queryBuilder ->select('u') ->from('User', 'u') ->where('u.type_id = ' .

    $_GET['type_id']) ->getQuery() ->getResult(); Unsafe String Safe String Examples
  5. $users = $queryBuilder ->select('u') ->from('User', 'u') ->where('u.type_id = ' .

    $_GET['type_id']) ->getQuery() ->getResult(); Examples 'WHERE u.type_id = u.type_id'
  6. $sql = 'SELECT u FROM User u WHERE u.type_id =

    ' . $_GET['type_id']; $query = $entityManager->createQuery($sql); Doctrine - CreateQuery, DQL Examples
  7. Taint Checking $pre fi x = 'Hi '; $welcome_html =

    $pre fi x . 'Name'; Untainted Untainted Untainted
  8. Taint Checking $pre fi x = 'Hi '; $welcome_html =

    $pre fi x . 'Name'; Untainted, Safe
  9. Taint Checking $name = $_GET['name']; $pre fi x = 'Hi

    '; $welcome_html = $pre fi x . $name;
  10. Taint Checking $name = $_GET['name']; $pre fi x = 'Hi

    '; $welcome_html = $pre fi x . $name; Tainted
  11. Taint Checking $name = $_GET['name']; $pre fi x = 'Hi

    '; $welcome_html = $pre fi x . $name;
  12. Taint Checking $name = $_GET['name']; $pre fi x = 'Hi

    '; $welcome_html = $pre fi x . $name; Untainted
  13. Taint Checking $name = $_GET['name']; $pre fi x = 'Hi

    '; $welcome_html = $pre fi x . $name; Untainted Tainted
  14. Taint Checking $name = $_GET['name']; $pre fi x = 'Hi

    '; $welcome_html = $pre fi x . $name; Tainted, not safe... complain when printed.
  15. Taint Checking $name = $_GET['name']; $pre fi x = 'Hi

    '; $welcome_html = $pre fi x . htmlentities($name);
  16. Taint Checking $name = $_GET['name']; $pre fi x = 'Hi

    '; $welcome_html = $pre fi x . htmlentities($name); Un-Taint
  17. Taint Checking $name = $_GET['name']; $pre fi x = 'Hi

    '; $welcome_html = $pre fi x . htmlentities($name); Untainted, should be safe.
  18. Taint Checking $url = $_GET['url']; $name = $_GET['name']; $link_html =

    '<a href="' . htmlentities($url) . '">' . htmlentities($name) . '</a>';
  19. Taint Checking $url = $_GET['url']; $name = $_GET['name']; $link_html =

    '<a href="' . htmlentities($url) . '">' . htmlentities($name) . '</a>'; Tainted Tainted
  20. Taint Checking $url = $_GET['url']; $name = $_GET['name']; $link_html =

    '<a href="' . htmlentities($url) . '">' . htmlentities($name) . '</a>'; Tainted Tainted
  21. Taint Checking $url = $_GET['url']; $name = $_GET['name']; $link_html =

    '<a href="' . htmlentities($url) . '">' . htmlentities($name) . '</a>'; Un-Taint Un-Taint
  22. Taint Checking $url = $_GET['url']; $name = $_GET['name']; $link_html =

    '<a href="' . htmlentities($url) . '">' . htmlentities($name) . '</a>'; Untainted, surely this is safe?
  23. Taint Checking $url = $_GET['url']; $name = $_GET['name']; $link_html =

    '<a href="' . htmlentities($url) . '">' . htmlentities($name) . '</a>'; 'javascript:alert("hi")'
  24. Untainted, surely this is safe? Taint Checking $sql .= 'WHERE

    id = ' . mysqli_real_escape_string($link, $_GET['id']);
  25. Taint Checking $img_html = '<img src=' . htmlentities($_GET['url']) . '>';

    $img_html = '<img src=/ onerror=evil-js>'; "/ onerror=evil-js"
  26. Taint Checking The PDO Quote function clearly says it's only

    "theoretically safe". https://www.php.net/pdo.quote
  27. Injection problems are rarely solved by Static Analysis. Static Analysis

    But it's hard to follow every variable from Source to Sink.
  28. $mysqli = new mysqli('localhost', 'test', '???', 'test'); $result = $mysqli->query('SELECT

    * FROM user WHERE id = ' . $_GET['id']); if ($result instanceof mysqli_result) { print_r($result->fetch_all()); } Static Analysis PHPStan
  29. $mysqli = new mysqli('localhost', 'test', '???', 'test'); $id = (string)

    $_GET['id']; $result = $mysqli->query('SELECT * FROM user WHERE id = ' . $id); if ($result instanceof mysqli_result) { print_r($result->fetch_all()); } Static Analysis Avoid MixedAssignment Psalm
  30. $mysqli = new mysqli('localhost', 'test', '???', 'test'); $id = (string)

    $_GET['id']; $result = $mysqli->query('SELECT * FROM user WHERE id = ' . $id); if ($result instanceof mysqli_result) { print_r($result->fetch_all()); } Static Analysis Psalm, Taint Analysis I'm not using "mysqli_query()" https://github.com/vimeo/psalm/issues/4155 Avoid MixedAssignment
  31. $a = 'This is a Literal'; is_literal($a . htmlentities($_GET['name'])); Still

    false is_literal() is not the same as Taint Checking.
  32. $a = 'This is a Literal'; is_literal($a . strtoupper('abc')); Sorry,

    this is false - 'ABC' is no longer the string de fi ned in the PHP script.
  33. $users = $queryBuilder ->select('u') ->from('User', 'u') ->where('u.type_id = 1') ->getQuery()

    ->getResult(); public function where($predicates) { if (!is_literal($predicates)) { throw new Exception('Can only accept a literal'); } ... } Is a Safe Literal?
  34. $users = $queryBuilder ->select('u') ->from('User', 'u') ->where('u.type_id = ' .

    $_GET['type_id']) ->getQuery() ->getResult(); public function where($predicates) { if (!is_literal($predicates)) { throw new Exception('Can only accept a literal'); } ... } Not a Safe Literal
  35. $template = $twig->createTemplate('<p>Hello {{ name }}</p>'); echo $template->render(['name' => $_GET['name']])

    public function createTemplate(string $template, string $name = null): TemplateWrapper { if (!is_literal($template)) { throw new Exception('Can only accept a literal'); } ... } Is a Safe Literal?
  36. $template = $twig->createTemplate('<p>Hello ' . $_GET['name'] . '</p>'); echo $template->render()

    public function createTemplate(string $template, string $name = null): TemplateWrapper { if (!is_literal($template)) { throw new Exception('Can only accept a literal'); } ... } Not a Safe Literal
  37. if (function_exists('is_literal') && !is_literal($a)) { trigger_error('Can only accept a literal',

    E_USER_NOTICE); } Backwards compatibility Notices might be safer for legacy projects.
  38. $sql .= 'ORDER BY ' . $ fi eld_name; What

    about Table and Field names?
  39. $ fi elds = [ 'name', 'created', 'admin', ]; $

    fi eld_id = array_search(($_GET['sort'] ?? 'created'), $ fi elds); $sql = ' ORDER BY ' . $ fi elds[$ fi eld_id];
  40. $ids = [1, 2, 3]; $sql .= 'WHERE id IN

    (' . implode(', ', $ids) . ')'; From an unknown source
  41. $ids = [1, 2, 3]; $sql .= 'WHERE id IN

    (' . implode(',', array_ fi ll(0, count($ids), '?')) . ')';
  42. $ids = [1, 2, 3]; $sql .= 'WHERE id IN

    (' . implode(',', array_ fi ll(0, count($ids), '?')) . ')'; $sql .= 'WHERE id IN (?, ?, ?)';
  43. Google have a similar solution in Go: https://github.com/google/go-safeweb/tree/master/safesql A Query

    Builder, with an Append() method that only accepts compile-time constant strings (literal or const). Other Implementations SQL Injection
  44. JavaScript may get a similar solution: https://github.com/tc39/proposal-array-is-template-object "Distinguishing strings from

    a trusted developer, from strings that may be attacker controlled" Other Implementations
  45. Concerns • Performance Might not be able to justify string

    concatenation. But could introduce a new function to combine an array of literals:
  46. Concerns • Performance Might not be able to justify string

    concatenation. But could introduce a new function to combine an array of literals: $sql = ['SELECT u.name FROM user AS u']; if ($id) { $sql[] = 'WHERE id = ?'; $parameters[] = $id; } $sql = literal_implode(' ', $sql);
  47. • Performance • Can array_ fi ll() + implode() pass

    though the "is_literal" fl ag? (WHERE IN) • Name it something else? • Variables, like a table pre fi x, being stored in an INI / JSON / YAML fi le? Concerns
  48. The Future • Internal PHP functions, like mysqli_query(), preg_match(), exec(),

    etc; could all be setup to only accept safe literals. • When printing strings (i.e. sending HTML to the browser), this could be blocked, with an exception for your trusted HTML templating engine. Maybe PHP could only accept a speci fi c value object that has a __toString() method?