VYPR
High severityNVD Advisory· Published Oct 17, 2023· Updated Sep 13, 2024

Prototype pollution vulnerability leading to arbitrary code execution in synchrony deobfuscator

CVE-2023-45811

Description

A prototype pollution vulnerability in synchrony deobfuscator before v2.4.4 allows remote code execution via crafted input.

AI Insight

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

A prototype pollution vulnerability in synchrony deobfuscator before v2.4.4 allows remote code execution via crafted input.

Vulnerability

Overview A __proto__ pollution vulnerability exists in synchrony deobfuscator versions before v2.4.4, specifically within the LiteralMap transformer. This flaw allows an attacker to modify properties of the Object prototype through crafted input [1][4].

Exploitation

Method An attacker can exploit this by providing a malicious input file that sets __proto__.parser to a path containing a JavaScript module. When synchrony processes this file, it triggers a require() call via the prettier module, leading to arbitrary code execution [4].

Impact

Successful exploitation enables an attacker to execute arbitrary code on the system running synchrony, potentially compromising the host environment [1][4].

Mitigation

The vulnerability is fixed in version v2.4.4 [2]. Users unable to upgrade should run Node.js with the --disable-proto=delete or --disable-proto=throw flags [1][4].

AI Insight generated on May 20, 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
deobfuscatornpm
>= 2.0.1, < 2.4.42.4.4

Affected products

2

Patches

1
b583126be94c

Merge pull request from GHSA-jg82-xh3w-rhxx

https://github.com/relative/synchronyrelativeOct 17, 2023via ghsa
7 files changed · +72 112
  • package.json+0 2 modified
    @@ -31,7 +31,6 @@
         "@types/estree": "0.0.51",
         "@types/mersenne-twister": "1.1.2",
         "@types/node": "17.0.17",
    -    "@types/prettier": "2.4.4",
         "@types/yargs": "17.0.8",
         "esbuild": "0.14.21",
         "escodegen": "2.0.0",
    @@ -47,7 +46,6 @@
         "acorn-walk": "8.2.0",
         "eslint-scope": "7.1.1",
         "mersenne-twister": "1.1.0",
    -    "prettier": "2.5.1",
         "yargs": "17.3.1"
       }
     }
    
  • pnpm-lock.yaml+4 16 modified
    @@ -1,5 +1,9 @@
     lockfileVersion: '6.0'
     
    +settings:
    +  autoInstallPeers: true
    +  excludeLinksFromLockfile: false
    +
     dependencies:
       '@javascript-obfuscator/escodegen':
         specifier: 2.3.0
    @@ -19,9 +23,6 @@ dependencies:
       mersenne-twister:
         specifier: 1.1.0
         version: 1.1.0
    -  prettier:
    -    specifier: 2.5.1
    -    version: 2.5.1
       yargs:
         specifier: 17.3.1
         version: 17.3.1
    @@ -42,9 +43,6 @@ devDependencies:
       '@types/node':
         specifier: 17.0.17
         version: 17.0.17
    -  '@types/prettier':
    -    specifier: 2.4.4
    -    version: 2.4.4
       '@types/yargs':
         specifier: 17.0.8
         version: 17.0.8
    @@ -122,10 +120,6 @@ packages:
         resolution: {integrity: sha512-e8PUNQy1HgJGV3iU/Bp2+D/DXh3PYeyli8LgIwsQcs1Ar1LoaWHSIT6Rw+H2rNJmiq6SNWiDytfx8+gYj7wDHw==}
         dev: true
     
    -  /@types/prettier@2.4.4:
    -    resolution: {integrity: sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA==}
    -    dev: true
    -
       /@types/yargs-parser@20.2.1:
         resolution: {integrity: sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==}
         dev: true
    @@ -952,12 +946,6 @@ packages:
         resolution: {integrity: sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=}
         engines: {node: '>= 0.8.0'}
     
    -  /prettier@2.5.1:
    -    resolution: {integrity: sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==}
    -    engines: {node: '>=10.13.0'}
    -    hasBin: true
    -    dev: false
    -
       /process@0.11.10:
         resolution: {integrity: sha1-czIwDoQBYb2j5podHZGn1LwW8YI=}
         engines: {node: '>= 0.6.0'}
    
  • src/context.ts+3 5 modified
    @@ -69,7 +69,7 @@ interface ControlFlowLiteral {
       identifier: string
    
       value: string | number
    
     }
    
    -interface ControlFlowStorage {
    
    +export interface ControlFlowStorage {
    
       identifier: string
    
       aliases: string[]
    
       functions: ControlFlowFunction[]
    
    @@ -96,9 +96,7 @@ export default class Context {
       stringDecoders: DecoderFunction[] = []
    
       stringDecoderReferences: DecoderReference[] = []
    
     
    
    -  controlFlowStorageNodes: {
    
    -    [x: BlockId]: ControlFlowStorage
    
    -  } = {}
    
    +  controlFlowStorageNodes = new Map<BlockId, ControlFlowStorage>()
    
     
    
       removeGarbage: boolean = true
    
       transformers: InstanceType<typeof Transformer>[]
    
    @@ -112,7 +110,7 @@ export default class Context {
         ast: Program,
    
         transformers: [string, Partial<TransformerOptions>][],
    
         isModule: boolean,
    
    -    source?: string,
    
    +    source?: string
    
       ) {
    
         this.ast = ast
    
         this.transformers = this.buildTransformerList(transformers)
    
    
  • src/deobfuscator.ts+2 36 modified
    @@ -4,7 +4,6 @@ import * as acornLoose from 'acorn-loose'
     import { Transformer, TransformerOptions } from './transformers/transformer'
    
     import { Node, Program, sp } from './util/types'
    
     import Context from './context'
    
    -import prettier from 'prettier'
    
     import { walk } from './util/walk'
    
     
    
     const FILE_REGEX = /(?<!\.d)\.[mc]?[jt]s$/i // cjs, mjs, js, ts, but no .d.ts
    
    @@ -44,6 +43,8 @@ export interface DeobfuscateOptions {
        * for work with Prettier
    
        * https://github.com/prettier/prettier/pull/12172
    
        * (default = true)
    
    +   *
    
    +   * @deprecated Prettier is no longer used in the deobfuscator
    
        */
    
       transformChainExpressions: boolean
    
     
    
    @@ -205,41 +206,6 @@ export class Deobfuscator {
         source = escodegen.generate(ast, {
    
           sourceMapWithCode: true,
    
         }).code
    
    -    try {
    
    -      source = prettier.format(source, {
    
    -        semi: false,
    
    -        singleQuote: true,
    
    -
    
    -        // https://github.com/prettier/prettier/pull/12172
    
    -        parser: (text, _opts) => {
    
    -          let ast = this.parse(text, acornOptions, options)
    
    -          if (options.transformChainExpressions) {
    
    -            walk(ast as Node, {
    
    -              ChainExpression(cx) {
    
    -                if (cx.expression.type === 'CallExpression') {
    
    -                  sp<any>(cx, {
    
    -                    ...cx.expression,
    
    -                    type: 'OptionalCallExpression',
    
    -                    expression: undefined,
    
    -                  })
    
    -                } else if (cx.expression.type === 'MemberExpression') {
    
    -                  sp<any>(cx, {
    
    -                    ...cx.expression,
    
    -                    type: 'OptionalMemberExpression',
    
    -                    expression: undefined,
    
    -                  })
    
    -                }
    
    -              },
    
    -            })
    
    -          }
    
    -          return ast
    
    -        },
    
    -      })
    
    -    } catch (err) {
    
    -      // I don't think we should log here, but throwing the error is not very
    
    -      // important since it is non fatal
    
    -      console.log(err)
    
    -    }
    
     
    
         return source
    
       }
    
    
  • src/transformers/controlflow.ts+27 19 modified
    @@ -10,11 +10,12 @@ import {
       Identifier,
    
       ObjectExpression,
    
       Statement,
    
    +  BlockStatement,
    
     } from '../util/types'
    
     import { Transformer, TransformerOptions } from './transformer'
    
     import { walk } from '../util/walk'
    
     import * as Guard from '../util/guard'
    
    -import Context from '../context'
    
    +import Context, { ControlFlowStorage } from '../context'
    
     import {
    
       immutate,
    
       literalOrIdentifierToString,
    
    @@ -38,24 +39,33 @@ export default class ControlFlow extends Transformer<ControlFlowOptions> {
         if (!fx.body.body[0].argument)
    
           throw new TypeError('Function in CFSN was invalid (void return)')
    
     
    
    -    let params = fx.params as Identifier[],
    
    -      paramMap: { [ident: string]: Node } = {}
    
    +    const params = fx.params as Identifier[],
    
    +      paramMap = new Map<string, Node>()
    
         let i = 0
    
         for (const p of params) {
    
    -      paramMap[p.name] = cx.arguments[i]
    
    +      paramMap.set(p.name, cx.arguments[i])
    
           ++i
    
         }
    
         let immRtn = immutate(fx.body.body[0].argument)
    
         walk(immRtn, {
    
           Identifier(id) {
    
    -        if (!paramMap[id.name]) return
    
    -        sp<Node>(id, paramMap[id.name])
    
    +        const node = paramMap.get(id.name)
    
    +        if (!node) return
    
    +        sp<Node>(id, node)
    
           },
    
         })
    
     
    
         return immRtn as Node
    
       }
    
     
    
    +  private getStorageNode(
    
    +    context: Context,
    
    +    node: BlockStatement
    
    +  ): ControlFlowStorage | undefined {
    
    +    const bid = getBlockId(node)
    
    +    return context.controlFlowStorageNodes.get(bid)
    
    +  }
    
    +
    
       // fixes empty object inits where there are setters in the same block
    
       populateEmptyObjects(context: Context) {
    
         walk(context.ast, {
    
    @@ -125,7 +135,8 @@ export default class ControlFlow extends Transformer<ControlFlowOptions> {
             // /shrug
    
             let bid = getBlockId(node)
    
     
    
    -        if (context.controlFlowStorageNodes[bid]) return
    
    +        let cfsn = context.controlFlowStorageNodes.get(bid)
    
    +        if (cfsn) return
    
             if (node.body.length === 0) return
    
     
    
             walk(node, {
    
    @@ -145,13 +156,14 @@ export default class ControlFlow extends Transformer<ControlFlowOptions> {
                     )
    
                   )
    
                     continue
    
    -              context.controlFlowStorageNodes[bid] = {
    
    +
    
    +              cfsn = {
    
                     identifier: decl.id.name,
    
                     aliases: [decl.id.name],
    
                     functions: [],
    
                     literals: [],
    
                   }
    
    -              const cfsn = context.controlFlowStorageNodes[bid]
    
    +              context.controlFlowStorageNodes.set(bid, cfsn)
    
                   for (const prop of decl.init.properties as PropertyLiteral[]) {
    
                     let kn: Identifier | Literal = prop.key
    
                     let key = (
    
    @@ -228,12 +240,10 @@ export default class ControlFlow extends Transformer<ControlFlowOptions> {
     
    
       findStorageNodeAliases = (context: Context, ast: Node) => {
    
         walk(ast, {
    
    -      BlockStatement(node) {
    
    -        let bid = getBlockId(node)
    
    -
    
    -        if (!context.controlFlowStorageNodes[bid]) return
    
    +      BlockStatement: (node) => {
    
             if (node.body.length === 0) return
    
    -        const cfsn = context.controlFlowStorageNodes[bid]
    
    +        const cfsn = this.getStorageNode(context, node)
    
    +        if (!cfsn) return
    
     
    
             walk(node, {
    
               VariableDeclaration(vd) {
    
    @@ -268,11 +278,9 @@ export default class ControlFlow extends Transformer<ControlFlowOptions> {
       replacer = (context: Context, ast: Node) => {
    
         const { translateCallExp } = this
    
         walk(ast, {
    
    -      BlockStatement(node) {
    
    -        const bid = getBlockId(node)
    
    -        if (!context.controlFlowStorageNodes[bid]) return
    
    -        const cfsn = context.controlFlowStorageNodes[bid]
    
    -
    
    +      BlockStatement: (node) => {
    
    +        const cfsn = this.getStorageNode(context, node)
    
    +        if (!cfsn) return
    
             walk(node, {
    
               MemberExpression(mx) {
    
                 if (!Guard.isIdentifier(mx.object)) return
    
    
  • src/transformers/jsconfuser/controlflow.ts+30 27 modified
    @@ -48,9 +48,7 @@ function inverseOperator(operator: BinaryOperator) {
           throw new Error("Invalid operator to inverse '" + operator + "'")
       }
     }
    -interface VarStack {
    -  [x: string]: number
    -}
    +type VarStack = Map<string, number>
     function generateCode(ast: Node): string {
       return escodegen.generate(ast as any, {
         sourceMapWithCode: true,
    @@ -69,41 +67,46 @@ function evaluateAssignmentExpr(
       operator: AssignmentOperator,
       value: number
     ) {
    +  if (operator === '=') return stack.set(vk, value)
    +
    +  const stackVal = stack.get(vk)
    +  if (typeof stackVal !== 'number')
    +    throw new Error(
    +      'Unexpected non-numeric value in jsconfuser controlflow stack'
    +    )
    +
       switch (operator) {
    -    case '=':
    -      return (stack[vk] = value)
         case '+=':
    -      return (stack[vk] += value)
    +      return stack.set(vk, stackVal + value)
         case '-=':
    -      return (stack[vk] -= value)
    +      return stack.set(vk, stackVal - value)
         case '*=':
    -      return (stack[vk] *= value)
    +      return stack.set(vk, stackVal * value)
         case '/=':
    -      return (stack[vk] /= value)
    +      return stack.set(vk, stackVal / value)
         case '%=':
    -      return (stack[vk] %= value)
    +      return stack.set(vk, stackVal % value)
         case '<<=':
    -      return (stack[vk] <<= value)
    +      return stack.set(vk, stackVal << value)
         case '>>=':
    -      return (stack[vk] >>= value)
    +      return stack.set(vk, stackVal >> value)
         case '>>>=':
    -      return (stack[vk] >>>= value)
    +      return stack.set(vk, stackVal >>> value)
         case '&=':
    -      return (stack[vk] &= value)
    +      return stack.set(vk, stackVal & value)
         case '^=':
    -      return (stack[vk] ^= value)
    +      return stack.set(vk, stackVal ^ value)
         case '|=':
    -      return (stack[vk] |= value)
    +      return stack.set(vk, stackVal | value)
         default:
           throw new Error(
             'Invalid assignment expression operator "' + operator + '"'
           )
       }
     }
     function updateIdentifiers(stack: VarStack, obj: any) {
    -  for (const vk in stack) {
    -    let value = stack[vk],
    -      node = createLiteral(value)
    +  for (const [vk, value] of stack) {
    +    const node = createLiteral(value)
     
         walk(obj, {
           Identifier(id) {
    @@ -149,7 +152,7 @@ function evaluateSequenceAssignments(
           continue
         }
         if (!Guard.isIdentifier(expr.left)) continue
    -    if (!(expr.left.name in stack)) continue
    +    if (!stack.has(expr.left.name)) continue
         const vk = expr.left.name,
           operator = expr.operator
     
    @@ -179,7 +182,7 @@ function evaluateSequenceAssignments(
     
         let effect = literalOrUnaryExpressionToNumber(ie)
         evaluateAssignmentExpr(stack, vk, operator, effect)
    -    log(`stack[${vk}] = ${stack[vk]}`)
    +    log(`stack[${vk}] = ${stack.get(vk)}`)
         log('='.repeat(32))
         ;(expr as any).type = 'EmptyStatement'
       }
    @@ -208,24 +211,24 @@ export default class JSCControlFlow extends Transformer<JSCControlFlowOptions> {
             )
               continue
     
    -        const stack: VarStack = {}
    +        const stack: VarStack = new Map()
     
             let bx = w.test,
               additive = false
             while (Guard.isBinaryExpression(bx)) {
               additive = bx.operator === '+'
               if (Guard.isIdentifier(bx.left)) {
    -            stack[bx.left.name] = bx.left.start
    +            stack.set(bx.left.name, bx.left.start)
               }
               if (Guard.isIdentifier(bx.right)) {
    -            stack[bx.right.name] = bx.right.start
    +            stack.set(bx.right.name, bx.right.start)
               }
               bx = bx.left as BinaryExpression
             }
             if (!additive) continue
    -        for (const vk in stack) {
    +        for (const [vk, value] of stack) {
               let vref = scope.references.find(
    -            (i) => i.identifier.range![0] === stack[vk]
    +            (i) => i.identifier.range![0] === value
               )
               if (!vref) continue
               if (
    @@ -246,7 +249,7 @@ export default class JSCControlFlow extends Transformer<JSCControlFlowOptions> {
                   i.range![0] !== def.node.range![0] &&
                   i.range![1] !== def.node.range![1]
               )
    -          stack[vk] = literalOrUnaryExpressionToNumber(def.node.init)
    +          stack.set(vk, literalOrUnaryExpressionToNumber(def.node.init))
             }
             const endState = literalOrUnaryExpressionToNumber(w.test.right)
             context.log(stack, endState)
    
  • src/transformers/literalmap.ts+6 7 modified
    @@ -24,7 +24,7 @@ export default class LiteralMap extends Transformer<LiteralMapOptions> {
       demap(context: Context) {
    
         walk(context.ast, {
    
           BlockStatement(node) {
    
    -        const map: { [x: string]: { [x: string]: any } } = {}
    
    +        const map = new Map<string, Map<string, any>>()
    
     
    
             walk(node, {
    
               VariableDeclaration(vd) {
    
    @@ -48,16 +48,16 @@ export default class LiteralMap extends Transformer<LiteralMapOptions> {
                     continue
    
     
    
                   const name = decl.id.name
    
    -              map[name] = map[name] || {}
    
    -
    
    +              const localMap = map.get(name) || new Map<string, any>()
    
                   for (const _prop of decl.init.properties) {
    
                     const prop = _prop as Property
    
                     let key =
    
                       prop.key.type === 'Identifier'
    
                         ? prop.key.name
    
                         : ((prop.key as Literal).value as string)
    
    -                map[name][key] = (prop.value as Literal).value as string
    
    +                localMap.set(key, (prop.value as Literal).value as string)
    
                   }
    
    +              if (!map.has(name)) map.set(name, localMap)
    
     
    
                   if (context.removeGarbage) {
    
                     rm.push(`${decl.start}!${decl.end}`)
    
    @@ -77,13 +77,13 @@ export default class LiteralMap extends Transformer<LiteralMapOptions> {
                     !Guard.isIdentifier(exp.property))
    
                 )
    
                   return
    
    -            let mapObj = map[exp.object.name]
    
    +            let mapObj = map.get(exp.object.name)
    
                 if (!mapObj) return
    
     
    
                 let key = Guard.isIdentifier(exp.property)
    
                   ? exp.property.name
    
                   : ((exp.property as Literal).value as string)
    
    -            let val = mapObj[key]
    
    +            let val = mapObj.get(key)
    
                 if (typeof val === 'undefined') return // ! check causes !0 == true.
    
                 sp<Literal>(exp, {
    
                   type: 'Literal',
    
    @@ -104,7 +104,6 @@ export default class LiteralMap extends Transformer<LiteralMapOptions> {
           if (!scope) return
    
     
    
           for (const v of scope.variables) {
    
    -        if (/*func.start === 3547 && */ v.name === 'q') debugger
    
             if (v.name === 'arguments') continue
    
             if (v.identifiers.length !== 1) continue // ?
    
             if (v.defs.length !== 1) continue // ?
    
    

Vulnerability mechanics

Generated on May 9, 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.