Add page with additional details about a CVE

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.
This commit is contained in:
Jonathan S. Katz
2021-03-21 14:15:19 -04:00
parent 62a686f34d
commit cd616da557
7 changed files with 162 additions and 5 deletions

View File

@ -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),
),
]

View File

@ -70,6 +70,7 @@ class SecurityPatch(models.Model):
cvenumber = models.IntegerField(null=False, blank=False, db_index=True) cvenumber = models.IntegerField(null=False, blank=False, db_index=True)
detailslink = models.URLField(null=False, blank=True) detailslink = models.URLField(null=False, blank=True)
description = models.TextField(null=False, blank=False) 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) 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') 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']) 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'))) 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): 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 # Calculate a number from the CVE, that we can use to sort by. We need to

9
pgweb/security/struct.py Normal file
View File

@ -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)

View 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.util.contexts import render_pgweb
from pgweb.core.models import Version from pgweb.core.models import Version
from .models import SecurityPatch from .models import SecurityPatch, make_cvenumber
def GetPatchesList(filt): 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): def index(request):
# Show all supported versions # Show all supported versions
return _list_patches(request, "v.supported") return _list_patches(request, "v.supported")

View File

@ -81,6 +81,7 @@ urlpatterns = [
url(r'^support/security/$', pgweb.security.views.index), url(r'^support/security/$', pgweb.security.views.index),
url(r'^support/security/(\d\.\d|\d{2})/$', pgweb.security.views.version), 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/security_archive/$', RedirectView.as_view(url='/support/security/', permanent=True)),
url(r'^support/professional_(support|hosting)/$', pgweb.profserv.views.root), url(r'^support/professional_(support|hosting)/$', pgweb.profserv.views.root),

View File

@ -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%}

View File

@ -75,7 +75,7 @@ You can filter the view of patches to show just patches for version:<br/>
{%for p in patches%} {%for p in patches%}
<tr> <tr>
<td> <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%} {%if p.newspost%}<a href="/about/news/{{p.newspost.title|slugify}}-{{p.newspost.id}}/">Announcement</a><br/>{%endif%}
</td> </td>
<td>{{p.affected|join:", "}}</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/> <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> {%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> {%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> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>