{"id":412,"date":"2025-11-04T13:28:32","date_gmt":"2025-11-04T13:28:32","guid":{"rendered":"https:\/\/hackingwithj.com\/?p=412"},"modified":"2025-11-04T13:28:32","modified_gmt":"2025-11-04T13:28:32","slug":"thm-valley","status":"publish","type":"post","link":"https:\/\/hackingwithj.com\/?p=412","title":{"rendered":"THM: Valley"},"content":{"rendered":"\n<p>Back again with the <strong>Valley<\/strong> challenge from TryHackMe \u2014 a tidy box with a bit of everything: network analysis, web fuzzing and a little reverse engineering. Good practice and a fun warm-up.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">NMAP<\/h1>\n\n\n\n<pre class=\"wp-block-code has-vivid-purple-color has-text-color has-link-color wp-elements-5a04326512dc580408f3265dac54dc38\"><code>nmap -sC -sV -p- -T4 &lt;ip>\nPORT      STATE SERVICE VERSION\n22\/tcp    open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)\n80\/tcp    open  http    Apache httpd 2.4.41 ((Ubuntu))\n| http-methods: \n|_  Supported Methods: HEAD GET POST OPTIONS\n|_http-server-header: Apache\/2.4.41 (Ubuntu)\n|_http-title: Site doesn't have a title (text\/html).\n37370\/tcp open  ftp     vsftpd 3.0.3\nMAC Address: 02:83:0C:3F:32:E9 (Unknown)\nService Info: OSs: Linux, Unix; CPE: cpe:\/o:linux:linux_kernel\nNmap done: 1 IP address (1 host up) scanned in 13.70 seconds\n           Raw packets sent: 65536 (2.884MB) | Rcvd: 65536 (2.621MB)<\/code><\/pre>\n\n\n\n<p>First thing I checked was the FTP service \u2014 odd port (37370) so it\u2019s worth a look. Anonymous login was attempted but denied, so I moved on to the next sensible target (HTTP) to continue enumeration.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">HTTP (80)<\/h2>\n\n\n\n<p>Gobuster didn\u2019t reveal anything useful at the root or the usual subdirs, and there was no <code>robots.txt<\/code> or <code>sitemap.xml<\/code>. Something about the <code>\/static<\/code> page kept nagging at me, so I ran Gobuster on that path specifically:<\/p>\n\n\n\n<pre class=\"wp-block-code has-vivid-purple-color has-text-color has-link-color wp-elements-03a283cc83c5e77948ae76770ac3cb95\"><code>gobuster dir -u valley.thm\/static -w \/usr\/share\/wordlists\/dirbuster\/directory-list-2.3-medium.txt\n===============================================================\nGobuster v3.6\nby OJ Reeves (@TheColonial) &amp; Christian Mehlmauer (@firefart)\n===============================================================\n&#91;+] Url:                     http:\/\/valley.thm\/static\n&#91;+] Method:                  GET\n&#91;+] Threads:                 10\n&#91;+] Wordlist:                \/usr\/share\/wordlists\/dirbuster\/directory-list-2.3-medium.txt\n&#91;+] Negative Status codes:   404\n&#91;+] User Agent:              gobuster\/3.6\n&#91;+] Timeout:                 10s\n===============================================================\nStarting gobuster in directory enumeration mode\n===============================================================\n\/12                   (Status: 200) &#91;Size: 2203486]\n\/11                   (Status: 200) &#91;Size: 627909]\n..\n\/7                    (Status: 200) &#91;Size: 5217844]\n\/8                    (Status: 200) &#91;Size: 7919631]\n\/00                   (Status: 200) &#91;Size: 127]\nProgress: 218275 \/ 218276 (100.00%)\n===============================================================\nFinished\n===============================================================<\/code><\/pre>\n\n\n\n<p>That <code>00<\/code> entry looked promising \u2014 it was small, which often means a text file or a short page. Navigating to <code>\/static\/00<\/code> revealed a short note from the author and a link to a login form.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"541\" height=\"209\" src=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-72.png\" alt=\"\" class=\"wp-image-413\" srcset=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-72.png 541w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-72-300x116.png 300w\" sizes=\"auto, (max-width: 541px) 100vw, 541px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"1025\" height=\"802\" src=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-73.png\" alt=\"\" class=\"wp-image-414\" style=\"width:654px;height:auto\" srcset=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-73.png 1025w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-73-300x235.png 300w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-73-768x601.png 768w\" sizes=\"auto, (max-width: 1025px) 100vw, 1025px\" \/><\/figure>\n\n\n\n<p>The login form didn\u2019t behave like a normal form: submitting it in the browser produced no network request. I fired up Burp Suite to intercept the attempt and confirmed \u2014 nothing hit the server. That told me the login check is client-side (hardcoded logic in JavaScript), so the next step was to inspect the page source.<\/p>\n\n\n\n<p>Inside the static folder there was a <code>dev.js<\/code>. Inspecting <code>dev.js<\/code> revealed the expected hardcoded credentials:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"592\" height=\"190\" src=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-74.png\" alt=\"\" class=\"wp-image-416\" srcset=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-74.png 592w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-74-300x96.png 300w\" sizes=\"auto, (max-width: 592px) 100vw, 592px\" \/><\/figure>\n\n\n\n<p>With that credential pair in hand the next clue made sense:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"512\" height=\"181\" src=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-75.png\" alt=\"\" class=\"wp-image-417\" srcset=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-75.png 512w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-75-300x106.png 300w\" sizes=\"auto, (max-width: 512px) 100vw, 512px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">FTP (37370)<\/h2>\n\n\n\n<p>That hint pointed at credential reuse: try the same username\/password on other services. The SSH creds weren\u2019t the same, but they worked for FTP on the odd port (37370). Logging in via FTP allowed me to download three files:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"672\" height=\"304\" src=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-76.png\" alt=\"\" class=\"wp-image-418\" srcset=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-76.png 672w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-76-300x136.png 300w\" sizes=\"auto, (max-width: 672px) 100vw, 672px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Analyzing pcap files<\/h3>\n\n\n\n<p>I started with <code>siemFTP.pcapng<\/code>. Right away it showed an anonymous FTP login that succeeded \u2014 somebody logged in with empty creds:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"566\" height=\"141\" src=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-77.png\" alt=\"\" class=\"wp-image-419\" srcset=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-77.png 566w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-77-300x75.png 300w\" sizes=\"auto, (max-width: 566px) 100vw, 566px\" \/><\/figure>\n\n\n\n<p>Shortly after that we can see the user listing a few files:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"333\" height=\"137\" src=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-78.png\" alt=\"\" class=\"wp-image-420\" srcset=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-78.png 333w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-78-300x123.png 300w\" sizes=\"auto, (max-width: 333px) 100vw, 333px\" \/><\/figure>\n\n\n\n<p>Next I opened <code>siemHTTP1.pcapng<\/code> and <code>siemHTTP2.pcapng<\/code>. The capture titles suggested HTTP traffic and, based on experience, I suspected a POST request might contain credentials. In Wireshark I filtered for POSTs:<\/p>\n\n\n\n<pre class=\"wp-block-code has-vivid-purple-color has-text-color has-link-color wp-elements-89a5a45aba57200775f3f444e1e57222\"><code>http.request.method == \"POST\" <\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"563\" src=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-79-1024x563.png\" alt=\"\" class=\"wp-image-421\" style=\"width:941px;height:auto\" srcset=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-79-1024x563.png 1024w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-79-300x165.png 300w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-79-768x422.png 768w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-79.png 1108w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>That turned up a login POST that contained a password in the request body. Using that username\/password allowed an SSH login later on. Always check POSTs first when you\u2019re hunting for creds \u2014 it\u2019s one of the first places people leak passwords.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Initial access<\/h2>\n\n\n\n<p>With the SSH creds from the pcap I logged in as a lower-privileged user and ran my usual checklist-style enumeration. Most of the obvious checks came up empty, but there was one oddity: a cron job running <code>photosEncrypt.py<\/code> every minute. The script itself didn\u2019t appear to give us an immediate foothold.<\/p>\n\n\n\n<p>In the user\u2019s home directory I found an executable called <code>valleyAuthenticator<\/code>. I downloaded it and opened it in Ghidra to figure out what it did. <\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"901\" height=\"88\" src=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-80.png\" alt=\"\" class=\"wp-image-423\" srcset=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-80.png 901w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-80-300x29.png 300w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-80-768x75.png 768w\" sizes=\"auto, (max-width: 901px) 100vw, 901px\" \/><\/figure>\n\n\n\n<p>The binary looked packed, so I used UPX to decompress it before re-inspecting:<\/p>\n\n\n\n<pre class=\"wp-block-code has-vivid-purple-color has-text-color has-link-color wp-elements-cbbcd11d5ad553042367878cf4796af2\"><code>upx -d valleyAuthenticator<\/code><\/pre>\n\n\n\n<p>After unpacking and re-analyzing the binary in Ghidra I found a blob that looked suspiciously like hashed credentials:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"926\" height=\"180\" src=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-81.png\" alt=\"\" class=\"wp-image-424\" srcset=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-81.png 926w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-81-300x58.png 300w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-81-768x149.png 768w\" sizes=\"auto, (max-width: 926px) 100vw, 926px\" \/><\/figure>\n\n\n\n<p>I fed the hashes to CrackStation and got two plaintexts back:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>liberty123<\/li>\n\n\n\n<li>valley<\/li>\n<\/ul>\n\n\n\n<p>Those creds gave me access to a new user <code>valley<\/code> \u2014 who, importantly, is part of a <code>valleyAdmin<\/code> group.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"656\" height=\"102\" src=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-82.png\" alt=\"\" class=\"wp-image-425\" srcset=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-82.png 656w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-82-300x47.png 300w\" sizes=\"auto, (max-width: 656px) 100vw, 656px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Privilege escalation<\/h2>\n\n\n\n<p>The <code>valley<\/code> home directory didn\u2019t immediately reveal anything useful, so I searched for files owned by (or accessible to) the <code>valleyAdmin<\/code> group:<\/p>\n\n\n\n<pre class=\"wp-block-code has-vivid-purple-color has-text-color has-link-color wp-elements-65a7d0fa85d33f996f18a9774dfdd665\"><code>valley@valley:~$ find \/ -group valleyAdmin -type f 2>\/dev\/null\n\/usr\/lib\/python3.8\/base64.py<\/code><\/pre>\n\n\n\n<p>That was the eureka moment. A core Python library (<code>base64.py<\/code>) was writable by a group that included <code>valley<\/code>, and the writable library is used by a script that runs as root (the <code>photosEncrypt.py<\/code> cronjob you noticed earlier). In short: a script running as root imports a module we can modify. I modified the library so that when the root-owned script imports it, my injected payload runs with root privileges:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"328\" height=\"65\" src=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-83.png\" alt=\"\" class=\"wp-image-426\" srcset=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-83.png 328w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-83-300x59.png 300w\" sizes=\"auto, (max-width: 328px) 100vw, 328px\" \/><\/figure>\n\n\n\n<p>invoke <code>bash -p<\/code> to keep the elevated permissions. After the system executed the altered library via the root cronjob, I ran:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"784\" height=\"70\" src=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-84.png\" alt=\"\" class=\"wp-image-427\" srcset=\"https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-84.png 784w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-84-300x27.png 300w, https:\/\/hackingwithj.com\/wp-content\/uploads\/2025\/11\/image-84-768x69.png 768w\" sizes=\"auto, (max-width: 784px) 100vw, 784px\" \/><\/figure>\n\n\n\n<p>\u2026and I had a root shell.<\/p>\n\n\n\n<p><strong>Why this works (short explanation):<\/strong> when a root-owned process imports a module that an attacker can edit, any code in that module will execute as root. By inserting a command that gives the attacker a persistent escalation (SUID on a root shell), you can escalate to root when the vulnerable process runs. This is why writable system libraries or dependencies are a major misconfiguration.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Learning notes<\/h2>\n\n\n\n<p><strong>Reverse engineering<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Unpack first, inspect second: try UPX \u2192 Ghidra\/IDA \u2192 strings \u2192 decompiled functions.<\/li>\n\n\n\n<li>Focus on I\/O and auth routines: look for hardcoded keys, checksum routines, and network endpoints.<\/li>\n\n\n\n<li>Extract any embedded hashes\/credentials and test them offline (hashcat\/john) before trying online.<\/li>\n\n\n\n<li>Keep tooling handy: <code>upx<\/code>, <code>ghidra<\/code>, <code>radare2<\/code>, <code>strings<\/code>, <code>binwalk<\/code>.<\/li>\n<\/ul>\n\n\n\n<p><strong>Privilege escalation through core system files<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Check writable libraries and modules: <code>find \/ -writable -type f 2>\/dev\/null<\/code> and <code>find \/ -group &lt;group> -type f<\/code>.<\/li>\n\n\n\n<li>Audit cronjobs and services that run as root \u2014 anything they import or execute is an escalation target.<\/li>\n\n\n\n<li>If a root process loads a modifiable dependency, you can inject code that executes as root (SUID bash, reverse shell, etc.).<\/li>\n<\/ul>\n\n\n\n<p><strong>PCAP analysis<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Start with obvious filters: <code>http.request.method == \"POST\"<\/code>, <code>ftp<\/code>, <code>smtp<\/code> \u2014 creds often show up in POST bodies or plain protocols.<\/li>\n\n\n\n<li>Use follow-streams and export objects (HTTP, files) \u2014 they often hide config files or credentials.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Back again with the Valley challenge from TryHackMe \u2014 a tidy box with a bit of everything: network analysis, web fuzzing and a little reverse engineering. Good practice and a fun warm-up. NMAP First thing I checked was the FTP service \u2014 odd port (37370) so it\u2019s worth a look. Anonymous login was attempted but [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"site-container-style":"default","site-container-layout":"default","site-sidebar-layout":"default","disable-article-header":"default","disable-site-header":"default","disable-site-footer":"default","disable-content-area-spacing":"default","footnotes":""},"categories":[8],"tags":[],"class_list":["post-412","post","type-post","status-publish","format-standard","hentry","category-ctf"],"_links":{"self":[{"href":"https:\/\/hackingwithj.com\/index.php?rest_route=\/wp\/v2\/posts\/412","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hackingwithj.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hackingwithj.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hackingwithj.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/hackingwithj.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=412"}],"version-history":[{"count":4,"href":"https:\/\/hackingwithj.com\/index.php?rest_route=\/wp\/v2\/posts\/412\/revisions"}],"predecessor-version":[{"id":429,"href":"https:\/\/hackingwithj.com\/index.php?rest_route=\/wp\/v2\/posts\/412\/revisions\/429"}],"wp:attachment":[{"href":"https:\/\/hackingwithj.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=412"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hackingwithj.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=412"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hackingwithj.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=412"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}