Title: Lightweight post-exploitation hardening in PHP via call-site freezing and ghetto-CFI with Snuffleupagus
Date: 2022-03-01 17:00

If you're running a lot of WordPress instances, or any other *popular* CMS,
you might be worried about getting some of the compromised, either by one of
the regularly published new vulnerabilities impacting them, or because people
tend to install **a lot** of badly written plugins.

A [lot of different *passive*
features](https://snuffleupagus.readthedocs.io/features.html) can be
enabled in Snuffleupagus to harden your php stack, but there are some *manual*
low-hanging tricks to make post-exploitation a bit more tedious, eg. by
"freezing" *dangerous functions*™ usage.

By running [this
script](https://github.com/jvoisin/snuffleupagus/blob/master/scripts/generate_rules.php)
on a [WordPress](https://wordpress.org/) installation, you should obtain something
looking like this:

```
$ php ./generate_rules.php /wordpress/
sp.disable_function.function("exec").filename("/wordpress/wp-includes/SimplePie/Cache/MySQL.php").hash("3c5e413bd5348d3a895b682eb0d05ea98b0626fc2a4ea45bd9eca1b6fe8444cc").allow();
sp.disable_function.function("exec").filename("/wordpress/wp-includes/class-snoopy.php").hash("ca6f5b4951c14bb0f59e507a39edbc4464f8cda0eb2f6614ca8b0adf6bab8e8b").allow();
sp.disable_function.function("assert").filename("/wordpress/wp-includes/Text/Diff.php").hash("09660398af966e4752715f286b315266854f995b5c74a36fc62d2133e981c901").allow();
sp.disable_function.function("assert").filename("/wordpress/wp-includes/Text/Diff/Engine/native.php").hash("7e57f1eb27c60bb77dd396548b43d243976708683f9f8e5f7febc4cc5b6cabbc").allow();
sp.disable_function.function("shell_exec").filename("/wordpress/wp-includes/Text/Diff/Engine/shell.php").hash("7450f72131686ca0207f4ca5c0da48aba1861cc0bfc18c0aaa92eca29926d49d").allow();
sp.disable_function.function("assert").filename("/wordpress/wp-includes/Text/Diff/Engine/shell.php").hash("7450f72131686ca0207f4ca5c0da48aba1861cc0bfc18c0aaa92eca29926d49d").allow();
sp.disable_function.function("popen").filename("/wordpress/wp-includes/class-phpmailer.php").hash("f6aa236a511cb2d96c58eb2aaef77eb8c37e6308289879ec95c2972cf1fd246f").allow();
sp.disable_function.function("php_uname").filename("/wordpress/wp-includes/class-phpmailer.php").hash("f6aa236a511cb2d96c58eb2aaef77eb8c37e6308289879ec95c2972cf1fd246f").allow();
sp.disable_function.function("php_uname").filename("/wordpress/wp-admin/includes/class-pclzip.php").hash("420b5ee436011db89953454e54b9a87b97b1f5e2c02b7c990ac481ff60bd2fec").allow();
sp.disable_function.function("dl").filename("/wordpress/wp-admin/includes/class-ftp.php").hash("5cd4df3749fa29aefca71455d14bf0239a92554b0080004d9b78b81161fc2b4b").allow();
sp.disable_function.function("shell_exec").drop();
sp.disable_function.function("exec").drop();
sp.disable_function.function("passthru").drop();
sp.disable_function.function("php_uname").drop();
sp.disable_function.function("popen").drop();
sp.disable_function.function("posix_kill").drop();
sp.disable_function.function("posix_mkfifo").drop();
sp.disable_function.function("posix_setpgid").drop();
sp.disable_function.function("posix_setsid").drop();
sp.disable_function.function("posix_setuid").drop();
sp.disable_function.function("posix_setgid").drop();
sp.disable_function.function("posix_uname").drop();
sp.disable_function.function("proc_close").drop();
sp.disable_function.function("proc_nice").drop();
sp.disable_function.function("proc_open").drop();
sp.disable_function.function("proc_terminate").drop();
sp.disable_function.function("proc_get_status").drop();
sp.disable_function.function("dl").drop();
sp.disable_function.function("pnctl_exec").drop();
sp.disable_function.function("pnctl_fork").drop();
sp.disable_function.function("assert").drop();
sp.disable_function.function("system").drop();
$
```

Dangerous functions can only be executed from specific files, matching a specific
hash; should an attacker try to call one them from an other file,
the call would be dropped and their tentative logged. The only way to call
dangerous function would be to use gadgets, à la [ROP](https://en.wikipedia.org/wiki/Return-oriented_programming).

Since snuffleupagus can [match on calltraces](
https://snuffleupagus.readthedocs.io/config.html#details), it's possible to
implement [CFI](https://en.wikipedia.org/wiki/Control-flow_integrity).
On most old-ish codebases, it's enough to use simple static analysis to generate
stacktraces, false-positive-wise. A proof-of-concept script to do this exists,
but I don't plan to share it for now.

The performance impact of the generated allowlist is negligible: since all the functions
are [builtin](https://secure.php.net/manual/en/functions.internal.php) ones,
Snuffleupagus will hook them directly, meaning that the "is this function
allowed?" routine will only kick in when the function is called. If calling a
dangerous functions is a bottleneck of your PHP application, odds are that you
made poor life choices (beside running WordPress) in the first place.

Feel free to modify the
[script](https://github.com/jvoisin/snuffleupagus/blob/master/scripts/generate_rules.php)
to fit your needs and to send me a pull-request.
