CVE-2023-22475: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Canarytokens is an open source tool which helps track activity and actions on your network. A Cross-Site Scripting vulnerability was identified in the history page of triggered Canarytokens prior to sha-fb61290. An attacker who discovers an HTTP-based Canarytoken (a URL) can use this to execute Javascript in the Canarytoken's trigger history page (domain: canarytokens.org) when the history page is later visited by the Canarytoken's creator.

This vulnerability could be used to disable or delete the affected Canarytoken, or view its activation history. It might also be used as a stepping stone towards revealing more information about the Canarytoken's creator to the attacker. For example, an attacker could recover the email address tied to the Canarytoken, or place Javascript on the history page that redirect the creator towards an attacker-controlled Canarytoken to show the creator's network location.

This vulnerability is similar to CVE-2022-31113, but affected parameters reported differently from the Canarytoken trigger request. An attacker could only act on the discovered Canarytoken. This issue did not expose other Canarytokens or other Canarytoken creators. Canarytokens Docker images sha-fb61290 and later contain a patch for this issue.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2023-01-06T15:15Z
Repository: https://github.com/thinkst/canarytokens
Language: Python
Files changed: httpd_site.py
Commit date: 2023-01-04 13:55:43 +0200
Commit message: Use an autoescaped env for history page (#170)

This fixes a recently reported XSS in the canarytokens history page.
Diff: diff --git a/httpd_site.py b/httpd_site.py
index 1d17af9..68a64a6 100644
--- a/httpd_site.py
+++ b/httpd_site.py
@@ -42,12 +42,15 @@ import hashlib
 import os
 from cStringIO import StringIO
 import csv
 import wireguard as wg
 
-env = Environment(loader=FileSystemLoader('templates'),
+unsafe_env = Environment(loader=FileSystemLoader('templates'),
                   extensions=['jinja2.ext.loopcontrols'])
+env = Environment(loader=FileSystemLoader('templates'),
+                  extensions=['jinja2.ext.loopcontrols'],
+                  autoescape=True)
 
 with open('/srv/templates/error_http.html', 'r') as f:
     twisted.web.resource.ErrorPage.template = f.read()
 
 class GeneratorPage(resource.Resource):
@@ -57,11 +60,11 @@ class GeneratorPage(resource.Resource):
         if name == '':
             return self
         return Resource.getChild(self, name, request)
 
     def render_GET(self, request):
-        template = env.get_template('generate_new.html')
+        template = unsafe_env.get_template('generate_new.html')
         sites_len = len(get_all_canary_sites())
         now = datetime.datetime.now()
         return template.render(settings=settings, sites_len=sites_len, now=now).encode('utf8')
 
 
@@ -209,11 +212,11 @@ class GeneratorPage(resource.Resource):
 
             try:
                 procname = request.args['cmd_process'][0]
                 if not procname:
                     raise KeyError
-                
+
                 canarydrop['cmd_process'] = procname
                 canarydrop['memo'] += "\r\n\r\n(This token was created to monitor the execution of: " + procname + ")"
                 save_canarydrop(canarydrop)
             except (IndexError, KeyError):
                 pass
@@ -535,11 +538,11 @@ class DownloadPage(resource.Resource):
                 return signed_contents
 
 
         except Exception as e:
             log.error('Unexpected error in POST download: {err}'.format(err=e))
-            template = env.get_template('error.html')
+            template = unsafe_env.get_template('error.html')
             return template.render(error=e.message).encode('utf8')
 
         return NoResource().render(request)
 
 class ManagePage(resource.Resource):
@@ -568,13 +571,13 @@ class ManagePage(resource.Resource):
             return NoResource().render(request)
         g_api_key = get_canary_google_api_key()
         now = datetime.datetime.now()
         try:
             canarydrop['type']
-            template = env.get_template('manage_new.html')
+            template = unsafe_env.get_template('manage_new.html')
         except KeyError:
-            template = env.get_template('manage.html')
+            template = unsafe_env.get_template('manage.html')
         return template.render(canarydrop=canarydrop, API_KEY=g_api_key, now=now).encode('utf8')
 
     def render_POST(self, request):
         try:
             try:
@@ -619,18 +622,18 @@ class ManagePage(resource.Resource):
             canarydrop['web_image_enabled']   = web_image_status
 
             save_canarydrop(canarydrop=canarydrop)
 
             g_api_key = get_canary_google_api_key()
-            template = env.get_template('manage.html')
+            template = unsafe_env.get_template('manage.html')
             return template.render(canarydrop=canarydrop, saved=True,
                                         settings=settings, API_KEY=g_api_key).encode('utf8')
 
         except Exception as e:
             import traceback
             log.error('Exception in manage.html: {e}, {stack}'.format(e=e, stack=traceback.format_exc()))
-            template = env.get_template('manage.html')
+            template = unsafe_env.get_template('manage.html')
             return template.render(canarydrop=canarydrop, error=e,
                                         settings=settings).encode('utf8')
 
 class HistoryPage(resource.Resource):
     isLeaf = True
@@ -739,11 +742,11 @@ class AUP(resource.Resource):
             return self
         return Resource.getChild(self, name, request)
 
     def render_GET(self, request):
         now = datetime.datetime.now()
-        template = env.get_template('legal.html')
+        template = unsafe_env.get_template('legal.html')
         return template.render(now=now).encode('utf8')
 
 class CanarytokensHttpd():
     def __init__(self, port=80):
         self.port = port

Vulnerable code: env = Environment(loader=FileSystemLoader('templates'),
        template = env.get_template('generate_new.html')
                
            template = env.get_template('error.html')
            template = env.get_template('manage_new.html')
            template = env.get_template('manage.html')
            template = env.get_template('manage.html')
            template = env.get_template('manage.html')
        template = env.get_template('legal.html')
Fixed code: unsafe_env = Environment(loader=FileSystemLoader('templates'),
env = Environment(loader=FileSystemLoader('templates'),
                  extensions=['jinja2.ext.loopcontrols'],
                  autoescape=True)
        template = unsafe_env.get_template('generate_new.html')

            template = unsafe_env.get_template('error.html')
            template = unsafe_env.get_template('manage_new.html')
            template = unsafe_env.get_template('manage.html')
            template = unsafe_env.get_template('manage.html')
            template = unsafe_env.get_template('manage.html')
        template = unsafe_env.get_template('legal.html')