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.



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.



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.



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



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



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