Skip to content

Commit

Permalink
BUGS-2506: Update API calls to wpvulndb.com (#91)
Browse files Browse the repository at this point in the history
* Update API calls to wpvulndb.com

Add new `getPluginVulnerability` method that returns the results of an API call to wpvulndb.com for the specified plugin slug.

Refactor `is_vulnerable` to call `getPluginVulnerability` per plugin as `https://wpvulndb.com/data/plugin_vulns.json` is now a `404`.

Addresses #89.

* Formatting updates

* Load PANTHEON_WPVULNDB_API_TOKEN in Behat

* Capitol U in the WP_CLI\Utils class name
  • Loading branch information
ataylorme authored and greg-1-anderson committed Feb 13, 2019
1 parent d5471b9 commit 0ce9fec
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 54 deletions.
3 changes: 3 additions & 0 deletions features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ private static function get_process_env_variables() {
if ( $config_path = getenv( 'WP_CLI_CONFIG_PATH' ) ) {
$env['WP_CLI_CONFIG_PATH'] = $config_path;
}
if ( $wpvulndb_api_token = getenv( 'PANTHEON_WPVULNDB_API_TOKEN' ) ) {
$env['PANTHEON_WPVULNDB_API_TOKEN'] = $wpvulndb_api_token;
}
return $env;
}

Expand Down
2 changes: 1 addition & 1 deletion features/steps/given.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function ( $world ) {
function ( $world, $path, PyStringNode $content ) {
$content = (string) $content . "\n";
$full_path = $world->variables['RUN_DIR'] . "/$path";
Process::create( \WP_CLI\utils\esc_cmd( 'mkdir -p %s', dirname( $full_path ) ) )->run_check();
Process::create( \WP_CLI\Utils\esc_cmd( 'mkdir -p %s', dirname( $full_path ) ) )->run_check();
file_put_contents( $full_path, $content );
}
);
Expand Down
170 changes: 117 additions & 53 deletions php/pantheon/checks/plugins.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,77 +69,141 @@ public function run() {
}

/**
* Checks the plugin slug against the vulnerability db
* Checks the plugin slug against the vulnerability db
* @param $plugin_slug string (required) string representing the plugin slug
*
* @return array containing vulnerability info or false
*/
protected function getPluginVulnerability( $plugin_slug )
{
// Get the vulnerability API token from the platform
$wpvulndb_api_token = getenv('PANTHEON_WPVULNDB_API_TOKEN');

// Throw an exception if there is no token
if( false === $wpvulndb_api_token || empty( $wpvulndb_api_token ) ) {
throw new \Exception('No WP Vulnerability DB API Token. Please ensure the PANTHEON_WPVULNDB_API_TOKEN environment variable is set');
}

// Set the request URL to the requested plugin
$url = 'https://wpvulndb.com/api/v3/plugins/' . $plugin_slug;

// Add the token to the headers
$headers = array(
'Content-Type: application/json',
'User-Agent: pantheon/wp_launch_check',
'Authorization: Token token=' . $wpvulndb_api_token
);

// Make the request to the API
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$result = curl_exec($ch);
curl_close($ch);

// Return false if no result from the API
if( false === $result ) {
return false;
}

// Decode the result from the API
$result = json_decode( $result, true );

// Return false if the specified plugin slug is not in the result
if( ! isset( $result[$plugin_slug] ) ) {
return false;
}

// Return the requested plugin vulnerability info
return $result[$plugin_slug];
}

/**
* Checks a plugin by slug and version for vulnerabilities
* @param $plugin_slug string (required) string representing the plugin slug
* @param $current_version string (required) string representing the plugin version
*
* @return array containing the vulnerability or false ... 'unknown' if couldn't be verified
* @return array containing the vulnerability or false
*/
public function is_vulnerable($plugin_slug, $current_version) {
static $plugin_data;
if (!$plugin_data) {
$plugin_data_raw = json_decode(file_get_contents('https://wpvulndb.com/data/plugin_vulns.json'),1);
foreach ($plugin_data_raw as $plugin) {
foreach ($plugin as $plugin_name => $data) {
$plugin_data[$plugin_name] = (object) $data;
}

// Fetch the plugin data if we don't have it already
if( !isset( $plugin_data[$plugin_slug] ) ){
$plugin_results = $this->getPluginVulnerability( $plugin_slug );

// Return false if no plugin results from the vulnerability API
if( false === $plugin_results ){
return false;
}

}

// No issues if the plugin has no vulnerabilities
if ( ! isset( $plugin_results['vulnerabilities'] ) || empty( $plugin_results['vulnerabilities'] ) ) {
return false;
}
$data = $plugin_data;
if (!isset($data[$plugin_slug])) return false;
foreach ($data[$plugin_slug]->vulnerabilities as $vulnerability) {
// if the plugin hasn't been fixed then there's still and issue
if (!isset($vulnerability['fixed_in'])) {
return (array) $vulnerability;


// Loop through all vulnerabilities
foreach ( $plugin_results['vulnerabilities'] as $vulnerability ) {

// If the vulnerability hasn't been fixed, then there's an issue
if ( ! isset( $vulnerability['fixed_in'] ) ) {
return $vulnerability;
}

// If the vulnerability has been fixed, but not in the current version, there's an issue
if ( version_compare( $vulnerability['fixed_in'], $current_version,'>' ) ){
return $vulnerability;
}
// if fixed but in a version greater than installed, still vulnerable
//echo "$plugin_slug: Comparing vuln={$vulnerability['fixed_in']} current=$current_version".PHP_EOL;
if (version_compare($vulnerability['fixed_in'],$current_version,'>'))
return (array) $vulnerability;

}

// If we get this far the current version has no vulnerabilities
return false;
}

public function message(Messenger $messenger) {
if (!empty($this->alerts)) {
$headers = array(
'slug'=>"Plugin",
'installed'=>"Current",
'available' => "Available",
'needs_update'=>"Needs Update",
'vulnerable'=>"Vulnerabilities"
);
$rows = array();
$count_update = 0;
$count_vuln = 0;
foreach( $this->alerts as $alert ) {
$class = 'ok';
if ($alert['needs_update']) {
$class = 'warning';
$count_update++;
}
if ('None' != $alert['vulnerable']) {
$class = 'error';
$count_vuln++;
}
$rows[] = array('class'=>$class, 'data' => $alert);
if (!empty($this->alerts)) {
$headers = array(
'slug'=>"Plugin",
'installed'=>"Current",
'available' => "Available",
'needs_update'=>"Needs Update",
'vulnerable'=>"Vulnerabilities"
);
$rows = array();
$count_update = 0;
$count_vuln = 0;
foreach( $this->alerts as $alert ) {
$class = 'ok';
if ($alert['needs_update']) {
$class = 'warning';
$count_update++;
}
if ('None' != $alert['vulnerable']) {
$class = 'error';
$count_vuln++;
}
$rows[] = array('class'=>$class, 'data' => $alert);
}

$rendered = PHP_EOL;
$rendered .= sprintf("Found %d plugins needing updates and %d known vulnerabilities ... \n".PHP_EOL, $count_update, $count_vuln);
$rendered .= View::make('table', array('headers'=>$headers,'rows'=>$rows));
$rendered = PHP_EOL;
$rendered .= sprintf("Found %d plugins needing updates and %d known vulnerabilities ... \n".PHP_EOL, $count_update, $count_vuln);
$rendered .= View::make('table', array('headers'=>$headers,'rows'=>$rows));

$this->result .= $rendered;
if ($count_update > 0) {
$this->score = 1;
$this->action = "You should update all out-of-date plugins";
}
$this->result .= $rendered;
if ($count_update > 0) {
$this->score = 1;
$this->action = "You should update all out-of-date plugins";
}

if ($count_vuln > 0) {
$this->score = 2;
$this->action = "Update plugins to fix vulnerabilities";
}
if ($count_vuln > 0) {
$this->score = 2;
$this->action = "Update plugins to fix vulnerabilities";
}
} else {
$this->result .= "No plugins found.";
}
Expand Down

0 comments on commit 0ce9fec

Please sign in to comment.