Tuesday, 29 September 2026

Blind SSRF via Aliyun OSS Image Processing + Content-Type Upload Bypass

 

Bug Bounty Report: Blind SSRF via Aliyun OSS Image Processing + Content-Type Upload Bypass

Reported to: security@moonshot.cn (or appropriate channel) Date: 2026-06-26 Reporter: FrankSx Severity: High (Blind SSRF to Internal Cloud Infrastructure + File Upload Bypass)

1. Executive Summary

Two related vulnerabilities were identified in Kimi's file upload and CDN serving infrastructure:
  1. Content-Type Upload Bypass: The /apiv2/slides:upload endpoint accepts arbitrary file types as long as the Content-Type header is declared as an image type (e.g., image/svg+xml, image/png). This allows uploading non-image files (PDFs, HTML, scripts, etc.) that are then served from the CDN with the declared image type.
  2. Blind SSRF via Aliyun OSS Image Processing: User-uploaded files on kimi-img.moonshot.cn are stored in Aliyun OSS. The CDN URLs accept arbitrary x-oss-process query parameters, including the image/watermark operation. This operation fetches an attacker-controlled URL server-side to use as a watermark image, constituting a blind Server-Side Request Forgery (SSRF) vulnerability.
The SSRF was confirmed by successfully triggering a request to Alibaba Cloud's internal metadata service (100.100.100.200), which responded with a directory listing (not an image), causing OSS to return a NoSuchWatermarkImage error. The error message explicitly confirms the URL that was fetched, proving the SSRF.

2. Affected Endpoints / Assets

  • Upload Endpoint: https://www.kimi.com/apiv2/slides:upload
  • CDN Domain: https://kimi-img.moonshot.cn (Aliyun OSS-backed)
  • Vulnerable Parameter: x-oss-process query string on any user-uploaded file URL
  • Proxy Layer: Byte-nginx / Tengine

3. Vulnerability #1: Content-Type Upload Bypass

3.1 Description

The /apiv2/slides:upload endpoint does not validate the actual file content against the declared Content-Type. By setting the Content-Type header to an image MIME type (e.g., image/svg+xml, image/png), any file can be uploaded and stored on the CDN.

3.2 Impact

  • MIME Confusion: Attackers can upload HTML, JavaScript, PDF, or other file types disguised as images
  • Content Sniffing Risk: If the CDN serves these files without X-Content-Type-Options: nosniff, browsers may render them as the actual file type (e.g., HTML executing as a web page)
  • Polyglot Abuse: Could be used to smuggle executable content through image upload filters
  • Amplifies SSRF: Allows uploading specifically crafted files (e.g., SVG with external references) that increase the attack surface for the SSRF vulnerability

3.3 Evidence

The upload request accepts the declared type without content validation:
bash
curl 'https://www.kimi.com/apiv2/slides:upload' \
  -H 'Authorization: Bearer <TOKEN>' \
  -F 'slides_file=@malicious.pdf;type=image/svg+xml'
The file is stored and served from the CDN with the declared image/svg+xml type, regardless of actual content.

4. Vulnerability #2: Blind SSRF via OSS Watermark Parameter

4.1 Description

User-uploaded files on kimi-img.moonshot.cn are stored in Aliyun OSS. The returned CDN URLs accept arbitrary x-oss-process query parameters, including the image/watermark operation. This operation fetches an attacker-controlled URL server-side to use as a watermark image.

4.2 Steps to Reproduce

Step 1: Upload a File

Upload any file to Kimi to obtain a CDN URL:
bash
curl 'https://www.kimi.com/apiv2/slides:upload' \
  -H 'Authorization: Bearer <TOKEN>' \
  -F 'slides_file=@test.png;type=image/png'
Response includes an assetUrl like:
plain
https://kimi-img.moonshot.cn/pub/slides/26-06-26-19:25:53-d8v63gfk67e5lg4hfpf0.svg

Step 2: Trigger SSRF via Watermark Parameter

Append an x-oss-process=image/watermark parameter with a base64-encoded internal URL:
bash
# Base64 of "http://100.100.100.200/latest/meta-data/"
# = aHR0cDovLzEwMC4xMDAuMTAwLjIwMC9sYXRlc3QvbWV0YS1kYXRhLw==

curl 'https://kimi-img.moonshot.cn/pub/slides/26-06-26-19:25:53-d8v63gfk67e5lg4hfpf0.svg?x-oss-process=image/watermark,image_aHR0cDovLzEwMC4xMDAuMTAwLjIwMC9sYXRlc3QvbWV0YS1kYXRhLw==,t_90,g_se'

Step 3: Observe Server-Side Request

OSS attempts to fetch the watermark image from the internal metadata service. The response is not a valid image, so OSS returns:
xml
<Error>
  <Code>NoSuchWatermarkImage</Code>
  <Message>The specified watermark image: http://100.100.100.200/latest/meta-data/ does not exist.</Message>
  <RequestId>f3c8073e6393bcb76a3e6393-bed3ee3-1wd4oV-GO-cb-tos-1az-front-azc-17</RequestId>
  <HostId>ZaSNCufbcKcSfHduYXJxIpFIqEuQdEBZ</HostId>
  <EC>0022-00000018</EC>
</Error>
The error message explicitly confirms that the server made an HTTP request to http://100.100.100.200/latest/meta-data/.

4.3 Proof of Concept Evidence

Table
TestTarget URLResultEvidence
Internal Metadatahttp://100.100.100.200/latest/meta-data/NoSuchWatermarkImageError message confirms URL was fetched
External Webhookhttps://webhook.site/...504 Gateway TimeoutRequest reached processing layer but timed out (external fetch blocked/slow)
Basic Resize?x-oss-process=image/resize,w_100504 Gateway TimeoutConfirms query parameter is accepted
Key Observations:
  • The x-oss-process parameter is not validated or signed on user-uploaded content
  • OSS processes the watermark operation server-side, making outbound HTTP requests
  • The Byte-nginx / tengine proxy layer passes these parameters through to OSS
  • The SSRF is blind — the attacker does not see the response, but the request is confirmed via error messages and timeouts

5. Impact Assessment

5.1 Direct Impact (Content-Type Bypass)

  • MIME Type Confusion: Attackers can upload arbitrary file types disguised as images
  • Potential XSS: If HTML/JS files are uploaded and served without nosniff, they may execute in browser context
  • Filter Bypass: Security controls that rely on file type validation (e.g., image-only uploads) are bypassed
  • Amplified Attack Surface: Combined with the SSRF, crafted files (e.g., SVG with external references) can increase the impact

5.2 Direct Impact (SSRF)

  • Blind SSRF: An attacker can force Kimi's Aliyun OSS infrastructure to make HTTP requests to arbitrary internal and external URLs
  • Cloud Metadata Exposure: The metadata service (100.100.100.200) is reachable. While the response is not an image, other endpoints (e.g., those returning binary/image data) could leak sensitive information
  • Internal Reconnaissance: Attacker can probe internal services, K8s APIs, and other infrastructure by observing error messages and timing differences

5.3 Potential Escalation

If an internal endpoint can be found that returns a valid image (e.g., favicon.ico, static assets), the watermark operation would succeed and the watermarked image would be returned to the attacker. This could:
  • Leak internal service fingerprints
  • Potentially expose IAM credentials if metadata endpoints return structured data that happens to be image-like
  • Be chained with other vulnerabilities for full cloud compromise

5.4 CVSS-like Scoring

  • Attack Vector: Network (remote, unauthenticated if file URL is public)
  • Attack Complexity: Low
  • Privileges Required: None (any user with upload capability)
  • User Interaction: None
  • Scope: Changed (affects cloud infrastructure beyond the application)
  • Confidentiality: High (can probe internal services)
  • Integrity: Low
  • Availability: Low
  • Overall Severity: High

6. Affected Infrastructure

Based on the response headers and error messages:
  • CDN/Proxy: Byte-nginx / Tengine (ByteDance's nginx fork)
  • Object Storage: Aliyun OSS (confirmed by x-oss-process and error codes)
  • Cloud Provider: Alibaba Cloud (confirmed by 100.100.100.200 metadata service)
  • Internal Architecture: Kubernetes cluster (previously observed apiserver.c91192ba748f1481985f14c836cb5249b.cn-beijing.cs.aliyuncs.com:6443)

7. Recommended Fix

7.1 Immediate (Short-term)

  • Validate uploaded file content: Verify actual file content matches declared MIME type (magic bytes, not just extension/header)
  • Sign query parameters: Ensure x-oss-process parameters are included in the URL signature (kimi-sig). If the signature does not cover query parameters, an attacker can modify them at will.
  • Strip dangerous parameters: Remove or whitelist x-oss-process values on user-uploaded content URLs before serving them.
  • Block watermark on user uploads: Disable the image/watermark operation for objects in the user-uploaded bucket/prefix.

7.2 Long-term

  • Use presigned URLs with limited scope: Generate presigned URLs that only allow specific, safe operations (e.g., resize only, no watermark).
  • Implement URL validation: Validate that returned CDN URLs only contain allowed query parameters before serving them to users.
  • Network segmentation: Ensure OSS processing nodes cannot reach internal metadata services or sensitive internal APIs.
  • Content Security Policy: Add X-Content-Type-Options: nosniff to all CDN responses to prevent MIME confusion attacks
     

Monday, 28 September 2026

The AI Software Engineer That Read Us /etc/shadow

The AI Software Engineer That Read Us /etc/shadow

CRITICAL  ·  UNAUTH ARBITRARY FILE READ (root)
Series: "LongLost" post
Target: robocoders.ai — BLACKBOXAI agentic coding platform  (now defunct)
Evidence: single Firefox screenshot, Thu Sep 5 19:57
Vector: /api/select-file?file= — no auth, no token, no questions asked

The one-liner

We asked an AI coding assistant for /etc/passwd. It said 200 OK. Then it gave us /etc/shadow. Then it offered us .bashrc as a download.

What the screenshot shows

The browser sits on https://www.robocoders.ai, a chat UI branded:

"Hi! I'm BLACKBOXAI, an AI Software Engineer. What would you like to build with me today?"

A Code Editor pane, a Jupyter pane, and DevTools Network tab — which tells the whole story:

# DevTools → Network, three honest little requests GET /api/select-file?file=/etc/passwd&sandboxID=... 200 json 872 B GET /api/select-file?file=/etc/shadow&sandboxID=... 200 json 513 B GET .../api/list-files?path=/&sandboxID=... 200 json 486 ms

The Response pane shows /etc/passwd, JSON-wrapped. The Code Editor shows .bashrc with a Download button. The agent's status reads "Agent is initialized, waiting for task..." — it never got a task. It didn't need one.

Exhibit A — single frame, full request/response visible. One screenshot, whole compromise class.

Why this is worse than a normal LFI

1. It ran as root. A 513-byte /etc/shadow means the file service had euid 0. Full passwd visible in the pane: root, daemon, bin, sys, sync, games, man, lp, mail, news, uucp, proxy, backup, list, irc, gnats, nobody, the systemd family, messagebus, sshd, chrony, and a uid-1000 user. A stock container image, owned end-to-end by a query string.
2. The read isn't the only primitive. The editor lists .bashrc with a Download control. UIs that list-and-read usually save-and-write. Arbitrary read as root + probable arbitrary write = drop a key into /root/.ssh/authorized_keys, or overwrite something the app executes. We didn't need to prove the write. The read was enough for the frame.
3. Parameter-spray surface. select-file?file= is the obvious one. list-files?path=/ proves directory browsing is half-built already. And sandboxID does double duty — if it's ever concatenated into a path, it's not an arbitrary file read, it's every tenant's files, cross-sandbox. On a multi-tenant AI coding platform, that's the business model gone.

Reproduction (from the screenshot, verbatim)

GET /api/select-file?file=/etc/passwd&sandboxID=<id> HTTP/2 200 GET /api/select-file?file=/etc/shadow&sandboxID=<id> HTTP/2 200 # had we wanted more, the menu was open: # /proc/self/environ — every API key the process held # /proc/1/cmdline, /proc/1/cgroup — container & orchestrator layout # /root/.aws/credentials, /var/run/docker.sock # list-files?path=/app — find the server source, hunt the write path

That's it. That's the exploit.

Timeline / status

WhenWhat
Sep 5 (screenshot)File read observed and captured — passwd + shadow served to an unauthenticated tab.
Since thenrobocoders.ai has been shuttered. No live disclosure channel, no program to report to. This post is the record.
EvidenceOne screenshot. That's the point. One frame, full compromise class.

The funny part

The brand promise was "an AI Software Engineer." What shipped was an exfiltration agent with a chat frontend. No prompt injection, no jailbreak, no clever encoding — the file API simply never asked who we were or why we wanted the shadow file. The agent politely reported it was "waiting for task" while its plumbing served root's password database to a browser tab.

And there's a resonance for anyone tracking platform-security arcs: the same brand once shipped username pages loose enough that admin@ was registerable. Sites die, patterns don't. This screenshot is what the pattern looks like wearing an agentic-coding costume.

Takeaways (for builders)

› A file= parameter is a path. Treat it like one: allowlist, jail to a sandbox root, resolve symlinks after containment, and run the file service as nobody so /etc/shadow is unreadable even on a bad day.
› list-files?path=/ is a finding all by itself. If your editor can browse /, it can read /.
› Sandbox IDs must be looked up, never concatenated. Cross-tenant file read is a company-ender for exactly this product shape.
› Root file service + web UI = the entire host is the attack surface. The chat model was never the risk. The plumbing was.
Evidence: robocoders_etc_passwd_lfi.png — single frame, requests & responses visible.
Cross-reference: "LongLost" series — methodology over dramatization, screenshots over claims.