Add page with additional details about a CVE
authorJonathan S. Katz <jonathan.katz@excoventures.com>
Sun, 21 Mar 2021 18:15:19 +0000 (14:15 -0400)
committerJonathan S. Katz <jonathan.katz@excoventures.com>
Sun, 28 Mar 2021 16:22:24 +0000 (12:22 -0400)
This page contains most information that may be found on 3rd party
sites about a particular CVE, but with the benefit of being hosted
on the PostgreSQL infrastructure.

This does require inserting the CVE description into the website,
which will include backporting the CVE descriptions throughout
many existing CVEs, but the added benefit is that this information
is available when we publish a release, vs. waiting for a 3rd party
to publish the info.

This patch also adds sitemap indexing for each of the CVE entries,
and ensures the top-level CVE URL is in the sitemap.

pgweb/security/migrations/0003_add_security_patch_details.py [new file with mode: 0644]
pgweb/security/models.py
pgweb/security/struct.py [new file with mode: 0644]
pgweb/security/views.py
pgweb/urls.py
templates/security/details.html [new file with mode: 0644]
templates/security/security.html

diff --git a/pgweb/security/migrations/0003_add_security_patch_details.py b/pgweb/security/migrations/0003_add_security_patch_details.py
new file mode 100644 (file)
index 0000000..23acb72
--- /dev/null
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.13 on 2018-11-12 16:37
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('security', '0002_cve_visible'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='securitypatch',
+            name='details',
+            field=models.TextField(blank=True, help_text='Additional details about the security patch', null=True),
+        ),
+    ]
index e82c7d4f2eb59934d58727ec373052e27eb85ffa..208a9de7e5bdbd3bd6a51d5fba1355841297fa21 100644 (file)
@@ -70,6 +70,7 @@ class SecurityPatch(models.Model):
     cvenumber = models.IntegerField(null=False, blank=False, db_index=True)
     detailslink = models.URLField(null=False, blank=True)
     description = models.TextField(null=False, blank=False)
+    details = models.TextField(blank=True, null=True, help_text="Additional details about the security patch")
     component = models.CharField(max_length=32, null=False, blank=False, help_text="If multiple components, choose the most critical one", choices=component_choices)
 
     versions = models.ManyToManyField(Version, through='SecurityPatchVersion')
@@ -84,7 +85,9 @@ class SecurityPatch(models.Model):
     vector_a = models.CharField(max_length=1, null=False, blank=True, verbose_name="Availability Impact", choices=vector_choices['A'])
     legacyscore = models.CharField(max_length=1, null=False, blank=True, verbose_name='Legacy score', choices=(('A', 'A'), ('B', 'B'), ('C', 'C'), ('D', 'D')))
 
-    purge_urls = ('/support/security/', )
+    def purge_urls(self):
+        yield '/support/security/CVE-%s/' % self.cve
+        yield '/support/security/'
 
     def save(self, force_insert=False, force_update=False):
         # Calculate a number from the CVE, that we can use to sort by. We need to
diff --git a/pgweb/security/struct.py b/pgweb/security/struct.py
new file mode 100644 (file)
index 0000000..fd5a713
--- /dev/null
@@ -0,0 +1,9 @@
+from datetime import date, timedelta
+from .models import SecurityPatch
+
+
+def get_struct():
+    """create sitemap entries for each CVE entry and the top level CVE URL"""
+    yield ('support/security/', None)
+    for s in SecurityPatch.objects.filter(public=True).order_by('-cvenumber'):
+        yield ('support/security/CVE-{}'.format(s.cve), None)
index 0a7f2041452467cf14ffe01f63a504465f57f4b3..b36fcc047de04d89f3d7bb921f26176e257b5fe0 100644 (file)
@@ -1,9 +1,11 @@
-from django.shortcuts import get_object_or_404
+from django.core.validators import ValidationError
+from django.http import Http404
+from django.shortcuts import get_object_or_404, redirect
 
 from pgweb.util.contexts import render_pgweb
 
 from pgweb.core.models import Version
-from .models import SecurityPatch
+from .models import SecurityPatch, make_cvenumber
 
 
 def GetPatchesList(filt):
@@ -22,6 +24,33 @@ def _list_patches(request, filt):
     })
 
 
+def details(request, cve_prefix, cve):
+    """Provides additional details about a specific CVE"""
+    # First determine if the entrypoint of the URL is a lowercase "cve". If it
+    # is, redirect to the uppercase
+    if cve_prefix != "CVE":
+        return redirect('/support/security/CVE-{}/'.format(cve), permanent=True)
+    # Get the CVE number from the CVE ID string so we can look it up
+    # against the database. This shouldn't fail due to an ill-formatted CVE,
+    # as both use the same validation check, but we will wrap it just in case.
+    #
+    # However, we do need to ensure that the CVE does both exist and
+    # is published.
+    try:
+        security_patch = get_object_or_404(
+            SecurityPatch,
+            cvenumber=make_cvenumber(cve),
+            public=True,
+        )
+    except ValidationError:
+        raise Http404()
+
+    return render_pgweb(request, 'support', 'security/details.html', {
+        'security_patch': security_patch,
+        'versions': security_patch.securitypatchversion_set.select_related('version').order_by('-version__tree').all(),
+    })
+
+
 def index(request):
     # Show all supported versions
     return _list_patches(request, "v.supported")
index 64fb616b681971a2655769c94b8b4b56e8611dc4..cc29aaba5b2d1bc872a364671d731382e52ad444 100644 (file)
@@ -81,6 +81,7 @@ urlpatterns = [
 
     url(r'^support/security/$', pgweb.security.views.index),
     url(r'^support/security/(\d\.\d|\d{2})/$', pgweb.security.views.version),
+    url(r'^support/security/(?P<cve_prefix>CVE|cve)-(?P<cve>\d{4}-\d{4,7})/$', pgweb.security.views.details),
     url(r'^support/security_archive/$', RedirectView.as_view(url='/support/security/', permanent=True)),
 
     url(r'^support/professional_(support|hosting)/$', pgweb.profserv.views.root),
diff --git a/templates/security/details.html b/templates/security/details.html
new file mode 100644 (file)
index 0000000..0e894c5
--- /dev/null
@@ -0,0 +1,95 @@
+{%extends "base/page.html"%}
+{%block title%}CVE-{{ security_patch.cve }}: {{ security_patch.description }}{%endblock%}
+{%block contents%}
+
+<h1>CVE-{{ security_patch.cve }} <i class="fas fa-lock"></i></h1>
+<h3>{{ security_patch.description }}</h3>
+
+{% if security_patch.details %}
+<p>{{ security_patch.details }}</p>
+{% endif %}
+
+<h2>Version Information</h2>
+
+<table class="table">
+  <thead>
+    <tr>
+      <th>Affected Version</th>
+      <th>Fixed In</th>
+      {% if security_patch.newspost %}
+      <th>Fix Published</th>
+      {% endif %}
+  </thead>
+  <tbody>
+    {% for version in versions %}
+    <tr>
+      <td>
+        {% if version.version.tree >= 10 %}
+          {{ version.version.tree|floatformat:"0" }}
+        {% else %}
+          {{ version.version.tree }}
+        {% endif %}
+      </td>
+      <td>
+        <a href="/docs/release/{% if version.version.tree >= 10 %}{{ version.version.tree|floatformat:"0" }}.{{ version.fixed_minor }}{% else %}{{ version.version.tree }}.{{ version.fixed_minor }}{% endif %}">
+          {% if version.version.tree >= 10 %}
+            {{ version.version.tree|floatformat:"0" }}.{{ version.fixed_minor }}
+          {% else %}
+            {{ version.version.tree }}.{{ version.fixed_minor }}
+          {% endif %}
+        </a>
+      </td>
+      {% if security_patch.newspost %}
+      <td>
+        <a href="/about/news/{{ security_patch.newspost.title|slugify }}-{{ security_patch.newspost.id }}/">
+          {{ security_patch.newspost.date }}
+        </a>
+      </td>
+      {% endif %}
+    </tr>
+    {% endfor %}
+  </tbody>
+</table>
+
+<p>
+  For more information about <a href="/support/versioning/">PostgreSQL versioning</a>,
+  please visit the <a href="/support/versioning/">versioning page</a>.
+</p>
+
+{% if security_patch.cvssscore >= 0 %}
+<h2>CVSS 3.0</h2>
+
+<table class="table">
+  <tbody>
+    <tr>
+      <th>Overall Score</th>
+      <td><strong>{{ security_patch.cvssscore }}</strong></td>
+    </tr>
+    <tr>
+      <th>Component</th>
+      <td>{{ security_patch.component }}</td>
+    </tr>
+    <tr>
+      <th>Vector</th>
+      <td>
+        <a href="https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector={{ security_patch.cvssvector }}&version=3.0" target="_blank" rel="noopener noreferer">
+          {{ security_patch.cvssvector }}
+        </a>
+      </td>
+    </tr>
+  </tbody>
+</table>
+{% endif %}
+
+<h2>Reporting Security Vulnerabilities</h2>
+
+<p>
+  If you wish to report a new security vulnerability in PostgreSQL, please
+  send an email to
+  <a href="mailto:security@postgresql.org">security@postgresql.org</a>.
+</p>
+
+<p>
+  For reporting non-security bugs, please see the <a href="/account/submitbug/">Report a Bug</a> page.
+</p>
+{%endblock%}
index a6e0a6fca8db15ee5e03a2e484140a23d2bd9550..13fcc1696a65a0d5142e4dc531bd37df74bbb6e9 100644 (file)
@@ -75,7 +75,7 @@ You can filter the view of patches to show just patches for version:<br/>
     {%for p in patches%}
       <tr>
         <td>
-          {%if p.cve%}<nobr>{%if p.cve_visible%}<a href="{{p.cvelink}}">CVE-{{p.cve}}</a>{%else%}CVE-{{p.cve}}{%endif%}</nobr><br/>{%endif%}
+          {%if p.cve%}<nobr><a href="/support/security/CVE-{{ p.cve }}/">CVE-{{p.cve}}</a></nobr><br/>{%endif%}
           {%if p.newspost%}<a href="/about/news/{{p.newspost.title|slugify}}-{{p.newspost.id}}/">Announcement</a><br/>{%endif%}
         </td>
         <td>{{p.affected|join:", "}}</td>
@@ -83,7 +83,7 @@ You can filter the view of patches to show just patches for version:<br/>
         <td>{{p.component}}<br/>
           {%if p.cvssscore >= 0%}<a href="https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector={{p.cvssvector}}">{{p.cvssscore}}</a><br/><span class="cvssvector">{{p.cvssvector}}</span>
     {%else%}Legacy: {{p.legacyscore}}{%endif%}</td>
-        <td>{{p.description}}{%if p.detailslink%}<br/><br/><a href="{{p.detailslink}}">more details</a>{%endif%}</td>
+        <td>{{p.description}}<br/><br/><a href="/support/security/CVE-{{ p.cve }}/">more details</a></td>
       </tr>
     {% endfor %}
   </tbody>