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
  • Loading branch information
nateweller committed Jan 15, 2025
1 parent 8cb89d6 commit 51ecb63
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: changed

TBD
97 changes: 97 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,92 @@ 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.
* @param array $vulnerabilities The vulnerabilities to get the ID from.
*
* @return string
*/
private static function get_id_from_vulnerable_extension( Extension_Model $extension, array $vulnerabilities ) {
// Create a single unique ID for the threat by hashing the extension and vulnerability IDs it contains.
return md5(
array_reduce(
$vulnerabilities,
function ( $carry, $vulnerability ) {
return $carry . $vulnerability->id;
},
$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
);
}

/**
* Generate a threat from extension vulnerabilities.
*
* @param Extension_Model $extension The extension to generate the threat for.
* @param array $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, $vulnerabilities ),
'title' => self::get_title_from_vulnerable_extension( $extension ),
'description' => self::get_description_from_vulnerable_extension( $extension, $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 ) );

Check failure on line 89 in projects/packages/protect-models/src/class-vulnerability-model.php

View workflow job for this annotation

GitHub Actions / Static analysis

UndefError PhanUndeclaredClassMethod Call to method get_url from undeclared class \Automattic\Jetpack\Redirect
}

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.
45 changes: 17 additions & 28 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,6 @@
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\Sync\Functions as Sync_Functions;
use Jetpack_Options;
use WP_Error;
Expand Down Expand Up @@ -223,21 +222,21 @@ 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 ) ) {
// convert the vulnerability into a threat
$threat = Threat_Model::generate_from_extension_vulnerabilities( $extension, $checked_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 +281,17 @@ 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,
)
);

$threat_extension = clone $core;
$extension_threat = clone $threat;
// Generate a threat from core vulnerabilities.
if ( ! empty( $report_data->core->vulnerabilities ) ) {
$threat = Threat_Model::generate_from_extension_vulnerabilities( $core, $report_data->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

0 comments on commit 51ecb63

Please sign in to comment.