Creating Vulnerable Machines: Beginner Guide

Kavishka Gihan
16 min readDec 1, 2022

In this article, I will be walking you through the whole process of how vulnerable machine creation happens, the approach, the thought process, and concept representation.

This is a topic that I have been meaning to talk about for a long time. With all the requests I have been receiving I thought it’s time to just go for it.

Before we get into the it, something to mention is that in this article, I won’t be talking about what makes a good machine or what steps/techniques would make the box enjoyable. It’s up to you to make the box interesting and let the players learn something new, useful yet fun.

One more thing, if you have been following me for a while, you know I am an active content creator in hackthebox. That said, this will not be a specified guide for HacktheBox machines stand-alone, the goal of this article is to make you understand how things are done in the process of creating machines generally.

If you have no idea about what and how the quality of the box is measured, I would really encourage you to read the HackTheBox’s and Micah’s articles about this.

Introduction

Whether you like it or not, the process of creating vulnerable machines is kind of a lengthy process. So for the sake of simplicity, I will be dividing the process into 4 sections,

  1. Research
  2. Design
  3. Building
  4. Testing

Under these 4 sections, I will talk about pretty much everything you need to know to get started with building your own machine. In the end, I will be showcasing the way I approach it by creating a sample machine so that you can get the full idea of the process.

# Research

As the initial stage, this is one of the most important stages of the process. It’s almost okay to say, Good Research = Good Machine.

Before even thinking about creating a machine, you need to have some vulnerabilities in your pocket to set up in the machine. These can be either misconfigurations or insecure software(SQLi, SSRF, LFI). What you want to use is up to you.

One crucial thing to note is that always try to build custom applications. Don’t get me wrong, there is no problem in using an open-source application with a CVE. But when it comes to learning, with custom build applications, you can learn more and have more control over what you are trying to showcase.

Resources

Even though I said custom applications are better in place, there is no problem with using a CVE if you can fit it into the design correctly. Some of the best resources where I find these vulnerabilities are listed below.

  1. Exploit Database: This includes public findings of different researchers on various types of open-source software showcasing different types of techniques.

2. HackTricks: This is a collection of different techniques, and concepts that you may find interesting indexed together.

3. VulnHub (GitHub repo): This is a large collection of vulnerable docker images that you can use in your machines.

4. PayloadAllTheThings: Just like hacktricks, this is another large index of attack techniques and payloads indexed together that you may find very useful.

Other Resources

Apart from these resources, some of the other resources I personally use are python bug index where you can find different bugs reported in accordance with different python modules.

Bug reports from hackerone are another really nice resource. The best part about this is that since these are real word attacks, it would look very nice if you could simulate such a real work scenario.

# Design

On to the next stage, yet another important stage which is the design of your box. Personally, this is a stage that I pay real attention to as I like doing themed boxes instead of having exploits here and there. Also, this is the stage where you could really do good in the realism phase of your box.

Context

Once you have your chosen vulnerabilities on you, now it's time to make up an outline. With the vectors, you have found, you will have to design a certain attack chain that the players will have to go through. But make sure you don’t use these attack vectors without any context.

I.e don’t configure a MySQL-based SQLi for the foothold and an Oracle-based attack for root in the same box. It's not realistic! Think for yourself, why would there be an Oracle DB just to be exploited when all the applications are using MySQL? If you find both of these vectors are worth showcasing, add them in 2 separate boxes. Or make a believable outline story. Like all the applications are migrating from MySQL to Oracle and only the old applications are still using MySQL while the updated, user-presented applications are using Oracle.

Simply said, when you add steps to your box those have to be things that actual users would do. They shouldn’t be there just to get exploited. That’s what makes a box realistic.

Theme

Players, including myself like boxes that are built to a theme, I.e my box “Seventeen ”was made according to a High school theme where the players had to interact with a Student management system, Exam management system, and webmails.

As another example, the “Noter ”box was based on a fictional company that provides services for users to take their personal notes quickly. It had an FTP server that only VIP users can use for their benefit.

Again, this is not a “crucial”, or a “must have” thing but take it from me, it really does help. The more realistic your box feels, the better experience the players going to get. So, always try to show the underlining story of the box whenever possible.

# Building

Now on to the fun part. This is where most of beginners get stuck as it may get a little overwhelming for starters. From all the stages above mentioned, this is the utmost important one. Regardless of how nice your vulnerabilities and story are, if you can’t set them up correctly, it's worthless.

Not to get technical yet, but there are 2 models that I typically get inspiration from when it comes to building the machine.

  1. Waterfall model
  2. Iterative Incremental model

Don’t worry, you don’t need to be familiar with these names. These are just fancy words saying things in SDLC (System Development Life Cycle)

Waterfall model

With this, we need to know exactly what we are going to do in the beginning. For example, we need to find vectors for the foothold, user, and root stages. We need to know how each step interacts with the others. Testing, which we will be talking about soon, comes after all the steps are configured.

Personally speaking, I found this method to be very efficient with easy and medium-rated boxes where you don’t have many steps involved. Also, with this method, the process becomes much faster. I.e to create “Late”, it took me less than 2 weeks.

You may think, well isn’t this the ideal way of doing this? You find some vulnerabilities, make up the outline design and put them all together in the end. Not really.

Iterative Incremental model

With this method, there is no need to know all the steps that you are going to include at first. No need to configure all the steps at the beginning. You can unit-test every step once done.

I.e you can set up your application for the foothold, test it, skip user and move on to root, configure that part, test it, and then come to user whenever you need. After everything is done, then you can do the system testing including all the unit-tested parts.

This method is ideal when you are going for a hard or an insane boxe (even some medium boxes) as they include a lot of steps that are hard to test at once without unit-testing at least once. This method is very ideal for busy people like myself as it’s easy to get the steps done one by one as you see fit.

Tips

These are some things that I follow that help me to keep things organized and clean during the building process.

  1. Take snapshots: Always take snapshots! I can not tell you how many times I had to redo the whole configuration process just because I messed something up at the last step. Once you are done with a certain step, i.e say foothold, test it fully and take a snapshot and call it “Tested foothold ” (or anything that suits you) so that if for some reason, you mess up the box, you can easily revert to that snapshot and don’t have to redo all the stuff again.
  2. Keep backups: Again, I don’t remember how many files I have accidentally deleted with rm -r * . Once you are done coding the application, before you transfer it to the VM, make sure you get a backup first. Then if something like this happens, you don’t need to worry about rewriting 100s of lines of code.
  3. Stay clean and organized: This may not be a problem when you are working on a small or medium-sized box, but when the number of steps increases, the configuration files, backup files, and the cleanup script gets pilled up. If you don’t organize them properly it becomes hard to deal with them.
  4. Use updated software: Whenever you are setting something up, that isn’t directly related to the attack or isn’t meant to be exploited, make sure you install the latest versions. I.e when using an Ubuntu Image to install the VM, make sure you install the newest possible version to stay away from unwanted kernel exploits.

# Testing

This is a stage that I paid zero attention to when I got started. Once I have tested all the steps one by one, I used to assume as it was done. But testing is one of the most important phases in the process.

We all make mistakes. So there could be a chance that you may have accidentally left something unchecked. Maybe an undeleted file or some commented lines of code in the application itself or unwanted permission given to a user or a file. The possibilities are endless. Therefore, always make sure you spend enough time testing each and every step at least 2 times.

Unit testing stand-alone is not enough. Once you are fully done setting up the box, it's necessary that you test the box again with all the steps configured at once. Approach the box as a player would see if you have left any unwanted ports open or see if your application is running as the wanted user with the necessary privileges. See if all the applications are working as they should.

Once the box is fully tested, make sure to take another snapshot and name it “Final”. After, you can export the VM, do the writeup (which I will be talking about soon), and publish it on the platform of your choice.

And that’s it! Wasn’t so hard, was it? :) I know it’s a lot to take at once. So let’s build a simple machine together so that you can understand the process even better.

Example Machine Build

For this example, I have created a fictional story with some really simple vulnerabilities.

  • The box has 2 open ports, 22 and 80. Doing some vhost enumeration on port 80, we will find a vhost that lets us perform an SSTI attack. Using that we will get a shell as a user
  • For root, we exploit the sudo permission that was given to the user.

Before any of this, we need to set up a Virtual Machine so that we can set these up. You can use your choice of virtualization software, but for this demonstration, I will be using VMware. If you don’t have VMware installed, get it from here.

Then we need to install Ubuntu Server on a Virtual Machine. Since this is not an article about installing Ubuntu on a VMware Virtual Machine, I won’t be talking about how that’s done. Here is a detailed guide with everything you need to know to get it installed.

Once you have a VM that has Ubuntu server installed, we can begin.

  1. Setting up the environment

First, we need to ssh into the VM to start configuring it. If you followed the guide I linked above, OpenSSH should be installed in your system. (just need to tick the box that asks if you want to install OpenSSH when installing)

After logging in as the root user, you need to create 2 ssh key pairs for the root user.

ssh-keygen
echo '<Host user public ssh key>' > ~/.ssh/authorized_keys

Then you should add your host user’s ssh public key(id_rsa.pub) to the .ssh/authorized_keys file in root user's home directory so that we can ssh in as root without specifying a password.

Now you should be able to log in as root without specifying a password.

I created a user called svc when I installed the server. This is the user that will be running our vulnerable service.

2. Setting up the Flask Application

As the vulnerable application, I am using a very simple Flask application that has an SSTI (Server-Side Template Injection) vulnerability. For that, I will first create a srv directory in svc user’s home directory. And put the app.py file there.

from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route('/')
def index():
return render_template_string('<p align="center"> Example App </p>')

@app.route('/hello', methods=['GET'])
def hello():
try:
name = request.args.get('name')
print(name)
hello_template = '''<p align="center"> Hello {} </p>'''.format(name)
return render_template_string(hello_template)
except:
return render_template_string('<p align="center"> An error occured!</p>')

if __name__ == '__main__':
app.run(debug = False, host = '127.0.0.1', port = 5000)

The vulnerability here is, in the /hello endpoint, it is passing user input to a render function without any validation. Again, this is not an article about Flask or Python so I won’t be talking about every aspect in this application. Since the choice of the vulnerability and the representation is up to you, you will need to figure out how to build the application yourself.

After you have tested the application and made sure that everything (finding the SSTI vulnerability , getting RCE with it, getting a reverse shell) works as it should, then you can go ahead and deploy it permanently.

3. Deploying the Flask Application

For this, there are a couple of ways to do it. One is to use a systemd service file and start a service so that this will start in the startup. But that is more of a manual approach. The accepted and personally, my way is to use pm2 instead. pm2 is a process manager that can be used to manage processes easily.

Install it with:

npm install pm2@latest -g

To run this with pm2 all you need to do is issue one command.

pm2 start /home/svc/srv/app.py --user svc --interpreter python3

And notice that we are running this as root Yes, that's right. Since we are specifying the user that this should run as with --user svc regardless we run this as root, the flask application will run as svc We can confirm it with ps

ps aux|grep app.py

Now to save this as a startup service, just do:

pm2 save

But if you notice, our application is running in the localhost interface. We need to let players access it through an HTTP server. How do we do that?

4. Setting up Apache and Virtual Host Routing

To do that, we need to install the Apache service and some modules to set up vhost routing.

apt update
apt install apache2 libxml2-dev

Then we have to edit its configuration file so that players can access the local port 5000 from a virtual host. This is called virtual host routing.

nano /etc/apache2/sites-enabled/000-default.conf
<VirtualHost *:80>

ServerName hello.host.local
ServerAdmin admin@host.local
ProxyPass / http://127.0.0.1:5000/
ProxyPassReverse / http://127.0.0.1:5000/

ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined

</VirtualHost>

Paste this blob at the end of the file. This will route any requests for hello.host.local to http://127.0.0.1:5000 which is exactly what we need.

Once that’s saved, we need to enable the required modules so that vhost routing works correctly.

a2enmod proxy
a2enmod proxy_http
a2enmod proxy_ajp
a2enmod rewrite
a2enmod deflate
a2enmod headers
a2enmod proxy_balancer
a2enmod proxy_connect
a2enmod proxy_html

Finally, we just start the apache2 service with:

service apache2 restart

Now we should be able to access the Flask application by visiting hello.host.local Once again, it's time to test and see if everything works with the Flask Application after setting up the vhost. If so, we can conclude the user step here.

Now that that’s done, let’s go ahead and take a snapshot so that we don’t lose our configurations if we mess something up.

From VMware, you can take a snapshot by going to VM->Snapshot->Take Snapshot

As you see above, I like to add the changes I did in the description so that it's easy for me to refer to in the future.

5. Setting up sudo permissions for the svc user

For root, we will be giving the svc user sudo privileges to run systemctl as root. You may be thinking, Hmm… why would this user have such privileges? You are correct. That's exactly the way to question the realism of any step you add.

Well, the answer to that question is simple. As the name of the user implies ( svc -> service ), this is supposed to be a service user. It’s typical that service accounts have special privileges. In this case, the svc user can control the services running on the system.

5.1. Removing the svc user from unwanted groups

To set it up, first, we need to remove the svc user from all the unwanted groups he’s in.

groups

As you see, he is a part of all those groups. We only want him to be a part of svc the group. He should be removed from all the other groups.

gpasswd -d svc <group name>

If we log out, re-login, and see, we can see that now he is only a part of the svc group.

5.2. Editing the /etc/sudoers file to grant sudo permissions

Now we use sudoedit to edit the sudoers file so that we can give the svc user permissions to execute systemctl as root without any password.

NOTE: Since we are using sudoedit it automatically takes a backup of the files we edit. If you were to edit a configuration file with anything else, make sure you get a backup first.

sudoedit /etc/sudoers

Then add this line under the User Privilege section.

svc     ALL=(root) NOPASSWD: /usr/bin/systemctl

If you now look at sudo permissions of the svcuser, you will see he can run systemctl as root.

And just like gtfobin says this works fine.

sudo -u root /usr/bin/systemctl
!sh

Since this doesn’t have anything to do with our Flask Application, it’s not that important to test it again but if this step was something that is even indirectly connected with the application, it's best to test it before concluding the root part.

Once that is done, we take another snapshot including the stuff we added just like before.

6. Running Final Tests

Once both the user and root parts are done, now it’s time to do the final tests. There are 2 phases of this final test.

  1. Inside Testing: Look at all the scripts, and backup files that you added in every step and see if there are any unwanted files. In our case, the only script we added was app.py so nothing to worry about there. See if there are any unwanted processes running.
  2. Outside Testing: Spin up your attacker box and try to approach this as a player. See if there are any unwanted ports open. See if all the applications are working. Go through the whole attack path one more time to see if everything works as it should.

There are some things that I usually do before exporting the VM.

  1. ln -s /dev/null ~/.bash_history so that the history of the user commands doesn’t get saved
  2. Delete unwanted hidden files in the user home directory i.e .python_history , .wget-hsts
  3. If there are flags included, make sure that the right permissions are given to them. (chown root:svc /home/svc/user.txt )

If all these steps are completed successfully, then congratulation, you have now built your first-ever vulnerable machine!

Now all that’s left to do is to export the VM as a .ovf (for VMware) or .vdi (for Vbox) and publish it on the platform of your choice.

Final thoughts

As you might already know I am a huge fan of creating vulnerable machines like this and I have already published almost 10 boxes in Hackthebox including Late, Noter, Seventeen, Moderators, Three and many others.

For me, it is one of the best ways to learn new techniques and concepts. I won’t possess the knowledge and the skills I possess now if it wasn’t for making these boxes. I made a lot of mistakes on the way and I learned a lot from them. That is why I encourage each and every one of you to do the same.

Find some vulnerabilities, build a simple custom web application, put them all together in a box, and submit it somewhere you can get good feedback about your submission. You might not be successful at first. I wasn’t. I had to resubmit my first box, Moderators, 4 times to get it accepted. Don’t get discouraged by the failures. Learn from your mistakes, make sure that never happens again and move on!

Anyways, I hope you enjoyed this little walkthrough. As always, if you have any questions, leave them in the comments or contact me through social media.

Happy Hacking!

--

--

Kavishka Gihan

Cyber Security Student | Machine author @hackthebox | find me on instagram @_kavi.gihan