Уязвимость CallStranger (CVE-2020-12695) в Universal Plug and Play (UPnP) и PowerShell скрипт для сканирования сети по протоколу SSDP

Vulnerability CallStranger CVE-2020-12695 in SSDP UPnPНа прошлой неделе в Российском сегменте Интернета снова заговорили о проблемах безопасности в технологии Universal Plug and Play (UPnP). Например, можно ознакомится со статьями Уязвимость в UPnP, подходящая для усиления DDoS-атак и сканирования внутренней сети и Уязвимость UPnP, позволяющая сканировать сети и организовывать DDoS-атаки, признана официально. В разных источниках проблемы безопасности UPnP рассматриваются в большей степени в контексте устройств и ПО, подключенных к глобальной сети Интернет. Однако, хочется отметить тот факт, что неконтролируемые администраторами технологии UPnP несут в себе определённые риски и в рамках локальных корпоративных сетей. В этой статье мы рассмотрим простейший пример возможности эксплуатации уязвимости UPnP в локальной сети и поговорим о том, как можно выявить уязвимые устройства в сети для последующей их защиты.

Масса сетевых устройств разных типов (сетевые принтеры, МФУ, IP-телефоны, IP-камеры, устройства Wi-Fi, устройства IoT и т.д.) могут иметь включенные по умолчанию функции для поддержки UPnP. Кроме того, существуют программные продукты, использующие UPnP, что вводит в зону риска компьютеры на базе даже самых актуальных версий ОС Windows/Linux/MacOS и т.д.

На специальном сайте, посвящённом CallStranger можно найти описание проблемы и перечень устройств, для которых уже точно подтверждена возможность эксплуатации. Однако, следует учесть, что это далеко не полная информация. Например, относительно уязвимых пакетов в Linux, получить информацию можно здесь или здесь.

Согласно представленной информации об уязвимости CallStranger, любое устройство с неконтролируемым UPnP можно превратить в участника распределённой атаки DDoS на какой-либо веб-ресурс. Очевидно, что на устройствах, выставленных в глобальную сеть, крайне желательно ограничить или вовсе отключить функциональность UPnP.

Чем это опасно для локальных сетей ?

Вопрос того, какой веб-ресурс (внутренний или внешний) может быть атакован уязвимыми устройствами UPnP, зависит только от того, в какую сторону направлены эти устройства – в LAN или WAN. Устройства, выставленные в Интернет могут выступать в качестве узлов бот-сети, которой можно не только положить внешний веб-сайт/портал/веб-службу, но и создать проблемы на уровне провайдеров Интернет-услуг. Устройства в локальной сети по тому же принципу могут быть использованы для атаки на любые внутренние веб-ресурсы, не имеющие ограничения по доступу в рамках этой локальной сети.

Простейший сценарий эксплуатации уязвимости UPnP в локальной сети может быть таким:

  • В сети появляется временный вредительский хост и первично опрашивает сеть для составления списка UPnP-девайсов, отвечающих на специально сформированные запросы по протоколу SSDP;
  • Затем с хоста (а может и с нескольких, для усиления эффекта) одновременно на все UPnP-устройства посылается специальный SSDP-пакет, вынуждающий сделать Callback на тот веб-ресурс, который нужно атаковать;
  • В результате на веб-ресурс приходит шквал однотипных HTTP-запросов с множества разных устройств. И если веб-ресурс с создавшейся нагрузкой не справляется, может наступить его отказ.

Чтобы продемонстрировать то, что это действительно работает, проведём небольшой эксперимент.

Просканируем с помощью утилиты nmap локальную сеть (например, сегмент сети с периферийной техникой) в отдельно взятом структурном подразделении на предмет выявления устройств с открытым портом UDP 1900. У ответивших устройств запросим информацию о UPnP-сервисе:

nmap -sU -p 1900 10.10.1.0/24 --open --script=upnp-info

Scanning UPnP host via nmap with upnp-info script

Как видим, среди прочих нам ответило устройство, имеющее встроенный UPnP Server и URL Location, с которого уже по протоколу HTTP мы можем запросить об устройстве гораздо больше информации:

curl http://10.10.1.83:5200/Printer.xml

Get XML data from UPnP host vis HTTP request

В полученном XML-ответе нас интересует параметр eventSubURL, в значении которого мы обнаружим относительный путь к скрипту для вызова функции SUBSCRIBE. В данном примере, это путь "/printercontrol".

Теперь нам нужно выбрать жертву, то есть веб-сервер, который мы с помощью уязвимых UPnP устройств будем засыпать HTTP-запросами. Выберем любой веб-сервер (в нашем примере это сервер с IP 10.0.0.24) и для наглядности запустим на нём утилиту netcat, с помощью которой включим режим прослушивания любого порта, например 80:

nc -l -p 80

Далее вернёмся на наш вредительский хост и отправим с него специально сформированный HTTP-запрос на уязвимое UPnP-устройство, заставив его выполнить функцию SUBSCRIBE, указав при этом в качестве Callback -адреса URL стороннего атакуемого веб-сервера:

curl -X SUBSCRIBE -H 'User-Agent: Test' -H 'CALLBACK: <http://10.0.0.24:80/knock-knock>' -H 'NT: upnp:event' -H 'TIMEOUT: Second-300' http://10.10.1.83:5200/printercontrol

Вернёмся на консоль веб-сервера и увидим, что туда действительно с нашего UPnP-устройства прилетел HTTP-запрос:

Web server get HTTP reqest via UPnP Callback

Собственно, что и требовалось доказать.

Таким образом, произвольный вредительский хост (или несколько хостов для усиления эффекта) может вынудить отсылать подобные HTTP-запросы со стороны сотен или даже тысяч уязвимых UPnP устройств, организуя тем самым распределённую атаку (DDoS) в рамках большой корпоративной локальной сети.

Что делать?

Если мы попытаемся немного углубиться в историю проблем безопасности UPnP, то сможем обнаружить то, что специалисты в области ИБ уже неоднократно давали рекомендации либо по принятию мер защиты, либо по отказу от использования протоколов и механизмов, так или иначе связанных с UPnP. Так, например в статье трёх-летней давности Stupidly Simple DDoS Protocol (SSDP) generates 100 Gbps DDoS описывалась тяжёлая распределённая флуд-атака по протоколу SSDP по методу, рассмотренному нами выше (очень позабавила интерпретация аббревиатуры SSDP). Эту информацию транслировали и в русскоязычном сегменте Интернета:
Предупреждение о задействовании UPnP/SSDP в качестве усилителя DDoS-атак. Ещё тремя годами ранее, в 2014, фиксировалось большое количество распределённых атак с использованием тех же методов. И как я понял, сами по себе проблемы UPnP известны уже много лет. Чтобы убедиться в этом, можно почитать презентации и доклады 2006-2008 годов на ресурсе UPnP Hacks.

Учитывая то, что в большинстве случаев на практике в больших корпоративных локальных сетях механизмы UPnP могут не использоваться вовсе и при этом находиться в активном состоянии, следует озадачиться выявлением потенциально опасных устройств и проведению мер по защите или отключению UPnP.

В целом, к мерам диагностики и защиты можно отнести:

  1. Проведение внутреннего аудита всех систем и устройств, подключенных к глобальной сети Интернет на предмет выявления всех открытых портов. На этом этапе следует убедиться в том, что из глобальной сети нет возможности подключения на порт UDP 1900 для каких-либо устройств/систем в локальной сети.
  2. Проведение сканирования локальной сети на предмет обнаружения устройств и систем, отвечающих на запросы по протоколу SSDP. На обнаруженных в ходе сканирования системах необходимо провести мероприятия по ограничению или отключению функционала UPnP.
  3. Учитывая то обстоятельство, что не все типы или модели устройств имеют механизмы штатной деактивации UPnP, на маршрутизаторах и внутренних системах фильтрации трафика с поддержкой Layer7 рекомендуется организовать ограничение или полную блокировку пересылки HTTP-пакетов с методами запроса SUBSCRIBE и NOTIFY.

Далее мы рассмотрим вариант обнаружения устройств согласно пункта 2, который отчасти можно будет применять и для пункта 1.

Как выявить в сети устройства UPnP?

Авторы веб-ресурса CallStranger в качестве инструмента выявления уязвимый UPnP-устройств предлагают использовать одноимённый комплект скриптов. Однако в рамках локальной корпоративной сети использование скриптов в представленном виде "сомнительное удовольствие", так как:

  • Во первых, для выявления уязвимых устройств скрипт CallStranger.py выполняет сканирование только в рамках одного физического сегмента сети (того, где запущен), используя групповые multicast-рассылки и не адаптирован для проверки больших локальных сетей с маршрутизацией трафика между сегментами;
  • Во вторых, ответы уязвимых устройств для анализа скрипт пересылает на внешний интернет-ресурс verify.callstranger.com. Хотя и доступен для скачивания отдельный скрипт CallStranger.php, который, вроде как, можно использовать в качестве альтернативы для организации подставного (атакуемого) веб-сервера в рамках локальной сети.

Безрезультатным оказался мой поиск каких-либо других автономных (не использующих внешних Интернет-соединений) и свободно распространяемых средств сканирования по протоколу SSDP, которые бы могли дать конечный результат без необходимости дополнительной обработки полученных данных. Поэтому было решено сделать собственный скриптовый инструмент.

Первая попытка решения этой задачи с помощью PowerShell получилась немного громоздкой, так как в качестве сканера сети использовался вызов  утилиты nmap с выводом в формат XML и дополнительный внешний PS-скрипт Parse-Nmap.ps1, разбирающий этот вывод. В ходе экспериментов с опросом сетей было выявлено довольно странное поведение UPnP-устройств, которое проявлялось в нестабильном ответе на SSDP-запросы методом M-SEARCH. То есть, при сканировании nmap одно и тоже устройство может то ответить на запрос, то не ответить. Мысль о том, что виновником такой ситуации может быть хромая реализация SSDP у какого-то отдельно взятого производителя (или модели устройств) была отброшена после того, как аналогичная картина была замечена при опросе оборудования разных производителей. Например, одинаково загадочно отвечает на запросы и периферийная техника Xerox и интерфейсы управления iLO5 серверов HPE ProLiant и другие "железяки". Тогда грешным делом подозрения упали на nmap, как на основной инструмент сканирования. Крутились все его возможные "вертелки" и "крутилки" с режимами агрессивности и таймаутами, чтобы как-то повлиять на результат сканирования, но результат оставался плавающим. В конечном итоге была предпринята попытка замены инструмента сканирования и появилась вторая версия скрипта, использующего для сканирования уже возможности самого PowerShell, без использования сторонних средств.

[System.Net.IPAddress]$StartScanIP = "10.10.0.1"
[System.Net.IPAddress]$EndScanIP =   "10.10.10.254"
[int]$UPnPport = 1900
[int]$EchoCount = 1
[int]$UDPReceiveTimeout = 2000
[int]$WebRequestTimeout = 4000
[bool]$UPnPExtendData = $true
[string]$CSVReportPath = "C:\Temp\UPnP-Devices-Scan.csv"
#
#
$Watch = [System.Diagnostics.Stopwatch]::StartNew()
$Watch.Start()
#
$ScanIPRange = @() 
if($EndScanIP -ne $null) 
{
  $StartIP = $StartScanIP -split '\.' 
  [Array]::Reverse($StartIP)   
  $StartIP = ([System.Net.IPAddress]($StartIP -join '.')).Address  
  #               
  $EndIP = $EndScanIP -split '\.' 
  [Array]::Reverse($EndIP)   
  $EndIP = ([System.Net.IPAddress]($EndIP -join '.')).Address  
  #               
  For ($x=$StartIP; $x -le $EndIP; $x++) {     
      $IP = [System.Net.IPAddress]$x -split '\.' 
      [Array]::Reverse($IP)    
      $ScanIPRange += $IP -join '.'  
  }
} 
  else 
{ 
 $ScanIPRange = $StartScanIP 
} 
#
#
Workflow Network-Scan {
  param($ippool,$echoc,$udpOutTimeout,$webOutTimeout,$port,$getupnp)    
  $WFResult = @()
  ForEach -parallel -throttlelimit 64 ($ip in $ippool) 
  {   
    $ItemReturn = inlinescript 
    {
      $SSDPHostIP   = $USING:ip
      $SSDPHostName = "-"
      $SSDPHostPort = $USING:port

      # Check host is online
      If (Test-Connection -Computername $SSDPHostIP -BufferSize 16 -Count $USING:echoc -Quiet) 
      {
        # Send data to remote UDP port
        $udpobject = New-Object system.Net.Sockets.Udpclient
        $udpobject.Connect($SSDPHostIP,$SSDPHostPort)
        $udpobject.Client.ReceiveTimeout = $USING:udpOutTimeout
        $a = New-Object system.text.asciiencoding
        #
        $SSDPRequestData = "M-SEARCH * HTTP/1.1" `
        +"`r`n"+"HOST:239.255.255.250:1900" `
        +"`r`n"+"ST:upnp:rootdevice" `
        +"`r`n"+"MAN:`"ssdp:discover`"" `
        +"`r`n"+"MX:3" `
        +"`r`n`r`n"
        #
        $byte = $a.GetBytes($SSDPRequestData)
        [void]$udpobject.Send($byte,$byte.length)
        #
        # Receive SSDP data from remote host
        $receivebytes = ""
        $remoteendpoint = New-Object system.net.ipendpoint([system.net.ipaddress]::Any,0)
        #
        Try {
            $receivebytes = $udpobject.Receive([ref]$remoteendpoint)
        } Catch {}
        #        
        If ($receivebytes) {
            $SSDPReplyRAW = $a.GetString($receivebytes)           
            $SSDPReply = $SSDPReplyRAW -split "`n"
            [string]$upnpURL = ""
            [string]$upnpSRV = ""
            [string]$upnpFName = ""
            [string]$upnpManuf = ""
            [string]$upnpModel = ""
            [string]$upnpMNumb = ""
            [string]$upnpDescr = ""
            $upnpURL = $SSDPReply | Select-String -Pattern "LOCATION:"
            $upnpURL = ($upnpURL -replace "LOCATION:","").Trim()
            $upnpSRV = $SSDPReply | Select-String -Pattern "SERVER:"
            $upnpSRV = ($upnpSRV -replace "SERVER:","").Trim()       
            If ($USING:getupnp) {
                [System.Xml.XmlDocument]$xd = New-Object System.Xml.XmlDocument
                Try{                            
                    $WebRequest = [System.Net.WebRequest]::Create($upnpURL)
                    $WebRequest.Timeout = $USING:webOutTimeout
                    $WebRequest.AuthenticationLevel = "None"
                    $WebResponse = $WebRequest.GetResponse()
                    $sr = New-Object System.IO.StreamReader($WebResponse.GetResponseStream())
                    $xd = $sr.ReadToEnd()        
                    $upnpFName = $xd.root.device.friendlyName
                    $upnpManuf = $xd.root.device.manufacturer
                    $upnpModel = $xd.root.device.modelName
                    $upnpMNumb = $xd.root.device.modelNumber
                    $upnpDescr = $xd.root.device.modelDescription
                }
                Catch {}
            }
            Try{
                $SSDPHostName = [System.Net.Dns]::GetHostByAddress($SSDPHostIP).HostName
            } 
            Catch {}
            $Item = New-Object System.Object
            $Item | Add-Member -MemberType NoteProperty -Name "HostIP" $SSDPHostIP
            $Item | Add-Member -MemberType NoteProperty -Name "HostDNSName" $SSDPHostName
            $Item | Add-Member -MemberType NoteProperty -Name "UPnPURL" $upnpURL
            $Item | Add-Member -MemberType NoteProperty -Name "UPnPServer" $upnpSRV
            $Item | Add-Member -MemberType NoteProperty -Name "UPnPfriendlyName" $upnpFName
            $Item | Add-Member -MemberType NoteProperty -Name "UPnPmanufacturer" $upnpManuf
            $Item | Add-Member -MemberType NoteProperty -Name "UPnPmodelName" $upnpModel
            $Item | Add-Member -MemberType NoteProperty -Name "UPnPmodelNumber" $upnpMNumb
            $Item | Add-Member -MemberType NoteProperty -Name "UPnPmodelDescription" $upnpDescr
            return $Item
        }
        $udpobject.Close()
      } # Check host is online               
     } #inlinescript
     $WORKFLOW:WFResult += $ItemReturn
   }
  return $WORKFLOW:WFResult
}
#
#
[array]$Result = Network-Scan $ScanIPRange $EchoCount $UDPReceiveTimeout $WebRequestTimeout $UPnPport $UPnPExtendData
If (!$UPnPExtendData) {
$Result | Sort-Object HostIP | Format-Table -Autosize `
  @{Name="Host IP";Expression = { $_.HostIP }; Alignment="Center"},
  @{Name="Host Name from DNS";Expression = { $_.HostDNSName }; Alignment="Center"},
  @{Name="UPnP Server";Expression = { $_.UPnPServer };Alignment="Left"},
  @{Name="UPnP Location URL";Expression = { $_.UPnPURL };Alignment="Left"}
} Else {
$Result | Sort-Object HostIP | Format-List `
  @{Name="Host IP";Expression = { $_.HostIP }},
  @{Name="Host Name from DNS";Expression = { $_.HostDNSName }},
  @{Name="UPnP Server";Expression = { $_.UPnPServer }},
  @{Name="UPnP Location URL";Expression = { $_.UPnPURL }},
  @{Name="UPnP Friendly Name";Expression = { $_.UPnPfriendlyName }},
  @{Name="UPnP Manufacturer";Expression = { $_.UPnPmanufacturer }},
  @{Name="UPnP Model Name";Expression = { $_.UPnPmodelName }},
  @{Name="UPnP Model Number";Expression = { $_.UPnPmodelNumber }},
  @{Name="UPnP Model Description";Expression = { $_.UPnPmodelDescription }}
}
#
Write-Host $Result.Count "hosts found on network range " $StartScanIP "-" $EndScanIP
#
If (($CSVReportPath) -and ($Result.Count -gt 0)) {
  If(!(Test-Path $CSVReportPath)) {
    Try {
        New-Item -ItemType File -Path $CSVReportPath -ErrorAction Stop | Out-Null
    } Catch {
        Write-Warning "Failed to create report file ($CSVReportPath)!`r`n$_`r`nPlease check the variable `$CSVReportPath in the script."
        Break
    }
  }
  $Result + (Import-Csv $CSVReportPath) `
  | Select-Object -Property "HostIP","HostDNSName","UPnPServer","UPnPURL", `
  "UPnPfriendlyName","UPnPmanufacturer","UPnPmodelName","UPnPmodelNumber","UPnPmodelDescription" `
  | Sort-Object -Unique  HostIP `
  | Export-Csv -NoTypeInformation -Path $CSVReportPath
}
#
$Watch.Stop()
Write-Host "`r`nScript time:" $Watch.Elapsed

В переменных $StartScanIP и $EndScanIP указывается начало и конец диапазона сканирования сети.
Доступность каждого хоста в указанном диапазоне проверяется отсылкой одного echo-пакета (в перегруженных сетях или для обработки "долго соображающих" устройств можно увеличить количество пакетов через переменную $EchoCount). Для каждого живого хоста предпринимается попытка отсылки SSDP-пакета M-SEARCH на порт UPD 1900. В случае включенной переменной $UPnPExtendData на ответивший UPnP-хост делается дополнительный HTTP-запрос на получение более детальной информации о хосте и выполняется разбор ответа. По окончании процесса сканирования результат выводится на консоль PowerShell в виде краткой таблицы (если переменная $UPnPExtendData была выключена),

Get UPnP devices in network via PowerShell as Table

либо в виде подробного списка с дополнительной информацией о каждом устройстве.

Get UPnP devices in network via PowerShell as List

Извлечение дополнительной информации дополнительным HTTP-запросом несколько увеличивает время сканирования в больших сетях, но даёт больше информации о происходящем и может помогать в идентификации хоста или сервиса, отвечающего на запросы UPnP.

Учитывая то, что в больших сетях управление разными типами устройств может находится в зоне ответственности разных людей, скрипт дополнительно выгружает данные о сканировании с файл формата CSV, который в дальнейшем можно будет дополнительно анализировать в табличных редакторах или передавать профильным специалистам для проведения работ по деактивации UPnP. Путь к файлу отчёта указывается в переменной $CSVReportPath (очистка значения переменной приводит к отключению формирования отчёта).

Опыт применения скрипта показал то, что UPnP-устройства, также как и при использовании утилиты nmap, отвечают на запрос M-SEARCH плавающим образом. Поэтому теперь нет оснований плохо думать о nmap и появилось предположение о том, что такое поведение может быть связано с особенностями работы протокола SSDP, базирующегося на UDP, который в свою очередь сам по себе не гарантирует доставки в отличии от TCP. Соответственно, чтобы получить при сканировании сети по протоколу SSDP более полную картину, потребуется несколько раз выполнять представленный скрипт (точно также, как и при сканировании с помощью nmap). Учитывая это обстоятельство, скрипт наделён возможностью дописывать данные о последнем проведённом сканировании в уже ранее наполненный CSV-файл, исключая при этом дубликаты записей и сортируя данные.

Деактивация UPnP

После составления окончательного списка сетевых устройств и сервисов, потенциально входящих в зону риска, можно приступать к мероприятиям по деактивации UPnP (там, где это поддерживается и возможно), либо к мероприятиям по ограничению доступа к таким устройствам и службам.

Например, если говорить об уже упомянутых в нашей статье сетевых многофункциональных устройствах Xerox, то можно отметить тот факт, что на некоторых моделях МФУ возможность отключения UPnP в явном виде присутствует в веб-интерфейсе встроенного веб-сервера:

Deactivate UPnP in Xerox WorkCentre 3325

При этом на других моделях этого же производителя мы не найдём подобных настроек деактивации UPnP. А опыт общения с тех.поддержкой вендора подтвердил тот факт, что никакой штатной возможности отключения UPnP в некоторых моделях нет, и вряд ли появится в обозримом будущем. В таких случаях в качестве обходного решения можно использовать другие функциональные возможности, позволяющие задействовать такие механизмы ограничения доступа, как встроенный IP фильтр. В этом фильтре можно настроить доступ к устройству только для узких сегментов сетей, в которых размещены основные пользователи устройства.

IP Filtering in Xerox WorkCentre 3335

Если устройство отвечает на запросы UPnP и не имеет при этом возможности отключения этого механизма, как и встроенных средств ограничения доступа, то можно ограничить доступ к сегменту подобных устройств на уровне сетевого оборудования.

На компьютерах с ОС Microsoft Windows, как самых массовых представителях большинства локальных корпоративных сетей, отключить работающий по умолчанию UDP-прослушиватель на порту 1900 можно, остановив и отключив системные службы "UPnP Device Host" (upnphost) и "SSDP Discovery" (SSDPSRV), либо настроив централизованно управляемые правила брандмауэра Windows Firewall.

Вообще универсальных рецептов по отключению UPnP на разных типах устройств, систем и ПО, конечно же нет (если не считать тотальное ограничение на сетевом оборудовании). Поэтому, выявив с результате сканирования сети UPnP устройства, в каждом конкретном случае придётся применять все доступные методы по каждому отдельно взятому типу устройств. И это будет уже тема отдельного разговора.

Добавить комментарий