diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php
index 8df4971db03206ce97cb4ff250381fd0483bf775..ec01d8bbeee71c8a3040f0c77610761a4b95f38e 100644
--- a/core/modules/system/src/Form/ModulesListForm.php
+++ b/core/modules/system/src/Form/ModulesListForm.php
@@ -17,10 +17,12 @@
 use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
 use Drupal\Core\Link;
 use Drupal\Core\Render\Element;
+use Drupal\Core\Render\Markup;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\user\PermissionHandlerInterface;
 use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Component\Utility\Xss;
 
 /**
  * Provides module installation interface.
@@ -207,7 +209,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     foreach (Element::children($form['modules']) as $package) {
       $form['modules'][$package] += [
         '#type' => 'details',
-        '#title' => $this->t($package),
+        '#title' => Markup::create(Xss::filterAdmin($this->t($package))),
         '#open' => TRUE,
         '#theme' => 'system_modules_details',
         '#attributes' => ['class' => ['package-listing']],
@@ -272,7 +274,7 @@ protected function buildRow(array $modules, Extension $module, $distribution) {
           ])
         )->toString();
     }
-    $row['description']['#markup'] = $this->t($module->info['description']);
+    $row['description']['#markup'] = (string) $this->t($module->info['description']);
     $row['version']['#markup'] = $module->info['version'];
 
     // Generate link for module's help page. Assume that if a hook_help()
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index bdadaddff9ae491e0867088940bc4e6085fe199c..b9fa2f7c32ecf97d3dafdbe5f6cdcb9b1a61cd62 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -6,8 +6,10 @@
  */
 
 use Drupal\Component\Utility\Html;
+use Drupal\Component\Utility\Xss;
 use Drupal\Core\Link;
 use Drupal\Core\Render\Element;
+use Drupal\Core\Render\Markup;
 use Drupal\Core\Template\Attribute;
 use Drupal\Core\Url;
 
@@ -296,7 +298,7 @@ function template_preprocess_system_themes_page(&$variables) {
       }
 
       // Localize the theme description.
-      $current_theme['description'] = t($theme->info['description']);
+      $current_theme['description'] = Markup::create(Xss::filterAdmin(t($theme->info['description'])));
 
       $current_theme['attributes'] = new Attribute();
       $current_theme['name'] = $theme->info['name'];
diff --git a/core/modules/system/tests/modules/evil/evil.info.yml b/core/modules/system/tests/modules/evil/evil.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f7b8df749f1ffd379f50368bc8a9aea1a3fb1551
--- /dev/null
+++ b/core/modules/system/tests/modules/evil/evil.info.yml
@@ -0,0 +1,5 @@
+name: <script>alert('Evil module name');</script>
+type: module
+description: <script>alert('Evil module desc');</script>
+package: Testing
+version: VERSION
diff --git a/core/modules/system/tests/src/Functional/ModuleThemePageXssVulnerabilityTest.php b/core/modules/system/tests/src/Functional/ModuleThemePageXssVulnerabilityTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c9af3437e2016549a66da28bbd76f55b2a1afeb7
--- /dev/null
+++ b/core/modules/system/tests/src/Functional/ModuleThemePageXssVulnerabilityTest.php
@@ -0,0 +1,53 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\system\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests module and theme pages do not have XSS vulnerabilities.
+ *
+ * @group system
+ */
+class ModuleThemePageXssVulnerabilityTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['system'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $admin = $this->drupalCreateUser([
+      'administer modules',
+      'administer themes',
+    ]);
+    $this->drupalLogin($admin);
+  }
+
+  /**
+   * Tests extension info cannot create XSS vulnerabilities.
+   */
+  public function testExtensionInfoXss(): void {
+    $this->drupalGet("admin/modules");
+    $this->assertSession()->pageTextContains("alert('Evil module name');");
+    $this->assertSession()->pageTextContains("alert('Evil module desc');");
+    $this->assertSession()->responseNotContains("<script>alert(");
+    $this->drupalGet("admin/appearance");
+    $this->assertSession()->pageTextContains("alert('Evil theme name');");
+    $this->assertSession()->pageTextContains("alert('Evil theme desc');");
+    $this->assertSession()->responseNotContains("<script>alert(");
+  }
+
+}
diff --git a/core/modules/system/tests/themes/evil/evil.info.yml b/core/modules/system/tests/themes/evil/evil.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..32b3196fe05fb143d8c4641d254444dc03160802
--- /dev/null
+++ b/core/modules/system/tests/themes/evil/evil.info.yml
@@ -0,0 +1,5 @@
+name: <script>alert('Evil theme name');</script>
+type: theme
+description: <script>alert('Evil theme desc');</script>
+version: VERSION
+base theme: false