Budibase S3 Presigned URL Authorization Regression (BASIC User Escalation)
A regression in Budibase's attachment upload endpoint lets any basic app user generate S3 PutObject presigned URLs using the platform's stored IAM credentials, and write to any bucket those…

The problem
In Budibase v3.39.4, the route POST /api/attachments/:datasourceId/url was changed from authorized(BUILDER) to authorized(PermissionType.TABLE, PermissionLevel.WRITE). BASIC app users hold TABLE/WRITE permissions by default, so they can call this endpoint without any special role.
The controller at packages/server/src/api/controllers/static/index.ts accepts bucket and key directly from the request body. It passes these values to getSignedUrl without validating them against the datasource's configured bucket. Any BASIC user who knows a valid datasource ID can obtain a presigned PutObject URL scoped to the stored S3 IAM credentials, and upload arbitrary content to any writable bucket.
Proof of concept
A working proof-of-concept for this issue in @budibase/server, with the exact payload below.
POST /api/attachments/ds_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/url HTTP/1.1
Host: budibase.example:10000
Content-Type: application/json
x-budibase-app-id: app_dev_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
{"bucket": "target-bucket", "key": "malicious-file.html"}The root cause is an authorization regression (CWE-863: Incorrect Authorization). The v3.39.3 route used authorized(BUILDER), which was replaced in v3.39.4 with authorized(PermissionType.TABLE, PermissionLevel.WRITE), a permission every BASIC user holds by default.
A second flaw compounds the impact: the controller trusts the caller-supplied bucket parameter rather than reading datasource.config.bucket. This means the attacker is not limited to the datasource's own bucket; they can target any S3 bucket the stored IAM credentials can write to.
The fix restores authorized(BUILDER) on the route, adds paramResource('datasourceId') to scope the datasource lookup to the caller's app, and pins the bucket to datasource.config.bucket in the controller, discarding the client-supplied value.
The fix
Upgrade to Budibase v3.40.0. The patch restores authorized(BUILDER) middleware on the attachment URL route, adds paramResource('datasourceId') to ensure the datasource belongs to the caller's app, and pins the target bucket to the datasource's own configured bucket value.
Related research
- high · 8.8Budibase: App-Scoped Builder Privilege Escalation via Public Role Assignment API
- highBudibase Email Change IDOR Allows Full Account Takeover via POST /api/v2/email
- high · 7.1Budibase MongoDB Integration NoSQL Operator Injection
- high · 8.5Budibase REST Datasource SSRF via DNS Rebinding (undici dispatcher bypasses IP pin)