VYPR
Moderate severityNVD Advisory· Published Sep 16, 2022· Updated Aug 3, 2024

Improper Control of Dynamically-Managed Code Resources in budibase/budibase

CVE-2022-3225

Description

CVE-2022-3225 is an improper control of dynamically-managed code resources vulnerability in Budibase prior to 1.3.20, allowing potential code injection via query string manipulation.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

CVE-2022-3225 is an improper control of dynamically-managed code resources vulnerability in Budibase prior to 1.3.20, allowing potential code injection via query string manipulation.

Vulnerability

Overview CVE-2022-3225 is an improper control of dynamically-managed code resources vulnerability in the Budibase open-source operations platform, affecting all versions prior to 1.3.20. The flaw involves inadequate encoding or validation of query string parameters in REST datasource queries, which could allow an attacker to inject malicious code that gets executed in the context of the application [1][2]. The fix in commit d35864be addresses this by introducing decodeURIComponent and findHBSBlocks for proper string handling, preventing injection of Handlebars templates or other code constructs [2].

Exploitation

To exploit this vulnerability, an attacker would need access to create or modify REST datasource queries within Budibase. The attack vector involves crafting a malicious query string that, when processed by the application, bypasses security controls and executes arbitrary code. The vulnerability is classified with a CVSS v3.1 score of 8.8 (High) per NVD metrics, indicating network-based exploitation with low complexity and no privileges required [1]. However, successful exploitation may require some level of authenticated access to create datasource configurations, though exact privileges are not fully detailed in public sources.

Impact

If successfully exploited, an attacker could achieve remote code execution within the Budibase server environment. This could lead to complete compromise of the application, including data exfiltration, modification of databases, and potential lateral movement to connected systems. Given Budibase's role as an operations platform handling business data and automations [3], the impact could extend to all integrated AI agents, workflows, and apps managed through the platform.

Mitigation

Budibase released version 1.3.20 on August 24, 2022, which patches this vulnerability by properly encoding query strings and sanitizing inputs [4]. Users are strongly advised to upgrade to this version or later. As of the publication date, this CVE is not listed in CISA's Known Exploited Vulnerabilities catalog. Organizations using Budibase should prioritize updating their instances, especially those exposed to untrusted users or the internet.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@budibase/workernpm
< 1.3.201.3.20
@budibase/buildernpm
< 1.3.201.3.20
@budibase/bbuinpm
< 1.3.201.3.20

Affected products

4

Patches

1
d35864be0854

Fixing issue introduced by fix for #7683 - encoding the query string caused handlebars statements to break, this rectifies that.

https://github.com/Budibase/budibasemike12345567Sep 15, 2022via ghsa
6 files changed · +39 31
  • packages/bbui/src/Tooltip/TooltipWrapper.svelte+1 1 modified
    @@ -47,7 +47,7 @@
         display: flex;
         justify-content: center;
         top: 15px;
    -    z-index: 100;
    +    z-index: 200;
         width: 160px;
       }
       .icon {
    
  • packages/builder/src/builderStore/dataBinding.js+5 7 modified
    @@ -9,14 +9,14 @@ import {
     import { store } from "builderStore"
     import {
       queries as queriesStores,
    -  tables as tablesStore,
       roles as rolesStore,
    +  tables as tablesStore,
     } from "stores/backend"
     import {
    -  makePropSafe,
    -  isJSBinding,
       decodeJSBinding,
       encodeJSBinding,
    +  isJSBinding,
    +  makePropSafe,
     } from "@budibase/string-templates"
     import { TableNames } from "../constants"
     import { JSONUtils } from "@budibase/frontend-core"
    @@ -118,8 +118,7 @@ export const readableToRuntimeMap = (bindings, ctx) => {
         return {}
       }
       return Object.keys(ctx).reduce((acc, key) => {
    -    let parsedQuery = readableToRuntimeBinding(bindings, ctx[key])
    -    acc[key] = parsedQuery
    +    acc[key] = readableToRuntimeBinding(bindings, ctx[key])
         return acc
       }, {})
     }
    @@ -132,8 +131,7 @@ export const runtimeToReadableMap = (bindings, ctx) => {
         return {}
       }
       return Object.keys(ctx).reduce((acc, key) => {
    -    let parsedQuery = runtimeToReadableBinding(bindings, ctx[key])
    -    acc[key] = parsedQuery
    +    acc[key] = runtimeToReadableBinding(bindings, ctx[key])
         return acc
       }, {})
     }
    
  • packages/builder/src/helpers/data/utils.js+16 2 modified
    @@ -1,4 +1,5 @@
     import { IntegrationTypes } from "constants/backend"
    +import { findHBSBlocks } from "@budibase/string-templates"
     
     export function schemaToFields(schema) {
       const response = {}
    @@ -31,7 +32,8 @@ export function breakQueryString(qs) {
       let paramObj = {}
       for (let param of params) {
         const split = param.split("=")
    -    paramObj[split[0]] = split.slice(1).join("=")
    +    console.log(split[1])
    +    paramObj[split[0]] = decodeURIComponent(split.slice(1).join("="))
       }
       return paramObj
     }
    @@ -46,7 +48,19 @@ export function buildQueryString(obj) {
           if (str !== "") {
             str += "&"
           }
    -      str += `${key}=${encodeURIComponent(value || "")}`
    +      const bindings = findHBSBlocks(value)
    +      let count = 0
    +      const bindingMarkers = {}
    +      bindings.forEach(binding => {
    +        const marker = `BINDING...${count++}`
    +        value = value.replace(binding, marker)
    +        bindingMarkers[marker] = binding
    +      })
    +      let encoded = encodeURIComponent(value || "")
    +      Object.entries(bindingMarkers).forEach(([marker, binding]) => {
    +        encoded = encoded.replace(marker, binding)
    +      })
    +      str += `${key}=${encoded}`
         }
       }
       return str
    
  • packages/builder/src/pages/builder/app/[application]/data/datasource/[selectedDatasource]/rest/[query]/index.svelte+2 0 modified
    @@ -347,6 +347,7 @@
         const datasourceUrl = datasource?.config.url
         const qs = query?.fields.queryString
         breakQs = restUtils.breakQueryString(qs)
    +    console.log(breakQs)
         breakQs = runtimeToReadableMap(mergedBindings, breakQs)
     
         const path = query.fields.path
    @@ -708,6 +709,7 @@
       .url-block {
         display: flex;
         gap: var(--spacing-s);
    +    z-index: 200;
       }
       .verb {
         flex: 1;
    
  • packages/worker/src/api/controllers/global/self.js+14 17 modified
    @@ -80,16 +80,15 @@ const addSessionAttributesToUser = ctx => {
       ctx.body.csrfToken = ctx.user.csrfToken
     }
     
    -/**
    - * Remove the attributes that are session based from the current user,
    - * so that stale values are not written to the db
    - */
    -const removeSessionAttributesFromUser = ctx => {
    -  delete ctx.request.body.csrfToken
    -  delete ctx.request.body.account
    -  delete ctx.request.body.accountPortalAccess
    -  delete ctx.request.body.budibaseAccess
    -  delete ctx.request.body.license
    +const sanitiseUserUpdate = ctx => {
    +  const allowed = ["firstName", "lastName", "password", "forceResetPassword"]
    +  const resp = {}
    +  for (let [key, value] of Object.entries(ctx.request.body)) {
    +    if (allowed.includes(key)) {
    +      resp[key] = value
    +    }
    +  }
    +  return resp
     }
     
     exports.getSelf = async ctx => {
    @@ -117,25 +116,23 @@ exports.updateSelf = async ctx => {
       const db = getGlobalDB()
       const user = await db.get(ctx.user._id)
       let passwordChange = false
    -  if (ctx.request.body.password) {
    +
    +  const userUpdateObj = sanitiseUserUpdate(ctx)
    +  if (userUpdateObj.password) {
         // changing password
         passwordChange = true
    -    ctx.request.body.password = await hash(ctx.request.body.password)
    +    userUpdateObj.password = await hash(userUpdateObj.password)
         // Log all other sessions out apart from the current one
         await platformLogout({
           ctx,
           userId: ctx.user._id,
           keepActiveSession: true,
         })
       }
    -  // don't allow sending up an ID/Rev, always use the existing one
    -  delete ctx.request.body._id
    -  delete ctx.request.body._rev
    -  removeSessionAttributesFromUser(ctx)
     
       const response = await db.put({
         ...user,
    -    ...ctx.request.body,
    +    ...userUpdateObj,
       })
       await userCache.invalidateUser(user._id)
       ctx.body = {
    
  • packages/worker/src/api/controllers/global/users.ts+1 4 modified
    @@ -14,7 +14,6 @@ import {
       errors,
       events,
       tenancy,
    -  users as usersCore,
     } from "@budibase/backend-core"
     import { checkAnyUserExists } from "../../../utilities/users"
     import { groups as groupUtils } from "@budibase/pro"
    @@ -148,9 +147,7 @@ export const bulkDelete = async (ctx: any) => {
       }
     
       try {
    -    let response = await users.bulkDelete(userIds)
    -
    -    ctx.body = response
    +    ctx.body = await users.bulkDelete(userIds)
       } catch (err) {
         ctx.throw(err)
       }
    

Vulnerability mechanics

Root cause

"Missing input allowlist in user profile update allows arbitrary properties to be written to the database."

Attack vector

An authenticated attacker can send a crafted HTTP request to the `updateSelf` endpoint with arbitrary JSON properties in the request body (e.g., `role`, `admin`, `permissions`). Because the original code only deleted a handful of known session attributes and the `_id`/`_rev` fields, any other property was spread into the user document via `...ctx.request.body` and persisted to the database [CWE-284]. This allows a low-privileged user to escalate privileges or modify sensitive account fields by including them in the update payload [CWE-913].

Affected code

The primary vulnerable code path is in `packages/worker/src/api/controllers/global/self.js`, where the `updateSelf` endpoint previously deleted only a few session attributes (`csrfToken`, `account`, `accountPortalAccess`, `budibaseAccess`, `license`) and the `_id`/`_rev` fields from `ctx.request.body`, but did not restrict arbitrary user-controlled properties from being written to the database. The patch replaces this fragile blocklist approach with a strict allowlist (`sanitiseUserUpdate`) that only permits `firstName`, `lastName`, `password`, and `forceResetPassword` [patch_id=1705202].

What the fix does

The patch replaces the `removeSessionAttributesFromUser` function (which deleted only a fixed set of session-based keys) with a new `sanitiseUserUpdate` function that builds a response object containing only keys from an explicit allowlist: `["firstName", "lastName", "password", "forceResetPassword"]` [patch_id=1705202]. Instead of spreading `ctx.request.body` directly into the user document, the code now spreads `userUpdateObj`, ensuring no arbitrary properties (such as role or permission fields) can be injected. This closes the improper access control by enforcing a strict allowlist rather than a fragile blocklist.

Preconditions

  • authAttacker must have a valid authenticated session with the Budibase application
  • networkAttacker must be able to send HTTP requests to the /api/self endpoint
  • configThe vulnerable version must be prior to 1.3.20

Generated on May 23, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.