github-advanced-security[bot] commented on code in PR #28260:
URL: https://github.com/apache/superset/pull/28260#discussion_r1583037545
##########
superset/views/img_proxy.py:
##########
@@ -0,0 +1,93 @@
+from io import BytesIO
+from typing import Any
+from urllib.parse import urlparse
+
+import requests
+from flask import abort, request, Response
+from flask_appbuilder.api import expose
+from flask_wtf.csrf import same_origin
+from PIL import Image
+
+from superset import event_logger
+from superset.superset_typing import FlaskResponse
+from superset.utils.core import get_user_id
+
+from .base import BaseSupersetView
+
+
+class ImgProxyView(BaseSupersetView):
+ route_base = "/img_proxy"
+
+ @expose("/")
+ @event_logger.log_this
+ def img_proxy(self) -> FlaskResponse:
+ """
+ Proxy to an external URL, to overcome CORS restrictions.
+ Returns a HTTP response containing the resource fetched from the
external URL.
+ """
+ if not get_user_id():
+ abort(403)
+
+ url = request.args.get("url")
+ allowed_content_types = ["image/"]
+
+ if not url:
+ abort(400)
+
+ parsed_url = urlparse(url)
+ if parsed_url.scheme not in ["http", "https"]:
+ abort(400)
+
+ if not request.referrer or (
+ request.referrer and not same_origin(request.referrer,
request.url_root)
+ ):
+ abort(403)
+
+ try:
+ response = self.fetch_resource(url)
+ except Exception:
+ abort(500)
+
+ content_type = response.headers.get("content-type", "")
+
+ if not any(
+ content_type.startswith(content_type_prefix)
+ for content_type_prefix in allowed_content_types
+ ):
+ abort(400)
+
+ try:
+ is_valid_image = self.validate_image(response.content)
+ if not is_valid_image:
+ abort(400)
+ except Exception:
+ abort(500)
+
+ headers: dict[str, Any] = {
+ key: value for (key, value) in response.headers.items()
+ }
+
+ return Response(response.content, response.status_code, headers)
+
+ def validate_image(self, image_content) -> bool:
+ """
+ Load the image from bytes, Pillow will raise an IOError if the file is
not an image
+ """
+ try:
+ with Image.open(BytesIO(image_content)) as img:
+ if img.format:
+ return True
+ return False
+ except OSError as e:
+ raise e
+ except Exception as e:
+ raise e
+
+ def fetch_resource(self, url: str) -> requests.Response:
+ """Fetch the resource from the external server and handle errors."""
+ try:
+ response = requests.get(url)
Review Comment:
## Full server-side request forgery
The full URL of this request depends on a [user-provided value](1).
[Show more
details](https://github.com/apache/superset/security/code-scanning/1220)
##########
superset/views/img_proxy.py:
##########
@@ -0,0 +1,93 @@
+from io import BytesIO
+from typing import Any
+from urllib.parse import urlparse
+
+import requests
+from flask import abort, request, Response
+from flask_appbuilder.api import expose
+from flask_wtf.csrf import same_origin
+from PIL import Image
+
+from superset import event_logger
+from superset.superset_typing import FlaskResponse
+from superset.utils.core import get_user_id
+
+from .base import BaseSupersetView
+
+
+class ImgProxyView(BaseSupersetView):
+ route_base = "/img_proxy"
+
+ @expose("/")
+ @event_logger.log_this
+ def img_proxy(self) -> FlaskResponse:
+ """
+ Proxy to an external URL, to overcome CORS restrictions.
+ Returns a HTTP response containing the resource fetched from the
external URL.
+ """
+ if not get_user_id():
+ abort(403)
+
+ url = request.args.get("url")
+ allowed_content_types = ["image/"]
+
+ if not url:
+ abort(400)
+
+ parsed_url = urlparse(url)
+ if parsed_url.scheme not in ["http", "https"]:
+ abort(400)
+
+ if not request.referrer or (
+ request.referrer and not same_origin(request.referrer,
request.url_root)
+ ):
+ abort(403)
+
+ try:
+ response = self.fetch_resource(url)
+ except Exception:
+ abort(500)
+
+ content_type = response.headers.get("content-type", "")
+
+ if not any(
+ content_type.startswith(content_type_prefix)
+ for content_type_prefix in allowed_content_types
+ ):
+ abort(400)
+
+ try:
+ is_valid_image = self.validate_image(response.content)
+ if not is_valid_image:
+ abort(400)
+ except Exception:
+ abort(500)
+
+ headers: dict[str, Any] = {
+ key: value for (key, value) in response.headers.items()
+ }
+
+ return Response(response.content, response.status_code, headers)
Review Comment:
## Reflected server-side cross-site scripting
Cross-site scripting vulnerability due to a [user-provided value](1).
[Show more
details](https://github.com/apache/superset/security/code-scanning/1219)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]