CVE-2022-46151: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Querybook is an open source data querying UI. In affected versions user provided data is not escaped in the error field of the auth callback url in `querybook/server/app/auth/oauth_auth.py` and `querybook/server/app/auth/okta_auth.py`. This may allow attackers to perform reflected cross site scripting (XSS) if Content Security Policy (CSP) is not enabled or `unsafe-inline` is allowed. Users are advised to upgrade to the latest, patched version of querybook (version 3.14.2 or greater). Users unable to upgrade may enable CSP and not allow unsafe-inline or manually escape query parameters in a reverse proxy.
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: 2022-12-06T01:15Z
Repository: https://github.com/pinterest/querybook
Language: Python
Files changed: querybook/server/app/auth/oauth_auth.py, querybook/server/app/auth/okta_auth.py
Commit date: 2022-11-28 14:01:09 -0800
Commit message: Merge pull request from GHSA-mrrw-9wf7-xq6w

Co-authored-by: Lena <meowcodes@gmail.com>
Diff: diff --git a/querybook/server/app/auth/oauth_auth.py b/querybook/server/app/auth/oauth_auth.py
index 7af02e77..70f66f5e 100644
--- a/querybook/server/app/auth/oauth_auth.py
+++ b/querybook/server/app/auth/oauth_auth.py
@@ -1,10 +1,10 @@
 import certifi
 import requests
 
 
-from flask import request, session as flask_session, redirect
+from flask import Markup, request, session as flask_session, redirect
 import flask_login
 from requests_oauthlib import OAuth2Session
 
 from app.db import with_session, DBSession
 from env import QuerybookSettings
@@ -73,11 +73,11 @@ class OAuthLoginManager(object):
 
     def oauth_callback(self):
         LOG.debug("Handling Oauth callback...")
 
         if request.args.get("error"):
-            return f"<h1>Error: {request.args.get('error')}</h1>"
+            return f"<h1>Error: { Markup.escape(request.args.get('error')) }</h1>"
 
         code = request.args.get("code")
         try:
             access_token = self._fetch_access_token(code)
             username, email = self._get_user_profile(access_token)
diff --git a/querybook/server/app/auth/okta_auth.py b/querybook/server/app/auth/okta_auth.py
index f3af6ad4..a38dac6f 100644
--- a/querybook/server/app/auth/okta_auth.py
+++ b/querybook/server/app/auth/okta_auth.py
@@ -3,11 +3,11 @@ import requests
 import flask_login
 
 from app.auth.oauth_auth import OAuthLoginManager, OAUTH_CALLBACK_PATH
 from app.db import with_session, DBSession
 from env import QuerybookSettings, get_env_config
-from flask import request, session as flask_session, redirect
+from flask import Markup, request, session as flask_session, redirect
 from lib.logger import get_logger
 from lib.utils.decorators import in_mem_memoized
 from logic.user import (
     get_user_by_name,
     create_user,
@@ -83,11 +83,11 @@ class OktaLoginManager(OAuthLoginManager):
 
     def oauth_callback(self):
         LOG.debug("Handling Oauth callback...")
 
         if request.args.get("error"):
-            return f"<h1>Error: {request.args.get('error')}</h1>"
+            return f"<h1>Error: { Markup.escape(request.args.get('error')) }</h1>"
 
         code = request.args.get("code")
         try:
             access_token = self._fetch_access_token(code)
             username, email, fullname = self._get_user_profile(access_token)

Vulnerable code: from flask import request, session as flask_session, redirect
            return f"<h1>Error: {request.args.get('error')}</h1>"
from flask import request, session as flask_session, redirect
            return f"<h1>Error: {request.args.get('error')}</h1>"
Fixed code: from flask import Markup, request, session as flask_session, redirect
            return f"<h1>Error: { Markup.escape(request.args.get('error')) }</h1>"
from flask import Markup, request, session as flask_session, redirect
            return f"<h1>Error: { Markup.escape(request.args.get('error')) }</h1>"