CVE-2024-3234 (published 2024-06-06T19:16Z)

The gaizhenbiao/chuanhuchatgpt application is vulnerable to a path traversal attack due to its use of an outdated gradio component. The application is designed to restrict user access to resources within the `web_assets` folder. However, the outdated version of gradio it employs is susceptible to path traversal, as identified in CVE-2023-51449. This vulnerability allows unauthorized users to bypass the intended restrictions and access sensitive files, such as `config.json`, which contains API keys. The issue affects the latest version of chuanhuchatgpt prior to the fixed version released on 20240305.


Weakness information:
cwe_id: CWE-22
cwe_name: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
extended_description: 
            Many file operations are intended to take place within a restricted directory. By using special elements such as ".." and "/" separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system. One of the most common special elements is the "../" sequence, which in most modern operating systems is interpreted as the parent directory of the current location. This is referred to as relative path traversal. Path traversal also covers the use of absolute pathnames such as "/usr/local/bin" to access unexpected files. This is referred to as absolute path traversal.
         
         

Fix information:
repo_url: https://github.com/gaizhenbiao/chuanhuchatgpt
commit_details: repo_url: https://github.com/gaizhenbiao/chuanhuchatgpt
author: Tusik
author_date: 2024-02-17 13:46:12+08:00
msg: feat:替代并实现gradio logout route并添加退出按钮 (#1034)

* feat:替代并实现gradio logout route并添加退出按钮

* 在无验证情况下隐藏
repository: repo_url: https://github.com/gaizhenbiao/chuanhuchatgpt
repo_name: GaiZhenbiao/ChuanhuChatGPT
description: GUI for ChatGPT API and many LLMs. Supports agents, file-based QA, GPT finetuning and query with web search. All with a neat UI.
repo_language: Python
owner: gaizhenbiao
file_changes: filename: ChuanhuChatbot.py
change_type: ModificationType.MODIFY
diff: @@ -16,7 +16,9 @@ from modules.config import *
 from modules import config
 import gradio as gr
 import colorama
+from modules.gradio_patch import reg_patch
 
+reg_patch()
 
 logging.getLogger("httpx").setLevel(logging.WARNING)
 
@@ -33,6 +35,8 @@ def create_new_model():
 
 with gr.Blocks(theme=small_and_beautiful_theme) as demo:
     user_name = gr.Textbox("", visible=False)
+    # 激活/logout路由
+    logout_hidden_btn = gr.LogoutButton(visible=False)
     promptTemplates = gr.State(load_template(get_template_names()[0], mode=2))
     user_question = gr.State("")
     assert type(my_api_key) == str
@@ -391,6 +395,8 @@ with gr.Blocks(theme=small_and_beautiful_theme) as demo:
                         single_turn_checkbox = gr.Checkbox(label=i18n(
                             "单轮对话"), value=False, elem_classes="switch-checkbox", elem_id="gr-single-session-cb", visible=False)
                         # checkUpdateBtn = gr.Button(i18n("🔄 检查更新..."), visible=check_update)
+                        logout_btn = gr.Button(
+                            i18n("退出用户"), variant="primary", visible=authflag)
 
                     with gr.Tab(i18n("网络")):
                         gr.Markdown(
@@ -801,7 +807,12 @@ with gr.Blocks(theme=small_and_beautiful_theme) as demo:
         outputs=[saveFileName, systemPromptTxt, chatbot, single_turn_checkbox, temperature_slider, top_p_slider, n_choices_slider, stop_sequence_txt, max_context_length_slider, max_generation_slider, presence_penalty_slider, frequency_penalty_slider, logit_bias_txt, user_identifier_txt],
         _js='(a,b)=>{return bgSelectHistory(a,b);}'
     )
-
+    logout_btn.click(
+            fn=None,
+            inputs=[],
+            outputs=[],
+            _js='self.location="/logout"'
+        )
 # 默认开启本地服务器，默认可以直接从IP访问，默认不创建公开分享链接
 demo.title = i18n("川虎Chat 🚀")
 

programming_language: Python
filename: gradio_patch.py
change_type: ModificationType.ADD
diff: @@ -0,0 +1,114 @@
+import logging
+import os
+
+import fastapi
+import gradio
+from fastapi.responses import RedirectResponse
+from gradio.oauth import MOCKED_OAUTH_TOKEN
+
+from modules.presets import i18n
+
+OAUTH_CLIENT_ID = os.environ.get("OAUTH_CLIENT_ID")
+OAUTH_CLIENT_SECRET = os.environ.get("OAUTH_CLIENT_SECRET")
+OAUTH_SCOPES = os.environ.get("OAUTH_SCOPES")
+OPENID_PROVIDER_URL = os.environ.get("OPENID_PROVIDER_URL")
+def _add_oauth_routes(app: fastapi.FastAPI) -> None:
+    """Add OAuth routes to the FastAPI app (login, callback handler and logout)."""
+    try:
+        from authlib.integrations.starlette_client import OAuth
+    except ImportError as e:
+        raise ImportError(
+            "Cannot initialize OAuth to due a missing library. Please run `pip install gradio[oauth]` or add "
+            "`gradio[oauth]` to your requirements.txt file in order to install the required dependencies."
+        ) from e
+
+    # Check environment variables
+    msg = (
+        "OAuth is required but {} environment variable is not set. Make sure you've enabled OAuth in your Space by"
+        " setting `hf_oauth: true` in the Space metadata."
+    )
+    if OAUTH_CLIENT_ID is None:
+        raise ValueError(msg.format("OAUTH_CLIENT_ID"))
+    if OAUTH_CLIENT_SECRET is None:
+        raise ValueError(msg.format("OAUTH_CLIENT_SECRET"))
+    if OAUTH_SCOPES is None:
+        raise ValueError(msg.format("OAUTH_SCOPES"))
+    if OPENID_PROVIDER_URL is None:
+        raise ValueError(msg.format("OPENID_PROVIDER_URL"))
+
+    # Register OAuth server
+    oauth = OAuth()
+    oauth.register(
+        name="huggingface",
+        client_id=OAUTH_CLIENT_ID,
+        client_secret=OAUTH_CLIENT_SECRET,
+        client_kwargs={"scope": OAUTH_SCOPES},
+        server_metadata_url=OPENID_PROVIDER_URL + "/.well-known/openid-configuration",
+    )
+
+    # Define OAuth routes
+    @app.get("/login/huggingface")
+    async def oauth_login(request: fastapi.Request):
+        """Endpoint that redirects to HF OAuth page."""
+        redirect_uri = str(request.url_for("oauth_redirect_callback"))
+        if ".hf.space" in redirect_uri:
+            # In Space, FastAPI redirect as http but we want https
+            redirect_uri = redirect_uri.replace("http://", "https://")
+        return await oauth.huggingface.authorize_redirect(request, redirect_uri)
+
+    @app.get("/login/callback")
+    async def oauth_redirect_callback(request: fastapi.Request) -> RedirectResponse:
+        """Endpoint that handles the OAuth callback."""
+        token = await oauth.huggingface.authorize_access_token(request)
+        request.session["oauth_profile"] = token["userinfo"]
+        request.session["oauth_token"] = token
+        return RedirectResponse("/")
+
+    @app.get("/logout")
+    async def oauth_logout(request: fastapi.Request) -> RedirectResponse:
+        """Endpoint that logs out the user (e.g. delete cookie session)."""
+        request.session.pop("oauth_profile", None)
+        request.session.pop("oauth_token", None)
+        # 清除cookie并跳转到首页
+        response = RedirectResponse(url="/", status_code=302)
+        response.delete_cookie(key=f"access-token")
+        response.delete_cookie(key=f"access-token-unsecure")
+        return response
+
+
+def _add_mocked_oauth_routes(app: fastapi.FastAPI) -> None:
+    """Add fake oauth routes if Gradio is run locally and OAuth is enabled.
+
+    Clicking on a gr.LoginButton will have the same behavior as in a Space (i.e. gets redirected in a new tab) but
+    instead of authenticating with HF, a mocked user profile is added to the session.
+    """
+
+    # Define OAuth routes
+    @app.get("/login/huggingface")
+    async def oauth_login(request: fastapi.Request):
+        """Fake endpoint that redirects to HF OAuth page."""
+        return RedirectResponse("/login/callback")
+
+    @app.get("/login/callback")
+    async def oauth_redirect_callback(request: fastapi.Request) -> RedirectResponse:
+        """Endpoint that handles the OAuth callback."""
+        request.session["oauth_profile"] = MOCKED_OAUTH_TOKEN["userinfo"]
+        request.session["oauth_token"] = MOCKED_OAUTH_TOKEN
+        return RedirectResponse("/")
+
+    @app.get("/logout")
+    async def oauth_logout(request: fastapi.Request) -> RedirectResponse:
+        """Endpoint that logs out the user (e.g. delete cookie session)."""
+        request.session.pop("oauth_profile", None)
+        request.session.pop("oauth_token", None)
+        # 清除cookie并跳转到首页
+        response = RedirectResponse(url="/", status_code=302)
+        response.delete_cookie(key=f"access-token")
+        response.delete_cookie(key=f"access-token-unsecure")
+        return response
+
+
+def reg_patch():
+    gradio.oauth._add_mocked_oauth_routes = _add_mocked_oauth_routes
+    gradio.oauth._add_oauth_routes = _add_oauth_routes
+    logging.info(i18n("覆盖gradio.oauth /logout路由"))

programming_language: Python