msfrpc
Version:
Metasploit Framework RPC api and command line interface
203 lines (188 loc) • 6.06 kB
JavaScript
/**
* Allow self-signed ssl sertificates.
*/
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
/**
* Load dependencies.
*/
const URL = require('url');
const decamelize = require('decamelize');
const request = require('request');
const MsgPack5 = require('msgpack5');
const Promise = require('bluebird');
/**
* Instance MsgPack5.
*/
const msgpack = MsgPack5();
/**
* MsfRpc Class.
* @constructor constructor(uri, options)
* @constructor constructor(options)
*/
module.exports = class MsfRpc {
/**
* MsfRpc constructor.
* @param {string|object} uri - Url string with auth credentials, hostname and port.
* @example 'https://msfUser:myPassword@msfrpchost:55553'
* @param {object} options - MsfRpc options.
* @property {string} options.user - MsfRpc username.
* @property {string} options.pass - MsfRpc password.
* @property {string} options.host - MsfRpc hostname.
* @property {number} options.port - MsfRpc port.
* @property {boolean} options.ssl - MsfRpc ssl - true: use https protocol.
* @property {string} options.token - MsfRpc token.
* @return {MsfRpc}
*/
constructor(uri, options) {
options = options || {};
// If uri is an object, it must be options.
if(typeof uri === 'object') {
options = uri;
} else {
// Parse uri.
const url = URL.parse(uri);
const { hostname, port, protocol } = url;
const [ user, pass ] = (url.auth || '').split(':');
// Put uri parameters in options.
options.user = user;
options.pass = pass;
options.host = hostname;
options.port = port;
options.ssl = protocol === 'https:';
}
// Save options to this.
this.user = options.user;
this.pass = options.pass;
this.host = options.host;
this.port = options.port;
this.ssl = options.ssl;
this.token = options.token;
// Load api methodGroups proxys.
const methodGroups = [ "auth", "base", "console", "core", "db", "job", "module", "plugin", "session" ];
methodGroups.forEach((methodGroup) => {
this[methodGroup] = new Proxy({}, {
get: (target, methodName) => {
methodName = decamelize(methodName, '_');
return (...args) => {
return this._exec(methodGroup + '.' + methodName, ...args);
}
}
});
});
}
/**
* Connects to the remote MsfRpc server.
* This is not required if a token was specified in the constructor options.
* @return {Promise}
*/
connect() {
return this._call("auth.login", this.user, this.pass).then((res) => {
// Save token to this.
this.token = res.token;
});
}
/**
* Call an MsfRpc method.
* Take a look at the following links for more information about abailable methods:
* http://www.nothink.org/metasploit/documentation/RemoteAPI_4.1.pdf
* https://help.rapid7.com/metasploit/Content/api/rpc/overview.html
* https://help.rapid7.com/metasploit/Content/api/rpc/standard-reference.html
* https://rapid7.github.io/metasploit-framework/api/Msf/RPC.html
* @param {string} method - MsfRpc method.
* @param {...[any]} args - MsfRpc method's arguments.
* @return {Promise<object>} - MsfRpc call response.
*/
_call(method, ...args) {
// Encode method and arguments as msgpack buffer.
const buffer = msgpack.encode([method, ...args]);
return new Promise((resolve) => {
// Send post request to the MsfRpc server.
request({
method: 'POST',
uri: `${this.ssl ? 'https' : 'http'}://${this.host}:${this.port}/api/1.0`,
body: buffer,
headers: {
'content-type': 'binary/message-pack'
},
// This translates to encode body as binary data.
encoding: null
}, (error, response, body) => {
if(error) {
throw error;
} else {
// Parse body.
body = this._parseBody(body)
// If server responded with an exception, build and throw error.
if(body.error) {
const errorLines = [];
errorLines.push(`${body.error_message}`);
errorLines.push(` Backtrace:`);
body.error_backtrace.forEach((trace) => {
errorLines.push(` ${trace}`);
});
errorLines.push('');
throw new Error(errorLines.join('\n'));
} else {
// Else, resolve with parsed body.
resolve(body);
}
}
});
});
}
/**
* Call a MsfRpc method and include the access token.
* @param {string} method - MsfRpc method.
* @param {...[any]} args - MsfRpc method's arguments.
* @return {Promise<object>} - MsfRpc call response.
*/
_exec(method, ...args) {
return this._call(method, this.token, ...args);
}
/**
* Decodes and debufferizes response body.
* @param {object} body - Response body.
* @return {object}
*/
_parseBody(body) {
const decoded = msgpack.decode(body);
const debufferized = this._debufferize(decoded);
return debufferized;
}
/**
* Debufferizes an object.
* @description
* When msgpack5 decodes a buffer, it returns an object.
* All object values are buffers.
* So this method recursively converts all buffes to strings.
* @param {any} value - Value to debufferize.
* @return {any}
*/
_debufferize(value) {
if(value instanceof Buffer) {
return value.toString();
} else if(value instanceof Array) {
const arr = [];
value.forEach((val) => {
arr.push(this._debufferize(val));
});
return arr;
} else if(value instanceof Array) {
const arr = [];
value.forEach((val) => {
arr.push(this._debufferize(val));
});
return arr;
} else if(value instanceof Object) {
const obj = {};
Object.keys(value).forEach((key) => {
if(value.hasOwnProperty(key)) {
obj[key] = this._debufferize(value[key]);
}
});
return obj;
} else {
return value;
}
}
}