Introduction
TickTock Global Platforms, Inc. is under attack! An elite hacker going by the
moniker fr3ddy
has broken into TickTock’s servers and encrypted all of our
files. He sent us a message to tell us he won’t give us back our files unless we
pay a ransom.

We need you once again to help us recover from this attack, and to protect us from getting attacked in the future!
You only need to do two of the problems on this final! You should clearly indicate which two problems you wish to have graded.
There is no additional credit for answering more than two problems. If you answer more than two problems and don’t indicate which problems should be graded, then the first two problems you selected will be graded.
Instructions
You should read the following section before you start working on the problems.
For this exam, you will need to answer two of the following four problems. Make sure that it’s clear which of the problems you selected!
When you are finished, you should submit a writeup with your answers to the problems via Collab. You may submit your writeup under the “Final” assignment listed there; you can resubmit it as many times as you like before time runs out.
Question components: each problem contains an interactive component as well as a written component. The interactive component should be solved in Virginia Cyber Range; you can select one of the provided VMs to test your solutions to the problems. The written component can be solved without the use of VCR.
Your answers to the interactive component should clearly outline your methodology and any steps you took. For both parts of the problem, you should answer thoroughly and outline all necessary details using clear and concise language.
Partial answers: If you are unable to completely finish a problem in time, you should still write down whatever work you completed towards that problem. Partial credit will be given for incomplete solutions that make a lot of progress towards the correct answer.
Problem 1: Cryptography
The attacker encrypted all of our files with his ransomware. We didn’t feel like backing up our data (who wants to pay to store their data twice? Cloud storage is expensive 🤷), so we need you to figure out another way to get our data back.
Question 1.1 (4 points)
The attacker sent all of TickTock’s files over to http://crypto.fr3ddy.lab:1337 (accessible in Virginia Cyber Range). This host takes files uploaded from TickTock’s servers, and sends back encrypted versions to overwrite the originals.
By looking at similar attacks that this hacker has performed, we’ve deduced that
they’re using the following code to encrypt our files. The encrypt
function
(shown below) gets called every time a file is sent to crypto.fr3ddy.lab
:
import os
from base64 import b64encode
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
from cryptography.hazmat.primitives.hashes import SHA256
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
ikm = os.environ["CRYPTO_IKM"].encode("utf-8")
if len(ikm) < 32:
raise RuntimeError("Input key material must be at least 32 bytes")
hkdf = HKDF(SHA256(), length=32, salt=b"", info=b"Encrypt")
CHACHA_KEY: bytes = hkdf.derive(ikm)
hkdf = HKDF(SHA256(), length=32, salt=b"", info=b"GenerateNonce")
NONCE_KEY: bytes = hkdf.derive(ikm)
def encrypt(filename: str, contents: bytes):
cipher = ChaCha20Poly1305(CHACHA_KEY)
filename_as_bytes = filename.encode("utf-8")
hkdf = HKDF(SHA256(), length=12, salt=b"", info=filename_as_bytes)
nonce = hkdf.derive(NONCE_KEY)
ciphertext = cipher.encrypt(nonce, contents, filename_as_bytes)
# Poly1305 tag is stored in the last 16 bytes of the
# ChaCha20Poly1305 output
ct, tag = ciphertext[:-16], ciphertext[-16:]
return {"ciphertext": b64encode(ct), "tag": b64encode(tag), "filename": filename}
We’ve given you a file that was encrypted using this code; it is stored in
/usr/local/share/cs3710/encrypted_manifesto.txt.json
Identify a flaw in the way the attacker is performing encryption, and use it to
decrypt the encrypted_manifesto.txt.json
file. You should submit the decrypted
file contents, and detail the steps you used to decrypt the file.
fr3ddy
’s encryption server by going to
http://crypto.fr3ddy.lab:1337/docs,
clicking the /crypto/encrypt
tab, and then pressing Try it out
. This will
allow you to upload a file to the server and get back an encrypted version of
it.Question 1.2 (3 points)
What is the vulnerability in the code snippet above? As a defender, what would you do differently to protect against this vulnerability?
Hints
- You can use the
b64decode
function to convert Base64-encoded data back into raw bytes, e.g.:
>>> from base64 import b64decode
>>> b64encoded_msg = "aGVsbG8sIHdvcmxkIQ=="
>>> b64encoded_msg_as_bytes = b64encode_msg.encode("utf-8")
>>> b64decode(b64encoded_msg_as_bytes)
b'hello, world!'
- There is a Jupyter Notebook available
(link)
for you to play around with this code in your browser (note that
CRYPTO_IKM
is set differently from what the webserver uses, so encryptions generated in the notebook will be different from encryptions generated by the server).
Problem 2: Networking
Before we can bring our servers back online, we need to set up a firewall so that vulnerable services aren’t exposed to the outside world.
Question 2.1 (4 points)
TickTock runs a few different services to keep its site up and running. These include the TickTock web server, written in Python; a PostgreSQL database to store posts and user data; a MinIO server for static files and media; and a reverse proxy to log traffic and direct it to the create service. For this problem, we will write some firewall rules that allow other machines to connect to our web services and our Postgres database.
Create an nftables firewall that meets the following requirements:
- Allow loopback connections: you should allow all traffic going from your machine back to itself.
- Allow inbound HTTP(S): you should allow all HTTP and HTTPS traffic going from external hosts to your machine, so that they can access your webserver.
- Allow inbound connections to PostgreSQL from the internal network: other
hosts on TickTock’s internal network need to connect to the PostgreSQL
database on your machine, which communicates on TCP port 5432. You should
allow all traffic to the database that comes from an internal IP address in
the range
10.0.0.0/8
. - Allow outbound DNS and HTTP(S): the web server should still be allowed to send out DNS and HTTP/HTTPS requests.
- Log dropped packets: you should log any packets that are dropped by your firewall.
You may structure your rules in any way you like, but all packets that are accepted must match one of the above conditions. You should build off of the base rules provided in
/usr/local/share/cs3710/base.nft
in your Virginia Cyber Range VMs.
Do not run
nft flush ruleset
to delete existing firewall rules, or delete thefilter
table.Do not delete any of the rules that are in the original version of
base.nft
that you are given.
Doing either of these may risk either locking you out of the VM or making it
impossible to route traffic to the webserver. If you need to reset your firewall
to its initial state, reboot your VM. You can find a copy of the original
base.nft
in the /usr/local/share/cs3710/backup
directory.
Question 2.2 (3 points)
After setting up your firewall, things seem to go alright for a while. Suddenly, we notice a huge burst packets, most of which are being dropped. These are the logs that these packets are generating:
Dec 15 10:40:37 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=32433 DF PROTO=TCP SPT=44466 DPT=5900 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:37 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=23073 DF PROTO=TCP SPT=42770 DPT=1720 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:37 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=37492 DF PROTO=TCP SPT=38036 DPT=110 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:37 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=37897 DF PROTO=TCP SPT=56482 DPT=113 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:37 desktop.example.com kernel: input.accept: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=4962 DF PROTO=TCP SPT=40068 DPT=80 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:38 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=34695 DF PROTO=TCP SPT=56698 DPT=3306 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:38 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=26883 DF PROTO=TCP SPT=57852 DPT=139 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:38 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=37898 DF PROTO=TCP SPT=56482 DPT=113 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:38 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=26535 DF PROTO=TCP SPT=41658 DPT=995 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:38 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=37493 DF PROTO=TCP SPT=38036 DPT=110 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:38 desktop.example.com kernel: input.accept: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=56456 DF PROTO=TCP SPT=57046 DPT=443 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:39 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=14821 DF PROTO=TCP SPT=40286 DPT=2105 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:39 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=4226 DF PROTO=TCP SPT=46608 DPT=2107 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:39 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=1887 DF PROTO=TCP SPT=57414 DPT=3077 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:39 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=19133 DF PROTO=TCP SPT=35684 DPT=1 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:39 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=59201 DF PROTO=TCP SPT=42922 DPT=3878 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:40 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=60063 DF PROTO=TCP SPT=56580 DPT=1048 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:40 desktop.example.com kernel: input.accept: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=12335 DF PROTO=TCP SPT=40076 DPT=80 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:40 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=4532 DF PROTO=TCP SPT=50260 DPT=1455 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:40 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=48623 DF PROTO=TCP SPT=43640 DPT=4848 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:40 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=10400 DF PROTO=TCP SPT=41208 DPT=2009 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:40 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=16723 DF PROTO=TCP SPT=36528 DPT=9200 WINDOW=62727 RES=0x00 SYN URGP=0
Dec 15 10:40:41 desktop.example.com kernel: input.drop: IN=eth0 OUT= MAC=0e:b7:1d:ef:04:fd:0e:09:d3:6b:95:97:08:00 SRC=10.1.56.72 DST=10.1.52.67 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=39799 DF PROTO=TCP SPT=60980 DPT=5033 WINDOW=62727 RES=0x00 SYN URGP=0
Assuming we didn’t do anything wrong or make any other changes, what could be causing this sudden activity? Does this behavior indicate that we’ve been compromised?
Your answer should explain in detail what could be triggering this kind of behavior. You should also justify whether or not you believe it could be indicative of a compromise, or otherwise concerning from a security perspective.
Problem 3: Access control
We can’t keep our servers down forever! Time is money. And for TickTock, a multinational conglomerate cornering the global timekeeping and time-related social media market, that’s a lot of money.
Question 3.1 (4 points)
Write an AppArmor profile to help sandbox the TickTock web servers.
There is an base AppArmor profile1 you can work off of in
/etc/apparmor.d/ticktock.profile
. The ticktock-web
profile listed in this
file automatically gets applied to the TickTock web server when it starts up.
If you want to try stopping and restarting the services used to run TickTock, you can run the following commands:
cd /usr/local/share/cs3710
# Stop the servers
docker compose down
# Start the webservers
docker compose up -d
You should browse the TickTock website, which is running inside the Virginia Cyber Range machines at http://localhost, and thoroughly test its functionality. and thoroughly test its functionality. Your profile should include any permissions needed by the webserver and no others.
Hints
The web server that you are writing an AppArmor profile for is the same one that appeared all the way back in Labs 2 and 3. It is a Python-based web server running Gunicorn, which may help you a bit in figuring out what files it needs to access.
Question 3.2 (3 points)
TickTock is setting up its enterprise file sharing service, TUFF (TickTock’s Unbelievably Fast Files). Everyone who signs up will be assigned to a user on a Linux server, as well as a group that defines the “friends group” for that user. In addition, they will be given three directories:
/srv/<user>/public
: files uploaded by the user that anybody should be allowed to see./srv/<user>/private
: files uploaded by the user that only they should be able to see./srv/<user>/friends
: files uploaded by the user that they and the people in their friends group should be able to see, but that nobody else has access to.
In all cases, only the owner of the files should be allowed to upload them to any of these directories.
For instance, suppose Alice is given the username alice
, and her friends group
is called alice-friends
. Then she would be given three directories,
/srv/alice/public
, /srv/alice/private
, and /srv/alice/friends
. In terms of
Linux access controls, each of these directories would be owned by alice
and
assigned to the group alice-friends
:
$ tree -ug /srv
/srv
└── [alice alice-friends] alice
├── [alice alice-friends] friends
├── [alice alice-friends] private
└── [alice alice-friends] public
How should the permissions be set on each of these directories (/srv/alice
and
its three subdirectories), as well as files under these directories, in order
for TUFF to provide these access controls? Explain in detail.
Problem 4: Detection
We’ve analyzed the attack and identified some logs that were generated by our reverse proxy before and after it. We need you to help us detect this attacker the next time he infects our systems!
Question 4.1 (4 points)
We have a reverse proxy, Caddy, that we put in front of our webserver to log and redirect traffic. We’ve saved some of Caddy’s logs from before and during the attack to two directories:
/usr/local/share/cs3710/goodlog/
: logs generated by standard, non-malicious web traffic before the attack./usr/local/share/cs3710/badlog/
: logs generated by the attacker’s tools during the attack.
Each log file contains a record of traffic sent to the webserver. For instance,
here’s the first log recorded in goodlog/normal.log.1
:
$ head -n1 goodlog/normal.log.1
{"level":"info","ts":1671095313.9216814,"logger":"http.log.access","msg":"handled request","request":{"remote_ip":"172.25.1.1","remote_port":"47680","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/","headers":{"Sec-Fetch-User":["?1"],"Accept-Language":["en-US,en;q=0.9"],"Cookie":[],"Sec-Ch-Ua":["\"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"108\", \"Google Chrome\";v=\"108\""],"Sec-Ch-Ua-Mobile":["?0"],"Upgrade-Insecure-Requests":["1"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"],"Sec-Fetch-Site":["same-origin"],"Sec-Ch-Ua-Platform":["\"Linux\""],"Referer":["http://localhost/search?query=%27+or+1%3D1%3B+--"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"],"Sec-Fetch-Mode":["navigate"],"Connection":["keep-alive"],"Sec-Fetch-Dest":["document"],"Accept-Encoding":["gzip, deflate, br"]}},"user_id":"","duration":0.036822772,"size":2259,"status":200,"resp_headers":{"Content-Type":["text/html; charset=utf-8"],"Content-Length":["2259"],"Vary":["Cookie"],"Date":["Thu, 15 Dec 2022 09:08:33 GMT"],"Server":["Caddy","gunicorn"]}}
… and a slightly prettier version of it, via the jq
command:
$ head -n1 goodlog/normal.log.1 | jq -C
{
"level": "info",
"ts": 1671095804.031732,
"logger": "http.log.access",
"msg": "handled request",
"request": {
"remote_ip": "172.25.1.1",
"remote_port": "58402",
"proto": "HTTP/1.1",
"method": "GET",
"host": "localhost",
"uri": "/",
"headers": {
"Sec-Ch-Ua": [
"\"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"108\", \"Google Chrome\";v=\"108\""
],
... (cut out for space) ...
}
},
"user_id": "",
"duration": 0.004653378,
"size": 2259,
"status": 200,
"resp_headers": {
"Server": [
"Caddy",
"gunicorn"
],
... (cut out for space) ...
}
}
As you can see, each log contains information about an HTTP request received by the webserver, including the IP address that sent the request, the HTTP method they used, the headers they sent, and so on. The log also contains information about HTTP data sent in the response.
Write one or more YARA rules to detect log files containing the malicious
activity. Your rule(s) should only flag logfiles containing malicious activity,
i.e. logfiles in badlog/
, while ignoring log files with normal activity (those
in goodlog/
).
# Your rules should flag all of the files in this directory
yara -r rules.yar /usr/local/share/cs3710/badlog/
# None of the files in this directory should be flagged
yara -r rules.yar /usr/local/share/cs3710/goodlog/
Be sure to explain how you came up with the YARA rules that you wrote, and why you think they will be effective in detecting the attacker’s tools in the future. Your rules will be graded on their ability to accurately flag malicious activity while ignoring benign web traffic, as well as their applicability to future attempts to repeat this attack.
Question 4.2 (3 points)
sudo apt install -y yara
to fix this.The good news is that we’ve found all of fr3ddy
’s malware on our systems and
managed to remove it completely. The bad news is that we’ve figured out their
command-and-control (C2) channel: TickTock’s own website!

It looks like the attackers were exfiltrating data from their implants by encrypting it in chunks, Base64-encoding those chunks, and then posting them on our website. That way, they could get data back to the operator while making it look like “normal” web traffic – because it was going back to our own web servers! Sneaky stuff.
How would you detect this kind of malware communication? You should consider
various properties of these communications and how they differ from the normal
messages that appear on TickTock’s website (which you can find by going to
http://localhost
in your Virginia Cyber Range machine).
You should also consider the advantages and drawbacks of your method, such as
whether somebody could (intentionally or unintentionally) trigger a false
positive with it.
Your proposal will be graded on the basis of its accuracy in correctly flagging malicious communications while avoiding false positives; its technical feasibility; and on your ability to give a detailed and accurate assessment of its advantages and disadvantages.
You can also find a read-only copy of this AppArmor profile in
/usr/local/share/cs3710/backups/ticktock.profile
, if you need to revert back to the original profile. ↩︎