Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
We worked pretty hard over at $DAYJOB on helping to quantify and remediate a fairly significant configuration weakness in Ubiquiti network work gear attached to the internet.
Ubiquiti network gear — routers, switches, wireless access points, etc. — are enterprise grade components and are a joy to work with. Our home network is liberally populated with this gear. Ubiquiti nodes have a “discovery service” so they can be identified and brought under management of a controller. About half-a-million folks neglected to either join a controller or just left the discovery service running and accessible via the internet interface.
We use a myriad of tech to perform discovery and content scans on the internet, everything from zmap to Python and other bits in-between. It’s super easy to craft a quick UDP client in Ruby or Python to talk to nodes that speak UDP and get a response. Unfortunately, R only has built-in connection
support for TCP communications. This makes a ton of sense given how R came to be and the primary uses of it for the bulk of its existence. I wanted to be able to test my own, non-internet-exposed Ubiquiti gear and do it from R, thus begat the udpprobe
You can install it via your social coding platform of choice (after checking out the source code since you shouldn’t blindly trust any internet git repo):
devtools::install_git("https://git.sr.ht/~hrbrmstr/udpprobe") # or devtools::install_gitlab("hrbrmstr/udpprobe") # or devtools::install_github("hrbrmstr/udpprobe")
Some good news for you Windows folks is that it actually works on your legacy OS as well!
What can one do with this package?
I’m glad you asked.
We’ll get to the Ubiquiti portion in a bit. First, we’ll hand craft the payload for a DNS lookup for the address of example.com
. You make thousands of DNS lookups every day but likely never poked at what they really look like. These lookups are still generally performed over UDP and the protocol and record formats are [thoroughly documented]. Here’s what that A
record request for example.com
looks like:
library(udpprobe) c( 0xaa, 0xaa, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01 ) -> dns_req
The string example.com
is 0x65 0x78 0x61 0x6d 0x70 0x6c 0x65 0x2e 0x63 0x6f 0x6d
and you can find it at position 14 in the raw vector (technically it’s still a numeric vector but I made it in hex so it’s easier to refer to it that way). Right after it is a terminator then a 2 byte sequence to tell the DNS server we’re looking for an A
record. Let’s make Google do some work:
(resp <- udp_send_payload("8.8.8.8", 53, dns_req)) ## [1] aa aa 81 80 00 01 00 01 00 00 00 00 07 65 78 61 6d 70 ## [19] 6c 65 03 63 6f 6d 00 00 01 00 01 c0 0c 00 01 00 01 00 ## [37] 00 0c 47 00 04 5d b8 d8 22
You should be able to find example.com
in there again if you look closely. We’ll assume the response was OK and yank out the IP address it sent back:
paste0(as.integer(tail(resp, 4)), collapse = ".") ## [1] "93.184.216.34"
and, verify it with Jeroen’s spiffy curl
package:
curl::nslookup("example.com") ## [1] "93.184.216.34"
Having some fun with Ubiquiti kit
If you read the aforelinked blog post you’d know that to talk to Ubiquiti gear we need to send 0x01 0x00 0x00 0x00
to UDP port 10001. Since I plan on expanding the ubpprobe
package to include helper functions for standard probes, we’ll use the singular one provided so far to talk to a known exposed system. Rather than give you the IP address I’ve stored it in an environment variable:
(x <- ubnt_discovery_probe(Sys.getenv("UBNT_TEST_HOST"))) ## [1] 01 00 00 a0 02 00 0a dc 9f db 3a 5f 09 8a ff bd a9 02 ## [19] 00 0a dc 9f db 3b 5f 09 c0 a8 02 01 01 00 06 dc 9f db ## [37] 3a 5f 09 0a 00 04 00 01 cb 0d 0b 00 15 39 36 39 20 2d ## [55] 20 4a 75 76 65 6e 61 6c 20 52 69 62 65 69 72 6f 0c 00 ## [73] 03 4c 4d 35 0d 00 11 4e 45 54 53 55 50 45 52 2d 53 49 ## [91] 51 55 45 49 52 41 0e 00 01 02 03 00 22 58 4d 2e 61 72 ## [109] 37 32 34 30 2e 76 35 2e 36 2e 35 2e 32 39 30 33 33 2e ## [127] 31 36 30 35 31 35 2e 32 31 31 39 10 00 02 e8 a5 14 00 ## [145] 13 4e 61 6e 6f 53 74 61 74 69 6f 6e 20 4c 6f 63 6f 20 ## [163] 4d 35
That shortcut function just calls:
udp_send_payload(Sys.getenv("UBNT_TEST_HOST"), 10001L, c(0x01, 0x00, 0x00, 0x00))
Unlike DNS, the Ubiquiti response payload is not formally documented, but folks on the Ubiquiti forums figured most of it out and we added some additional coverage for more “unknown” fields. We can use the built-in parser for these payload responses to see what kind of device it is and what firmware it’s running:
(y <- parse_ubnt_discovery_response(x)) ## [Model: LM5; Firmware: XM.ar7240.v5.6.5.29033.160515.2119; Uptime: 1.4 (hrs)
Yep, it even has a pretty-printer. Here’s some of what’s under the hood (again, I’ve redacted things you shouldn’t know about since it could harm the target:
str(y, 1) List of 10 $ name : chr "969 - Juvenal Ribeiro" $ model_long : chr "NanoStation Loco M5" $ model_short: chr "LM5" $ firmware : chr "XM.ar7240.v5.6.5.29033.160515.2119" $ essid : chr "NETSUPER-SIQUEIRA" $ uptime : int 118619 $ macs : chr [1:2] "dc:9f:db:3a:xx:xx" "dc:9f:db:3b:xx:xx" $ ips : chr [1:2] "REDACTED", "REDACTED" $ ux0e : raw 02 $ ux10 : raw [1:2] e8 a5 - attr(*, "class")= chr "ubnt_d"
That’s way too much info to be leaking to the internet and 500,000 nodes were gleefully giving it away for anyone that asked.
FIN
I need to add support for UDP timeouts and dynamic response sizes (there’s a temporary hard-coded limit of 4K). There is support for UDP timeouts and dynamic response sizes (provided you give a good enough buffer hint). I tested it on a Windows VM and it does work but more testing would be appreciated by those of you on that platform.
Kick the tyres, file issues & PRs and welcome to the world of UDP in R!
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.