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
-
Scan YApi Web Service - > register account - > create interface in personal project
-
Add a custom script in the advanced Mock of the interface and execute the command whoamI && PS - EF on the server
image-20210718190004792 -
Access the mock interface, trigger the script, and obtain the execution result of the command whoamI && PS - EF
image-20210718190154336 -
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.
-
Install safeify in the project
npm i safeify --save
-
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
}; -
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
-
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.
-
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.
-
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.
-
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.
-
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