How to find PHP code in Drupal nodes

Before Drupal 8 was released, the PHP Filter module was part of Drupal core and allowed site builders and developers to create a PHP filter text format. While very convenient for developers and themers, this was also very risky as you could easily introduce security or even performance issues on your site, as a result of executing arbitrary PHP code.

What's the use case for injecting PHP code in content anyway?

There never is a truly good reason to do so except when you're developing the site and willing to quickly test something. Most of the time, using PHP in content is either the result of laziness, lack of time (easiest to add raw PHP directly rather than having to build a custom module) or lack of Drupal API knowledge. PHP Filter is most often used to inject logic in nodes or blocks. As horrible as it sounds, there are very interesting (and smart!) use cases people have come up with and you have to respect the effort. But this is just not something acceptable as you should always advise a clear separation of concerns and use the Drupal API in every instance.

In the past 5 years I've seen things such as:

  • Creating logic for displaying ads
  • Injecting theming elements on the page
  • Redirecting users via drupal_goto() (which often breaks cron and search indexing)
  • Using variable_set() to store data on node_view()
  • Including raw PHP files
  • ...

The list goes on and on and on.

After heated discussions, and because it was far too easy to have users shoot themselves in the foot, it was finally decided to remove the module from core for Drupal 8. But as the usage statistics for Drupal core page shows, we still have more than 1 million Drupal 6 and 7 sites out there that are potentially using it.

If you're still building Drupal 7 sites or if you're taking over maintaining a Drupal 6 or 7 site, it's thus your responsibility to ensure no PHP code is being executed in nodes, blocks, comments, views, etc.

Determine if the PHP text format is in use

So, before you start wondering if you have an issue to fix, let's find out if the PHP module is enabled.

mysql> SELECT name FROM system WHERE name = 'php';  
+------+
| name |
+------+
| php  |
+------+
1 row in set (0.00 sec)  

Now, we need to confirm there is indeed a PHP filter text format on your site. You can use the Security Review module, navigate through the Drupal UI, or query MySQL, which is preferred here and later on because it gives us the granularity we need.

mysql> SELECT format,name,status FROM filter_format WHERE format="php_code";  
+----------+----------+--------+
| format   | name     | status |
+----------+----------+--------+
| php_code | PHP code | 1      |
+----------+----------+--------+
1 row in set (0.00 sec)  

When you do have the php_code text format in use on a site, then you need to start your investigation. In this post we'll focus only on nodes. But the same logic applies for all entities.

Audit all nodes with the php_code text format

In the below example we only have 4 nodes. This means php_code was used only when it was required. But it might very well be that all nodes on a site would use the PHP text filter by default. Tracking down issues would then become more challenging. Worse, removing the text filter entirely would be a very time-consuming task in terms of site auditing, as you might not know what is or isn't going to break when you do the change.

mysql> SELECT nid,title,bundle,entity_type FROM field_data_body LEFT JOIN node ON node.nid=field_data_body.entity_id WHERE body_format='php_code';  
+------+-----------------------+----------+-------------+
| nid  | title                 | bundle   | entity_type |
+------+-----------------------+----------+-------------+
| 7571 | Test nid 7571         | article  | node        |
+------+-----------------------+----------+-------------+
|  538 | Test nid 538          | page     | node        |
+------+-----------------------+----------+-------------+
| 5432 | Test nid 5432         | article  | node        |
+------+-----------------------+----------+-------------+
| 1209 | Test nid 1209         | article  | node        |
+------+-----------------------+----------+-------------+

Find PHP code in nodes

Now that we know which nodes have the php_code text filter set, it's easy to find out if there's indeed PHP code in them, and if it's breaking the site in any way, causing performance troubles, or introducing a security hole.

mysql> SELECT body_value FROM field_data_body WHERE entity_id=7571;  
+--------------------------------------------------------------+
| body_value                                                   |
+--------------------------------------------------------------+
| Thank you for participating! Your results can be found below.
<?php include path_to_theme()."/calculator-results.php"; ?>  |  
+--------------------------------------------------------------+

What about Drupal 8?

As we said in the introduction, the PHP Filter module now lives in contrib instead of Drupal core. And it's very good like that, because it'll prevent the vast majority of Drupal users from installing it. Because, you know, if they can, they will.

If it does exist in production though, then you're in for the same investigation. Fortunately, with Drupal 8 it's even easier to determine when a node is using the php_code text format as you only need one MySQL query and no JOIN.

mysql> SELECT entity_id,bundle,body_value,body_format FROM node__body WHERE body_format = 'php_code';  
+-----------+---------+----------------------------+-------------+
| entity_id | bundle  | body_value                 | body_format |
+-----------+---------+----------------------------+-------------+
|         1 | article | <?php echo 'hi there!'; ?> | php_code    |
+-----------+---------+----------------------------+-------------+
1 row in set (0.00 sec)

Now that you know how to find PHP code in nodes, it's your job to review the code and fix it if necessary, then find ways to remove it completely (custom / contrib module? Theming?). You'll feel a sense of joy when you can switch back to Basic HTML, Markdown, or any other controlled and secure text format.

Aurelien Navarre

Read more posts by this author.

Lyon, France