First thing first, using the finger program.
The finger
program is pretty old, you might not find the source easily. I
suggest you search for things like "finger-bsd" or "GNU finger" (be aware GNU's
pinky
isn't capable of really handling the finger protocol)
You might also be interested by this News Thread "finger on arch linux".
The basic use of finger
is to either supply nothing, a host (@host
) a
username (username
) or a user address (username@host
).
The first two options allow to get informations either on the localhost or the targeted host, the last twos allow to get information either on a local user or a remote user. For example :
dimension $ finger cedilla
Login: cedilla Name:
Directory: /home/cedilla Shell: /bin/bash
On since jeu. nov. 7 11:43 (UTC) on pts/0 from XXX.XXX.XXX.XXX
No mail.
Project:
Working on a personnal window manager : cdwm (cedilla's dynamic window manager)
Plan:
Would like to learn German.
Pretty neat ! And as previously stated :
localstation $ finger @tilde.club
[tilde.club]
welcome to
__ _ __ __ __ __
/ /_(_) /___/ /__ _____/ /_ __/ /_
/ __/ / / __ / _ \ / ___/ / / / / __ \
/ /_/ / / /_/ / __// /__/ / /_/ / /_/ /
\__/_/_/\__,_/\___(_)___/_/\__,_/_.___/
tilde.club is not a social network; it is one tiny,
totally standard Unix computer that people respectfully
use together in their shared quest to build awesome web pages.
visit us on the web or gopher for more info
users currently logged in are:
XXXXXXXXXXX
XXXXXXXXX
XXX
XXXXXXX
XXX
XXXXXX
XXXXXXXXX
And a specific user from a remote host ?
localstation $ finger cedilla@dimension.sh
Plan:
No Plan.
Online.
Oh, well this didn't go well ... This is because on ~dimension we don't allow people to get informations over users from an outerspace location. Be we still can get informations on the host :
localstation $ finger @dimension.sh
__ __
/\ \ __ __ /\ \
\_\ \/\_\ ___ ___ __ ___ ____/\_\ ___ ___ ____\ \ \___
/'_` \/\ \ /' __` __`\ /'__`\/' _ `\ /',__\/\ \ / __`\ /' _ `\ /',__\\ \ _ `\
/\ \L\ \ \ \/\ \/\ \/\ \/\ __//\ \/\ \/\__, `\ \ \/\ \L\ \/\ \/\ \ __/\__, `\\ \ \ \ \
\ \___,_\ \_\ \_\ \_\ \_\ \____\ \_\ \_\/\____/\ \_\ \____/\ \_\ \_\/\_\/\____/ \ \_\ \_\
\/__,_ /\/_/\/_/\/_/\/_/\/____/\/_/\/_/\/___/ \/_/\/___/ \/_/\/_/\/_/\/___/ \/_/\/_/
dimension.sh is a shared linux shell system (or pubnix), open to everyone and anyone to
use, learn, and experiment.
If you're interested in joining check out the website: https://dimension.sh
users currently logged in are:
XXXX
XXX
cedilla
There is multiple way to restrict the informations one can get with the finger protocol, we'll discuss this later, but for now let's talk about what is the finger protocol.
What is the finger protocol ?
The finger protocol is a pretty old specification on how a client program and a server might communicate to do this pretty cool thing.
The first specification is dated to 1977 (RFC 742), it's last definition is dated to 1991 (RFC 1288) and its last errata to 2021 (RFC Errata ID 6706).
What you'll see here is not a specification on what you would type on the command line ! It is the reference of the actual network communication that happens between the client program and the server program.
The protocol is a single request / single response protocol, its name is pretty representative. The flow is as follow :
- The client open a connection to the server's TCP port 79.
- The server (or the
finger
daemon) interface a Remote user information program on this connection. - The client send its payload : a one line query (here called The Request) and wait.
- The RUIP process the request (this might means opening connection to other servers), returns an answer (here called The Response) and send a TCP close of connection signal.
- The client receives the response, wait for the closing signal and end the connection on its side.
The response is just a simple byte stream, meant to be printed on a terminal. Technically the specification require it to be printable ASCII, but it could be UTF-8 codepoints and the client will still print it to the screen (if your terminal support UTF-8 you'll see it at least).
Now, what does this Request looks like ? Well it is also pretty simple.
The actual real BNF can be found in the specification, but here is the global pseudo-scheme :
[/W | [/W <SPACE>] {USERNAME}] [@{HOSTNAME} ... ] <CRLF>
Note that spaces here are just for clarity, the only real spaces that can be
added is denoted by <SPACE>
, and has the semantic "one or more spaces".
A few addition to this specification :
[@{HOSTNAME} ... ]
is recursive, this means that you can do things like@host1@host2@host3
. In practice this means that your request should be forwarded fromhost3
tohost2
tohost1
(and the response the other way around), we'll get to this later./W
is the verbosity flag. It is not stricly defined, but it could be used to indicate that we want a more verbose response (from testing this doesn't change much).- Specifiying a
{USERNAME}
likecedilla
orcedilla@dimension.sh
will ask for the specific informations of this user. The later will be interpreted as a forwarding request and might not work if you are already sending it to the specific server (see example bellow). - The only mandatory part of the request is the
<CRLF>
characters. When only sending this to a server it should respond with a list of all the online users (like tilde.club above) and machine informations.
Simple isn't it ? The informations recommended by the specification in the response are pretty diverse, I'll discuss this as well as the security consideraions of them.
What informations the response might contains, and their security considerations.
Depending on the implementation of the RUIP you might get a lot of informations or nothing, here is a non-exhaustive list :
- List of logged user.
- Host status
- IP Address of a logged user
- Last typed or currently running command line of a user.
- Real Name of a user
- Phone, email, contact details
- Work place, work place contact details
- Last time logged, last time the user read its mail
- Whether or not mail are awaiting for them, even from who the most read email was.
This can be very very sensitive informations, allowing fine tracking people, even those not using a shell session. Even getting the list of logged users is an issues on a lot of sites.
But there is more. Remember when I talked about the forwarding feature, that it allow a client to ask the server to do another request to another server.
Well, depending on the situation this might be a very bad idea to allow that. For public servers, not sitting behing a NAT or a Bastion of some sort this is fine, but imagine your server is on the same network as others, not accessible from outerspace ? You might get into trouble.
Things like private network / local DNS scanning can be very dangerous; and if your implementation of the finger daemon is sensible to other vulnerability this become very chaotic. Also you might very fast get some sort of Remote File Inclusion to Remote Code Execution pipeline.
But there is more, always.
Some implementations allow users to specify a program for execution when somebody finger their username, obviously this can also become a real problem.
finger also allow to do some serious username scanning.
An authenticated user might also put malicious code in its session. Even tough
the code isn't executed by the finger daemon, they could automate some tasks
with their included files (think about the ~/.plan
I have) to facilitate
attacks from outerspace; this might even become a sync tool for racing
conditions.
There is also concerns client-side. The program receiving the data will have to check every byte to make sure they are not some nasty control code. In a terminal, with the correct control code for example, you can change the title of the current window (and X server/client has some history of vulnerability on those things). Basically, a malicious server could do some pretty nasty things.
To resolve those issues, most servers doesn't really run a full featured
finger
daemon or RUIP. And most of the time the finger
command isn't
implemented as a real finger query tool, at least for local users.
Ok, now the funny part.
Implementing a finger client in bash and C.
Did you know you can open a TCP connection in bash ? And did you know you can do
(almost) all the necessaries operations to be compliant with the specification
with bash builtins ? I'm not going to use any telnet
or nc
for a
protocol so simple !
The bash script
On a Unix with a modern bash interpreter (with net-redirection enabled, so the vast majority of bashs today), you can do it using almost using only builtin command.
If your version of Bash as been compiled with the net-redirection
option
(the vast majority of bashs today), you can pretty easily open a TCP or UDP
connection to any host or port.
To do this you are going to tell bash to open a socket and redirect its output/input stream to a file descriptor. I'll use descriptor 3 here, see the syntax (UDP is similar, I'll let you discover):
$ # Open a TCP connection to port 79 of tilde.institute
$ # Here we are opening it in input AND output
$ exec 3<>/dev/tcp/tilde.institute/79
After using it of course I need to close the connection :
$ # Close the input file descriptor
$ exec 3<&-
$ # Close the output file descriptor
$ exec 3>&-
With this in mind, we can put something up !
#!/bin/bash
TARGETHOST=dimension.sh
REQUEST="\r\n"
# Open the TCP connection on the fd 3
exec 3<>/dev/tcp/$TARGETHOST/79
# Send our request
echo -ne "$REQUEST" >&3
# Get the result and print it
while IFS='' read -r CONTENT; do
printf '%s\n' "$CONTENT"
done <&3
# Close outgoing and ingoing
exec 3>&-
exec 3<&-
And guess what ! It works !
Here we did a <CRLF>
only request to dimension.sh, you can play a little bit
with it, for example we can extend this script a to make it allow the user to
specify the target :
#!/bin/bash
set -e
# Verifying we get enough arguments
if [ $# != 1 ]; then
>&2 printf "Only one argument !\n"
exit 1
fi
IFS="@" read -r USERNAME TARGETHOST <<< "$1"
[ -z "$TARGETHOST" ] && TARGETHOST=localhost
REQUEST="$USERNAME"
# Open the TCP connection on the fd 3
exec 3<>/dev/tcp/$TARGETHOST/79
# Send our request
>&3 printf "%s\r\n" "$REQUEST"
# Get the result and print it
# NOTE: that checking for newline character is useless since we are already
# using the read buitltin
while IFS='' read -r CONTENT; do
printf '%s\n' "${CONTENT//[^\r\n\t[:print:]]/}"
done <&3
# Close outgoing and ingoing
exec 3>&-
exec 3<&-
For most usage this is enough : we can get the list of connected people on a given server, we can get status and informations on a person, even check locally through a the localhost loopback ! Nice !
This script will not work if you want forwarding, for example the user can't
type $ finger cedilla@dimlension.sh@tilde.club
because we don't really strip
correctly the argument. Also I think it miss the verbose flag, I'll let you get
to that.
Also note that I use here a bash substitution to strip non tab-newline-return-printable character from the output, this is required by the specification (for client security) but might break international character sets. The specification suggests that clients allow the user to print those characters (through some flag or option), so you might want to do that too.