PATCH DIFFING: MOVEIT TRANSFER PRE-AUTHENTICATED SQL INJECTION VULNERABILITY (CVE-2023-34362) – PART2

In the previous blog post, we analysed the MOVEit Transfer patch that mitigates a SQL injection vulnerability (CVE-2023-34362) and figured out the entire call flow to reach the vulnerable method, SetAllSessionVarsFromHeaders(). It looks like this: /moveitisapi/moveitisapi.dll?action=m2 –> Machine2.aspx –> DoTransaction() –> SetAllSessionVarsFromHeaders().

What we did was just figured out the entry point and we still need to work on the following:

  1. Find and exploit the SQL injection vulnerability.
  2. Since there are lots of changes for UserGetUsersWithEmailAddress() function as well as related SQL queries in the patched version, check if that’s something can give us SQL injection.
  3. We’ve been accessing this endpoint with a low privileged user, User1. We need to find a way to access it without any authentication.

Tracing it back

Since we already know that the SetAllSessionVarsFromHeaders() method processes the X-siLock-SessVar header value without sanitising it, we need to figure out where this tainted header value is being used or read. For that, we can load all the .Net compiled DLLs from “C:\MOVEiTransfer\wwwroot\” into ILSpy, decompile them all, click Save this code into a directory, and then grep it for X-siLock-SessVar.

Unfortunately, these are the class files that we already know about. Our other bet would be to review the DMZ_WEB.LOG file for string MySessVar:Kapil since that’s the X-siLock-SessVar  header value we supplied in our last request to  /moveitisapi/moveitisapi.dll?action=m2.

Looking at this debug log, it’s clear that we were able to reach /moveitisapi/moveitisapi.dll?action=m2 –> Machine2.aspx –> DoTransaction() –> SetAllSessionVarsFromHeaders() and the transaction folder_add_by_path led to an error “Not allowed to create folder ‘/’ ” which we already know. Apart from that, we see that a SQL query was executed successfully and then the SILCurrentUser.fillInfo() method was called which says “Found Session values, not ignoring session vars, skipping database query.”

The statement “Not ignoring session vars” sounds interesting since this could be the one dealing with our session variable X-siLock-SessVar? We can search the decompiled DLLs for this string.

As you can see from the screenshots above, the MOVEit.DMS.ClassLib –> SILCurrentUser class –> FillInfo() method contains this string and at some point it checks to see if the ignoreSessionVars is True. If yes, it then ignores the global session variables and returns an error. Otherwise, it calls the FillInfoFromSession() method, perhaps for processing the session vars? Let’s check the function/method definition.

Another bad news for us. 😦 As you can see from the excerpt above, This FillInfoFromSession() method doesn’t process the user supplied key/pair for X-siLock-SessVar, so we get no control here. We need to find another way!

From our earlier observations, we know that the SetAllSessionVarsFromHeaders() method derives a key/pair from the X-siLock-SessVar header. To see where this key/value pair is used, I tried grepping the decompiled code for strings such as = key, sessionvar, sessionvars, session-setvar, sessvar, and x-siLock-SessVar etc., but got nothing useful (unless I missed or overlooked something).

Bringing back UserGetUsersWithEmailAddress()

Considering all our failed attempts, it may be a good idea to shift our focus back to UserGetUsersWithEmailAddress(). We see two calls to this method have been replaced in the MOVEit.DMZ.CLassLib.UserEngine class and SQL queries have been updated.

These are the some of the changes that we can see in the Diff. When we analyze this UserGetUsersWithEmailAddress() method in ILSpy, we see that it’s being called two other methods from the same class, UserGetSelfProvisionUserRecipsWithEmailAddress() and UserGetListOfUsersWithEmailAddress().

Also, we see references to these methods in other classes or files.

In the IUserEngine class, it’s just the interface and in the SILEngine class, it ‘s being called from the IsValidEmailCheck() method. Let’s check the MsgEngine class.

So, this is how the call graph looks like: MOVEit.DMZ.ClassLib.MsgEngine.MsgPostForGuest() –> MOVEit.DMZ.ClassLib.UserEngine.UserGetSelfProvisionUserRecipsWithEmailAddress() –> MOVEit.DMZ.ClassLib.UserEngine.UserGetUsersWithEmailAddress().

But where this MsgPostForGuest() is being called from? I don’t know why but the ILSy Analyze doesn’t show where this method is being called from. Let’s try to grep the decompiled code and see if we can figure it out.

Though it’s being referenced by four class files, we can ignore the first two. We already know that it’s defined in MsgEngine class and IMsgEngine seems to be just an interface. The last one, midmz.dll –> SILMachine class is something we may want to ignore at least for now since this MsgPostForGuest() method is being referenced by the ProcessXMLRequest() method which seems to be irrelevant. Let’s analyse the midmz.dll –> SILGuestAccess class in ILSpy.

Skimming through this SILGuestAccess class, we come across debug logging that says “guestaccess.aspx starting” which appears to deal with Guest user account.

So, this is how the full call graph looks like:

midmz.dll –> MOVEit.DMZ.WebApp.SILGuestAccess.GetHTML() –> MOVEit.DMZ.WebApp.SILGuestAccess.PerformAction() –> MOVEit.DMZ.ClassLib.MsgEngine.MsgPostForGuest() –> MOVEit.DMZ.ClassLib.UserEngine.UserGetSelfProvisionUserRecipsWithEmailAddress() –> MOVEit.DMZ.ClassLib.UserEngine.UserGetUsersWithEmailAddress().

From guestaccess.aspx to UserGetUsersWithEmailAddress()

Since the GetHTML() seems to be the first method in this call graph/chain that we can reach from guestaccess.aspx, it would be a good idea to examine it first.Also, this is where the PerformAction() method is invoked from.

So, this is what the GetTHML() does:

  1. Invokes LoadFromSession() method to get session values such as MyPkgAccessCode, MyPkgValidationCode, MyPkgID, MyGuestEmailAddr, MyPkgInstID, IsSelfProvisioned, MyPkgSelfProvisionedRecips, and MyPkgViewed and stores them into appropriate variables. We need to figure out if these are taken from the Cookie header or Session Variables. Also, looking at LoadFromSession() definition, the IsSelfProvisioned or PkgID always needs to be zero.
  2. Gets a value from MyUsername and stores it into a variable text3.
  3. Checks if text3 (MyUsername) is set to “Guest” and AccessCode is empty. If so, it resets the text3 variable to an empty string.
  4. Otherwise, in a Switch statement, it checks if there’s an active session for the username provided in text3 (MyUsername).
  5. If the username is “Guest”, and sessionInstId is empty, it sets the following variables: m_foundactivesession, m_sessioninstid, m_access_code, and m_validationcode from either siGlobs global variables or m_pkginfo that was retrieved from LoadFromSession() in step 1.
  6. If SessionAcessCode is not empty and m_temppkginfo.InstID and sessionInstId are same, then it sets the variables mentioned in step 5 as well as m_sessionfromdb.
  7. Finally, it breaks out of the Switch.

So, it looks like we need to set MyUsername along with all the variables mentioned in step 1 and/or 6. There’s lots of code after the aforementioned break statement, but I think we should focus only on a call to PerformAction() method only since it’s a part of the call chain.

8. As you an see from the screenshots above, this is what the PerformAction() method does:

  • Perform some string comparisons as well as validate the CSRF toke provided in the HTTP request. Exit if the CSRF token is not same as the one from .GetCT() method.
  • Check if the Transaction string is valid. The most interesting string is “sendauto” or “send” Switch/Case block, since that’s where the MsgPostForGuest() method is called from and a database update query is fired which appears to be taking an unsanitized user input, m_accesscode.
  • As we know from Step 1, the m_accesscode comes from m_pkginfo.AccessCode which in turn comes from MyPkgAccessCode session value. ( A call to m_pkginfo.LoadFromSession() ).

Connecting the Dots

Alright, so far, we figured out that we can reach the patched function UserGetUsersWithEmailAddress() through guestaccess.aspx by sending all the parameters mentioned in step 1, step 8, and/or step 6. Now we need to figure out if we need to send these parameters or arguments in HTTP query Strings, Session Variables or Cookies? Also, how. to get the right CSRF Token?

If we search through the SILGlobals class, we see several methods that get various user inputs such as Transaction, CsrfTokenIncoming and Arg01 through Arg12 from HTTP GET as well as HTTP POST requests.

I haven’t gone through the entire code from this SILGlobals class file, but it looks like we can send these parameters in Query string, Cookie value, Session Variables, or HTTP POST request body.

From Step 8.1 , we know that the first thing the PerformAction() method does is, it checks if the Transaction parameter contains a valid transaction string and compares the incoming CSRF token with the one it gets from siGlobs.objUtility.GetCT() method.

Looking at the PerformAction() code and fiddling little bit around the GET request parameters, I was able to get the CSRF token in an unauthenticated request. We need to supply just the Arg06 with any dummy value to get the CSRF token.

And then send this token in the HTTP POST request for the SQLi. The next step is to craft the exact HTTP request that would exploit SQL injection through the vulnerable parameter MyPkgAccessCode.

I tried a few things, but didn’t have a luck exploiting this vulnerability. I kept getting error “Your session has expired”. I guess I need to look at the code carefully and try again. I will update this blog post once I examine it again.