YAPI security vulnerabilities are fully solved in one article

YApi is an efficient, easy-to-use and powerful API management platform, which aims to provide more elegant interface management services for developers, products and testers. It can help developers easily create, publish and maintain APIs. YApi also provides users with excellent interactive experience. Developers can realize interface management only by using the interface data writing tool provided by the platform and simple click operation.

At present, Yapi has 523 followers in GitHub, 21.7k stars and 3.7k forks. It is widely used by enterprises, including Alibaba, Tencent, Baidu, qunar, etc.

Recently, many users reported that YApi platform has a high-risk vulnerability. Attackers can use this vulnerability to execute arbitrary code on the target server, which can cause the server to be controlled by attackers. According to the official Github issue, many users reported that after using the platform, the server was invaded by hackers and implanted into Trojans

Intrusion process

  1. Scan YApi Web Service - > register account - > create interface in personal project

  2. Add a custom script in the advanced Mock of the interface and execute the command whoamI && PS - EF on the server

    image-20210718190004792
  3. Access the mock interface, trigger the script, and obtain the execution result of the command whoamI && PS - EF

    image-20210718190154336
  4. This is just a demonstration. Of course, hackers may execute other dangerous commands.

Intrusion principle

Since it is a security problem caused by Mock script, you can directly view YApi's relevant source code on Mock script processing:

//Handling mockJs scripts
exports.handleMockScript = function (script, context) {
  let sandbox = {
    header: context.ctx.header,
    query: context.ctx.query,
    body: context.ctx.request.body,
    mockJson: context.mockJson,
    params: Object.assign({}, context.ctx.query, context.ctx.request.body),
    resHeader: context.resHeader,
    httpCode: context.httpCode,
    delay: context.httpCode,
    Random: Mock.Random
  };
  sandbox.cookie = {};

  context.ctx.header.cookie &&
    context.ctx.header.cookie.split(';').forEach(function (Cookie) {
      var parts = Cookie.split('=');
      sandbox.cookie[parts[0].trim()] = (parts[1] || '').trim();
    });
  //Execute script
  sandbox = yapi.commons.sandbox(sandbox, script);
  sandbox.delay = isNaN(sandbox.delay) ? 0 : +sandbox.delay;

  context.mockJson = sandbox.mockJson;
  context.resHeader = sandbox.resHeader;
  context.httpCode = sandbox.httpCode;
  context.delay = sandbox.delay;
};

/**
 * Sandbox execution js code
 * @sandbox Object context
 * @script String script
 * @return sandbox
 *
 * @example let a = sandbox({a: 1}, 'a=2')
 * a = {a: 2}
 */
exports.sandbox = (sandbox, script) => {
  try {
    const vm = require('vm');
    sandbox = sandbox || {}; 
    script = new vm.Script(script); 
    const context = new vm.createContext(sandbox); 
    script.runInContext(context, { 
      timeout: 3000 
    });       
    return sandbox
  } catch (err) {
    throw err
  }
};

The main thing is to encapsulate the parameters, then call the sandbox method sandbox to execute the JS code and execute the vm library. When executing JS code, write dangerous shell commands to execute, so as to obtain machine operation permissions.

Problem root

VM is node JS is a built-in module provided by default. The VM module provides a series of API s for compiling and running code in the V8 virtual machine environment. By calling script Runincontext seems to isolate the code execution environment, but it is actually easy to "escape". Node.js official document also mentioned that "don't use VM as a safe sandbox to execute any untrusted code".

We analyze the following scripts that generate security vulnerabilities:

const sandbox = this
//Gets the constructor of the current object
const ObjectConstructor = this.constructor
//Gets the construction method of the method
const FunctionConstructor = ObjectConstructor.constructor
//Dynamically define the method, return the process object, and process is available globally
 const myfun = FunctionConstructor('return process')
 const process = myfun()
 //Execute shell commands
 mockJson = process.mainModule.require("child_process").execSync("whoami && ps -ef").toString()

To sum up: the script execution sandbox used by YAPI has a security vulnerability and has been exploited by hackers.

The places where YAPI supports script execution include Mock script, interface preprocessing script and assertion script of test set.

Therefore, the current scheme: it's best not to deploy on the public network and turn off the registration function, which are all solutions to solve the symptoms rather than the root cause.

Whether the vm sandbox used by YAPI can be replaced with a safe sandbox becomes the key to solve the problem.

safeify: a safer sandbox

There is no safest, only safer. Compared with VM, we introduce a safer safe sandbox, which has the following characteristics:

  • A special process pool is established for the dynamic code to be executed, which is separated from the host application and executed in different processes
  • The maximum number of processes that support configuring the sandbox process pool
  • It supports limiting the maximum execution time of synchronous code and asynchronous code
  • Supports limiting the overall CPU resource quota (decimal) of the sandbox process pool
  • The maximum memory limit (in m) that supports limiting the overall sandbox process pool

YAPI code modification

The safeify library is introduced to implement the sandbox executed by the script to replace the previous sandbox implementation.

  1. Install safeify in the project

    npm i safeify --save
  2. Create a sandbox.exe in the server/utils / directory JS file, as follows:

    const Safeify = require('safeify').default;

    module.exports = async function sandboxFn(context, script) {
        //Create a safe instance
        const safeVm = new Safeify({
            timeout: 3000,
            asyncTimeout: 60000,
            //quantity: 4, / / number of sandbox processes, the same as the number of CPU cores by default
            //memoryQuota: 500, / / the maximum memory that can be used by the sandbox (unit: 500m), which is 500m by default
            // cpuQuota: 0.5,
            //true is not limited by CPU to solve the problem of Docker startup
            unrestricted: true,
            unsafe: {
                modules: {
                  //Introduce assert assertion Library
                    assert: 'assert'
                }
            }
        });

        safeVm.preset('const assert = require("assert");');
        
        script += "; return this;";
        //Execute dynamic code
        const result = await safeVm.run(script, context);

        //Release resources
        safeVm.destroy();
        return result
    };

  3. Replace server / utils / Commons Yapi. JS commons. sandbox

    const sandboxFn = require('./sandbox')

    Introduce the sandbox file and replace Yapi. Com with await sandboxFn commons. sandbox

    Note: sandboxFn is an asynchronous method and await and exports need to be added async should be added before the handlemockscript method is implemented

    At this point, the existing security problem of YAPI can be repaired.

Problem description

  1. Error in mockjason script call: Error: Cannot read property 'delay' of undefined

    In sandbox JS, add script += "; return this;";, The return value of the variable result can only be obtained if there is a return statement in the script.

  2. Error when starting the service in the image: error: erofs: read only file system, MKDIR '/ sys / FS / CGroup / CPU / safeify'

    In order to limit CPU resource usage, safeify needs to write files under this path, but in the image, this path is read-only. Set unrestricted: true in the new Safeify object.

  3. Assertion function not available, assert equal is not a function

    safeify introduces the library in the execution script in a different way than before. It needs to be implemented through unsafe. For details, see sandbox JS implementation.

  4. Invalid log object in test collection

    In the test set, when the assertion fails, log is used for debugging. However, after accessing safeify, there is a problem with the log function. You can pay attention to the solutions for subsequent updates.

  5. assert strict mode, Cannot convert object to primitive value

    In the previous assert Equal ({}, '') can be passed. After safeify is introduced, an error will be reported, but the problem is not big. It is better to write according to the strict mode.

More questions, welcome to pay attention: Wheezing

Keywords: Javascript YAPI

Added by misterph on Mon, 27 Dec 2021 19:41:11 +0200