Think about the services that only a few people need to access, but they need to be accessible from anywhere, over the internet. The internet is a giant untrusted monster network, full of evil people actively trying to harm you. If only a few people (let’s say in the ten-thousands) need to access your service, why do you make the service accessible to billions? It’s so asymmetrical.
VPN Gateways
VPN Gateways! They’re great! They sit on the edge of your network, and they let you access all your internal stuff! But if you want someone to be able to connect to your VPN over the internet, the VPN gateway itself must be publicly accessible. Yes, you likely have authentication on your gateway such as an IPSec Pre-Shared Key, and/or a username and password. But your gateway is still internet-facing, and let’s just say there are no guarantees for security.
- https://nvd.nist.gov/vuln/detail/CVE-2024-21888
- https://nvd.nist.gov/vuln/detail/CVE-2023-20269
- https://nvd.nist.gov/vuln/detail/CVE-2021-26109
- https://nvd.nist.gov/vuln/detail/CVE-2024-22394
- https://nvd.nist.gov/vuln/detail/CVE-2024-21894
- https://nvd.nist.gov/vuln/detail/CVE-2024-3400
- https://nvd.nist.gov/vuln/detail/CVE-2023-27997
- https://nvd.nist.gov/vuln/detail/CVE-2024-21762
- https://nvd.nist.gov/vuln/detail/CVE-2022-3236
- https://nvd.nist.gov/vuln/detail/CVE-2024-21893
- https://nvd.nist.gov/vuln/detail/CVE-2022-42475
This isn’t even all of them, but I got rate-limited by NIST’s webservers for opening too many pages in a few minutes as if I’m some kind of scumbag attacker trying to DDoS them or something. Newsflash: there are just a lot of vulnerabilities for these services, and when they come out publicly, the likelihood they will be exploited is high.
Having authentication built into your service is not enough. We need to do better. We need authentication before someone can even connect.
Portscanning, Fingerprinting, Profiling
When your service is exposed on the internet, anyone can connect to your service’s port. There are threat actors out there who routinely perform port scans across the entire internet and attempt to connect to common or sometimes every port, and when they can perform a connection, or elicit some kind of response from your system, they can use that to identify what service you’re running. If your service responds to a connection and request in some kind of unique way, the threat actors can use this to fingerprint your service, and identify exactly what it is. They can build a profile of the entire internet.
Perhaps there is no publicly known vulnerability now, but perhaps a new zero-day vulnerability is discovered in private, it would be quite easy for a threat actor to jump into action with their database of the services available on the internet.
A Solution
I’ve been obsessed with this problem for a few years now (proof), and one solution I’ve come up with is to watch for a pattern of incoming packets, all of which get dropped by the firewall, leaving no trace or response to each. Once the specific pattern is observed, then we know the source of that traffic is trusted. The way we identify the pattern is based on the destination UDP ports of four individual packets. These four UDP ports make up what is called the authentic knock sequence.
To improve upon this, the authentic knock sequence must be rotated or changed, otherwise, anyone wiretapping your connection can just repeat the same pattern, performing a replay attack. To work around this problem, we can use an existing proven standard, TOTP: the same technology used in multifactor authenticator apps such as Duo, Okta, Microsoft Authenticator, Google Authenticator, etc.
Using the TOTP algorithm, we can generate enough entropy to generate four random UDP ports, as seen below.
// A loose implementation of hotp meant for our specific purposes of generating
// four random port numbers. Accepts a base32 encoded shared secret and a time
func GeneratePorts(sharedSecret []byte, t time.Time) (ports [4]uint16, err error) {
// 30 second key rotation
movingFactor := uint64(math.Floor(float64(t.Unix()) / float64(30)))
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, movingFactor)
// calculate hmac and offset
mac := hmac.New(sha1.New, sharedSecret)
mac.Write(buf)
sum := mac.Sum(nil)
offset := sum[len(sum)-1] & 0xf
// deviation from RFC4226's dynamic truncate and modulo reduction algorithm
// we don't need base10 human friendliness and instead just care about 64 bits / 4
// which represents 4 UDP ports
ports = [4]uint16{
uint16((int(sum[offset]) & 0xff) << 8),
uint16((int(sum[offset+1] & 0xff)) << 8),
uint16((int(sum[offset+2] & 0xff)) << 8),
uint16((int(sum[offset+3] & 0xff)) << 8),
}
return ports, err
}
This can be used, along with a pre-shared key and the clock to keep a trusted client and network edge device such as a firewall in sync, both knowing what the current authentic knock sequence is.
I have created a working implementation of this concept that I call “Hide Your Ports (hyp)”. hyp will ensure no threat actors can fingerprint your services on the internet that are accessed by only a few people. hyp enables you to authenticate traffic before traffic can even flow to your service. hyp itself cannot be fingerprinted.
Project Links
Check out the source code, and the releases page: https://code.stevenpolley.net/steven/hyp
And here’s a few YouTube Videos