Kev Posted September 9, 2020 Report Posted September 9, 2020 The StorageFolder class when used out of process can bypass security checks to read and write files not allowed to an AppContainer. advisory-info: Windows: StorageFolder Marshaled Object Access Check Bypass EoP Windows: StorageFolder Marshaled Object Access Check Bypass EoP Platform: Windows 10 2004/1909 Class: Elevation of Privilege Security Boundary: AppContainer Summary: The StorageFolder class when used out of process can bypass security checks to read and write files not allowed to an AppContainer. Description: When a StorageFolder object is passed between processes it's custom marshaled using the CStorageFolderProxy class (CLSID: a5183349-82de-4bfc-9c13-7d9dc578729c) in windows.storage.dll. The custom marshaled data contains three values, a standard marshaled OBJREF for a Proxy instance in the originating process, a standard marshaled OBJREF for the original CStorageFolder object and a Property Store. When the proxy is unmarshaled the CStorageFolderProxy object is created in the client process, this redirects any calls to the storage interfaces to the creating process's CStorageFolder instance. The CStorageFolder will check access based on the COM caller. However, something different happens if you call a method on the marshaled Proxy object. The call will be made to the original process's Proxy object, which will then call the real CStorageFolder method. The problem is the Proxy and the real object are running in different Apartments, the Proxy in the MTA and the real object in a STA. This results in the call to the real object being Cross-Apartment marshaled, this breaks the call context for the thread as it's not passed to the other apartment. As shown in a rough diagram. [ Client (Proxy::Call) ] => [Server [ MTA (Proxy::Call) ] => [ STA (Real::Call) ] ] As the call context is only captured by the real object this results in the real object thinking it's being called by the same process, not the AppContainer process. If the process hosting the StorageFolder is more privileged this can result in being able to read/write arbitrary files in specific directories. Note that CStorageFile is similarly affected, but I'm only describing CStorageFolder. In any case it's almost certainly the shared code which is a problem. I've no idea why the classes aren't using the FTM, perhaps they're not marked as Agile? If they were then the real object would be called directly and so would still be running in the original caller's context. Even if the FTM was enabled and the call context was maintained it's almost certainly possible to construct the proxy in a more privileged, but different process because of the asymmetric nature of the marshaling, invoke methods in that process which will always have to be performed out of process. Fixing wise, firstly I don't think the Proxy should ever end up standard marshaled to out of process callers, removing that might help. Also when a call is made to the real implementation perhaps you need to set a Proxy Blanket or enable dynamic cloaking and impersonate before the call. There does seem to be code to get the calling process handle as well, so maybe that also needs to be taken into consideration? This code looks like it's copied and pasted from SHCORE which is related to the bugs I've already reported. Perhaps the Proxy is not supposed to be passed back in the marshal code, but the copied code does that automatically? I'd highly recommend you look at any code which uses the same CFTMCrossProcClientImpl::_UnwrapStream code and verify they're all correct. Proof of Concept: I've provided a PoC as a C# project. The code creates an AppContainer process (using a temporary profile). It then uses the Partial Trust StorageFolderStaticsBrokered class, which is instantiated OOP inside a RuntimeBroker instance. The class allows opening a StorageFolder object to the AC profile's Temporary folder. The StorageFolderStaticsBrokered is granted access to any AC process as well as the \u"lpacAppExperience\u" capability which means it also works from Classic Edge LPAC. The PoC then uses the IStorageItem2::GetParentAsync method to walk up the directory hierarchy until it reaches %LOCALAPPDATA%. It can't go any higher than that as there seems to be some condition restriction in place, probably as it's the base location for package directories. The code then writes an arbitrary file abc.txt to the Microsoft sub-directory. Being able to read and write arbitrary files in the user's Local AppData is almost certainly enough to escape the sandbox but I've not put that much time into it. 1) Compile the C# project. It will need to grab the NtApiDotNet from NuGet to work. 2) Run the POC executable. Expected Result: Accessing files outside of the AppContainers directory is blocked. Observed Result: An arbitrary file is written to the %LOCALAPPDATA%\\Microsoft directory. This bug is subject to a 90 day disclosure deadline. After 90 days elapse, the bug report will become visible to the public. The scheduled disclosure date is 2020-09-23. Disclosure at an earlier date is also possible if agreed upon by all parties. Related CVE Numbers: CVE-2020-0886. Found by: forshaw@google.com Download: GS20200908185407.tgz (18 KB) Source 1 Quote