Skip to content

Commit

Permalink
Combine multiple vulnerabilities for a single extension into one vuln…
Browse files Browse the repository at this point in the history
…erable extension threat

changelog

minor adjustments

changelog

add source to generator

minor adjustments

Add typed params

minor adjustments

use generator for core vulns threat

update tests, minor adjustments

adjust tests

Fix phan issues

add jetpack-redirect dep

fix tests

update lock files

changelog

Update projects/packages/protect-models/changelog/protect-status-combine-vulns-into-extension-threat
  • Loading branch information
nateweller committed Jan 16, 2025
1 parent be0d34b commit fb06d1e
Show file tree
Hide file tree
Showing 23 changed files with 422 additions and 153 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: changed

Combine vulnerabilities for the same extension into single vulnerable extension threats.
3 changes: 2 additions & 1 deletion projects/packages/protect-models/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"php": ">=7.2"
"php": ">=7.2",
"automattic/jetpack-redirect": "@dev"
},
"require-dev": {
"yoast/phpunit-polyfills": "^1.1.1",
Expand Down
113 changes: 113 additions & 0 deletions projects/packages/protect-models/src/class-threat-model.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,15 @@ class Threat_Model {
*/
public $extension;

/**
* The threat's related vulnerabilities.
*
* @since $$next-version$$
*
* @var null|Vulnerability_Model[]
*/
public $vulnerabilities;

/**
* Threat Constructor
*
Expand All @@ -139,4 +148,108 @@ public function __construct( $threat ) {
}
}
}

/**
* Get the ID value of the threat based on its related extension and vulnerabilities.
*
* @param Extension_Model $extension The extension to get the ID from.
*
* @return string
*/
private static function get_id_from_vulnerable_extension( Extension_Model $extension ) {
return "$extension->type-$extension->slug-$extension->version";
}

/**
* Get the title from a vulnerable extension.
*
* @param Extension_Model $extension The extension to get the title from.
*
* @return string|null
*/
private static function get_title_from_vulnerable_extension( Extension_Model $extension ) {
$titles = array(
'plugins' => sprintf(
/* translators: placeholders are the theme name and version number. Example: "Vulnerable theme: Jetpack (version 1.2.3)" */
__( 'Vulnerable plugin: %1$s (version %2$s)', 'jetpack-protect-models' ),
$extension->name,
$extension->version
),
'themes' => sprintf(
/* translators: placeholders are the theme name and version number. Example: "Vulnerable theme: Jetpack (version 1.2.3)" */
__( 'Vulnerable theme: %1$s (version %2$s)', 'jetpack-protect-models' ),
$extension->name,
$extension->version
),
'core' => sprintf(
/* translators: placeholder is the version number. Example: "Vulnerable WordPress (version 1.2.3)" */
__( 'Vulnerable WordPress (version %s)', 'jetpack-protect-models' ),
$extension->version
),
);

return $titles[ $extension->type ] ?? null;
}

/**
* Get the description from a vulnerable extension.
*
* @param Extension_Model $extension The extension to get the description from.
* @param array $vulnerabilities The vulnerabilities to get the description from.
*
* @return string
*/
private static function get_description_from_vulnerable_extension( Extension_Model $extension, array $vulnerabilities ) {
return sprintf(
/* translators: placeholders are the theme name and version number. Example: "The installed version of Jetpack (1.2.3) has a known security vulnerability." */
_n( 'The installed version of %1$s (%2$s) has a known security vulnerability.', 'The installed version of %1$s (%2$s) has known security vulnerabilities.', count( $vulnerabilities ), 'jetpack-protect-models' ),
$extension->name,
$extension->version
);
}

/**
* Get the latest fixed_in version from a list of vulnerabilities.
*
* @param array $vulnerabilities The vulnerabilities to get the fixed_in version from.
*
* @return string|bool|null The latest fixed_in version, or false if any of the vulnerabilities are not fixed.
*/
private static function get_fixed_in_from_vulnerabilities( array $vulnerabilities ) {
$fixed_in = null;

foreach ( $vulnerabilities as $vulnerability ) {
// If any of the vulnerabilities are not fixed, the threat is not fixed.
if ( ! $vulnerability->fixed_in ) {
break;
}

// Use the latest available fixed_in version.
if ( ! $fixed_in || ( $fixed_in && version_compare( $vulnerability->fixed_in, $fixed_in, '>' ) ) ) {
$fixed_in = $vulnerability->fixed_in;
}
}

return $fixed_in;
}

/**
* Generate a threat from extension vulnerabilities.
*
* @param Extension_Model $extension The extension to generate the threat for.
* @param Vulnerability_Model[] $vulnerabilities The vulnerabilities to generate the threat from.
*
* @return Threat_Model
*/
public static function generate_from_extension_vulnerabilities( Extension_Model $extension, array $vulnerabilities ) {
return new Threat_Model(
array(
'id' => self::get_id_from_vulnerable_extension( $extension ),
'title' => self::get_title_from_vulnerable_extension( $extension ),
'description' => self::get_description_from_vulnerable_extension( $extension, $vulnerabilities ),
'fixed_in' => self::get_fixed_in_from_vulnerabilities( $vulnerabilities ),
'vulnerabilities' => $vulnerabilities,
)
);
}
}
94 changes: 94 additions & 0 deletions projects/packages/protect-models/src/class-vulnerability-model.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php
/**
* Model class for vulnerability data.
*
* @package automattic/jetpack-protect-models
*/

namespace Automattic\Jetpack\Protect_Models;

use Automattic\Jetpack\Redirect;

/**
* Model class for vulnerability data.
*/
class Vulnerability_Model {
/**
* Threat ID.
*
* @var null|string
*/
public $id;

/**
* Threat Title.
*
* @var null|string
*/
public $title;

/**
* Threat Description.
*
* @var null|string
*/
public $description;

/**
* The version the threat is fixed in.
*
* @var null|string
*/
public $fixed_in;

/**
* The version the threat was introduced.
*
* @var null|string
*/
public $introduced_in;

/**
* The type of threat.
*
* @var null|string
*/
public $type;

/**
* The source URL for the threat.
*
* @var null|string
*/
public $source;

/**
* Threat Constructor
*
* @param array|object $threat Threat data to load into the class instance.
*/
public function __construct( $threat ) {
// Initialize the threat data.
foreach ( $threat as $property => $value ) {
if ( property_exists( $this, $property ) ) {
$this->$property = $value;
}
}

// Ensure the source URL is set.
$this->get_source();
}

/**
* Get the source URL for the threat.
*
* @return string
*/
public function get_source() {
if ( empty( $this->source ) && $this->id ) {
$this->source = Redirect::get_url( 'jetpack-protect-vul-info', array( 'path' => $this->id ) );
}

return $this->source;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: changed

Combine multiple vulnerability results for the same extension into a single vulnerable extension threat result.
61 changes: 34 additions & 27 deletions projects/packages/protect-status/src/class-protect-status.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
use Automattic\Jetpack\Protect_Models\Extension_Model;
use Automattic\Jetpack\Protect_Models\Status_Model;
use Automattic\Jetpack\Protect_Models\Threat_Model;
use Automattic\Jetpack\Redirect;
use Automattic\Jetpack\Protect_Models\Vulnerability_Model;
use Automattic\Jetpack\Sync\Functions as Sync_Functions;
use Jetpack_Options;
use WP_Error;
Expand Down Expand Up @@ -223,21 +223,29 @@ protected static function normalize_extension_data( &$status, $report_data, $ext
continue;
}

$extension->checked = true;
$extension->checked = true;
$extension_threats[ $slug ] = $extension;

foreach ( $checked_extension->vulnerabilities as $vulnerability ) {
$threat = new Threat_Model( $vulnerability );
$threat->source = isset( $vulnerability->id ) ? Redirect::get_url( 'jetpack-protect-vul-info', array( 'path' => $vulnerability->id ) ) : null;
if ( ! empty( $checked_extension->vulnerabilities ) ) {
// normalize the vulnerabilities data
$vulnerabilities = array_map(
function ( $vulnerability ) {
return new Vulnerability_Model( $vulnerability );
},
$checked_extension->vulnerabilities
);

// convert the detected vulnerabilities into a vulnerable extension threat
$threat = Threat_Model::generate_from_extension_vulnerabilities( $extension, $vulnerabilities );

$threat_extension = clone $extension;
$extension_threat = clone $threat;
$extension_threat->extension = null;
$threat_extension = clone $extension;
$extension_threat = clone $threat;

$extension_threat->extension = null;
$extension_threats[ $slug ]->threats[] = $extension_threat;

$threat->extension = $threat_extension;
$status->threats[] = $threat;

}
}

Expand Down Expand Up @@ -282,27 +290,26 @@ protected static function normalize_core_data( &$status, $report_data ) {
// If we've made it this far, the core version has been checked.
$core->checked = true;

// Extract threat data from the report.
if ( is_array( $report_data->core->vulnerabilities ) ) {
foreach ( $report_data->core->vulnerabilities as $vulnerability ) {
$threat = new Threat_Model(
array(
'id' => $vulnerability->id,
'title' => $vulnerability->title,
'fixed_in' => $vulnerability->fixed_in,
'description' => isset( $vulnerability->description ) ? $vulnerability->description : null,
'source' => isset( $vulnerability->id ) ? Redirect::get_url( 'jetpack-protect-vul-info', array( 'path' => $vulnerability->id ) ) : null,
)
);
// Generate a threat from core vulnerabilities.
if ( ! empty( $report_data->core->vulnerabilities ) ) {
// normalize the vulnerabilities data
$vulnerabilities = array_map(
function ( $vulnerability ) {
return new Vulnerability_Model( $vulnerability );
},
$report_data->core->vulnerabilities
);

$threat_extension = clone $core;
$extension_threat = clone $threat;
// convert the detected vulnerabilities into a vulnerable extension threat
$threat = Threat_Model::generate_from_extension_vulnerabilities( $core, $vulnerabilities );

$core->threats[] = $extension_threat;
$threat->extension = $threat_extension;
$threat_extension = clone $core;
$extension_threat = clone $threat;

$status->threats[] = $threat;
}
$core->threats[] = $extension_threat;
$threat->extension = $threat_extension;

$status->threats[] = $threat;
}

$status->core = $core;
Expand Down
Loading

0 comments on commit fb06d1e

Please sign in to comment.