Class: Msf::Handler::BindAwsSsm::AwsSsmSessionChannel

Inherits:
Object
  • Object
show all
Includes:
Rex::IO::StreamAbstraction
Defined in:
lib/msf/core/handler/bind_aws_ssm.rb

Overview

This module implements SSM R/W abstraction to mimic Rex::IO::Stream interfaces These methods are not fully synchronized/thread-safe as the req/resp chain is itself async and rely on a cursor to obtain responses when they are ready from the SSM API.

Instance Method Summary collapse

Constructor Details

#initialize(framework, ssmclient, peer_info) ⇒ AwsSsmSessionChannel

Returns a new instance of AwsSsmSessionChannel.

[View source]

30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/msf/core/handler/bind_aws_ssm.rb', line 30

def initialize(framework, ssmclient, peer_info)
  @framework = framework
  @peer_info = peer_info
  @ssmclient = ssmclient
  @cursor    = nil
  @cmd_doc   = peer_info['CommandDocument']

  initialize_abstraction

  self.lsock.extend(AwsSsmSessionChannelExt)
  # self.lsock.peerinfo  = peer_info['ComputerName'] + ':0'
  self.lsock.peerinfo  = peer_info['IpAddress'] + ':0'
  # Fudge the portspec since each client request is actually a new connection w/ a new source port, for now
  self.lsock.localinfo = Rex::Socket.source_address(@ssmclient.config.endpoint.to_s.sub('https://', '')) + ':0'

  monitor_shell_stdout
end

Instance Method Details

#closeObject

Closes the stream abstraction and kills the monitor thread.

[View source]

114
115
116
117
118
119
# File 'lib/msf/core/handler/bind_aws_ssm.rb', line 114

def close
  @monitor_thread.kill if (@monitor_thread)
  @monitor_thread = nil

  cleanup_abstraction
end

#monitor_shell_stdoutObject

Funnel data from the shell’s stdout to rsock

StreamAbstraction#monitor_rsock will deal with getting data from the client (user input). From there, it calls our write() below, funneling the data to the shell’s stdin on the other side.

[View source]

55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/msf/core/handler/bind_aws_ssm.rb', line 55

def monitor_shell_stdout
  @monitor_thread = @framework.threads.spawn('AwsSsmSessionHandlerMonitor', false) {
    begin
      while true
        Rex::ThreadSafe.sleep(0.5) while @cursor.nil?
        # Handle data from the API and write to the client
        buf = ssm_read
        break if buf.nil?
        rsock.put(buf)
      end
    rescue ::Exception => e
      ilog("AwsSsmSession monitor thread raised #{e.class}: #{e}")
    end
  }
end

#ssm_read(length = nil, opts = {}) ⇒ Object

Find command response on cursor and return to caller - doesn’t respect length arg, yet

[View source]

72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/msf/core/handler/bind_aws_ssm.rb', line 72

def ssm_read(length = nil, opts = {})
  maxw = opts[:timeout] ? opts[:timeout] : 30
  start = Time.now
  resp = @ssmclient.list_command_invocations(command_id: @cursor, instance_id: @peer_info['InstanceId'], details: true)
  while (resp.command_invocations.empty? or resp.command_invocations[0].status == 'InProgress') and
    (Time.now - start).to_i.abs < maxw do
    Rex::ThreadSafe.sleep(1)
    resp = @ssmclient.list_command_invocations(command_id: @cursor, instance_id: @peer_info['InstanceId'], details: true)
  end
  # SSM script invocation states are: InProgress, Success, TimedOut, Cancelled, Failed
  if resp.command_invocations[0].status == 'Success' or resp.command_invocations[0].status == 'Failed'
    # The big limitation: SSM command outputs are only 2500 chars max, otherwise you have to write to S3 and read from there
    output = resp.command_invocations.map {|c| c.command_plugins.map {|p| p.output}.join}.join
    @cursor = nil
    return output
  else
    @cursor = nil
    ilog("AwsSsmSession error #{resp}")
    raise resp
  end
  nil
end

#write(buf, opts = {}) ⇒ Object

[View source]

95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/msf/core/handler/bind_aws_ssm.rb', line 95

def write(buf, opts = {})
  resp = @ssmclient.send_command(
    document_name: @cmd_doc,
    instance_ids: [@peer_info['InstanceId']],
    parameters: { commands: [buf] }
  )
  if resp.command.error_count == 0
    @cursor = resp.command.command_id
    return buf.length
  else
    @cursor = nil
    ilog("AwsSsmSession error #{resp}")
    raise resp
  end
end