CVE-2022-3225 (published 2022-09-16T17:15Z)

Improper Control of Dynamically-Managed Code Resources in GitHub repository budibase/budibase prior to 1.3.20.\n\n

CVSS v3 base score: 5.7, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N)

Weakness information:
cwe_id: CWE-913
cwe_name: Improper Control of Dynamically-Managed Code Resources
description: The product does not properly restrict reading from or writing to dynamically-managed code resources such as variables, objects, classes, attributes, functions, or executable instructions or statements.
extended_description: Many languages offer powerful features that allow the programmer to dynamically create or modify existing code, or resources used by code such as variables and objects. While these features can offer significant flexibility and reduce development time, they can be extremely dangerous if attackers can directly influence these code resources in unexpected ways.
         

Fix information:
repo_url: https://github.com/budibase/budibase
commit_details: repo_url: https://github.com/budibase/budibase
author: mike12345567
author_date: 2022-09-15 19:35:55+01:00
msg: Fixing issue introduced by fix for #7683 - encoding the query string caused handlebars statements to break, this rectifies that.
repository: repo_url: https://github.com/budibase/budibase
repo_name: Budibase/budibase
description: Low code platform for building business apps and workflows in minutes. Supports PostgreSQL, MySQL, MSSQL, MongoDB, Rest API, Docker, K8s, and more 🚀  
repo_language: TypeScript
owner: budibase
file_changes: filename: TooltipWrapper.svelte
change_type: ModificationType.MODIFY
diff: @@ -47,7 +47,7 @@
     display: flex;
     justify-content: center;
     top: 15px;
-    z-index: 100;
+    z-index: 200;
     width: 160px;
   }
   .icon {

programming_language: HTML
filename: dataBinding.js
change_type: ModificationType.MODIFY
diff: @@ -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
   }, {})
 }

programming_language: TypeScript
filename: utils.js
change_type: ModificationType.MODIFY
diff: @@ -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

programming_language: Swift
filename: index.svelte
change_type: ModificationType.MODIFY
diff: @@ -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;

programming_language: Swift
filename: self.js
change_type: ModificationType.MODIFY
diff: @@ -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,10 +116,12 @@ 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,
@@ -128,14 +129,10 @@ exports.updateSelf = async ctx => {
       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 = {

programming_language: TypeScript
filename: users.ts
change_type: ModificationType.MODIFY
diff: @@ -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)
   }

programming_language: TypeScript