Traditional port knocking

Port knocking is a special security authentication scheme. There is no set standard, and everyone’s implementation is different. Of course, many people have similar ideas and implementations even if they haven’t heard of the term.

For example, WHEN I used a Windows server, I was always unhappy with the remote desktop. It’s not that the service isn’t good. On the contrary, it’s so good that even a stranger who doesn’t know the password tries to access it will draw a window and transmit a picture. The service is so considerate! Attackers can waste server resources and network traffic even if they can’t guess the password. Moreover, the more complex a program is, the more vulnerable it is to a vulnerability such as a buffer overflow, allowing an attacker to take control of the server without knowing the password. It’s not perfect!

In my opinion, certification should be done sooner rather than later. Even a TCP connection is not worth setting up for a stranger who doesn’t know the authentication rules! This benefits both system security (reduces the risk of password explosion and vulnerability attacks) and network security (reduces the risk of malicious traffic consumption and DDoS attacks).

So wrote a simple firewall small program, the default interception of all IP. Only when an IP address accesses the secret port, it is whitelisted to allow further communication. This is a typical port knocking scheme, and the packet that accesses the secret port is the stepping stone.

Of course, using only one port as a code is still a bit crude and easy to crack, so it needs to be strengthened. Examples include multiple ports, UDP packets using specific data, and so on. It is even possible to add more complex authentication information to the packet, which of course requires knocking through a special program rather than simply using system commands to knock manually.

In fact, knocking on doors with programs is a common solution. For example, some companies require employees to install a program to access the Intranet, which may send knock packets to the gateway.

So, does the knocking procedure apply to the public network? Further, can we make Web version?

Public Network Web Knocking

It seems that it is not good to put the knocking program on the public network. After all, this is a security scheme based on confidentiality, and the effect will be greatly reduced after the details are made public, unless the program is strongly protected from confusion.

As mentioned earlier, knocking on doors serves two purposes: to protect system security and network security. Now for the latter.

And we picked up where we left off with the firewall. It was just a toy app, but it worked well and performed extremely well (driver layer filter package). After sharing it with a few friends to try it out, it didn’t take long for someone to figure out how to apply it to the real world — those game servers that are often attacked.

But that’s not easy to do. You can’t ask the player to copy and paste a bunch of CMD commands to manually knock on doors. Let the player download the knock app? This is the equivalent of promoting software, and the cost is not small. Unless the knocker is bundled with a log-in, the game is riddled with variations and logins that are cumbersome to implement.

After a bit of digging around, it turns out that almost all of them have built-in web pages that display announcements and the like. If the implementation of a JS version of the door, so directly let the administrator insert into the announcement page can, even the login do not have to upgrade!

TCP/SYN knocks directly

V1 single port

The first experimental version was very simple, and didn’t even use JS, just hitting a fixed port on the server.

<img src="http://xxx.xxx:12345/logo.gif">
Copy the code

To hide this, the URL does not reveal the server’S IP address, but replaces it with a domain name pointing to the IP address and adding a deceptive path.

In fact, the server discards the SYN knock packet, so the connection cannot be established and the URL path is useless

The effect is very good after trial. TCP, a rogue robot that forged the game’s protocol, could not be set up, let alone logged in; Even if SYN Flood attacks the game port, the server does not respond with an ACK, which greatly saves system resources and traffic costs (outbound traffic is expensive). Both L7 and L4 attacks can be easily defended with this simple and brutal knock on door solution. Of course, unless extremely heavy traffic directly into the server IP into the black hole, there is no solution, but software firewall can not do that.

Although the knocker is open to the public, most people would not think of hiding it on a web page

Of course, it’s only a matter of time before they find out. The scheme was modified several times before it was cracked.

V2 multiport

Knock port only one, feel too shabby. But even if there are more than one, an attacker can always hit them by scanning all the ports.

Therefore, multi-port knocking must be sequential, otherwise it is meaningless.

The server may not receive SYN packets in the same order as the client sends them. Therefore, we can only delay sending to ensure the order as far as possible.

function load(url) {
  new Image().src = url
}

load('http://xxx.xxx:50000')

setTimeout(function() {
  load('http://xxx.xxx:10000')
}, 100)

setTimeout(function() {
  load('http://xxx.xxx:30000')
}, 200)
Copy the code

Of course, this has the effect of increasing knock time.

The onError event doesn’t make sense here. Because the server discards the SYN packet, the client needs to repeat the SYN multiple times to generate a TCP connection timeout error, which can take seconds or even tens of seconds to trigger.

V3 Dynamic multi-port

Multi-port is implemented, but the port number is still fixed and still feels imperfect. So it keeps improving.

If the port number is dynamic, how can the front and back end be consistent? The most obvious is to generate port numbers through time.

var port = gen_port(time())
load('http://xxx.xxx:' + port)
Copy the code

But there is a problem. Not all users are on the right time. If the error exceeds the allowable range, the knock will be ineffective.

Therefore, calibrate the time before knocking. We provide an interface to return the time, or just use the interface that is freely available on the Internet. In order to ensure stability, multiple interfaces are prepared in JS, as long as one is available, it will not fail.

Of course, there are problems with dynamic ports. For example, a server with a hardware firewall may not be usable unless the server can interact with the firewall device. (Cloud firewall of cloud server can modify rules in real time through API.)

UDP/DNS direct knocking

In fact, the first few improvements are of little significance, based on what the attacker has not yet discovered. Yet secrets are always revealed.

How to continue to improve? Consider the problem: if only a few SYN packets can pass through the firewall, it is too easy for an attacker to simulate and too cheap to forge. But if the knock bag can hold more information, then you can carry jS-generated authentication data, which is much more difficult to forge.

But TCP is obviously not good, after all, the port number is only 16bit, can carry too little information, need to send dozens or hundreds of requests to barely transfer authentication data, low efficiency. Therefore, only UDP can be used.

DNS is the easiest thing to think of for UDP-related communication on the Web. In addition, it can carry considerable data to the specified server at a time through NS generic domain name.

var auth = gen_auth()
load('http://' + encode(auth) + '.xxx.xxx.xxx')
Copy the code

However, this scheme has an essential problem: the server receives UDP packets not from users, but from the DNS of the user carrier. So the source IP that the server sees is the carrier DNS IP, not the user IP! What’s the point if you can’t even get the user’S IP.

At that time, I came up with an interesting solution: JS first obtains public IP through some free and public interfaces, and then encrypts it into authentication information. The server ends up using the IP in the authentication message, not the IP of the packet. So you can get the user IP!

jsonp_public_ip_callback = function(ip) {
  var auth = gen_auth(ip, ...)
  load('http://' + encode(auth) + '.xxx.xxx.xxx')
}

load_js('http://xxx.com/get_public_ip')
Copy the code

Of course, this scheme relies heavily on JS code obfuscation. Once the encryption algorithm is cracked, an attacker can easily whitelist any IP.

In the SYN knock scheme, an attacker can also forge source IP addresses and whiten any IP address. However, the attacker can send packets only in a special network environment. UDP/DNS can be used in any network environment

UDP/DNS transfer knock

There is another problem with UDP/DNS knocking: the server needs to have UDP:53 ports open, but some networks may not allow it.

This can only be done through transit. The NS domain name points to our server, we analyze the UDP data and then push the user IP to the game server (the game server keeps a long connection with us). So you don’t have to worry about the network.

TCP/HTTP relay knock

UDP/DNS based knocking still has many problems. JS needs to obtain the public IP address first, and UDP data needs to be forwarded through DNS. These extra links greatly increase the knocking time and the probability of failure.

So ultimately it’s back to a stable HTTP transfer scheme. The principle of this scheme is very simple, there is no SAO operation. JS submits the authentication information to our HTTP server, which we verify and then push to the game server. There is no limit to the amount of information HTTP can carry. In addition to basic authentication information, HTTP can collect a large amount of information such as browser environment, Flash environment, and user behavior to further enhance security defense.

But centralized authentication services are clearly a target for criticism. An attacker can simply take down the service, without having to attack the game server

However, compared with traditional C/S architecture online game services, B/S architecture Web services are easier to defend, and there are many ready-made solutions, such as CDN, WAF, etc. So it’s worth knocking on the door of a more vulnerable game service with a relatively stable Web service.

UDP Port Knocking

With the advent of WebRTC, JS can finally send UDP packets, and can specify IP and port.

var pc = new RTCPeerConnection({ iceServers: [ {urls: 'stun:1.2.3.4:56789'}]}) pc.createDatachannel (') pc.createOffer(function(v) {pc.setLocalDescription(v)}, function() {})Copy the code

However, if you can only specify ports without carrying data, how is that different from a SYN knock packet?

According to the API documentation, the iceServers parameter configures the user name and password. If a user name can appear in a UDP packet, it can carry custom data.

But after many unsuccessful (or perhaps undiscovered) experiments, we changed our thinking.

UDP data knocking

The essence of WebRTC is P2P communication, which enables two Intranet users to connect directly. The general steps are as follows:

1. Each user obtains its own public IP address and hole port through the STUN service

2. Exchange information using a server

3. Two users communicate with each other

The previous code is only the first step, access 1.2.3.4:56789 is only to query the public address. This step is not critical for port knocking. If the address is known, skip step 1 and step 2 and start with step 3.

We can make up a Session Description Protocol (SDP) to deceive WebRTC and pretend to know the other party’s information. Then add the IP address and port to the Ice Candidate, and the browser can send UDP packets!

Unlike the first step, this step sets the UFRAg field and eventually appears in UDP packets in plain text!

SendUDP ('1.2.3.4', 56789, 'Hello_World_1234567890') Async function sendUDP(addr, port, data) { const pc = new RTCPeerConnection() const sd = new RTCSessionDescription({ type: 'offer', sdp: '\ v=0 o= -1234567890 2 IN IP4 127.0.0.1s = -t =0 0 a=group:BUNDLE data m= Application 9 UDP/DTLS/SCTP webrtC-datachannel C = IN IP4 0.0.0.0 a = ice - ufrag: ${data} a = ice - the PWD: a = 0000000000000000000000 ice - options: trickle a = fingerprint: sha - 256 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 a=setup:actpass a=mid:data a=sctp-port:5000 a=max-message-size:262144 ` }) await pc.setRemoteDescription(sd) const answer = await pc.createAnswer() const desc = new RTCSessionDescription(answer) await pc.setLocalDescription(desc) pc.addIceCandidate({  candidate: Candidate :842163049 1 UDP 1677729535 ${addr} ${port} typ SRFLX raddr 0.0.0.0 rport 0 generation 0 uFRAg ${data} network-cost 999`, sdpMLineIndex: 0, sdpMid: 'data', }) setTimeout(_ => pc.close(), 30) }Copy the code

The value can contain 68 letters, digits, #+-/=_, and a maximum of 256 characters. Even with Base64 encoding, it can hold 192 bytes.

Although the capacity is not large, but the compact coding point, or can collect a lot of information on the end. And JS and server direct communication, no need to go through the third party transfer, the link is shorter!

Of course, knocking schemes based on UDP/STUN have some drawbacks. Some carriers lower UDP priorities and even use different public IP addresses! WebRTC is not supported by older browsers, and users of older browsers may have WebRTC disabled.

In addition, knocking may fail after the HTTP/SOCKS proxy is enabled in the browser. Because UDP does not go through the proxy, and the subsequent TCP access through the proxy, the two IP is inconsistent, obviously cannot pass the authentication.

The online demo

You can only access the Test page by clicking the Knock button, otherwise the TCP connection to the Test server will not be established. You can try to capture the package

For demonstration purposes, the knocking service is implemented directly with iptables:

Iptables \ -t raw --append PREROUTING \ -m recent --name knocked --rcheck --seconds 600 \ -j Iptables \ -t raw --append PREROUTING \ -p udp --dport 30000 \ -m string --string "OpenSesame" --algo BM \ -m recent --name knocked --set -j DROP # Default reject all IP iptables \ -t raw --append PREROUTING \ -j DROP # Real-time view whitelist IP watch -n1 \ cat /proc/net/xt_recent/knockedCopy the code

In addition to the recent module, you can use the more flexible ipset module

Of course, there is no parsing of the data, only to determine whether the UDP packet carries a certain code (OpenSesame).

In practice, you can use your imagination with more meaningful authentication information and better encryption.

More Certification Information

The growth of HTML5 has brought more and more interesting apis to the Web. Examples include WebGL, WebAssembly, the Web Crypto API, and so on, all of which can participate in security defense.

For example, WebGL2 calls GPU to realize the proof of workload, so that the attacker needs a lot of computing power to generate knock authentication data, but cannot generate it in large quantities.

For example, WebAssembly confuses the generation logic of knock data, making it possible for DDoS attackers to master binary reverse engineering.

If an attacker needs to perform complex and time-consuming JS to get an IP onto the firewall whitelist, then we’re done.

More Knocking solutions

The Web is still evolving; Chrome’s QUIC protocol, for example, also uses UDP for communication, although it’s not yet available for knocking on doors. Will there be more improvements to HTTP/3 in the future, opening up more powerful network communication capabilities? Wait and see…