I recently came across a situation where I had to download my SSH public key over the internet, but without common tools like netcat or a webserver. Therefore, I decided to download the key via DNS and I'll describe my method in this blogpost.
SSH keys are useful when you don't want to login into a remote machine (usually a server) with a password, but with a cryptographically stronger authentication method. A properly configured OpenSSH will check a user's public keys against the ~/.ssh/authorized_keys
.
The SSH key is usually generated using the ssh-keygen
command. Let's generate a new keypair for this blogpost.
$> ssh-keygen -t rsa -f dnskey -b 4096
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in dnskey.
Your public key has been saved in dnskey.pub.
The key fingerprint is:
SHA256:/cjFYFr11/8teGpsLrvkM96no7Wbp5asnvcscj9+YMc gehaxelt@LagTop
This will create two files in your current directory:
dnskey
- The private keydnskey.pub
- The public key
$> cat dnskey.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCduVhp18zQrC6wRub25qNAz9FLuMKNUVwA+zvJ3/ypckylFyt7wC5ISWPdZxTcIfOHxeXzSbmFJwt/xay/M5SSw64VbznsVuphxKJOosUlUyJhpySI0zf4ceAXB2VX2U+z6DW8dJQKPW0sZdH6IIOYxbc8dbTTkMr2Esx29DqwwTvQe72IhDa8AsHTSYpM1Lx8bILiru2SItTTpLoW/aev9sYz6rGty48TGP62CB5xsi2R3peAzJFHdlFC5iC1eSEc/3zJhr6Ez4NYYQsjy/I/SgJ7WypRBovk+UKUpLAwcFwrZXh/o1bO+kM1VEmlPn5qOZccZ8QxsItCNvr7w0mEBPGkySZ0DmrGgtAnI0xyra/1iiapDaqv6pQB82tyMPps8HAhMnMgTmTM9hINsvVvnJfYV+gbfZtW9OYakBpA+xRn6MyCz4Hbu0cyz9oJoGERxeqLMNNp4Tlw8ECUyWvm1CotcLTkbfamZb5pk1Z3kM41xXf8gxxEMf5Avjc4ipDpVqeEfXffD96Kz4MpHzO2eFFNbBeXgiXCViXGbvqybUW2VEW3ah1/8GB3rHI3JRmAbGlNMyyEI6PH1Rr9m6JTDfG4vOvFxGBNf95lggNNlVtdiDU0vAEXNfkZBkK/wGK0yl7BtngTFnt7XosI2o88wWLi6D8ZAkjxGRWz7YusPQ== gehaxelt@LagTop
$> sha256sum dnskey.pub
e46041fc3182446f7c464c96abd5749a68f5fd55839bff490800ccb2770823df dnskey.pub
The private key should be kept secret, but the public part can be published anywhere - e.g. uploaded to your server or the domain name system. And this is exactly, what we are going to do!
Login to your DNS registrar/provider and create a new text (TXT
) record that contains your public key:
ssh.key IN TXT [RSA_PUBKEY]
We can look at the result with dig
. Your DNS registrar/management interface will split the the quite long string into multiple chunks:
$> dig TXT ssh.key.neef.it +short
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCduVhp18zQrC6wRub25qNAz9FLuMKNUVwA+zvJ3/ypckylFyt7wC5ISWPdZxTcIfOHxeXzSbmFJwt/xay/M5SSw64VbznsVuphxKJOosUlUyJhpySI0zf4ceAXB2VX2U+z6DW8dJQKPW0sZdH6IIOYxbc8dbTTkMr2Esx29DqwwTvQe72IhDa8AsHTSYpM1Lx8bILiru2SItTTpLoW/aev9sY" "z6rGty48TGP62CB5xsi2R3peAzJFHdlFC5iC1eSEc/3zJhr6Ez4NYYQsjy/I/SgJ7WypRBovk+UKUpLAwcFwrZXh/o1bO+kM1VEmlPn5qOZccZ8QxsItCNvr7w0mEBPGkySZ0DmrGgtAnI0xyra/1iiapDaqv6pQB82tyMPps8HAhMnMgTmTM9hINsvVvnJfYV+gbfZtW9OYakBpA+xRn6MyCz4Hbu0cyz9oJoGERxeqLMNNp4Tlw8ECUyWvm1C" "otcLTkbfamZb5pk1Z3kM41xXf8gxxEMf5Avjc4ipDpVqeEfXffD96Kz4MpHzO2eFFNbBeXgiXCViXGbvqybUW2VEW3ah1/8GB3rHI3JRmAbGlNMyyEI6PH1Rr9m6JTDfG4vOvFxGBNf95lggNNlVtdiDU0vAEXNfkZBkK/wGK0yl7BtngTFnt7XosI2o88wWLi6D8ZAkjxGRWz7YusPQ== gehaxelt@LagTop"
So we can't just redirect the output into a .ssh/authorized_keys
file, because of injected "
and " "
characters. Running the output through sha256sum
will also tell us that the keys differ. (Compare the hash below with the one above.)
$> dig TXT ssh.key.neef.it +short | sha256sum
33b00c47d5f5c774e340b6e1e050dbbd8aabfcdcb5cdf9c4edc0eeafcc0fe253 -
Luckily, we can use some sed
-magic to remove the superfluous characters:
$> dig TXT ssh.key.neef.it +short | sed -e 's/" "//g' -e 's/"//g'
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCduVhp18zQrC6wRub25qNAz9FLuMKNUVwA+zvJ3/ypckylFyt7wC5ISWPdZxTcIfOHxeXzSbmFJwt/xay/M5SSw64VbznsVuphxKJOosUlUyJhpySI0zf4ceAXB2VX2U+z6DW8dJQKPW0sZdH6IIOYxbc8dbTTkMr2Esx29DqwwTvQe72IhDa8AsHTSYpM1Lx8bILiru2SItTTpLoW/aev9sYz6rGty48TGP62CB5xsi2R3peAzJFHdlFC5iC1eSEc/3zJhr6Ez4NYYQsjy/I/SgJ7WypRBovk+UKUpLAwcFwrZXh/o1bO+kM1VEmlPn5qOZccZ8QxsItCNvr7w0mEBPGkySZ0DmrGgtAnI0xyra/1iiapDaqv6pQB82tyMPps8HAhMnMgTmTM9hINsvVvnJfYV+gbfZtW9OYakBpA+xRn6MyCz4Hbu0cyz9oJoGERxeqLMNNp4Tlw8ECUyWvm1CotcLTkbfamZb5pk1Z3kM41xXf8gxxEMf5Avjc4ipDpVqeEfXffD96Kz4MpHzO2eFFNbBeXgiXCViXGbvqybUW2VEW3ah1/8GB3rHI3JRmAbGlNMyyEI6PH1Rr9m6JTDfG4vOvFxGBNf95lggNNlVtdiDU0vAEXNfkZBkK/wGK0yl7BtngTFnt7XosI2o88wWLi6D8ZAkjxGRWz7YusPQ== gehaxelt@LagTop
$> dig TXT ssh.key.neef.it +short | sed -e 's/" "//g' -e 's/"//g' | sha256sum
e46041fc3182446f7c464c96abd5749a68f5fd55839bff490800ccb2770823df -
As you can see, the SHA-256 sums match and we therefore obtained our initially uploaded, valid public key with DNS as the distribution method!
But what if the dig
command is not installed, because it is part of the dnsutils
package?
With a bit of grep
magic we can use nslookup
or host
to download our public key!
$> host -t txt ssh.key.neef.it ns.inwx.de | grep -oP '\".*\"' | sed -e 's/" "//g' -e 's/"//g' | sha256sum
e46041fc3182446f7c464c96abd5749a68f5fd55839bff490800ccb2770823df -
$> nslookup -q=TXT ssh.key.neef.it ns.inwx.de| grep -oP '\".*\"' | sed -e 's/" "//g' -e 's/"//g' | sha256sum
e46041fc3182446f7c464c96abd5749a68f5fd55839bff490800ccb2770823df -
Do you know other DNS-querying tools that I haven't mentioned here? Feel free to contact me and I'll try to build a one-line for it, too!
When running either of the commands above, for example nslookup
, but without the grep
-part, you should see a message similar to ";; Truncated, retrying in TCP mode."
. For small queries, DNS uses the UDP
protocol on port 53, but if the answer is too big to fit into a single UDP packet, it will instruct the client to retrieve the data using TCP
. This is fine, however, port tcp/53
might be blocked in strictly configured firewalls.
Before we start to explore possible bypasses, let's make sure that we cannot retrieve the key with such a firewall configuration:
$> sudo iptables -I OUTPUT -p tcp --dport 53 -j DROP
The commands from above will hang and not output our public key anymore! We need to find a method to bypass this filter.
From Wikipedia we can learn that the maximum length of a word
is supposed to be 255 bytes:
"Value: This can be free form text data of any type. Each word is treated as a separate string unless one or more strings are enclosed in quotes. The maximum length of a string is 255 bytes, but any number of strings can be present in each record, subject to the standard DNS RDATA size limit of 65535 bytes.[8]"
https://en.wikipedia.org/wiki/TXT_record
This also explains why the DNS server split the long string into several chunks separated by " "
. We can prevent this behaviour by splitting our key into chunks of 200 bytes and creating separate TXT records.
$> cat dnskey.pub | sed -r 's/(.{200})/\1\n/g'
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCduVhp18zQrC6wRub25qNAz9FLuMKNUVwA+zvJ3/ypckylFyt7wC5ISWPdZxTcIfOHxeXzSbmFJwt/xay/M5SSw64VbznsVuphxKJOosUlUyJhpySI0zf4ceAXB2VX2U+z6DW8dJQKPW0sZdH6IIOYxbc8dbTTkMr2
Esx29DqwwTvQe72IhDa8AsHTSYpM1Lx8bILiru2SItTTpLoW/aev9sYz6rGty48TGP62CB5xsi2R3peAzJFHdlFC5iC1eSEc/3zJhr6Ez4NYYQsjy/I/SgJ7WypRBovk+UKUpLAwcFwrZXh/o1bO+kM1VEmlPn5qOZccZ8QxsItCNvr7w0mEBPGkySZ0DmrGgtAnI0xy
ra/1iiapDaqv6pQB82tyMPps8HAhMnMgTmTM9hINsvVvnJfYV+gbfZtW9OYakBpA+xRn6MyCz4Hbu0cyz9oJoGERxeqLMNNp4Tlw8ECUyWvm1CotcLTkbfamZb5pk1Z3kM41xXf8gxxEMf5Avjc4ipDpVqeEfXffD96Kz4MpHzO2eFFNbBeXgiXCViXGbvqybUW2VEW3
ah1/8GB3rHI3JRmAbGlNMyyEI6PH1Rr9m6JTDfG4vOvFxGBNf95lggNNlVtdiDU0vAEXNfkZBkK/wGK0yl7BtngTFnt7XosI2o88wWLi6D8ZAkjxGRWz7YusPQ== gehaxelt@LagTop
Now create the four TXT records for each line, so that we can retrieve them separately:
ssh1.key
- Part 1ssh2.key
- Part 2sshN.key
- Part N
$> dig TXT ssh{1,2,3,4}.key.neef.it +short
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCduVhp18zQrC6wRub25qNAz9FLuMKNUVwA+zvJ3/ypckylFyt7wC5ISWPdZxTcIfOHxeXzSbmFJwt/xay/M5SSw64VbznsVuphxKJOosUlUyJhpySI0zf4ceAXB2VX2U+z6DW8dJQKPW0sZdH6IIOYxbc8dbTTkMr2"
"Esx29DqwwTvQe72IhDa8AsHTSYpM1Lx8bILiru2SItTTpLoW/aev9sYz6rGty48TGP62CB5xsi2R3peAzJFHdlFC5iC1eSEc/3zJhr6Ez4NYYQsjy/I/SgJ7WypRBovk+UKUpLAwcFwrZXh/o1bO+kM1VEmlPn5qOZccZ8QxsItCNvr7w0mEBPGkySZ0DmrGgtAnI0xy"
"ra/1iiapDaqv6pQB82tyMPps8HAhMnMgTmTM9hINsvVvnJfYV+gbfZtW9OYakBpA+xRn6MyCz4Hbu0cyz9oJoGERxeqLMNNp4Tlw8ECUyWvm1CotcLTkbfamZb5pk1Z3kM41xXf8gxxEMf5Avjc4ipDpVqeEfXffD96Kz4MpHzO2eFFNbBeXgiXCViXGbvqybUW2VEW3"
"ah1/8GB3rHI3JRmAbGlNMyyEI6PH1Rr9m6JTDfG4vOvFxGBNf95lggNNlVtdiDU0vAEXNfkZBkK/wGK0yl7BtngTFnt7XosI2o88wWLi6D8ZAkjxGRWz7YusPQ== gehaxelt@LagTop"
Finally, we need some more sed
and tr
magic to remove the quote-characters and new-lines, but in the end we obtain our public key again!
$> dig TXT ssh{1,2,3,4}.key.neef.it +short | sed -e 's/"//g' | tr -d '\n' | sed '$s/$/\n/' | sha256sum
e46041fc3182446f7c464c96abd5749a68f5fd55839bff490800ccb2770823df -
Two more words about the security implications of using this method. DNS is not encrypted per default. That means, that you should not simply append the output of one of the above commands to your .ssh/authorized_keys
without validating the downloaded public key first!
This can be achieved easily by writing the public key into a temporary file first.
$> dig TXT ssh{1,2,3,4}.key.neef.it +short | sed -e 's/"//g' | tr -d '\n' | sed '$s/$/\n/' > /tmp/key.pub
$> sha256sum /tmp/key.pub # Verify the sha256sum before proceeding!
$> cat /tmp/key.pub >> ~/.ssh/authorized_keys
Or if writing is not possible, saving it in a variable:
$> export PUBKEY=$(dig TXT ssh{1,2,3,4}.key.neef.it +short | sed -e 's/"//g' | tr -d '\n' | sed '$s/$/\n/')
$> echo $PUBKEY | sha256sum
e46041fc3182446f7c464c96abd5749a68f5fd55839bff490800ccb2770823df -
$> echo $PUBKEY >> ~/.ssh/authorized_keys
Hope this helps someone :)
-=-
PS: There are people who wrote a filesystem DNSFS based on DNS. Perhaps this is another method of hosting the public key...