www.horizon3.ai Open in urlscan Pro
104.197.16.226  Public Scan

Submitted URL: https://mdr.esentire.com/e/651833/-and-indicators-of-compromise-/2prwxj/954496554?h=XS-yr_q5Bxn_GjrttiKgRB9NZSvjgjUGlKhQA...
Effective URL: https://www.horizon3.ai/moveit-transfer-cve-2023-34362-deep-dive-and-indicators-of-compromise/
Submission: On June 14 via api from US — Scanned from DE

Form analysis 0 forms found in the DOM

Text Content

 * NodeZero™
   * What is NodeZero?
   * Internal Pentesting
   * External Pentesting
   * Documentation
   * FAQ
   * Credential Attacks
   * Ransomware Impact
 * Learn
   * Year in Review 2022
   * By Industry
     * Healthcare
     * Public Sector
   * Attack Content
     * Log4Shell
   * Security Controls
     * Compliance In Security
     * Effective Security
     * For Splunk Logging
     * Purple Team Culture
     * Vulnerable ≠ Exploitable
   * Customer Stories
   * Blogs
     * Customer Success
     * Hack the Box
     * Red Team
       * Disclosures
     * Videos
   * Podcasts
   * Whitepapers
 * About
   * Our Vision
   * The Team
   * Join Our Team
   * Contact Us
   * In the News
   * Awards
 * Events
 * Partners
   * Partners
   * MSSPs and MSPs
   * Partner Portal
 * Log In
 * Try NodeZero
   * Demo
   * Free Trial

Select Page
 * NodeZero™
   * What is NodeZero?
   * Internal Pentesting
   * External Pentesting
   * Documentation
   * FAQ
   * Credential Attacks
   * Ransomware Impact
 * Learn
   * Year in Review 2022
   * By Industry
     * Healthcare
     * Public Sector
   * Attack Content
     * Log4Shell
   * Security Controls
     * Compliance In Security
     * Effective Security
     * For Splunk Logging
     * Purple Team Culture
     * Vulnerable ≠ Exploitable
   * Customer Stories
   * Blogs
     * Customer Success
     * Hack the Box
     * Red Team
       * Disclosures
     * Videos
   * Podcasts
   * Whitepapers
 * About
   * Our Vision
   * The Team
   * Join Our Team
   * Contact Us
   * In the News
   * Awards
 * Events
 * Partners
   * Partners
   * MSSPs and MSPs
   * Partner Portal
 * Log In
 * Try NodeZero
   * Demo
   * Free Trial


MOVEIT TRANSFER CVE-2023-34362 DEEP DIVE AND INDICATORS OF COMPROMISE

by Zach Hanley | Jun 9, 2023 | Blog, Red Team

On May 31, 2023, Progress released a security advisory for their MOVEit Transfer
application which detailed a SQL injection leading to remote code execution and
urged customers to update to the latest version. The vulnerability,
CVE-2023-34362, at the time of release was believed to have been exploited
in-the-wild as a 0-day dating back at least 30 days.

Soon after publication, a flurry of threat intelligence by various companies was
released which indicated that this vulnerability was exploited further back than
initially thought – GreyNoise seeing activity 90 days prior and Kroll reporting
similar activity as far back as 2021. The attacks have been attributed to the
cl0p ransomware gang, which is attributed to several other recent 0-day
ransomware campaigns such as PaperCut, GoAnywhere MFT, SolarWinds Serv-U, and
Accellion FTA.



Figure 1. cl0p 0-day activities


TAKING A PEEK – PATCH DIFF’ING

Taking a look at the differences between the vulnerable and patched versions we
find three interesting areas.

The first difference found in the function UserGetUsersWithEmailAddress()
appears to update a SQL query from a concatenated string of several arguments
passed in, to a safer looking SQL builder utility. This helper function is
reachable from many code paths, interestingly from several unauthenticated paths
via guestaccess.aspx.



Figure 2. UserGetUserWithEmailAddress() function differences

The second difference found in the function SetAllSessionVarsFromHeaders()
removes the entire function and removes the only caller of that function from
the machine2.aspx handler, SILMachine2, when the received Transaction is
session_setvars. Unfortunately machine2.aspx requests will only be processed if
coming from localhost.



Figure 3. SetAllSessionVarsFromHeaders() function removed

The last difference found in GetFileUploadInfo() adds a single statement which
changes the way the uploadState is set by first checking if the State is null
before using a new decryption helper DecryptBytesForDatabase.



Figure 4. GetFileUploadInfo() function differences


A PATH TO EXPLOITATION

Foreword: looking at public threat intelligence about the series of endpoints
being hit and the types of indicators of compromise, we aren’t entirely sure the
path we’ve found is the exact same abuse of the patched functionality mixed with
abuse of intended functionality. There are likely several paths to exploitation
– there are many like it, but this one is ours.

Given that the description of the vulnerability was a SQL injection, the path to
the apparent patch in UserGetUsersWithEmailAddress() was pursued first. While
paths were discovered to reach this function from an unauthenticated
point-of-view, we were unable to discover a way to have the controllable
arguments passed to it without being ‘cleaned’ by XHTMLClean(), which converts
the typical unsafe SQL characters to their HTML encoded counterparts.

THE PATH TO UNCLEAN INPUT

We shifted our focus to the other removed function
SetAllSessionVarsFromHeaders(). We found that this function had the restriction
that only localhost is allowed to route. Threat actors were observed hitting the
/moveitisapi/moveitisapi.dll?action=m2 so we were hopeful that we could find a
path from moveitisapi.dll to SetAllSessionVarsFromHeaders(). moveitisapi.dll is
a compiled C program of which we can analyze with Ghidra. Opening it up, we find
that the function at 0x180080920, dubbed action_m2, is responsible for parsing
requests that contain the action=m2 request parameter. The action_m2 function
takes requests, and forwards those requests on to the machine2.aspx endpoint
only if the passed in header X-siLock-Transaction is equal to
folder_add_by_path.



Figure 5. action_m2() function in MOVEitISAPI.dll

Unfortunately, thats not ~exactly~ how it works. The function that extracts the
X-siLock-Transaction header to compare its value to folder_add_by_path has a
bug. It will incorrectly extract headers that end in X-siLock-Transaction, so an
attacker can trick the function to passing the request onto the machine2.aspx by
providing a header such as xX-siLock-Transaction=folder_add_by_path and
additionally providing the correctly formatted header with our own arbitrary
transaction to be executed by the machine2.aspx endpoint.



Figure 6. Transaction bypass via crafted headers

With entry into machine2.aspx via this backend relay of our request, we can now
reach SetAllSessionVarsFromHeaders() when we pass in a transaction of
session_setvars. Our Cookie header as well as all other X-siLock- headers will
be passed in with our request. Analyzing the functionality of this removed
function further, it will parse all headers, and if the header starts with
X-siLock-SessVar it will set the corresponding variable of the session in use to
the arbitrary value provided. For example, X-siLock-SessVar0: MyUsername:
sysadmin will set the username of session to the builtin sysadmin. This
capability unfortunately does not enable you to just assume the sysadmin role
and use the application, but it does provide access to set many variables loaded
in code paths which bypass being cleaned by the XHTMLClean() function from
earlier.

THE PATH TO SQL INJECTION

The path to the vulnerable UserGetUsersWithEmailAddress() function we took was
via an unauthenticated call to guestaccess.aspx when the passed Transaction is
secmsgpost. The full call chain of relevant calls is:

guestaccess.aspx -> SILGuestAccess -> SILGuestAccess.PerformAction() ->
MsgEngine.MsgPostForGuest() ->
UserEngine.UserGetSelfProvisionUserRecipsWithEmailAddress() ->
UserEngine.UserGetUsersWithEmailAddress()

While we will not analyze the call chain in depth and all of the variable
setting whack-a-mole that was needed to reach the vulnerable function, the crux
of what changed with our access to session variable manipulation is in the very
beginning of guestaccess.aspx’s handler in SILGuestAccess. The main function
calls this.m_pkginfo.LoadFromSession(), which sets variables from session
variables that we can now influence with session_setvars.

 



Figure 7. LoadFromSession() loads variables from the session

Along the call chain, the SelfProvisionedRecips value is extracted as a list of
comma separated email addresses and never cleaned before being passed to our
vulnerable function. Inspecting how the SQL query is built in our vulnerable
function, we see the InstID, EscapeLikeForSQL(EmailAddress), and finally
EmailAddress are formatted into the query statement. The final query statement
looks like:

SELECT Username, Permissions, LoginName, Email FROM users WHERE InstID=9389 AND
Deleted=0 AND (Email='<EmailAddress>' OR Email LIKE
(%EscapeLikeForSQL(<EmailAddress>)) or Email LIKE
(EscapeLikeForSQL(<EmailAddress>));

The part of the query AND Email='<EmailAddress>' has our uncleaned argument of
SelfProvisionedRecips inserted into the query. The only caveat to this
injection, is that just prior to the call the SelfProvisionedRecips variable is
split on comma’s (,). Our injected SQL statement should avoid having commas to
continue proper execution. We can work around needing commas by reusing the SQL
injection several times to do sequential statements such as INSERT then UPDATE.

All of this information combined, an example request in Python that will set the
right session variables via a request to the action=m2 endpoint and then a
request to the guestaccess.aspx endpoint to inject would look like the
following:



Figure 8. Python script excerpt to perform SQL injection

THE PATH TO ADMINISTRATOR SESSION

With the ability to read and write any data within the MOVEit database, our next
goal is to achieve elevated permissions from an unauthenticated session. Threat
intelligence showed logs that the attackers would hit the /api/v1/auth/token
endpoint, which is handled by MOVEit.DMZ.WebAPI. Authentication is handled here,
and based on the session_grant parameter passed in, different authentication
paths are taken. Several of these paths were explored, some more than others,
but the path we decided to go after is when session_grant=external_token, which
is handled by the function GrantTokenFromExtenralToken(). This type of
authentication flow is used when the MOVEit Transfer application has been
configured to use federated logins, specifically from Microsoft Outlook acting
as the identity provider.

Assuming the application has been configured to use a federated login flow,
users send a payload to the /api/v1/auth/token endpoint with a payload that
contains a RS256 JWT. The decoded JWT should look like the following:

Figure 9. Example RS256 JWT

The important information here is that the MOVEit Transfer application will
reach out the URL in the amurl field to retrieve the certificate that matches
the given x5t signature to extract and validate that the JWT was in fact signed
by the identity provider. Because we control the content of the JWT, we can
point it to our own endpoint that hosts our own matching certificate that will
pass validation.

We ultimately use the SQL injection from the previous paths to configure the
database to think the application is configured this way, to trust our identity
provider URL, and inject an external token for the builtin sysadmin user. We
also use the SQL injection to pass several checks along the way to allow the
sysadmin user to be able to login from any IP address.

Combining it all together we now obtain an access token for the sysadmin user
and use it to list files they have access to.



Figure 10. Chaining issues to obtain sysadmin access token

THE PATH TO REMOTE CODE EXECUTION

The last step of this exploit chain is to abuse the sysadmin access token to
achieve remote code execution. Threat actors were observed hitting the
/api/v1/folders, /api/v1/folders/<folder_id>/files?uploadType=resumable, and
/api/v1/folders/<folder_id>/files?uploadType=resumable&fileId=<file_id>
endpoints. Pairing that knowledge with the last difference observed in the patch
related to file uploads, we begin looking at the file upload handlers in within
MOVEit.DMZ.WebApi.

The only path to the function that was patched, GetFileUploadInfo(), is when a
file upload is resumed that was previous in progress – which matches the call to
/api/v1/folders/<folder_id>/files?uploadType=resumable&fileId=<file_id>. The
specific variable they now attempt to protect is this._uploadState. Examining
where that variable is referenced in the .NET DLL, we see that the function
DeserializeFileUploadStream() uses it to create a MemoryStream object and then
immediately uses it in a call to BinaryFormatter().Deserialize(). This is a
classic .NET deserialization vulnerability. Normally, the uploadState variable
would not be under attacker influence, but because we have a SQL injection, we
can influence the field from which that variable is set.



Figure 11. BinaryFormatter.Deserialize() on input we control

Looking at the state of the database from which the uploadState variable is set,
we find that the State value is NULL. We need this State value to contain our
base64 encoded serialized .NET payload.



Figure 12. Database tabe fileuploadinfo schema

Using a tool like ysoserial.net, we generate a payload for the formatter in use.

ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c "cmd.exe /C echo
DIRTY MIKE AND THE BOYS WERE HERE > C:\Windows\Temp\message.txt" -o base64



Figure 13. ysoserial payload generation

The only hurdle to overcome is, that when reading the State field from the
database, it expects the data to be encrypted with an organization specific
encryption key. We spent some time looking at how we could extract and
re-implement the encryption, but thankfully theres a simple workaround. When
initiating the file upload, you can optionally provide a Comment. This comment
is encrypted with that organization specific key. We can provide our base64
ysoserial payload as the comment when initiating the upload and have it do the
heavy lifting for us.

To prepare the application to reach this bit of code requires several
interactions:

 1. Retrieve the user’s FolderID by requesting /api/v1/folders
 2. Retrieve a FileID by starting a file upload by requesting
    /api/v1/folders/<folder_id>/files?uploadType=resumable and providing our
    payload as the Comment
 3. Use SQL injection to copy the Comment to the State field
 4. Resume the file upload triggering loading of State into uploadState and
    calling BinaryFormatter.Deserialize(uploadState)

The full exploit chain in action to write a file to C:\Windows\Temp\message.txt.



Figure 14. Executing the proof-of-concept exploit



Figure 15. Remote Code Execution

Our proof of concept can be found on our GitHub.

POST-EXPLOITATION BONUS

If you find yourself on a MOVEit Transfer server that was deployed via the Azure
Marketplace (and in some other cases), in C:\MOVEitDMZ_Install.INI you will find
cleartext credentials for the provisioned sysadmin account, database
credentials, and the service credential. All great targets for lateral movement.



Figure 16. MOVEitDMZ_Install.INI

This file is used for unattended installs, and users are given the optional to
preserve it after normal installations as well.

> MOVEitDMZ_Install.INI – The parameter input file for the installation. You can
> create an INI file by performing a standard MOVEit DMZ installation and NOT
> deleting the file at the end. Once you have the INI file, you can modify it in
> a text editor to customize the input for use as an unattended install.


INDICATORS OF COMPROMISE

Our exploit path may not be similar to paths taken by recent threat actors, but
there are several places to look for indicators.

The database tables userexternaltokens, trustedexternaltokenproviders, and
hostpermits all had entries inserted to achieve the sysadmin access token. The
fileuploadinfo table was altered to obtain RCE. One should inspect these tables
to look for any anomalous entries.

Log entries for endpoint traffic can be found in the following areas:

 * <InstallDir>/Logs/DMZ_WebApi.log when requests are made to /api/v1/ endpoints
 * <InstallDir>/Logs/DMZ_WEB.log when requests are made to /guestaccess.aspx and
   relayed messages to /machine2.aspx
 * <InstallDir>/Logs/DMZ_ISAPI.log when requests are made to
   /moveitisapi/moveitisapi.dll?action=m2




HOW CAN NODEZERO HELP YOU?

Let our experts walk you through a demonstration of NodeZero, so you can see how
to put it to work for your company.

Schedule a Demo

info@horizon3.ai • 650-445-4457

Contact Us

FOLLOW US



RECENT POSTS

 * MOVEit Transfer CVE-2023-34362 Deep Dive and Indicators of Compromise
   5 days ago
 * Clients Want Assessments to Prove Service Efficacy
   1 week ago

SUBSCRIBE TO COMMUNITY UPDATES


© 2022 All Rights Reserved.  |   Privacy Policy   |   Support Policy   |   Terms
and Subscriptions
We use cookies on our website to give you the most relevant experience by
remembering your preferences and repeat visits. By clicking “Accept All”, you
consent to the use of ALL the cookies. However, you may visit "Cookie Settings"
to provide a controlled consent.
Cookie SettingsAccept All
Manage consent
Close

PRIVACY OVERVIEW

This website uses cookies to improve your experience while you navigate through
the website. Out of these, the cookies that are categorized as necessary are
stored on your browser as they are essential for the working of basic
functionalities of the ...
Necessary
Necessary
Always Enabled
Necessary cookies are absolutely essential for the website to function properly.
These cookies ensure basic functionalities and security features of the website,
anonymously.

CookieDurationDescription__cfruidsessionCloudflare sets this cookie to identify
trusted web traffic._GRECAPTCHA5 months 27 daysThis cookie is set by the Google
recaptcha service to identify bots to protect the website against malicious spam
attacks.cookielawinfo-checkbox-advertisement1 yearSet by the GDPR Cookie Consent
plugin, this cookie is used to record the user consent for the cookies in the
"Advertisement" category .cookielawinfo-checkbox-analytics11 monthsThis cookie
is set by GDPR Cookie Consent plugin. The cookie is used to store the user
consent for the cookies in the category
"Analytics".cookielawinfo-checkbox-functional11 monthsThe cookie is set by GDPR
cookie consent to record the user consent for the cookies in the category
"Functional".cookielawinfo-checkbox-necessary11 monthsThis cookie is set by GDPR
Cookie Consent plugin. The cookies is used to store the user consent for the
cookies in the category "Necessary".cookielawinfo-checkbox-others11 monthsThis
cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the
user consent for the cookies in the category
"Other.cookielawinfo-checkbox-performance11 monthsThis cookie is set by GDPR
Cookie Consent plugin. The cookie is used to store the user consent for the
cookies in the category "Performance".CookieLawInfoConsent1 yearRecords the
default button state of the corresponding category & the status of CCPA. It
works only in coordination with the primary cookie.OptanonConsent1 yearOneTrust
sets this cookie to store details about the site's cookie category and check
whether visitors have given or withdrawn consent from the use of each
category.viewed_cookie_policy11 monthsThe cookie is set by the GDPR Cookie
Consent plugin and is used to store whether or not user has consented to the use
of cookies. It does not store any personal data.

Functional
Functional
Functional cookies help to perform certain functionalities like sharing the
content of the website on social media platforms, collect feedbacks, and other
third-party features.

CookieDurationDescriptionAnalyticsSyncHistory1 monthLinkedIn - Used to store
information about the time a sync took place with the lms_analytics
cookiebcookie2 yearsLinkedIn sets this cookie from LinkedIn share buttons and ad
tags to recognize browser ID.bscookie2 yearsLinkedIn sets this cookie to store
performed actions on the website.langsessionLinkedIn sets this cookie to
remember a user's language setting.li_gc2 yearsLInkedIn Used to store consent of
guests regarding the use of cookies for non-essential purposeslidc1 dayLinkedIn
sets the lidc cookie to facilitate data center selection.UserMatchHistory1
monthLinkedIn sets this cookie for LinkedIn Ads ID syncing.

Performance
Performance
Performance cookies are used to understand and analyze the key performance
indexes of the website which helps in delivering a better user experience for
the visitors.

CookieDurationDescription_calendly_session21 daysCalendly, a Meeting Schedulers,
sets this cookie to allow the meeting scheduler to function within the website
and to add events into the visitor’s calendar.

Analytics
Analytics
Analytical cookies are used to understand how visitors interact with the
website. These cookies help provide information on metrics the number of
visitors, bounce rate, traffic source, etc.

CookieDurationDescription_ga2 yearsThe _ga cookie, installed by Google
Analytics, calculates visitor, session and campaign data and also keeps track of
site usage for the site's analytics report. The cookie stores information
anonymously and assigns a randomly generated number to recognize unique
visitors._ga_V462VSRXXS2 yearsThis cookie is installed by Google
Analytics.6suuid2 years6sense is a B2B predictive intelligence engine for
marketing and sales.CONSENT2 yearsYouTube sets this cookie via embedded
youtube-videos and registers anonymous statistical data.pardotpastThe pardot
cookie is set while the visitor is logged in as a Pardot user. The cookie
indicates an active session and is not used for tracking.visitorId1
yearSalesforce

Advertisement
Advertisement
Advertisement cookies are used to provide visitors with relevant ads and
marketing campaigns. These cookies track visitors across websites and collect
information to provide customized ads.

CookieDurationDescriptionVISITOR_INFO1_LIVE5 months 27 daysA cookie set by
YouTube to measure bandwidth that determines whether the user gets the new or
old player interface.YSCsessionYSC cookie is set by Youtube and is used to track
the views of embedded videos on Youtube pages.yt.innertube::nextIdneverThis
cookie, set by YouTube, registers a unique ID to store data on what videos from
YouTube the user has seen.yt.innertube::requestsneverThis cookie, set by
YouTube, registers a unique ID to store data on what videos from YouTube the
user has seen.

Others
Others
Other uncategorized cookies are those that are being analyzed and have not been
classified into a category as yet.

CookieDurationDescriptionlpv97107330 minutesNo description

SAVE & ACCEPT
Powered by