Inspired by skeetos endlessh, this tarpit also works on the key exchange (kex) level to slow down ssh bots or malicious ssh clients and will use a database instead of logs for the trapped clients.
Depending on what is specified in the compose file, ssh-tarpit randomly chooses between tarpitting at banner and kex level or tarpits always at banner or kex level.
The server MAY send other lines of data before sending the version string. Each line SHOULD be terminated by a Carriage Return and Line Feed. Such lines MUST NOT begin with "SSH-", and SHOULD be encoded in ISO-10646 UTF-8 [RFC3629] (language is not specified). Clients MUST be able to process such lines.
[RFC4253, 4.2]
The idea is to never send a line that begins with SSH-
and therefore trap the client forever, because it will never get the answer it is waiting for.
Key exchange (kex) begins by each side sending name-lists of supported algorithms.
[RFC4253, 7]
The idea is to send very large name-lists at slow speed. After the initial message, each message is sent as type string
, which means
string
Arbitrary length binary string. Strings are allowed to contain
arbitrary binary data, including null characters and 8-bit
characters. They are stored as a uint32 containing its length
(number of bytes that follow) and zero (= empty string) or more
bytes that are the value of the string. Terminating null
characters are not used.
Strings are also used to store text. In that case, US-ASCII is
used for internal names, and ISO-10646 UTF-8 for text that might
be displayed to the user. The terminating null character SHOULD
NOT normally be stored in the string. For example: the US-ASCII
string "testing" is represented as 00 00 00 07 t e s t i n g. The
UTF-8 mapping does not alter the encoding of US-ASCII characters.
[RFC4251, 5]
The packet size is sent first as uint32, which means the packet can be theoretically 4GiB large. Unfortunately,
All implementations MUST be able to process packets with an uncompressed payload length of 32768 bytes or less and a total packet size of 35000 bytes or less [...]
[RFC4253, 6.1]
Because of these limitations, kex trapping will not last endless like banner trapping does and will also not reach its full potential, like when processing 4GiB of data was a requirement. At least it lasts a little while, when malicious clients are ready for an endless banner.
The left side is a boxplot for both tarpits and on the right side is a histogram.
Meehhh... As visible in the graph, the banner tarpit traps much longer than the kex one. Either the ssh scrapers are more interested in the SSH-2 server type or I screwed something up.
Make sure that the following packages are installed
- podman
- podman-compose
and also that the machine is configured for the user running the containers with
-
lingering enabled
loginctl enable-linger
that the containers do not stop when the user logs out.
-
enabled podman restart service
systemctl --user start podman-restart.service systemctl --user enable podman-restart.service
that the restart policy works.
Ensure that
- Port 22 is available and not used by the real ssh server (for openssh, change the line
#Port 22
toPort 999
or whatever number you like) or another service - Port 22 can be used by a non-root user (add
net.ipv4.ip_unprivileged_port_start=22
to/etc/sysctl.conf
)
Docker might be used instead of podman. Make sure to substitute all podman commands with docker.
-
Clone this repository and enter its directory by running
git clone https://github.com/kvnglb/ssh-tarpit.git cd ssh-tarpit
on the server, that should use the trap.
-
Create and edit the .env file
cp .env.example .env
Then edit
.env
and choose the database passwords. -
Optionally modify
docker-compose.yml
to your needs. Shouldn't be necessary, unless port5444
is already used on your server. -
Build and start the containers
podman-compose build podman-compose up -d
-
If everything was successfull
podman logs ssh-tarpit_ssh
should only output
Connected to db
and no errors.podman logs ssh-tarpit_db
should also not ouput any errors.
Enter the database with
podman exec -it ssh-tarpit_db psql -U ssh
To gather everything, run SELECT * FROM ip;
ssh=> SELECT * FROM ip;
id | address | identifier | service | tarpit | time | timespan
----+---------+------------+---------+--------+------+----------
id
: a consecutive numberaddress
: the ip address of the trapped clientidentifier
: a unique string (md5sum) of the clients address and time of connectionservice
: the name of the service, which the client is using [RFC4253, 4.2]tarpit
: the used tarpit, eitherbanner
orkex
time
: the time when tarpitting the client had beguntimespan
: how long the client was trapped
\q
SELECT * FROM ip WHERE timespan IS NOT NULL ORDER BY timespan DESC;
SELECT * FROM ip WHERE timespan IS NULL;
\copy (SELECT * FROM ip) TO 'ip.csv' WITH CSV
Attention: Now, the file is in the container but not on the host. To copy the file from the container to the host, exit the database with \q
and then run
podman cp ssh-tarpit_db:ip.csv .