Skip to main content

Sniping CDN IP Addresses

I had a bit of a problem when I started using multiple ISPs. One of them had great connectivity to a lot of CDNs such as Google’s GGC and Facebook’s FNA but terrible transit. The other one had great transit but crap CDN connectivity. So I wanted a way to somehow pull the addresses of the CDN nodes. For brevity, let’s call the CDN ISP Alice and the Transit ISP Bob

I could route www.youtube.com to Alice for example but Google would give me the GGC nodes of Alice. My router doesn’t know these addresses at all so it’d end up going through transit via Bob. Which is not desirable. I want the GGC nodes of Alice to go through Alice. But this was proving difficult. Google (and other CDN providers) do not provide a list of servers. The thing is, GGC is hosted behind the ASN of the ISP themselves, not behind Google’s ASN. So I can’t just route Google’s entire ASN to Alice and call it a day. So how do you mediate such an issue?

Enter masscan and TLS certificates

Let’s look at this from a hacker’s point of view. Time to do some reconnaissance.

  • I know all the CDN edge nodes I want have port 80 and 443 open
  • I know they are hosted behind the ISP’s ASN
  • We could use RIPE’s API to query all the IP ranges that are currently announced from that ASN

This info was easy to find and is just common sense. So let’s dig deeper. Port 443 is open, remember? Port 443 is almost always TLS. And TLS certificates are quite underrated, you can find a lot of very useful and juicy info hidden in them. Let’s look at the certificate of one of EarthLink’s GGC nodes, rr2---sn-x5guiapo3uxax-cbflr.googlevideo.com.

Interesting, right? Note the the presence of the *.googlevideo.com Common Name… We can use that to effectively identify the GGC nodes.

I wrote a terrible Python script that reads the IP ranges of a given ASN, explodes the ranges into individual IPs and writes them to a file called addresses.txt.

import requests
from ipaddress import IPv4Network
import sys

asn = sys.argv[1]

prefixes = requests.get(f'https://stat.ripe.net/data/announced-prefixes/data.json?resource={asn}').json()['data']['prefixes']

addresses = []
for prefix in prefixes:
    if ':' in prefix['prefix']:
        continue

    for addr in IPv4Network(prefix['prefix']):
        addresses.append(str(addr))

addresses = list(dict.fromkeys(addresses))

with open('addresses.txt', 'w') as f:
    for addr in addresses:
        f.write(f'{addr}\n')

The check for : in there is to remove all IPv6 addresses since my ISP unfortunately doesn’t support IPv6. We then remove all duplicate addresses with addresses = list(dict.fromkeys(addresses)) because you will end up with a huge amount of duplicate addresses. This is due to the ISP announcing a /16 range and then individual /24’s of that /16 for redundancy purposes. This is outside the scope of this post.

So.. Now what? We have every single address that’s announced by the ISP to the world. We’re gonna need a quick way to shift through them 1 by 1 and check their TLS certificates. Fortunately, masscan can do just that. Well I kind of lied, it can only fetch the TLS certificate, it won’t check for any strings inside it. However, we can use jq for that.

The masscan commands is as below:

masscan -iL addresses.txt -p 443 --open --rate 5000 --retries 2 --banners -oD hosts.json

We want masscan to read from addresses.txt, target port 443, only perform banner check on open ports and retry only 2 times then write the result to hosts.json.

Running this will give us a (relatively) massive json file with certificates with each json entry being a TLS certificate. For example

{"ip":"65.20.153.191","timestamp":"1683634606","port":443,"proto":"tcp","rec_type":"banner","data":{"service_name":"X509","banner":"MIID1jCCAr6gAwIBAgIEEDawrDANBgkqhkiG9w0BAQsFADCBrDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMREwDwYDVQQHDAhTYW4gSm9zZTEfMB0GA1UECgwWVWJpcXVpdGkgTmV0d29ya3MgSW5jLjEaMBgGA1UECwwRVGVjaG5pY2FsIFN1cHBvcnQxHzAdBgNVBAMMFlVCTlQtMjQ6NUE6NEM6MzI6NDA6NjcxHzAdBgkqhkiG9w0BCQEWEHN1cHBvcnRAdWJudC5jb20wHhcNMTgwNTIzMTMyNTAwWhcNMjMwNTIzMTMyNTAwWjCBrDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMREwDwYDVQQHDAhTYW4gSm9zZTEfMB0GA1UECgwWVWJpcXVpdGkgTmV0d29ya3MgSW5jLjEaMBgGA1UECwwRVGVjaG5pY2FsIFN1cHBvcnQxHzAdBgNVBAMMFlVCTlQtMjQ6NUE6NEM6MzI6NDA6NjcxHzAdBgkqhkiG9w0BCQEWEHN1cHBvcnRAdWJudC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCXzafYJ6kcwIaJr6HwRcozeGj+bOWZ7L4PlTo2DcbtTS+StutCOpeEX52XEJf1PVFUp24yVHmP5OCi1OPny3kTLz1uLzb9Yd/7H6JMGlU90MlirriUD/2M22hUs0f7Tz0a62+gkJbnhO7bYpdZ4aG47Dm5YscTJn+2ObVq8I2Fk+/IHconAsAAyM7CnL7bjgLk8cw1jbYEqv04ieiwSgqL4RKuJ7YQirdZrQBBegliE+Db/VUeccBN0p67oKzdyBAn2pssFo7jvgNd92zwTFNy92VXXQJkdXPKnoEBqPAk6OjDnDWJwG6OQ+vm76KaWb4j3Yd8itN33ObuAeyCmBxtAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAHlaQ7R/a1QhZ1XhOgExU9dgAPnXTPmHNzvoSoUQkxybkhVYgD9whNqSBuIm9tLoE+kEsLAAW/lCWccdsffHBzMRcmgOjWd+TvoG+PKY7rfAQx/C4WgukVCxL15pbNdqz6r3R8xCpCUWsZBWraHGcJlmkmvUH8MDRf8gFMhWZ+/qqLEKDANwG/WPtRmKfqQtq/xlckbj/ap5mlf9U7czqGt3gYddAA+FYuPzUmD1zRlAOQUZWh4JV0utzbwePUNmvkEfmIAKbkWh4RYTGyyLWmp36N0sWwmRJoaz5b7HoL263YNLanf50E6SIzRDpo2ecpRfkQ/5sGP5jVv5Qh/xwOg="}}

is the result of running masscan on one of the port 443 IPs. But this is unreadable and we still have no clue whether this is GGC, FNA, CDN77 or whatever trendy CDN it is. Fortunately, we have the results offline and now we can do a clean sweep of all the entries using jq with the following command:

jq -r 'select(.data.banner | @base64d | test("googlevideo.com";"i")) | .ip' hosts.json  > ggc.txt

This will take hosts.json, Decode the base64 data inside banner’s value and check if there’s a string inside that banner containing googlevideo.com. If yes then write the value of ip to ggc.txt.

And there you have it! For FNA, you just need to replace googlevideo.com with fbcdn.net and you’re done. It all boils down to a few steps:

  1. Get all the IPs of the ISP
  2. Scan for IPs with port 443 open
  3. Grab their TLS certificates
  4. Check for relevant strings inside that TLS certificate like the CN or the SAN.

Note that this approach is quite loud and it might be illegal depending on where you live and/or how nice your ISP is. If you want to be less noisy, reduce the rate in the masscan command.