Browse Source

csp: apply data scrubbing rules to urls in CSP reports

Matt Robenolt 7 years ago
parent
commit
86c259da58

+ 1 - 1
src/sentry/constants.py

@@ -149,7 +149,7 @@ CLIENT_RESERVED_ATTRS = (
 
 DEFAULT_SCRUBBED_FIELDS = (
     'password', 'secret', 'passwd', 'api_key', 'apikey', 'access_token', 'auth', 'credentials',
-    'mysql_pwd', 'stripeToken',
+    'mysql_pwd', 'stripeToken', 'card[number]',
 )
 
 NOT_SCRUBBED_VALUES = set([

+ 12 - 1
src/sentry/interfaces/csp.py

@@ -138,9 +138,20 @@ class Csp(Interface):
 
     def get_tags(self):
         return (
-            ('effective-directive', self.effective_directive), ('blocked-uri', self.blocked_uri),
+            ('effective-directive', self.effective_directive),
+            ('blocked-uri', self.sanitized_blocked_uri()),
         )
 
+    def sanitized_blocked_uri(self):
+        # HACK: This is 100% to work around Stripe urls
+        # that will casually put extremely sensitive information
+        # in querystrings. The real solution is to apply
+        # data scrubbing to all tags generically
+        uri = self.blocked_uri
+        if uri[:23] == 'https://api.stripe.com/':
+            return urlunsplit(urlsplit(uri)[:3] + (None, None))
+        return uri
+
     @memoize
     def _normalized_blocked_uri(self):
         return _normalize_uri(self.blocked_uri)

+ 26 - 0
src/sentry/utils/data_scrubber.py

@@ -9,6 +9,7 @@ from __future__ import absolute_import
 
 import re
 import six
+from six.moves.urllib.parse import urlsplit, urlunsplit
 
 from sentry.constants import DEFAULT_SCRUBBED_FIELDS, FILTER_MASK, NOT_SCRUBBED_VALUES
 
@@ -91,6 +92,9 @@ class SensitiveDataFilter(object):
         if 'sentry.interfaces.User' in data:
             self.filter_user(data['sentry.interfaces.User'])
 
+        if 'sentry.interfaces.Csp' in data:
+            self.filter_csp(data['sentry.interfaces.Csp'])
+
         if 'extra' in data:
             data['extra'] = varmap(self.sanitize, data['extra'])
 
@@ -169,3 +173,25 @@ class SensitiveDataFilter(object):
             val = data.get(key)
             if val:
                 data[key] = varmap(self.sanitize, val)
+
+    def filter_csp(self, data):
+        for key in 'blocked_uri', 'document_uri':
+            if key not in data:
+                continue
+            value = data[key]
+            if not isinstance(value, six.string_types):
+                continue
+            if '?' not in value:
+                continue
+            if '=' not in value:
+                continue
+            scheme, netloc, path, query, fragment = urlsplit(value)
+            querybits = []
+            for bit in query.split('&'):
+                chunk = bit.split('=')
+                if len(chunk) == 2:
+                    querybits.append((chunk[0], self.sanitize(*chunk)))
+                else:
+                    querybits.append(chunk)
+            query = '&'.join('='.join(k) for k in querybits)
+            data[key] = urlunsplit((scheme, netloc, path, query, fragment))

+ 12 - 0
tests/sentry/interfaces/test_csp.py

@@ -148,6 +148,18 @@ class CspTest(TestCase):
             ('effective-directive', 'style-src'), ('blocked-uri', 'http://example.com/lol.css'),
         )
 
+    def test_get_tags_stripe(self):
+        result = Csp.to_python(
+            dict(
+                blocked_uri='https://api.stripe.com/v1/tokens?card[number]=xxx',
+                effective_directive='script-src',
+            )
+        )
+        assert result.get_tags() == (
+            ('effective-directive', 'script-src'),
+            ('blocked-uri', 'https://api.stripe.com/v1/tokens'),
+        )
+
     def test_get_message(self):
         result = Csp.to_python(
             dict(

+ 14 - 0
tests/sentry/utils/test_data_scrubber.py

@@ -386,3 +386,17 @@ class SensitiveDataFilterTest(TestCase):
         assert proc.sanitize('is_authenticated', 'foobar') == FILTER_MASK
         assert proc.sanitize('is_authenticated', 'null') == 'null'
         assert proc.sanitize('is_authenticated', True) is True
+
+    def test_csp_blocked_uri(self):
+        data = {
+            'sentry.interfaces.Csp': {
+                'blocked_uri': 'https://example.com/?foo=4571234567890111&bar=baz',
+            }
+        }
+
+        proc = SensitiveDataFilter()
+        proc.apply(data)
+
+        assert 'sentry.interfaces.Csp' in data
+        csp = data['sentry.interfaces.Csp']
+        assert csp['blocked_uri'] == 'https://example.com/?foo=[Filtered]&bar=baz'