Creating a Linux CTF
Photo Credits: Unsplash
Disclaimer: In testing, we encountered issues when building and running the container on Linux. The container would occasionally refuse to build and the entrypoint (systemd) would always exit immediately. No issues were encountered building and running on Windows. All of my students had Windows machines, so this didn't pose an immediate issue. I do plan to investigate the issue, but for the moment it remains an enigma.
Introduction
A few months ago, I found myself responsible for delivering a Linux class to ~20 cybersecurity analysts and engineers. Before diving into the academic content, I wanted an engaging - and ideally productive - method of ascertaining individual Linux competency.
Enter the CTF.
Requirements and Restraints
I wanted to cover a wide variety of Linux concepts, starting with questions accessible to a complete beginner, but culminating with something at least a little difficult. I also wanted to wanted to cover systemd to some extent, considering a) how central it is to the administration and functioning of most Linux distros, and b) how little some of my audience had been exposed to it.
For this class, I did not have cloud infrastructure or a CTF platform available, so I settled on Docker containers as a delivery method. Knowing that my audience would bring their own Windows endpoints to the class, I designed and built the image accordingly.
I built the image from a dockerfile (rather than interactively) and distributed it to the students. They ran the image locally, read the clues, and completed the exercise. Grading was honor-system only; I was more concerned with observing how the students approached each problem.
Building the dockerfile
Before starting, I found a similar container-based ctf that provided several question ideas and accelerated development of my own container.
Setting the Base Image
I used a vanilla Debian image as my base. This was for no particular reason, other than my general (and mostly arbitrary) preference for Debian.
FROM debian:latest
(A heads up for the following sections: dockerfile explanations are ordered by question, not by line in the final code.)
Question #1
In the interests of starting off easy, I opened with filesystem navigation.
Knowledge Assessed | Do the students know how to navigate from the CLI? Are they aware of hidden files and directories? |
Clue | The first flag can be found hiding at home |
Thought Process | "home" is a clear reference to the /home directory, "hiding" is a clear reference to hidden files. Let me look for hidden objects under /home. |
Setting it up:
RUN mkdir -p /home/.hidden_flag_dir/ && echo FLAG1_42448 > /home/.hidden_flag_dir/.data
# Possibility 1
ls -la /home
# Possibility 2
find /home
# Possibility 3 - if you're an overachiever and comfortable with assumptions
( find /home -name ".*" -exec cat {} \; | grep -i flag ) 2>/dev/null
Question #2
Moving to something just a little harder:
Knowledge Assessed | Are students aware of the /tmp directory? Are they familiar with common file encodings? |
Clue | Flag two is disguised, and will be deleted on reboot |
Thought Process | "deleted on reboot" = tmp directory, either /tmp or /var/tmp, "disguised" (after seeing the file contents) should inspire thoughts of base64 or prompt the use of decoding tools like CyberChef |
Setting it up:
RUN echo FLAG2_63992 | base64 > /tmp/flag2
# Find the file
ls /tmp
# Investigate file contents (dangerous without first checking file type)
cat /tmp/flag2
# If you recognize the encoding
cat /tmp/flag2 | base64 -d
For those who don't immediately recognize base64 (most sane people), there's always CyberChef.
Question #3
Continuing with the themes of well-known directories and working with various filetypes...
Knowledge Assessed | Do the students know where to look for logs on Linux machines? Can they work with binary files? |
Clue | Flag three is hanging out among 1's and 0's in an unusual log |
Thought Process | "Unusual log" must mean an atypical file in /var/log. 1's and 0's must refer to a binary file |
Setting it up:
RUN cat /dev/random | head -1000 > /var/log/flaglog && \
echo 'FLAG3_55352' >> /var/log/flaglog && \
cat /dev/random | head -1000 >> /var/log/flaglog
strings
won't be installed by default, so I started building out a "prep" section at the start of the dockerfile:
FROM debian:latest
RUN apt-get update ; \
apt-get install -y binutils ;
# Find the file
ls /var/log
# Investigate file contents (dangerous without first checking file type)
cat /var/log/flaglog # or less, more, head, tail, etc.
# Now that you know it's binary...
strings /var/log/flaglog | grep -i flag
# Or without grep, but having seen the previous 'flag' strings:
strings -n 11 /var/log/flaglog
Question #4
Moving to the next category: web services.
Knowledge Assessed | Do the students know how to retrieve web content from the CLI? |
Clue | Grab flag four off the local web |
Thought Process | "local" = localhost, web = http server |
Setting it up:
I needed to install a web server, common CLI web clients, and add a flag to the index page.
I chose nginx for a web server. It's one of the most popular options for Linux, plus I wanted to use the service for several other questions - which you'll see later on.
Adding to the prep section:
FROM debian:latest
RUN apt-get update ; \
apt-get install -y netcat curl wget binutils nginx;
Replacing nginx's default page with a flag:
COPY flag_http /var/www/html/index.nginx-debian.html
See "Setting up the Web Service" for more about the service setup
See the repository for the flag_http
file
# curl, wget, or netcat (if you want to manually send a GET request)
curl http://localhost
Question #5
More web service questions! (Now with processes and users)
Knowledge Assessed | Can the students investigate running processes? Do they know how to find group memberships? |
Clue | Who's serving flag four? They associate with some shady users |
Thought Process | "who's serving flag four" = who's running the web server process. "associate with...users"... users are associated by groups |
Setting it up:
I needed to create a user, add them to a group, add a flag to the group, then make sure the user account was used to run nginx child processes. That required some additional prep lines, plus some question-specific setup. That also required me to install procps (for ps
).
FROM debian:latest
RUN useradd kevin -u 1000; \
addgroup sus && usermod -aG sus kevin; \
apt-get update ; \
apt-get install -y netcat curl wget binutils nginx sudo procps; \
# our ctf user (tfc) will need sudo access
usermod -aG sudo tfc; \
sed -i '/^%sudo/ s/ALL$/ NOPASSWD:ALL/' /etc/sudoers;
RUN sed -i '/^sus:/ s/$/,FLAG5_41442/' /etc/group
RUN sed -i 's/^user www-data/user kevin/' /etc/nginx/nginx.conf;
See "Setting up the Web Service" for more about the service setup
# find out who's running the process
sudo ps -AfH
# investigate their 'user associations'
cat /etc/group | grep kevin
Question #6
Another web service question, but more focused on the http interaction itself. This one proved tricky for my students.
Knowledge Assessed | Do students understand http sessions? Are they aware of http headers, and can they investigate them from the CLI? |
Clue | Users are reporting session anomalies with the web service. Could it be caused by a misplaced flag? |
Thought Process | HTTP sessions require the client to store some kind of data, often a cookie. How can I view cookies from the CLI? |
Setting it up:
# configure nginx to add a header to all responses
RUN sed -i '/root \/var\/www\/html;/a add_header Set-Cookie "flag6_28257";' \
/etc/nginx/sites-enabled/default
See "Setting up the Web Service" for more about the service setup
# After researching curl flags...
curl -i http://localhost # or increase curl verbosity
Question #7
Pivoting back to folders and filetypes:
Knowledge Assessed | Are students aware of /etc? Can they identify and work with various filetypes? |
Clue | An odd configuration file has been found. What's it say? |
Thought Process | "odd configuration file" = non-standard file in /etc. After discovering the non-ASCII content, investigate type and unpack. |
Setting it up:
# In this case I prepped a text file, gz'd it, and copied it during image building
COPY oddfile /etc/cron.d/
I needed some more package installs to make sure students had the right tools.
FROM debian:latest
RUN useradd kevin -u 1000; \
addgroup sus && usermod -aG sus kevin; \
apt-get update ; \
apt-get install -y netcat curl wget binutils nginx sudo procps file gzip; \
# our ctf user (tfc) will need sudo access
usermod -aG sudo tfc; \
sed -i '/^%sudo/ s/ALL$/ NOPASSWD:ALL/' /etc/sudoers;
find /etc -type f # can grep for "odd" or search for recently modified files to narrow down
# discover file type
file /etc/cron.d/oddfile
gunzip /etc/cron.d/oddfile # will get 'unrecognized extension' error
cp /etc/cron.d/oddfile ~/oddfile.gz && gunzip ~/oddfile.gz
cat ~/oddfile
Question #8
Grep is a crucial tool. There have already been several flags where it could have been used, but here I did my best to force its use.
Knowledge Assessed | Do the students know how to grep? |
Clue | "FLAG8" is somewhere within a normal configuration file. Find it. |
Thought Process | I need to search everything under /etc for "FLAG8" |
Setting it up:
RUN echo FLAG8_67923 >> /etc/timezone
grep -r FLAG8 /etc
Question #9
The final (and slightly difficult) flag. I wanted to force students to take a closer look at systemd services.
Knowledge Assessed | How familiar are students with systemd? Can they find and investigate unit files? |
Clue | What caused the service in #4-6 to run? Find the flag. |
Thought Process | Look for the parent process. After seeing systemd, locate the relevant unit file. |
Setting it up:
RUN echo FLAG9_49457 >> /lib/systemd/system/nginx.service
# find the parent process
sudo ps -AfH
# you'll see systemd along with its command-line argument, the targeted unit file
# leverage systemctl tools to read the target's unit file without needing to locate it on the filesystem
sudo systemctl cat boot.target
# see the one and only service specified by the target. Read its unit file.
sudo systemctl cat nginx.service
And we're done!
Setting up the Web Service
If you've read this far, you might be wondering about something: Docker containers don't use systemd. What's going on here?
Docker containers don't need their own init process, and running systemd in Docker is generally not a best practice. But it can be done, and I was convinced I needed systemd content in this CTF. So, I found a way to shoehorn systemd into Docker.
I added a couple of sections to the prep. I set environment variables to tell binaries that we're executing inside a container, then performed some cleanup of default unit files to ensure systemd didn't get carried away.
# New addition #1
ENV container docker
ENV DEBIAN_FRONTEND noninteractive
# New addition #2 - under our `RUN` block
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* ; \
rm -rf /lib/systemd/system/multi-user.target.wants/* ; \
rm -rf /etc/systemd/system/*.wants/* ; \
rm -rf /lib/systemd/system/local-fs.target.wants/* ; \
rm -rf /lib/systemd/system/sockets.target.wants/*udev* ; \
rm -rf /lib/systemd/system/sockets.target.wants/*initctl* ; \
rm -rf /lib/systemd/system/sysinit.target.wants/systemd-tmpfiles-setup* ; \
rm -rf /lib/systemd/system/systemd-update-utmp*
And finally, I added a custom target file to the image and specified the file as systemd's entrypoint.
COPY boot.target /etc/systemd/system/
CMD ["/lib/systemd/systemd", "--unit=boot.target"]
The full source code.
"Gotcha's" to be Aware Of
I made no attempt to "cheat-proof" this image. That leaves a number of ways to circumvent the intended solutions:
docker history
on the image itselffind -iname '*flag*'
will get you quite a few filesgrep -ir 'flag' /
will get you all but the encoded and gzipped flags- I didn't timestomp, so
find
+mtime
would also be quite useful
The counterpoint? If a student knows to use such tricks, they're probably beyond the entry-level intent of this CTF.
In Conclusion
I hope you find this useful! Don't hesitate to reach out with questions or comments.
Find my links in the footer or in my bio