Apologies for the delay; I have been away on a “pseudo-holiday”, or “service break” — I’ve been wanting to write something security-related this month, but found myself a little out of time.
This series will be broken into three parts.
- Part 1: General Design Principles
- Part 2: Building a Machine (with an example)
- Part 3: From Vulnerable Machine to A Simple Conceptual “Cyber Range” (3 parts, A, B and C.)
WARNING! I will use a Vulnhub machine I wrote to describe how vulnerable machines are built. If you want to preserve the fun, find “Google Drive link”, download the vulnerable VM, play with it, before coming back to this post. A walkthrough is available here.
Many penetration testers enjoy challenges from Vulnhub, HackTheBox, PentestIt or WizardLabs. Some of us put our skills to the test for constant improvement, whereas others seek vulnerable machines to practise for certifications such as the OSCP. The OSCP certification is so widely known today that there are vulnerable machines where the author(s) demarcate as an “OSCP-like machine”.
What is an OSCP-like Machine
I use the term to demarcate a certain realistic design for all my Vulnhub machines written thus far, but I think it’s a poor tag. What the OSCP trains a student in is in picking up core skills required to perform penetration testing of corporate environments. The corollary is that the practice machines should, too, be realistic and fun enough. Since these are training machines, the learning points much be elucidated clearly, even if not immediately clear during the process of compromising said vulnerable machine.
Building Security Challenges
Arguably, designing security challenges requires one to try much harder than breaking them. There are a few phases to this.
I shall use a machine, called DEVELOPMENT, that is already on Vulnhub to illustrate this point. (You can find download copies on the Vulnhub link, or this Google Drive link. DEVELOPMENT was a machine I built for purposes of a CTF, but has since started to see new life as a training machine. I could always set nastier challenges should I need to build for CTFs again.
Before formally entering this step, it is useful to recall how vulnerable machines are usually “broken into”. We can briefly break this down into (in the simplest, complete form):
- Obtaining a Low-Level Shell
- More Enumeration
- Privilege Escalation
It is possible to chain multiple exploitation steps and/or enumeration steps before a low-level shell or a SYSTEM/root shell can be obtained.
Thankfully, there are many types of exploits that are available. These include information disclosure exploits (e.g. null share enumeration on SMB), exploits that can directly spawn low-level shells (SQLi, RFI) and exploits that can bring one closer to a potential shell (XSS). It will be impractical to discuss every single type of exploit possible.
For the case of DEVELOPMENT, I sought to build a machine that resembled a testing environment. There could be new beta features, or dysfunctional web applications (both of which you will see when trying to compromise DEVELOPMENT). I decided to try to copy the “laziness” of a developer, and in so doing fell back to the usual problems real-world enterprise machines suffer from. These include:
- poorly architected solutions
- misconfigurations (whether deliberate for easy access or accidental)
- semblance of “security measures”.
At this point, I had quite a few ideas, which could be summarised as follows:
- Visitor visits a web application.
- Visitor finds some “hidden” development portal.
- Visitor finds compromising information on development portal.
- Visitor gets a low-level privilege shell.
At that point, I did not think too much about privilege escalation yet; the design for that happened to be trivial for this machine — I will look at privilege escalation in greater detail in Part II. In short, because of the machine’s origins in a tight time-based CTF, we wanted a simple, “off-the-shelf” privilege escalation.
Caution: It would be helpful to understand, as well as you can, how the vulnerabilities work. Depending on where you intend to host your machine, you may have to restrict the types of vulnerabilities you can implement on such machines. E.g. if we intend to implement DLL hijacking (e.g. a program calls for non-existent DLLs in a folder writable by the user), we must be aware that there may be multiple users trying to write malicious DLLs with the same name and uploading them to the machine! On a boot2root virtual machinr this is fine, but you should consider spinning duplicate instances in a cyber range like environment. More on this in Part III.
Perhaps the most fun portion of vulnerable machine building is in its actual construction. This is usually where many security testers balk at the effort required to build a machine. It can be intimidating because of the requirements. Without loss of generality, the machine must first be designed securely, before adding in the “insecure” features that allow for system compromise. Delivering a secure machine requires understanding of the operating system and services that are being deployed. Often, this means that the machine setter must be fairly conversant in a wide variety of services for different functions (e.g. configuring DoveCot for mail, Samba for Windows-like file-sharing on Linux, IRC). Sometimes, we learn while on the designing job!
I think building machines is also a wonderful blue-team experience. Defending all of those services introduced can be quite a hassle, but I decided, in the machine design, to add a few frivolous services too. Some were to add realism (many machines use Samba just for file-sharing), while others were just for fun and laughter (and Googling joy for the CTF).
In DEVELOPMENT, we eventually decided on:
- Configuring a web server, where the main path of exploitation will take place. We will make the web server look like a draft site — developers do not always have time to beautify their own pages since beautifying code can be enough sufferance.
- Configuring Samba, and designing it to be realistic as a file-share service, yet not quite exploitable.
- Configuring ident, just for fun and laughter (and a historical lesson on how services were written without security in mind at all)
- Configuring SSH, because we want a SSH entry point.
I wanted to add a few “defences” to make life annoying for the CTF participants. Some of these defences are useful (e.g. a host-based IDS like OSSEC), while some are dubious (e.g. jail shells). DEVELOPMENT features an extremely sensitive host-based IDS to stop vulnerability scanning tools in their tracks. No nikto or OpenVAS for you, sorry!
Machines need to be realistic-looking for people to believe they are training in a real-world environment. This step is often overlooked by technical geeks, but it is important in a penetration test. Reasons include the following:
- Users sometimes set strange passwords. There are enough people setting passwords of their favourite cat, dog, boy band e.t.c. that contextual clues such as these may well be useful in a real penetration test. But how do we weave the contextual clues into a realistic scenario?
- Services we install need a pretext to be there. Sometimes the pretext is where trouble begins. Example: higher management demanding the rollout of an application in spite of the lack of security testing.
In the case of DEVELOPMENT, we had a “boss” who shares the same terminal with the “intern”, who sat somewhere else. Thus, intern had SSH access to the DEVELOPMENT machine. Unfortunately, DEVELOPMENT being DEVELOPMENT, strange shenanigans happen. For instance, the developer thought it was a good idea to eliminate the database and use SiteFiLo in the hope of flattening a typical “presentation-application-database” structure. The machine shows how such a system can easily fall apart. Common problems such as credential reuse rear their ugly head in this machine as well.
This is probably the most frustrating aspect of creating a vulnerable machine. We want to know if:
- The machine is indeed solvable through the intended method(s).
- The machine is NOT solvable through unintended method(s).
Let us use DEVELOPMENT as an example. We must validate the following:
- Does the intended exploitation work as intended (easier validation)
- Do other paths NOT work, i.e. they are secured (harder validation)
The first validation is fairly easy to conduct. Simply put, it involves the replication of the series of exploitation steps required for various levels of privileges. In the case of DEVELOPMENT, this step is especially simple because of the easy exploitation and enumeration nature. In general, we can test the following. Below is a non-exhaustive list:
- DHCP: does it correctly obtain an IP address? (Trivial) — note if you are hosting this in a range, you should use a static IP address.
- Port scanning: can the required ports be reliably found with a port scanner like nmap or masscan?
- Services: do the services work as they should?
- Availability: how do the services behave upon certain denial of service attempts? This is especially important for machines implementing a remote buffer overflow. Is the service stable? Might we have to script for the service to recover at periodic intervals?
- Protections: do the protections work at the correct level? In the case for DEVELOPMENT, the host-based IDS had to be tested because it was, by design, supposed to perform active response against mass vulnerability scanning or fuzzing.
- Remote Exploitation: can we actually obtain a shell? How does this shell behave and what can we do with it?
- More enumeration: what happens when we would like to enumerate the insides of the machine?
- Kernel/OS: if it is meant to be stable, is it really non-vulnerable?
- Misconfigurations: are the misconfigurations left behind intended?
- Third party services: are the third party services correctly non-vulnerable or vulnerable?
- Reproducibility: if we a perform a boot to root once again, can the machine be rooted?
Checking for unintended solutions is a far more difficult endeavour. Usually, these arise from our own errors. This is essentially performing a vulnerability assessment on ourselves. For DEVELOPMENT, I checked for simple unintended solutions such as:
- Are the remotely accessible services vulnerable to exploits causing remote code execution (RCE), information disclosure, or any other issue that will provide a would-be attacker with other information?
- Can these services be brute forced when not designed for that?
It may appear to the casual observer that this is simple, but this is not the case. Any penetration tester should realise the laborious nature of such a test.
Testing the machine comprises difficulty and validation on a different environment. This is where like-minded penetration testers come in useful. In brief, we want to check for user experience. Some of these include (depends on environment)
- Can we establish a connection to it reliably? (For boot2root, boot under a different hypervisor. For a range, can I achieve a good VPN connection?)
- Is the machine, in its default state, rootable? If not, write appropriate start-up scripts to prep the machine accordingly.
- Is the difficulty indeed as what is planned for?
Part 2: uncovering the internals of a more interesting machine: TORMENT. Have fun building boot2root virtual machines!