ENOWARS 3 WriteUp deaddrop
12 July 2019 by Lukas and Markus
Deaddrop was a HTTP service written in Erlang. It models a simple bulletin board system, where users can create topics and reply to them. Topics can either be public or private (where users have to know their name to access them). Two logical flaws and a path traversal-like vulnerability allow attackers to list the private topics and steal data.
Service description
Deaddrop offers just the basic functionality of a bulletin board without fancy UI. In addition users could “subscribe” to topics using a websocket, to see past (“replay”) or future (“subscribe”) messages. There was no authentication in place, access is granted if the topic name is known. Private topics are just not listed. See the API description:
GET /topics
PATCH /add_topic (body: <topic-name>)
POST /publish (body: <topic-name>:<new-message>)
Websocket /subscribe (message: <method>:<topic-name>) // methods: REPLAY or SUBSCRIBE
The storage was equally simple: A file topics.txt
contains a list of all topic names, one per line. Private topics are prefixed with -
, public topics may optionally be prefixed with +
.
For every topic there is a file <name>.txt
which stores all messages written to that topic. Further published messages are appended. The initial message was All messages sent on topic: <topic-name>\n
, or ** <message>\n
for further messages.
The gameserver creates a new private topic every round and published the flags as a message to that topic.
Vulnerability 1: replay topics list
To get the flags, we have to first know the names of the private topics and then replay them using the websocket. All topic names are stored in the topics.txt
.
Let’s take a look on how topic filenames are created in file_handler.erl
:
The topic file is PATH/<topic-name>.txt
, without any filter on the topic name. If we could replay the topic named topics
, we would get the content of this file. However, topics
is not on the topics list. Let’s see where the topic list is loaded:
There is always a private topic called - topics
present, even if not in the file. That prevents us from adding a new topic with that name, but allows us to replay the private topic - topics
and read all private filenames. We can now open a websocket connection to /subscribe
and use the command REPLAY:- topics
to read the list of all topics, including private ones. That’s a trivial exploit, code is given below.
Vulnerability 2: Publish to topics
We already noticed that topics
is always known as a private topic. We know its name, so we can just publish a message there (which is written to topics.txt
). We publish (=write) \ntopics
to - topics
, so the topics list file ends up with a line suggesting that topics
is now public (no -
sign before the name) and a junk line. When looking for a topic, the service takes the first name it finds, and the - topics
line is appended to the file, hence the injected line from the file is considered first. After this message has been published, we can just subscribe and replay to topics
, get the list of all private topics and read them.
Drawback of this attack: It’s clearly visible from the public topic list, and the public list can also be used by other teams.
Vulnerability 3: New topic path traversal
If we could create a new topic with name topics
, it would be publicly readable and backed by the file topics.txt
. However, there is a check in place to prevent this:
We can’t create a new topic with a name that already exists - and the private topic topics
always exists. But the check is for equality, just exactly that name is blocked. We remember that the file belonging to a topic is PATH/<topic>.txt
. No filtering is in place, we can mount a path traversal style attack.
We can simply use ./topics
as topic name, which will result in the file PATH/./topics.txt
, but has a different name than topics
- we circumvented the lists:member
check for existing topics. No data is deleted in this process, files are always appended. topics.txt
now contains the line ./topics.txt
(our public topic) and some junk (the initial messages for our new topic) which doesn’t matter. We can now open a websocket connection to /subscribe
and use the command REPLAY:./topics
to read the list of all topics, including private ones. We issue a replay on all private topics and get all flags stored in that service. Exploit code is given below. In fact, we could have used this vulnerability to read all files ending on .txt
from the system.
Exploit code
Patching the service
The first vulnerability can be fixed by preventing any replay on topics (just filter it).
The second vulnerability can be removed by prepending - topics
to the topic list, not appending it, or forbidding publish to topics
.
The third vulnerability can be fixed by filtering the topic names and remove dangerous characters (./
for example).
On the other hand, all problems can be mitigated by renaming topics.txt
to something without .txt
extension, to prevent any collision between topic file and topics list, which was the solution we have chosen.
Conclusion
During the competition, we found the last vulnerability and exploited it successfully. After some time we got attacked with the second vulnerability but quickly patched it. We did not see anybody exploiting the first (and easiest) vulnerability, and just found it by accident when writing up.
Despite nobody of us knew Erlang we had fun with this service. Even without deeper language knowledge we could find our way towards our flags.