Artificial truth

archives | latest | homepage | atom/rss/twitter

The more you see, the less you believe.

Lightweight post-exploitation hardening in PHP via call-site freezing and ghetto-CFI with Snuffleupagus
Tue 01 March 2022 — download

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 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 on a WordPress 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.

Since snuffleupagus can match on calltraces, it's possible to implement CFI. 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 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 to fit your needs and to send me a pull-request.