high · 7.7Jul 24, 2026

Budibase S3 Presigned URL Authorization Regression (BASIC User Escalation)

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

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…

Package@budibase/server
Ecosystemnpm
Affected<= 3.38.1
Budibase S3 Presigned URL Authorization Regression (BASIC User Escalation)

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.

http
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.

Reporter not attributed.

References: [1][2][3]

Related research