Slide 1

Slide 1 text

PHP Hurts Programmers And other tales

Slide 2

Slide 2 text

$ whoami Keith Humm Button Actuator
 Solvam Corporation Ltd 
 @spronkey

Slide 3

Slide 3 text

“you pull out the hammer, but to your dismay, it has the claw part on both sides”

Slide 4

Slide 4 text

• The language itself • Dangerous stdlib & extensions • PHP deployment & php.ini

Slide 5

Slide 5 text

• The language itself • Dangerous stdlib & extensions • PHP deployment & php.ini

Slide 6

Slide 6 text

Type Juggling “a variable's type is determined by the context in which the variable is used”

Slide 7

Slide 7 text

Type Juggling when PHP screws with your variables


Slide 8

Slide 8 text

> $x = “2 plus 3”; string(8)“2 plus 3” > $x += 1; int(3) > $x *= 0.333; double(0.999) > $x = $x && true; bool(true)

Slide 9

Slide 9 text

> “foo” == TRUE true > “foo” == 0 true > TRUE == 0 false > “55” == “055” true > 55 != 055 true *https://www.destroyallsoftware.com/talks/wat WAT.

Slide 10

Slide 10 text

C. A. R. Hoare Invented quicksort … and the null pointer “The most important property of a program is whether it accomplishes the intention of its user.”

Slide 11

Slide 11 text

Real World Example #1 Type Juggling → WordPress Rest API content injection https://blog.sucuri.net/2017/02/content-injection- vulnerability-wordpress-rest-api.html

Slide 12

Slide 12 text

• Posts have an integer id property that’s populated by URL router • WordPress sometimes prioritises HTTP GET and POST vars instead • WordPress inconsistent in usage of router id vs request id /wp-json/wp/v2/posts/123 /wp-json/wp/v2/posts/123?id=456haxxed

Slide 13

Slide 13 text

> update_item_permissions_check() {
 $post = get_post( $request_id );
 
 if($post && ) {
 return WP_Error 
 }
 
 return true;
 } • Our request_id, 456haxxed is nonsense • So no $post is found • Skip right through to return true;

Slide 14

Slide 14 text

> update_item() {
 $id = (int) $request_id;
 $post = get_post( $id );
 if($post) do_update_stuff();
 }

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

> update_item() {
 $id = (int) $request_id;
 $post = get_post( $id );
 if($post) do_update_stuff();
 } /wp-json/wp/v2/posts/123?id=456haxxed hax0red j00r bl0g

Slide 17

Slide 17 text

Rule #1 Rules are hard to remember

Slide 18

Slide 18 text

How to protect yourself

Slide 19

Slide 19 text

✓ Avoid type juggling wherever possible
 Use strict comparison operators e.g. === ✓ Use the right tools for the job
 i.e. hash_equals for timing-safe string comparison ✓ … don’t forget to use them for things like CSRF tokens ✓ Consider promoting primitives to objects
 i.e. function verify(ResetToken $t) : boolean ✓ Consider phpdoc annotations & static analysis
 /** @returns boolean **/
 https://github.com/exakat/php-static-analysis-tools ✓ Read www.phptherightway.com

Slide 20

Slide 20 text

• The language itself • Dangerous stdlib & extensions • PHP deployment & php.ini

Slide 21

Slide 21 text

> array_filter($input, $callback)
 array_map($callback, $input)
 mostly just annoying ✓ Use the newer OO APIs where available ✓ Consider third party libs e.g. underscore-php for fnprog

Slide 22

Slide 22 text

> strip_tags() > strip_tags($str, $allowable_tags)
 welcome to the land of XSS ✓ Use HTMLPurifier or OWASP AntiSammy ✓ Check that your template engine does too

Slide 23

Slide 23 text

> addslashes()
 when all you have is a hammer, 
 everything looks like a nail. ✓ Use an escaping technique fit for purpose - whether you are escaping for the shell, or SQL

Slide 24

Slide 24 text

> (pre 7.0) mysql_escape_string()
 totally insecure. deprecated in 2002 (!)
 still works in PHP 5.6 (!?!) > (mysql/mysqli)_real_escape_string()
 be very careful—don’t trust this with your life ✓ Use PDO, and PDO::quote ✓ Use prepared statements / parameterised queries ✓ Try not to build SQL with _printf()

Slide 25

Slide 25 text

> escapeshellarg()
 Sometimes OK in controlled env
 But it’s impossible to use safely in all envs > escapeshellcmd()
 Completely insecure
 Actually undermines previous sanitisation ✓ Never use escapeshellcmd ✓ Avoid functions that use it internally - mail(), system() etc. ✓ Avoid shelling out if you can ✓ Use pcntl extension with pcntl_fork and pcntl_exec

Slide 26

Slide 26 text

> unserialize()
 Not for user input, ever
 Potentially lets users control code execution ✓ Never unserialise() user input ✓ Use an interchange format instead ✓ JSON, Protobufs, Msgpack, Thrift are all good choices

Slide 27

Slide 27 text

> include() / require()
 A bit of a relic. There’s a better way™ ✓ Use PHP’s autoloader ✓ Use PSR-1 or PSR-4 and Composer

Slide 28

Slide 28 text

> mcrypt
 Most popular* crypto extension. Unmaintained since 2007. ✓ Use libsodium (+Halite), or OpenSSL instead ✓ Use random_bytes or ircmaxell\RandomLib for csprng ✓ Don’t roll your own crypto!

Slide 29

Slide 29 text

Real World Example #2 Type Juggling → Bypass authentication in “Simple Machine Forums” https://www.alertlogic.com/blog/writing-exploits-for- exotic-bug-classes-php-type-juggling/

Slide 30

Slide 30 text

• Code verifying a password recovery token • Tokens generated randomly and emailed to user • User input hashed, then first 10 chars are compared if ( 
 substr(md5($input), 0, 10)
 == 
 substr($storedHash, 0, 10)
 )

Slide 31

Slide 31 text

> substr($storedHash,0,10) string “0e12345678” > $input = “190539”; > substr(md5($input),0,10) string “0e25261622” > (“0e12345678” == “0e25261622”) → bool(?)

Slide 32

Slide 32 text

> substr($storedHash,0,10) string “0e12345678” > $input = “190539”; > substr(md5($input),0,10) string “0e25261622” > (“0e12345678” == “0e25261622”) → bool(true) gained admin access

Slide 33

Slide 33 text

• Hashes can look like scientific notation “0e12345678” • == operator may coerce either/both sides to ints • …if they “look like numbers” if ( 
 substr(md5($input), 0, 10)
 == 
 substr($storedHash, 0, 10)
 )

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

Real World Example #3 Type Juggling → Object Injection → SQLi
 in Expression Engine https://foxglovesecurity.com/2017/02/07/type-juggling- and-php-object-injection-and-sqli-oh-my/

Slide 36

Slide 36 text

• Cookie $payload storing serialised PHP objects • Cookie signed with $signature, supplied in cookie itself • Valid signature in theory shows cookie untampered • sess_crypt_key is private • But $payload is entirely user controlled if ( md5( $payload . $this->sess_crypt_key ) == $signature )

Slide 37

Slide 37 text

• unserialize() sets username to an actual instance of EE’s Token\Variable class, instead of a plain string • System passes username from the new object to EE’s SQL engine where() a:1:{s:13:”:new:username”;O:67:"EllisLab\\\ \\ExpressionEngine\\\\\Library\\\\\Parser\\ \\\Conditional\\\\\Token\\\\Variable":1:{s: 6:"lexeme";s:1:"'; DROP TABLE `users`;”;}}

Slide 38

Slide 38 text

• EE’s SQL escape routine allows __toString() of this object to be included in query verbatim • Instance’s state can be crafted as needed by adjusting serialised data a:1:{s:13:”:new:username”;O:67:"EllisLab\\\ \\ExpressionEngine\\\\\Library\\\\\Parser\\ \\\Conditional\\\\\Token\\\\Variable":1:{s: 6:"lexeme";s:1:"'; DROP TABLE `users`;”;}}

Slide 39

Slide 39 text

Don’t roll your own cryptography

Slide 40

Slide 40 text

• The language itself • Dangerous stdlib & extensions • PHP deployment & php.ini

Slide 41

Slide 41 text

> @fopen(‘http://non-existant.file', ‘r’); • allow_url_fopen controls whether this works at all • … unless it’s in disable_functions • @ operator suppresses the does-not-exist warning • … unless scream.enabled is set in php.ini • … or it was set with ini_set() • error_reporting setting can also alter this

Slide 42

Slide 42 text

• display_errors
 Whether errors are output to the browser • allow_url_include
 Whether you can include() code from a remote URL • allow_url_fopen
 Whether fopen() can load remote files (use cURL!) • session.use_cookies
 Whether sessions use cookies or URL parameters • zend.multibyte
 Allows encodings like SJIS/BIG5 to be used for source files

Slide 43

Slide 43 text

✓ You must control your environment ✓ …or trust whoever does ✓ Make environment config part of your deployment ✓ Consider Suhosin if running third party apps ✓ Be careful with Apache mod_php
 What if a user uploads dodgy_script.php7 ? ✓ Use php.ini security analysis tools
 e.g. https://github.com/sektioneins/pcc 


Slide 44

Slide 44 text

–Porky Pig “That’s all folks!”

Slide 45

Slide 45 text

Link-fu • PHP The Right Way - http://www.phptherightway.com/ • PHP: A fractal of bad design
 https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/ • WordPress vulnerability discussed:
 https://blog.sucuri.net/2017/02/content-injection-vulnerability-wordpress-rest-api.html • Simple Machine Forums vulnerability discussed:
 https://www.alertlogic.com/blog/writing-exploits-for-exotic-bug-classes-php-type-juggling/ • ExpressionEngine vulnerability
 https://foxglovesecurity.com/2017/02/07/type-juggling-and-php-object-injection-and-sqli-oh-my/ • OWASP Resources
 Both of these are “work in progress” / drafts
 https://www.owasp.org/index.php/PHP_Security_Cheat_Sheet 
 https://www.owasp.org/index.php/PHP_Configuration_Cheat_Sheet 
 https://www.owasp.org/index.php/PHP_Object_Injection • PHP Configuration Checker (php.ini)
 https://github.com/sektioneins/pcc • All about shell escaping & php
 https://gist.github.com/Zenexer/40d02da5e07f151adeaeeaa11af9ab36 • List of static analysis tools for PHP
 https://github.com/exakat/php-static-analysis-tools • Gary Bernhardt’s “WAT” talk
 https://www.destroyallsoftware.com/talks/wat