NmapのNSEスクリプト「upnp-info」を使ってみた
Nmap の NSE(Nmap Scripting Engine)スクリプト「upnp-info」を使ってみたところ、使用前に思っていた実行結果と異なっていたため、スクリプトを一部編集してみました。この日記では、「upnp-info」の実行結果と編集したスクリプトについて書きます。
「upnp-info」の実行結果
まず「upnp-info」の実行環境は下表の通りです。
IPアドレス | デバイス | 説明 |
---|---|---|
192.168.0.101 | Windows XP SP3 マシン | 「upnp-info」を実行するデバイス |
192.168.0.106 | nasne | 「upnp-info」の実行対象デバイス |
この実行環境で「upnp-info」を実行してみると、nasne の UPnP に関する情報が実行結果に出力されませんでした。なお、Nmap の実行結果のうち、MAC アドレスおよび OUI 情報については「12:34:56:78:9a:bc」、「XXXXXXXXXXXXXXXX」に置換しています。
$>nmap -V Nmap version 6.25 ( http://nmap.org ) Platform: i686-pc-windows-windows Compiled with: nmap-liblua-5.2.1 openssl-1.0.1c nmap-libpcre-7.6 libpcap-4.1.2 nmap-libdnet-1.12 ipv6 Compiled without: Available nsock engines: select $>nmap -sU -p1900 --script=upnp-info -n 192.168.0.106 Starting Nmap 6.25 ( http://nmap.org ) at 2013-02-10 22:40 東京 (標準時) Nmap scan report for 192.168.0.106 Host is up (0.00s latency). PORT STATE SERVICE 1900/udp open|filtered upnp MAC Address: 12:34:56:78:9a:bc (XXXXXXXXXXXXXXXX) Nmap done: 1 IP address (1 host up) scanned in 6.92 seconds
一方で、UPnP を実装しているデバイスを探す NSE スクリプト「broadcast-upnp-info」の実行結果には、nasne の UPnP に関する情報が出力されました。「upnp-info」を使用する前には、このような出力を思い描いていました。
$>nmap -sU -p1900 --script=broadcast-upnp-info -n 192.168.0.106 Starting Nmap 6.25 ( http://nmap.org ) at 2013-02-10 23:37 東京 (標準時) Pre-scan script results:
broadcast-upnp-info: |
192.168.0.106 |
Server: Linux/2.6 UPnP/1.0 nasne/1.0 |
Location: http://192.168.0.106:58888/ |
_ Webserver: Linux/2.6 UPnP/1.0 nasne/1.0 |
「upnp-info」と「broadcast-upnp-info」のパケットキャプチャデータを確認したところ、UPnP の M-SEARCH リクエスト(後述)の送信方法に違いがありました。「upnp-info」の場合、この違いによって nasne が M-SEARCH リクエストに応答していないと考えました。
スクリプト実行時に送受信されるパケット
「upnp-info」の場合、Nmap で指定した 192.168.0.106 に M-SEARCH リクエストを送信していましたが、「broadcast-upnp-info」の場合、マルチキャストアドレス 239.255.255.250 に送信していました。
- 「upnp-info」のパケットキャプチャデータ(CloudShark)
- パケットキャプチャデータ No.3 を参照
- 「broadcast-upnp-info」のパケットキャプチャデータ(CloudShark)
- パケットキャプチャデータ No.1 を参照
「upnp-info」および「broadcast-upnp-info」ともに、下記の M-SEARCH リクエストを送信します。それぞれのパケットキャプチャデータを確認すると、宛先 IP アドレスおよび宛先 MAC アドレスが異なること*1が分かります。なお、「192.168.0.101」および「192.168.0.106」の MAC アドレスについては、それぞれ「bc:9a:78:56:34:12」、「12:34:56:78:9a:bc」に置換しています。
M-SEARCH * HTTP/1.1 Host:239.255.255.250:1900 ST:upnp:rootdevice Man:"ssdp:discover" MX:3
UPnP における M-SEARCH リクエスト
「UPnP Device Architecture 1.1」の「1.3 Search」では、M-SEARCH リクエストが「ネットワーク上の UPnP を実装しているデバイスを探すリクエスト」である旨が記述されています。この仕様書によると、マルチキャストアドレス宛てにM-SEARCH リクエストを送信する以外にも、ユニキャストアドレス宛てに送信できます*2。
「upnp-info」の場合、192.168.0.106 宛てにも関わらず、Host ヘッダを「239.255.255.250:1900」としていることが原因かと推測しましたが、違いました。Host ヘッダを「192.168.0.106:1900」に修正して ncatで送信してみましたが*3、やはり応答がありませんでした。結論としては、UPnP を実装したデバイスの中には、ユニキャスト宛て M-SEARCH リクエストに応答しないものもある、と理解しました。
「upnp-info」の編集
「broadcast-upnp-info」スクリプト broadcast-upnp-info.lua や upnp.lua を読みながら、「upnp-info」スクリプト upnp-info.lua を次のように編集してみました。
編集した upnp-info.lua は下記の通りとなります。僕が追加したコードは「-----追加 ここから」と「-----追加 ここまで」に囲まれたコード部分です。M-SEARCH リクエストに対して複数の応答が得られる環境で、簡単な動作確認のみ実施しています。
local nmap = require "nmap" local shortport = require "shortport" local stdnse = require "stdnse" local string = require "string" local upnp = require "upnp" -----追加 ここから local table = require "table" -----追加 ここまで description = [[ Attempts to extract system information from the UPnP service. ]] --- -- @output -- | upnp-info: System/1.0 UPnP/1.0 IGD/1.0 -- |_ Location: http://192.168.1.1:80/UPnP/IGD.xml -- -- @args upnp-info.override Controls whether we override the IP address information -- returned by the UPNP service for the location of the XML -- file that describes the device. Defaults to true for -- unicast hosts. -- 2010-10-05 - add prerule support <patrik@cqure.net> -- 2010-10-10 - add newtarget support <patrik@cqure.net> -- 2010-10-29 - factored out all of the code to upnp.lua <patrik@cqure.net> -- 2013-02-11 - call helper:setMulticast(true), and filter only result of target ip <kaito834@gmail.com> author = "Thomas Buchanan" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"default", "discovery", "safe"} --- -- Runs on UDP port 1900 portrule = shortport.portnumber(1900, "udp", {"open", "open|filtered"}) --- -- Sends UPnP discovery packet to host, -- and extracts service information from results action = function(host, port) local override = stdnse.get_script_args("upnp-info.override") local helper = upnp.Helper:new( host, port ) if ( override ~= nil ) and ( string.lower(override) == "false" ) then helper:setOverride( false ) else helper:setOverride( true ) end -----追加 ここから helper:setMulticast(true) -----追加 ここまで local status, result = helper:queryServices() -----追加 ここから for key, value in ipairs(result) do if(not(value['name'] == host['ip'])) then table.remove(result, key) end end -----追加 ここまで if ( status ) then nmap.set_port_state(host, port, "open") return stdnse.format_output(true, result) end end
編集した「upnp-info」を実行すると、下記のように「broadcast-upnp-info」と同様の出力が得られました。
$>nmap -sU -p1900 --script=upnp-info -n 192.168.0.106 Starting Nmap 6.25 ( http://nmap.org ) at 2013-02-11 09:46 東京 (標準時) Nmap scan report for 192.168.0.106 Host is up (0.00s latency). PORT STATE SERVICE 1900/udp open upnp
upnp-info: |
192.168.0.106 |
Server: Linux/2.6 UPnP/1.0 nasne/1.0 |
Location: http://192.168.0.106:58888/ |
_ Webserver: Linux/2.6 UPnP/1.0 nasne/1.0 |