Root > Articles > Hacks >

Bruteforcing tailnet "fun names"

24 Nov 2022 • Yousef Amar • 2 min read

I quite like Tailscale and tools like it that let you set up a private network for all your devices quite easily. Tailscale also lets you provision certificates for if you want to expose some services to the web over HTTPS. But once you do that, your tailnet name is locked in.

By default this name is tail plus a few random hex characters, but they also let you roll for what they call "fun names" which are two English words, usually animals, but sometimes other words, usually math-related. I got "king-ratio" at one point, which is quite memorable, but then replaced that with "han-hen" which is shorter and somewhat more poetic.

However, seeing that one of the suffixes is "yo", I was wondering if anyone ever tried to bruteforce the combination they want?

I reckon "king-yo" would be pretty sweet for me! The names are generated server-side (with a token, so you can't just send them an arbitrary name to rename to) but the requests look like they can be automated. So I did that. All you need to do is find the right requests, then copy as Node.js fetch. It only took a few minutes, which is shorter than I would have spent pressing the roll button, so I didn't fall into this trap.

Here's the code (with my cookie removed) where I search for names that contain things I might want, like cool words or part of my real name:

const fetch = require('node-fetch');

(async () => {
  while (true) {
    const res = await fetch("https://login.tailscale.com/admin/api/tcd/offers", {
      "headers": {
        "accept": "application/json, text/plain, */*",
        "accept-language": "en-GB,en;q=0.7",
        "cache-control": "no-cache",
        "pragma": "no-cache",
        "sec-fetch-dest": "empty",
        "sec-fetch-mode": "cors",
        "sec-fetch-site": "same-origin",
        "sec-gpc": "1",
        "cookie": "PUT YOUR TAILSCALE COOKIE HERE FOR AUTH",
        "Referer": "https://login.tailscale.com/admin/dns",
        "Referrer-Policy": "strict-origin-when-cross-origin"
      },
      "body": null,
      "method": "GET"
    });

    const data = (await res.json()).data;

    for (const tcd of data.tcds)
      if (tcd.tcd.includes('-yo')
	    || tcd.tcd.includes('ya-')
	    || tcd.tcd.includes('-am')
	    || tcd.tcd.includes('king-')
	    || tcd.tcd.includes('great-')
	    || tcd.tcd.includes('ratio')
	    || tcd.tcd.includes('ordinal')
	    || tcd.tcd.includes('tonic')
	    || tcd.tcd.includes('royal')
	    || tcd.tcd.length < 'xxx-xxx.ts.net'.length)
        console.log(tcd);

    await new Promise(resolve => setTimeout(resolve, 500));
  }
})();

I also added a half-second delay so as to not spam Tailscale's servers or get rate limited. Then, once I found a name I liked, I set it with this fetch:

const { tcd, token } = {
  tcd: 'awesome-name.ts.net',
  token: 'awesome-name.ts.net/...'
}

fetch("https://login.tailscale.com/admin/api/tcd", {
  "headers": {
    "accept": "application/json, text/plain, */*",
    "accept-language": "en-GB,en;q=0.7",
    "cache-control": "no-cache",
    "content-type": "application/json",
    "pragma": "no-cache",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-origin",
    "sec-gpc": "1",
    "x-csrf-token": "PUT YOUR CSRF TOKEN HERE",
    "cookie": "PUT YOUR COOKIE HERE",
    "Referer": "https://login.tailscale.com/admin/dns",
    "Referrer-Policy": "strict-origin-when-cross-origin"
  },
  "body": `{"tcd":"${tcd}","token":"${token}"}`,
  "method": "POST"
});

It wasn't actually awesome-name.ts.net, but I won't tell you what the real one is so you don't target me! Clue: it's very short.