This blog post is about the Automatic plugin vulnerabilities. If you’re an Automatic user, please update the plugin to at least version 3.92.1.
All paid Patchstack users are protected from this vulnerability. Sign up for the free Community account first, to scan for vulnerabilities and apply protection for only $5 / site per month with Patchstack.
For plugin developers, we have security audit services and Enterprise API for hosting companies.
About the Automatic Plugin
The plugin Automatic (premium version), which is estimated to have over 40,000 active installations, is known as the more popular automatic content posts plugin in WordPress. This plugin is developed by ValvePress.
This premium WordPress plugin has some features, one of which is to create posts from almost any website to WordPress automatically. It can import from popular sites like YouTube and Twitter utilizing the APIs or from almost any website of our choice using scraping modules. The plugin also now can generate content using OpenAI GPT.
The security vulnerability
This plugin suffers from multiple critical vulnerabilities and could allow any unauthenticated user to read local files and gain a full-scale SQL query execution on the WordPress site.
The first vulnerability is Unauthenticated Arbitrary SQL Execution. This vulnerability allows any unauthenticated user to fully control an SQL query that will be executed on the WordPress site. The second vulnerability is Unauthenticated Arbitrary File Download and SSRF. This vulnerability allows any unauthenticated user to read arbitrary local files and perform a Server-Side Request Forgery (SSRF) attack on the WordPress site server. The described vulnerabilities were fixed in version 3.92.1 and assigned CVE-2024-27956 and CVE-2024-27954 respectively.
Unauthenticated Arbitrary SQL Execution
The underlying vulnerability exists on inc/csv.php
file:
<?php
require_once('../../../../wp-load.php');
global $wpdb;
global $current_user;
wp_get_current_user();
// echo user_login . "'s email address is: " . $current_user->user_pass;
//get admin pass for integrity check
// extract query
$q = stripslashes($_POST['q']);
$auth = stripslashes($_POST['auth']);
$integ=stripslashes($_POST['integ']);
if(wp_automatic_trim($auth == '')){
echo 'login required';
exit;
}
if(wp_automatic_trim($auth) != wp_automatic_trim($current_user->user_pass)){
echo 'invalid login';
exit;
}
if(md5(wp_automatic_trim($q.$current_user->user_pass)) != $integ ){
echo 'Tampered query';
exit;
}
$rows=$wpdb->get_results( $q);
$date=date("F j, Y, g:i a s");
$fname=md5($date);
header("Content-type: application/csv");
header("Content-Disposition: attachment; filename=$fname.csv");
header("Pragma: no-cache");
header("Expires: 0");
echo "DATE,ACTION,DATA,KEYWORD \n";
foreach($rows as $row){
$action=$row->action;
if (stristr($action , 'New Comment Posted on :')){
$action = 'Posted Comment';
}elseif(stristr($action , 'approved')){
$action = 'Approved Comment';
}
//format date
$date=date('Y-n-j H:i:s',strtotime ($row->date));
$data=$row->data;
$keyword='';
//filter the data strip keyword
if(stristr($data,';')){
$datas=explode(';',$row->data);
$data=$datas[0];
$keyword=$datas[1];
}
echo "$date,$action,$data,$keyword \n";
}
// echo "record1,$q,record3\n";
?>
We can see that we are able to supply an arbitrary SQL query on $q
variable and it will be executed with $wpdb->get_results( $q)
. However, there are checks implemented using wp_automatic_trim($current_user->user_pass)
and then md5(wp_automatic_trim($q.$current_user->user_pass))
.
Let’s see the content of wp_automatic_trim
function:
function wp_automatic_trim($str)
{
if (is_null($str)) {
return '';
} else {
return trim($str);
}
}
The function just as stated in the function name, performs a basic trim
to the supplied value. The first check involves $current_user->user_pass value. This value would be an empty string if the file is accessed by an unauthenticated user. So, we can just supply an empty string to the $auth
variable. For the second check, we just need to supply only the MD5 value of our supplied SQL query to the $integ
since $current_user->user_pass
is an empty string. However, before the two checks, there is a check of if(wp_automatic_trim($auth == ''))
making us can’t just input an empty string to the $auth
. To bypass this, we can just supply a single whitespace (” “) to the $auth
and we are able to achieve an arbitrary SQL query execution.
Unauthenticated Arbitrary File Download and SSRF
The underlying vulnerability exists on downloader.php
file:
function curl_exec_follow( &$ch){
$max_redir = 3;
for ($i=0;$i<$max_redir;$i++){
$exec=curl_exec($ch);
$info = curl_getinfo($ch);
if($info['http_code'] == 301 || $info['http_code'] == 302 || $info['http_code'] == 307 ){
curl_setopt($ch, CURLOPT_URL, wp_automatic_trim($info['redirect_url']));
$exec=curl_exec($ch);
}else{
//no redirect just return
break;
}
}
return $exec;
}
$link=$_GET['link'];//urldecode();
$link=wp_automatic_str_replace('httpz','http',$link);
//$link='http://ointmentdirectory.info/%E0%B8%81%E0%B8%B2%E0%B8%A3%E0%B9%81%E0%B8%AA%E0%B8%94%E0%B8%87%E0%B8%A0%E0%B8%B2%E0%B8%9E%E0%B8%99%E0%B8%B4%E0%B9%88%E0%B8%87-%E0%B8%97%E0%B8%AD%E0%B8%94%E0%B8%9B%E0%B8%A5%E0%B8%B2%E0%B9%80%E0%B8%9E';
// echo $link ;
//exit ;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, wp_automatic_trim($link));
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
curl_setopt($ch,CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_REFERER, 'http://bing.com');
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.8) Gecko/2009032609 Firefox/3.0.8');
curl_setopt($ch,CURLOPT_MAXREDIRS, 5); // Good leeway for redirections.
curl_setopt($ch,CURLOPT_FOLLOWLOCATION, 0); // Many login forms redirect at least once.
$exec=curl_exec_follow($ch);
$res=array();
//get the link
$curlinfo=curl_getinfo($ch);
$original_link=$curlinfo['url'];
$original_link=wp_automatic_str_replace("?hop=zzzzz",'',$original_link);
$res['link']=$original_link;
//get the title
preg_match("/<title>(.*?)<\/title>/i",$exec,$matches );
$ret=$matches[1];
$res['title']=$ret;
$res['status']='success';
$ret = array();
/*** a new dom object ***/
$dom = new domDocument;
/*** get the HTML (suppress errors) ***/
@$dom->loadHTML($exec);
/*** remove silly white space ***/
$dom->preserveWhiteSpace = false;
/*** get the links from the HTML ***/
$text = $dom->getElementsByTagName('p');
/*** loop over the links ***/
foreach ($text as $tag)
{
$textContent = $tag->textContent;
if(wp_automatic_trim($textContent) == '' || strlen($textContent) < 25 || stristr($textContent, 'HTTP') || stristr($textContent, '$')) continue;
$ret[] = $textContent;
}
$res['text']=$ret;
echo json_encode($res);
exit;
@unlink('files/temp.html');
$cont=curl_exec($ch);
//$cont=file_get_contents($link);
if (curl_error($ch)){
echo 'Curl Error:error:'.curl_error($ch);
}
file_put_contents('files/temp.html',$link.$cont);
?>
The mentioned file can accessed by setting the wp_automatic
GET parameter (thru the to $wp->query_vars
) to “download” as stated in the wp_automatic_parse_request
function:
function wp_automatic_parse_request($wp) {
//secret word
$wp_automatic_secret = wp_automatic_trim(get_option('wp_automatic_cron_secret'));
if(wp_automatic_trim($wp_automatic_secret) == '') $wp_automatic_secret = 'cron';
// only process requests with "my-plugin=ajax-handler"
if (array_key_exists('wp_automatic', $wp->query_vars)) {
if($wp->query_vars['wp_automatic'] == $wp_automatic_secret){
require_once(dirname(__FILE__) . '/cron.php');
exit;
}elseif ($wp->query_vars['wp_automatic'] == 'download'){
require_once 'downloader.php';
exit;
}elseif ($wp->query_vars['wp_automatic'] == 'test'){
require_once 'test.php';
exit;
}elseif($wp->query_vars['wp_automatic'] == 'show_ip'){
$ch = curl_init();
curl_setopt($ch, CURLOPT_HEADER,0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_TIMEOUT,20);
curl_setopt($ch, CURLOPT_REFERER, 'http://www.bing.com/');
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.8) Gecko/2009032609 Firefox/3.0.8');
curl_setopt($ch, CURLOPT_MAXREDIRS, 5); // Good leeway for redirections.
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // Many login forms redirect at least once.
//curl get
$x='error';
$url='https://www.showmyip.com/';
curl_setopt($ch, CURLOPT_HTTPGET, 0);
curl_setopt($ch, CURLOPT_URL, wp_automatic_trim($url));
$exec=curl_exec($ch);
$x=curl_error($ch);
//<h2 id="ipv4">41.176.183.83</h2>
if(strpos($exec,'<h2 id="ipv4">')){
preg_match('{<h2 id="ipv4">(.*?)</h2>}', $exec , $ip_matches);
print_r($ip_matches[1]);
}else{
echo $exec.$x;
}
exit;
}
}
}
add_action('parse_request', 'wp_automatic_parse_request');
Back to the downloader.php
file, we are able to supply an arbitrary URL or even local files on $_GET['link']
parameter and it will be fetched using cURL.
The patch
For the Unauthenticated Arbitrary SQL Execution vulnerability, the vendor decided to remove entirely the inc/csv.php
file. For the Unauthenticated Arbitrary File Download and SSRF, the vendor decided to apply a nonce check (whereas the nonce value could only be fetched from a privileged user) and apply a check on the $link
variable.
Conclusion
The vulnerabilities discussed here underscore the importance of securing all aspects of a plugin, especially those designed for SQL query execution and URL fetch. In the context of SQL query execution, we recommend developers to not provide a full-scale SQL query execution feature even for a high-privilege user such as an Administrator user. For the URL fetch process, we recommend applying permission and nonce check on the action and applying some checks and limitations to the supplied URL. For the best security practice, we recommend to use wp_safe_remote_*
function to fetch the supplied URL by the user.
Timeline
Help us make the Internet a safer place
Making the WordPress ecosystem more secure is a team effort, and we believe that plugin developers and security researchers should work together.
- If you’re a plugin developer, join our mVDP program that makes it easier to report, manage and address vulnerabilities in your software.
- If you’re a security researcher, join Patchstack Alliance to report vulnerabilities & earn rewards.