banner

Creating a Linux CTF

Last updated 

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?
ClueThe 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
Solution
# 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?
ClueFlag 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
Solution
# 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?
ClueFlag 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:

Prep
FROM debian:latest

RUN apt-get update ; \
    apt-get install -y binutils ;
Solution
# 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?
ClueGrab 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:

Prep
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

Solution
# 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?
ClueWho'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).

Prep
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

Solution
# 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?
ClueUsers 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

Solution
# 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?
ClueAn 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.

Prep
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;
Solution
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
Solution
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?
ClueWhat 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
Solution
# 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.

Additions
# 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.

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:

  1. docker history on the image itself
  2. find -iname '*flag*' will get you quite a few files
  3. grep -ir 'flag' / will get you all but the encoded and gzipped flags
  4. 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

Github