Sitecore revisit - CVE-2024-46938 - Cracking the Assetnote's vulnerability with an easy twist
Turning a touch LFI exploitation into a child's play
Today, my living area was hit by blustery weather with pounding rain, and the internet was cut off. With nothing else to do, I recalled an excellent research by Assetnote about an unauthenticated LFI vulnerability in Sitecore. Fortunately, I still keep the Sitecore CMS VM instance.
You should read this blog first before continuing.
The main limitation of this vulnerability, as Assetnote said, is that you have to know the Sitecore installation folder to extract the web.config
file. In a production environment, this approach is rarely feasible - in fact, I’d say it’s nearly impossible. I also encountered a similar situation myself. As I had good memories with Sitecore before, I was full of beans and started to dig deeper into this bug.
This is one of my most challenging efforts, especially given the circumstances of working without the internet.
1. Finding the bypass of the protection mechanism
To recap the vulnerability, I will explain why Assetnote came up with the conclusion that we have to know the Sitecore installation folder. In Sitecore.Resources.Pipelines.ResolveScript.Bundle
, it checks if our filename starts with one of the Bundle.AllowedFile
instances. They include %Sc_Folder\sitecore\shell\client
, %Sc_Folder\sitecore\shell\client\Speak\Assets
and %Sc_Folder%\-\speak\v1
.
How can I find a way around something that seems utterly hopeless? This is how we get the file name.
In FileUtil.MapPath
, it checks whether the filename starts with /
or \
, and then handles each case accordingly. And when the filename starts with /
, it returns server.MapPath(path)
, which is attached to the server base path - %SC_Folder%
.
In short, if f=/sitecore/shell/client/zzz
, the filename will be %SC_Folder%/sitecore/shell/client/zzz
, which matches the condition. But the path traversal won't work here. The reason is, inside the above MapPath
function, the filename is normalized before appending to the end.
So, if f=/sitecore/shell/client/../web.config
, the filename will be %SC_Folder%/sitecore/shell/web.config
, not meet the requirement.
With this approach, we can read all the files that are inside folder %SC_Folder%/sitecore/shell/client
, for example /sitecore/shell/client/web.config
(ironically it doesn’t seem to be valuable).
I noticed one thing, it uses phisicalFileName
(the filename after being normalized) to check but then adds text3
(our original filename) to the queue for reading.
In the read file function, they take a weird action, by checking if the filename (our original one) has a character |
or not before reading. It takes the second part of the filename.
I thought it would be an ezpz like /sitecore/shell/client/zzz|C:\windows\win. ini
, but no. Do you remember about the server.MapPath
function I mentioned? Not only does it normalize the path, but it also checks the validation of the filename. It is a native function in C# that closely connects with Windows behavior. You know, the |
character is an invalid Windows filename character, so it will raise the exception.
The final "masterpiece" of work ties together all the scattered elements mentioned above. If f=/sitecore/shell/client/aa|\../windows/win.ini
, it is normalized before checking the validation characters, so the filename will be /sitecore/shell/client/windows/win.ini
and matches the condition.
Then as I said, the original filename will be read, which is /sitecore/shell/client/aa|\../windows/win.ini
, in the read file function, the filename was corrected to \../windows/win.ini
. It comes again into function FileUtil.MapPath()
This time, because the filename is \../windows/win. ini
- starts with \
, it is returned directly without being fixed.
And we got the LFI without knowing the Sitecore installation folder.
2. Leaking web.config file
To leak the web.config file, at first we still need to know the installation folder, but as we can read other files, we can think about many ways to extract it, like reading the internal host in C:\windows\system32\drivers\etc\hosts
or C:\windows\system32\inetsrv\config\applicationHost.config
(configuration file of IIS), etc.
And leaking the web.config
file is quite straightforward.
But it works on my local, not my target machine. So, again, I had to put my best foot forward to find an all-in-one payload.
Did you notice when the MapPath
function checks if our filename starts with /
or \
? In fact, we have another case that it starts with a normal letter. Let's dig deeper into how the filename will look like, with f=/sitecore/shell/client/|a/../web.config%23.js
In short, the filename after the combination and normalization will be %SC_Folder%/-/speak/v1/bundles/bundle.jsa/../web.config
== %SC_Folder%/-/speak/v1/bundles/web.config
How can we normalize 4 times more for totally breaking the web app? Just only need to put 4 more folders before |
, and that almighty character will break it for us, with f=/sitecore/shell/client/a/a/a/a/|a/../../../../../web. config%23.js
----> %SC_Folder%/-/speak/v1/bundles/bundle.js + a/../../../../../web. config
------> %SC_Folder%/web. config
Bonus: Dealing with some kinds of WAF
Some kinds of WAF only check for the first parameter, others check the last parameter, like if we send payload as target/endpoint?f=<normal>&f=<payload>
or target/endpoint?f=<payload>&f=<normal>
, we can bypass those wafs.
In IIS server behavior, multiple identical parameters are combined into a single value, separated by commas (,
), like if we send target/endpoint?f=aaa&f=bbb
, f will become aaa,bbb
. What a coincidence (or on purpose), Sitecore breaks the string by a comma and checks the filename. That helps us a lot when using the above trick.
Moreover, because the process only checks if the handler endpoint part starts with bundles/bundle.js
, so every endpoint that has bundles/bundle.js
(you know what I mean right?) should work.
We can have a quick POC to check if the host is vulnerable without ../
to not being blocked by WAF
This only reads the web.config inside %SC_Folder/sitecore/shell/client
so there is no sensitive information.
Thank you, Assetnote, for discovering such an impressive vulnerability!
I would appreciate if my readers could connect with me via my LinkedIn https://www.linkedin.com/in/huong-kieu/ or my twitter https://x.com/realalphaman_