Show HN: Ruroco – like port knocking, but better (github.com)
ruroco (RUn RemOte COmmand) is a tool that lets you execute commands on a server by sending UDP packets (instead of knocking on ports).
the tool consist of 3 binaries:
- client -> runs on your notebook/computer and sends the UDP packets
- server -> receives the UDP packets and makes sure that they are valid
- commander -> runs the command encoded by the data of the UDP packet if it's valid
The commands are configured on the server side, so the client does not define what is going to be executed, it only picks from existing commands.
I use this tool to open up the SSH port on my server via ufw, but only for the IP address from where I'm connecting, so the SSH port appears closed for everyone else, except me.
This is my very first "real" rust project, so any feedback is highly appreciated :)
Enjoy!
106 comments
[ 3.6 ms ] story [ 213 ms ] threadThanks for the feedback!
Also your headline claims that your system is "better", but it fails to explain why. Modern port knocking also incorporates secure cryptographic hashes.
that's because I run my ssh on port 80, but that's not standard, so I agree that it's confusing. Thanks for pointing it out. I will fix it :)
Are you referring to fwknop? Thats not "port knocking" but Single Packet Authorization. That is very different from port knocking.
How can one incorporate secure cryptographic hashes with simple port knocking?
Do you mean: client uses private key to sign -> server uses public key to verify?
My understanding is private keys decrypt/sign and public keys encrypt/verify. Either your usage, your docs, or my understanding seem to be wrong. I think I'll stick with fwknop for now.
Hence you always use the private key for decrypting or for signing. But other than switching which key to use signing and encrypting are the same thing
Hybrid asymmetric signing is: hash the payload, then use direct asymmetric encryption/signing to encrypt/sign the hash with the private key.
Hybrid asymmetric encryption is: encrypt the payload with symmetric encryption (e.g. AES) with a random key, then encrypt the random key with direct asymmetric encryption using the public key.
As you can see, with hybrid asymmetric cryptography, there's a difference between signing and encryption besides the public vs private key difference.
and decrypt with the public key, see https://github.com/beac0n/ruroco/blob/ce766751b51c8ff6246a2b...
using RSA, one can easily derive the public key from its private key (see https://security.stackexchange.com/questions/172274/can-i-ge...), that's why the private key is kept safely on the client
Also for SSH the public key is also stored on the server, while the private key is kept safely on the client, see https://www.ssh.com/academy/ssh/public-key-authentication#ke...
SSH can also be used with RSA: https://www.ssh.com/academy/ssh/keygen#creating-an-ssh-key-p...
The original (authorized) sender would then think something went wrong (packet loss), send a new packet and be none the wiser.
Port knocking is IMHO just to keep your sshd logs clean from huge lists of failed attempts, that prevent me from actually finding interesting information in them.
Ruroco can be used for more than just keeping sshd logs clean, for example I could also enable a service other than ssh, for example a private file server that I want to get access to when I'm on my phone (although I haven't implemented an android version yet, it should be doable).
Spoofing TCP is useless - you actually need to receive all the packets sent to the original TCP, which means either you are already on the receiving path, or managed to put yourself on it e.g. through a BGP route advertisement - either way, it leaves some trail and much harder to carry out.
(And even so, the attacker still has to go through SSH authentication or an SSH vulnerability)
See: https://en.wikipedia.org/wiki/TCP_sequence_prediction_attack
It's not practical but it is possible. It was more effective in the past when operating systems had more predictable initial sequence numbers. Famously this is how Kevin Mitnick (allegedly?) attacked Tsutomu Shimomura.
IIRC even SYN cookies are older than 20 years at this point.
RST attacks in particular are common enough to make TCP completely unsuitable for reliable long-term connections. And since TCP is also unsuitable for short-term connections, that leaves UDP the only option.
Now, RST attacks are still a thing, but mostly irrelevant to this port knocking alternative.
What do you mean "original TCP"? I'm talking about an attacker creating a new TCP connection with a spoofed source address.
> which means either you are already on the receiving path
Yes, I believe that's the threat model under discussion here. Tepix mentioned an attacker who can intercept a packet, which I believe means the attacker is already on the receiving path.
https://github.com/beac0n/ruroco/blob/ce766751b51c8ff6246a2b...
The encrypted information is current time, command and random data. So the server could feasibly detect that a retransmission has occurred but that's about it.
This doesn’t feel like an unpickable lock so much as a decorative cover that makes the lock less obvious.
Thanks for the feedback! It doesn't describe how it prevents that attack, because it doesn't prevent this attack :).
As someone else wrote, I could put the IP address of the sender into the encrypted data and validate that in the backend and drop the packet + block the IP address.
I will add that in the next release!
But if an adversary uses the SAME network, then the IP address that the server sees will be the same for the client and the adversary, so it only matters if the adversary takes the packet and sends it from a different network, which the adversary won't have to do, because they still control the network where the packet was originally sent from.
Instead of using UDP he reads the firewall log. Also he prevents replay attacks (even though his implementation is apparently not secure in this regard). Unfortunately his code is ancient and in Python 2, so a rust implementation would be awesome.
Hah! I'd never seen this before, but in a fit of pique driven by ssh scanning one day I did a similar pattern on my OpenBSD router: I added a block in log to a high port in pf.conf, wired up a little shell script that did tcpdump on pflog0 and watched for a packet to come in, then added the IP to the allow port 22 table.
I knocked on the door three times, two of which were testing, and ended up throwing it all out in favor of wireguard & making SSH no longer listen on em0, which seems infinitely less silly.
I agree that ultimately, with wg in the kernel, this is a much simpler setup.
IOW, why ever down the connection? Why not start your tunnel immediately when the network comes up and leave it running until the network goes down?
Wireguard interfaces are _cheap and easy_ - there's no reason not to set up an interface for normal client traffic that sshd doesn't listen on, and an interface for just sshd with different ACLs and routing logic if you want.
This is why you sometimes have to enable PersistentKeepalive on peers that are behind NAT and are calling in to the server. Without them keeping up the connection NAT would simply close it down and you wouldn't be able to connect.
Why's that?
E: oh, if you aren't familiar with OpenBSD I might get the confusion – pflogd/the kernel (not me!) watches the actual network device and dumps to a file. So the actual "knocking" daemon I bodged together is one part running as a privsep user watching a log file and giving IPs to the other part which just adds to the pf table allowing access.
My threat model didn't include people who can fuck with pflog(4) to attack tcpdump(8) - I'm sure they're out there, and if they wanted to be they'd already be in my network (or already are).
I used to use port knocking, but at some point found myself in a hotel where they blocked ALL ports, except TCP 80 and 443 (did not check UDP at the time).
My ssh port is on 80, so I can use all of my tools, even if the network I'm in blocks everything else.
Ultimately reading firewall logs to do port knocking is most secure way, because - as you said - there is virtually no attack surface.
I would argue that port knocking is extremely inconvenient and does not work in every scenario. So for me it's a tradeoff between "ultimate" security and convenience.
Instead of knocking on ports, send actual HTTP requests to different paths. Over TLS or just plain HTTP.
So where you’d port knock a sequence of ports here instead you send GET requests to some different, publicly known paths
GET /index.htm
GET /about_us.htm
GET /about_us.htm
GET /index.htm
GET /about_us.htm
GET /products.htm
You get the idea.
And now then the challenge is that if you’re on a network that does HTTP caching, it would interfere with this.
But we already have the well known cache-busting technique for that right, so
GET /css/main.css?ver=64729929
GET /js/bundle.js?ver=947367292
GET /js/bundle.js?ver=7483939
And so on. And version is for example current Unix time and is actually ignored in terms of “knocking”. Only the path matters.
ruroco DOES prevent replay attacks, by saving the deadline (which is in ns) in a blocklist. It does not matter if the deadline has "passed", the deadline is added to the blocklist as soon as the packet reaches the server and is deemed "valid". So each packet is only valid exacly ONCE
> The request is encrypted using AES in CTR mode, with an HMAC-SHA1 using the authenticate-then-encrypt paradigm.
A mere three months later, he would publish The Cryptographic Doom Principle [2] (dated 2011-12-13).
[1]: https://github.com/moxie0/knockknock/commit/e24eb33f666fc092...
[2]: https://moxie.org/2011/12/13/the-cryptographic-doom-principl...
http://www.thoughtcrime.org/software/knockknock/ has been excluded from the Wayback Machine, and archive.is is captchawalled.
Its value is in the simplicity of the crypto protocol used for this - basically "hash a password with a one time pad".
Although far more heavyweight and barely relevant to this discussion since it's not hidden, sometimes it's useful to be able to do things remotely in an emergency without a private key.
I would argue that it does improve security in the way that it reduces the attack surface of potential vulnerable services, because they are simply not accessible for adversaries.
On the other hand, having another tool running increases the attack surface, but imho that's very small.
But like I wanna stress that I like ruroco and I might end up using it to decrease the internet noise on my home lab, but I'm just worried that someone might end up relying on ruroco instead of proper SSH security
Once this UDP packet reaches the server the deadline will be added to the blocklist.
If an attacker sends the same packet again, the server will check its blocklist for the deadline. It does not matter if the deadline has been reached or not. once the packet reaches the server, the deadline of that packet will be added to the blocklist.
https://github.com/mrash/fwknop
And what have you got to protest against DoS attacks on your packet inspection mechanism?
I have no protection against DoS attacks, but I'm working on it (there is also a WIP in the README about that :) )
It seems there are two sides of the spectrum for secure SSH access:
+ Relatively infrequent access by limited # of people to servers which are not top targets for attacks. Solutions like the one above are great for this.
+ More frequent, more users, more sensitive servers. Close all the inbound ports, permanently. Example: https://github.com/openziti-test-kitchen/zssh (or with an integrated OIDC like KeyCloak - https://youtu.be/NZJtzSoS_g0?si=Qg6p6Hdkaq1ahefg)
[1] https://tailscale.com/tailscale-ssh
[2] https://atsign.com/resources/articles/close-port-22-forever-...
Thats exactly what I'm using it for - I'm the only one on my server :)
I have, but I do not want to run a VPN solution on my private sever, for which I barely have any need. Also Wireguard, although VERY secure is still not "simple" software.
In addition there are usecases where Wireguard would not help, for example when I want to open up an http service for the current network that Im in.
You can definitely run an HTTP server behind WireGuard. WireGuard just adds a network interface that your server can listen on (e.g. your server would listen on a private address like 10.0.0.1).
And Wireguard does use quite a bit of CPU if you are using a lot of network bandwidth. Small servers don't have that much compute power, so utilizing the port knocking somewhat removes that issue.
I suspect that RaspberryPi or old Intel NUC also would not be able to handle speeds anywhere near gigabit.
If you're using the Ruroco client to proxy requests to the server, then you could do the same with WireGuard. You could have HAProxy (or something similar) proxy requests from your local network to the WireGuard interface.
So instead I would go to my friend's house, connect to their Wifi, and accept incoming connections from computers and TVs over the local area network and forward them through WireGuard. The TV would connect to my device via plain HTTP (which is fine since it's all happening locally), and then my device would be responsible for securely connecting to the server via WireGuard. This also has the benefit of implicitly revoking access as soon as I leave their house with my device.
TLS is not mandatory in all cases, but if you want to use it, it is not an issue having a certificate. Certificate itself has nothing to do with DNS beyond the verification step.
And even then you do not have to open any ports, even to letsencrypt verification, since you can use DNS verification method instead (for example using Cloudflare API).
And there can be a public DNS record but it doesn't say anything about ports. And the CT transparency log doesn't say anything about ports or IP addresses.
> TLS is not mandatory in all cases, but if you want to use it, it is not an issue having a certificate.
TLS is mandatory if you want to ensure your connection isn't being eavesdropped on or tampered with.
> And even then you do not have to open any ports, even to letsencrypt verification, since you can use DNS verification method instead (for example using Cloudflare API).
Yes this is called the ACME DNS challenge. I've used it many times, but if you want to be able to type in your server's domain name on a friend's TV and have it resolve to your server's IP address, then you'll need a public DNS record.
> Certificate itself has nothing to do with DNS beyond the verification step.
Yes it does, because most certificate authorities only sign certificates for fully qualified domain names, not IP addresses [1], so it obviously does involve the domain name system (you can view the domain name associated with a certificate in your browser, it'll be listed under the field "common name"). On a desktop computer you could get around this by editing the hosts file to map any domain name to any arbitrary IP address, but you can't do that on a TV.
> And there can be a public DNS record but it doesn't say anything about ports. And the CT transparency log doesn't say anything about ports or IP addresses.
I never said anything about ports, I said a public DNS record exposes the server's IP address. The whole point of the domain name system is to convert domain names into IP addresses, so that you can actually use the internet protocol to connect to a server's IP (internet protocol) address. Try running dig from the command line on literally any domain name and watch it expose the server's IP. Malicious bots can watch the transparency log to find out about new certificates [2], then run dig or some other tool to issue a DNS request (to determine the IP address of the server), and then start hammering the server to search for vulnerabilities or even potentially to just DDOS it. If you use a wildcard certificate it's harder for attackers to figure out which subdomain to query to find your server's IP, but with WireGuard I don't need a domain name at all.
[1] https://community.letsencrypt.org/t/ssl-on-a-ip-instead-of-d...
[2] https://community.letsencrypt.org/t/suspicious-web-traffic-a...
Yeah and I have a lock on my door. I still won't post my address on HN. It's all about defense in depth.
Another thing I forgot to mention is that port knocking only works if your hypothetical friend has a dedicated static IP. If your ISP decides to reassign your IP address then IP allowlists are useless. Many ISPs in the US run this racket where they make you pay for a "business account" to get a dedicated IP, even if you only want an IP6 address (which are plentiful).
But your use-case with http server is a good one. For similar cases I have used custom forward-auth service, but that still requires to have the web server accessible, while your solution hides it completely.
The way I pronounce it is with long vowels.
So in an extreme way it would be ruuurooocooo :)
Super interesting! From looking at the readme, it looks like the configuration isn't specific to ssh either; I assume you could use it for any service that exposes a port.
The only difference is, knocking is via tcp as it makes unique challenges instead of this repeatable udp packet.
Kind advice from my PoV:
Your comment could be read as "your project is shit, there is ostiary which has replay protection and yours doesn't".
I'm sure you didn't intend for you comment to not come across that way, and I also did not read it that way, but others could have.
Also keep in mind that ruroco is a very young project and is by no means finished. I was thinking about using one-time-pads or other encryption algorithms as well. I also posted this here to get feedback to improve my project.
So hopefully when I release version 1.0.0 all the issues that this project has atpit are resolved ;)
A replay attack can only make the server do whatever the legit client intended it to do, just up to [timeout] seconds later.
the deadline that is sent from the client is being added to the blocklist after the command was executed, so sending the same packet again will not work, because the deadline (which is in nanoseconds) is already on the blocklist and therefore the command will not be executed again.
This effectively means that replaying a packet is not possible, because the server will deny it.