PHP Objection Injection Through a SQL Injection Vulnerability in a WordPress Plugin
Recently there have been claims that hackers have been causing PHP object injection through SQL injection vulnerabilities in WordPress plugins. The details needed to allow others to confirm whether or not that is true had not been provided (which didn’t stop journalist from repeating the claims) and in our testing we were not able to figure out a way to get that to work with the plugins that it has been claimed it had occurred with. It is possible that we have missed something or it is possible that there was a belief that it could occur leading to hackers attempting it, but it really wasn’t possible in those plugins.
One route we looked to recreate the claim was using UNION SELECT as part of the SQL injection to cause a value needed for the PHP object injection to be returned from the SQL statement susceptible to SQL injection. What we have run into in trying that is that we couldn’t get an appropriate value needed for PHP object injection through that, due to the escaping WordPress does of quote marks.
Recently, while looking into a plugin that hackers might be targeting, where we thought that type of issue might be occurring, we had a different issue. We found a possible SQL injection vulnerability in a SQL query whose result is then passed through unserialize() in the plugin Table Maker, but we didn’t see how the SQL injection vulnerability could be exploited. One of our customers, J.D. Grimes, left a comment on the post where we discussed that plugin, with an explanation of how that could be accomplished. With that we were then able to cause the PHP object injection to occur through SQL injection in the plugin. That doesn’t do anything to explain what happened in the other instances, since in this case, quote escaping didn’t come into play.
The good news is that even before we had figured out how the SQL injection could be exploited we had notified the developer of the issue and a fix was released on Monday.
For those interested in how you would have gotten from SQL injection to PHP object injection is this plugin and how a hacker may have already been, let’s go through it.
As of version 1.6, the function xml_download() ran once WordPress had loaded activated plugins, which occurs when visiting frontend and backed pages of WordPress:
30 | add_action('plugins_loaded', array($this, 'xml_download')); |
That function then allowed anyone to pass the value of the GET input “table” to the get() function:
395 396 397 | function xml_download() { if(isset($_POST['wpsm-export-table'])) { $result = $this->db->get( $_GET['table'] ); |
As of version 1.6, the function get() in /inc/class-wpsm-db-table.php improperly handled the security of a SQL statement that had its result unserialized:
94 95 96 97 98 99 100 101 102 103 | public function get($id){ if( is_array($id) ){ $id = sprintf('(%s)', implode(',', $id)); } else { $id = sprintf('(%d)', $id); } $row = $this->db->get_row("SELECT * FROM $this->table_name WHERE id IN $id", ARRAY_A); if($row){ $row['tvalues'] = $this->unserialize($row['tvalues']); |
The value of the GET input “id” becomes the variable $id in that. If the variable $id is anything other than an array the value is limited to an integer, which couldn’t be used for SQL injection. What we had not figured out as of yesterday was how to get that variable to be an array in that, which would allow SQL injection to occur. That is where J.D. comes in, as he commented:
Making
$_GET['table']
an array should be possible using a query string liketable[]=payload
, I think.
Once we had that we could get cause the SQL injection to occur. We then used UNION SELECT to cause the value being returned from the SQL statement to come from what was in the GET input. The value to unserialized is $row[‘tvalues’], which comes from “tvalues” column of the table wpsm_tables and that column is the eighth out of eight column in the table, so the UNION SELECT would look something like this:
UNION SELECT 1,2,3,4,5,6,7,[PHP object injection here]
The unserialize() function being used in the above code is not PHP’s, but this:
123 124 125 | private function unserialize($item){ return unserialize(base64_decode($item)); } |
So the PHP object injection value needs to be base64 encoded.
You can see that all in action with the proof of concept below.
Proof of Concept
With our plugin for testing for PHP object injection installed and activated, the following proof of concept will cause the message “PHP object injection has occurred.” be shown.
Make sure to replace “[path to WordPress]” with the location of WordPress.
<html> <body> <form action="http://[path to WordPress]/?table[]=1) UNION SELECT 1,2,3,4,5,6,7,('TzoyMDoicGhwX29iamVjdF9pbmplY3Rpb24iOjA6e30='" method="POST"> <input type="hidden" name="wpsm-export-table" /> <input type="submit" value="Submit" /> </form> </body>