Skip to content

Commit

Permalink
Resolves issue of privacy of URLs v1.50.2
Browse files Browse the repository at this point in the history
Signed-off-by: Vijay Sarvepalli <[email protected]>
  • Loading branch information
sei-vsarvepalli committed Aug 30, 2022
1 parent 142d39a commit 647fee1
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 14 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# VINCE Changelog

Version 1.50.2: 2022-08-29
==========================

Resolves issue of enumerating user_id and group_id - reported by Sharon Brizinov of Claroty Research [#51](https://github.com/CERTCC/VINCE/issues/51)
Removed lxml library no longer in use in requirements.txt - reported by dependabot via [#38](https://github.com/CERTCC/VINCE/pull/38)
Add [DISABLED] Keyword for users in `inactive` status in vincetrack `Teams` menu view.


Version 1.50.1: 2022-08-08
==========================

Expand Down
39 changes: 39 additions & 0 deletions lib/vince/m2crypto_encrypt_decrypt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""
M2Crypto based encrypt and decrypt routines that helps encrypt
a string using a key and later decrypt it. The key can be binary
it is provided to initialize the class ED in base64 encoded
format to facilitate random bytes as key like `os.urandom()`
"""
import M2Crypto
import base64

class ED(object):
"""
This class creates an encryption object using a base64encoded
random bytes :base64key: that will be used to create a "key" and "iv"
Once initialized successfully with a key, the encrypt and decrypt
methods can be used to encrypt a plaintext data and decrypt a cyphertext
data
"""
def __init__(self, base64key):
rawkey = base64.b64decode(base64key)
self.iv = rawkey[:16]
self.key = rawkey[16:]

def encrypt(self, plaintext):
"""
:param plaintext:
:return: ciphertext in hexadecimal form
"""
cipher = M2Crypto.EVP.Cipher(alg='aes_256_cbc', key=self.key, iv=self.iv, op=1)
ciphertext = cipher.update(plaintext.encode()) + cipher.final()
return ciphertext.hex()

def decrypt(self, cyphertext):
"""
:param cyphertext:
:return: plaintext as str
"""
cipher = M2Crypto.EVP.Cipher(alg='aes_256_cbc', key=self.key, iv=self.iv, op=0)
plaintext = cipher.update(bytes.fromhex((cyphertext))) + cipher.final()
return plaintext
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ future==0.17.1
idna==3.3
jmespath==0.9.3
kombu==4.5.0
lxml==4.6.5
M2Crypto==0.36.0
Markdown==3.1
pinax-messages==2.0.2
Expand Down
2 changes: 1 addition & 1 deletion vince/templates/vince/teams.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ <h3><a href="{% url 'vince:contact' team.groupsettings.contact.id %}">{{ team.na
<h4>Team Members</h4>
<ul>
{% for u in team.user_set.all %}
<li><a href="{% url 'vince:vvuser' u.id %}"> {{ u.usersettings.preferred_username }}</a></li>
<li><a href="{% url 'vince:vvuser' u.id %}"> {{ u.usersettings.preferred_username }} {% if not u.is_active %}<span style="color:red">[DISABLED]</span>{% endif %}</a></li>
{% endfor %}
</ul>
</div>
Expand Down
12 changes: 9 additions & 3 deletions vinny/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
from vinny.mailer import send_newmessage_mail
from bigvince.storage_backends import PrivateMediaStorage, SharedMediaStorage
from django.utils.encoding import smart_text
from lib.vince.m2crypto_encrypt_decrypt import ED
import base64
import os
import boto3
import random
Expand Down Expand Up @@ -142,7 +144,9 @@ def _get_track_access(self):
is_track = property(_get_track_access)

def _get_url(self):
return reverse("vinny:usercard", args=[self.user.pk])
ed = ED(base64.b64encode(settings.SECRET_KEY.encode()))
euid = ed.encrypt(str(self.user.pk))
return reverse("vinny:usercard", args=[euid])

url = property(_get_url)

Expand Down Expand Up @@ -493,7 +497,9 @@ class GroupContact(models.Model):
)

def _get_url(self):
return reverse("vinny:groupcard", args=[self.group.pk])
ed = ED(base64.b64encode(settings.SECRET_KEY.encode()))
euid = ed.encrypt(str(self.group.pk))
return reverse("vinny:groupcard", args=[egid])

url = property(_get_url)

Expand Down Expand Up @@ -974,7 +980,7 @@ def get_title(self):
return "%s%s: %s" % (settings.CASE_IDENTIFIER, self.vuid, self.title)

def get_vuid(self):
return f"{settings.CASE_IDENTIFIER}{self.vuid}"
return f"{settings.CASE_IDENTIFIER}%s" % self.vuid

vu_vuid = property(get_vuid)

Expand Down
2 changes: 1 addition & 1 deletion vinny/templates/vinny/allvendors.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<div class="participant-pic">
{% autoescape off %}{{ vendor|grouplogo:"profile-pic" }}{% endautoescape %}
</div>
<div class="participant-info"><span class="participant-name"><a href="{% url 'vinny:groupcardcase' vendor.group.id case.id %}" class="vendor-participant">{{ vendor.group.groupcontact.contact.vendor_name }}</a></span>
<div class="participant-info"><span class="participant-name"><a href="{{ vendor.group.groupcontact.url }}{{ case.id }}" class="vendor-participant">{{ vendor.group.groupcontact.contact.vendor_name }}</a></span>
{% if vendor.share_status %}
<a href="{% url 'vinny:statement' case.id vendor.id %}" class="openmodal {% show_status_class vendor.get_general_status %}" data-open="modal" title="View Statement">{% show_status vendor.get_general_status %}</a>
{% endif %}
Expand Down
2 changes: 1 addition & 1 deletion vinny/templates/vinny/case.html
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ <h4>Vendors</h4>
<div class="participant-pic">
{% autoescape off %}{{ vendor|grouplogo:"profile-pic" }}{% endautoescape %}
</div>
<div class="participant-info"><span class="participant-name"><a href="{% url 'vinny:groupcardcase' vendor.group.id case.id %}" class="vendor-participant">{{ vendor.group.groupcontact.contact.vendor_name }}</a></span>
<div class="participant-info"><span class="participant-name"><a href="{{ vendor.group.groupcontact.url }}{{ case.id }}" class="vendor-participant">{{ vendor.group.groupcontact.contact.vendor_name }}</a></span>
{% if vendor.share_status %}
<a href="{% url 'vinny:statement' case.id vendor.id %}" class="openmodal {% show_status_class vendor.get_general_status %}" data-open="modal" title="View Statement">{% show_status vendor.get_general_status %}</a>
{% endif %}
Expand Down
8 changes: 5 additions & 3 deletions vinny/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,11 @@
path('report/', views.ReportView.as_view(), name='report'),
re_path('^report/(?P<pk>[0-9]+)?/add/file/$', views.ReportDocumentCreateView.as_view(), name='addreportfile'),
re_path('^report/(?P<pk>[0-9]+)?/update/$', views.UpdateReportView.as_view(), name='reportupdate'),
re_path('profile/user_card/(?P<id>[0-9]+)?/$', views.UserCardView.as_view(), name='usercard'),
re_path('profile/group_card/(?P<id>[0-9]+)?/$', views.GroupCardView.as_view(), name='groupcard'),
re_path('profile/group_card/(?P<id>[0-9]+)?/(?P<case>[0-9]+)?/$', views.GroupCardView.as_view(), name='groupcardcase'),
re_path('profile/user_card/(?P<euid>[0-9a-fA-F]+)?/$', views.UserCardView.as_view(), name='usercard'),
re_path('profile/group_card/(?P<egid>[0-9a-fA-F]+)?/$', views.GroupCardView.as_view(), name='groupcard'),
#note: var "vinny:groupcardcase" is not used in templates anymore
# use {{groupcontact.url}}{{case.id}} to build groupcardcase URLs
re_path('profile/group_card/(?P<egid>[0-9a-fA-F]+)?/(?P<case>[0-9]+)?/$', views.GroupCardView.as_view(), name='groupcardcase'),
path('reports/pub/', views.AdminReportsView.as_view(), name='adminreports'),
path('reports/', views.ReportsView.as_view(), name='reports'),
path('api/vendor/', views.VendorInfoAPIView.as_view(), name='vendor_api'),
Expand Down
31 changes: 27 additions & 4 deletions vinny/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
from django.db.models import Q, OuterRef, Subquery, Max
from botocore.exceptions import ClientError
from botocore.client import Config

from lib.vince.m2crypto_encrypt_decrypt import ED
# Create your views here.

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -2209,7 +2209,19 @@ class UserCardView(LoginRequiredMixin, TokenMixin, PendingTestMixin, generic.Det
model = VinceProfile

def get_object(self):
return VinceProfile.objects.get(user__id=self.kwargs['id'])
euid = self.kwargs['euid']
if len(euid) % 32:
logger.info("Invalid encrypted EUID value found returning user's self profile %s length does not match" %(euid))
return VinceProfile.objects.get(user=self.request.user)
try:
ed = ED(base64.b64encode(settings.SECRET_KEY.encode()))
q = ed.decrypt(str(self.kwargs['euid']))
logger.debug("Decrypted value of user pk id is %s for %s" %(q,euid))
return VinceProfile.objects.get(user__id=int(q))
except Exception as e:
logger.info("Invalid encrypted EUID value found returning user's self profile %s error %s" %(euid,str(e)))
return VinceProfile.objects.get(user=self.request.user)


def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
Expand All @@ -2225,11 +2237,22 @@ class GroupCardView(LoginRequiredMixin, TokenMixin, PendingTestMixin, generic.De
model = GroupContact

def get_object(self):
return GroupContact.objects.get(group__id=self.kwargs['id'])
egid = self.kwargs['egid']
if len(egid) % 32:
logger.info("Invalid encrypted EGID value found returning user's self profile %s length does not match" %(egid))
return None
try:
ed = ED(base64.b64encode(settings.SECRET_KEY.encode()))
q = ed.decrypt(str(self.kwargs['egid']))
logger.debug("Decrypted value of user pk id is %s for %s" %(q,egid))
return GroupContact.objects.get(group__id=int(q))
except Exception as e:
logger.info("Invalid encrypted EGID value found returning user's self profile %s error %s" %(egid,str(e)))
return None

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.kwargs.get('case') and self.get_object().default_access == False:
if self.kwargs.get('case') and self.get_object() and self.get_object().default_access == False:
groupcontact = self.get_object()
admins = list(VinceCommGroupAdmin.objects.filter(contact=groupcontact.contact.id).values_list('email__email', flat=True))
casemember = CaseMember.objects.filter(case=self.kwargs.get('case'), group=groupcontact.group.id).first()
Expand Down

0 comments on commit 647fee1

Please sign in to comment.