Sitemap
Neelesh Pandey

Neelesh Pandey

Junior penetration tester writing about offensive security, labs, and what I’m learning along the way.

Checkmate — TryHackMe Writeup

5 min readSep 8, 2026

--

Press enter or click to view image in full size

Room: tryhackme.com/room/checkmate

Room Overview

Checkmate is a password-focused TryHackMe room built around a single idea: humans are bad at picking passwords, and that badness compounds. A fictional admin, Marco, has deployed several internal services on the same host — an admin panel, an employee/social platform, and a personal account with SSH access behind it. Each stage leaks something (a password, a birthdate, a habit) that makes the next stage crackable. Nothing in the room requires a software exploit; it’s entirely about weak, reused, and predictable credentials.

Target: 10.49.179.252, with services on ports 5001, 5002, 5003, and SSH.

Stage 1 — Default Credentials (Port 5001)

The first login form (/login, POST body of username + password, failure string Invalid credentials.) looked like an unconfigured admin panel, so instead of guessing I went straight for a known default-credentials list:

hydra -l admin -P /usr/share/wordlists/seclists/Passwords/Default-Credentials/default-passwords.txt \
-f -V -s 5001 10.49.179.252 http-post-form \
'/login:username=^USER^&password=^PASS^:Invalid credentials.'
  • -l admin — assumed/given username
  • -P — SecLists' default-passwords list
  • -f — stop as soon as a valid pair is found
  • -V — verbose, to confirm the failure string was matching correctly

Result:

[5001][http-post-form] host: 10.49.179.252   login: admin   password: 12345

Admin access, on a device that was never reconfigured past its factory defaults. Default credential lists are always worth trying first against an exposed login form before anything more elaborate.

Stage 2 — OSINT Wordlist with CeWL (Port 5002)

The second panel wasn’t going to fall to a generic list — this one needed vocabulary specific to the organization. Rather than guessing at company-flavored words, I let CeWL crawl the site and build a wordlist from whatever language actually appeared on the pages:

cewl -d 2 -m 6 --lowercase -w keywords.txt http://10.49.179.252:5002/
  • -d 2 — crawl two links deep
  • -m 6 — discard anything under 6 characters
  • --lowercase — normalize casing

This produced a tight, relevant list of job-posting and company-culture terms:

security, careers, excellence, engineering, innovation, digital,
employee, future, remote, engineer, tooling, response, design,
intern, search, talent, transformation, portal, designer, berlin,
centered, workshops, support, product, specialist, reliability,
automation, operations, endpoint, process, feature, disabled,
internal, access, profiles, username, password, features, enabled,
locations, benefits, candidate, opportunity, senior, featured,
analyst, threat, detection, culture, infrastructure, london,
projects, operational, internship, hybrid, incident, development,
mindset, devops, platform, pipelines

~60 words, all pulled straight from the target instead of a bloated generic list. Fed into Hydra against marco:

hydra -l marco -P keywords.txt -f -V -s 5002 10.49.179.252 http-post-form \
'/login:username=^USER^&password=^PASS^:Invalid credentials.'

Result (hit on attempt 3 of 61):

[5002][http-post-form] host: 10.49.179.252   login: marco   password: excellence

A raw keyword scraped from the site’s own content, no transformation applied. Lesson: a scraped, context-aware wordlist beats a huge generic one almost every time.

Stage 3 — Personalized Password Cracking (Port 5003)

Logging into Marco’s account on port 5002 leaked personal information that made a much stronger-looking password on port 5003 crackable:

Field Value First Name Marco Surname Bianchi Nickname marky Birthdate (DDMMYYYY) 14021995

Get Neelesh Pandey’s stories in your inbox

Join Medium for free to get updates from this writer.

Marco had also posted a “password tip” on his social profile revealing his personal methodology:

Take a company keyword, capitalize it, append the year (or any other number), and add an exclamation mark.

That single post ends up being the key to Stage 5 as well — but for Stage 3, the more immediate lead was his name and birthdate. I built a candidate list (marco.txt) combining name/surname/nickname variants with permutations of 14021995, in the spirit of what a tool like CUPP generates automatically:

hydra -l marco -P marco.txt -f -V -t4 -s 5003 10.49.179.252 http-post-form \
"/login:username=^USER^&password=^PASS^:Invalid credentials."
  • -t4 — throttled to 4 tasks since this was a larger list (2,816 candidates) and I wanted a stable run

Hydra worked through name/date permutations — Bianchi2495, Bianchi2018, Bianchi024, and dozens more — before landing on:

[5003][http-post-form] host: 10.49.179.252   login: marco   password: Bianchi2495

Surname plus a scrambled fragment of his birth year. Exactly the “personal but not obvious” pattern that OSINT-driven wordlists are built to catch.

Stage 4 — Recovering a Filename from Its Hash (Port 5003)

Challenge: Marco recently uploaded a new profile picture. The platform renames uploads to the SHA-256 hash of the original filename, saved as (hash).png. Task: recover the original filename.

After logging in with the Stage 3 credentials, I opened View Page Source on Marco’s profile and found the image reference:

d34a569ab7aaa54dacd715ae64953455d86b768846cd0085ef4e9e7471489b7b.png

That string is a hash of a filename, not the filename itself — but since it’s almost certainly a hash of a short, guessable string rather than a high-entropy secret, it’s crackable the same way a leaked password hash would be. Instead of running a local wordlist attack, I checked it against CrackStation, which maintains large precomputed tables for exactly this kind of common-input hash:

d34a569ab7aaa54dacd715ae64953455d86b768846cd0085ef4e9e7471489b7b
→ family

Marco’s profile picture had originally been uploaded as family (family.png) before the platform hashed the filename on upload — a small personal detail consistent with the human-centered OSINT theme running through the room.

Stage 5 — Pattern-Based SSH Brute Force

The final piece uses the one clue that’s been sitting in plain sight since Stage 3: Marco’s own “password tip” post.

Take a company keyword, capitalize it, append the year (or any other number), and add an exclamation mark.

That’s a full password-generation rule, not just a hint — capitalized keyword + 2-digit (or 4-digit) year-style number + !. Combined with the company keywords already scraped in Stage 2 (security, excellence, innovation, digital, and any others gathered from the site, e.g. cloud), this narrows SSH's keyspace enormously compared to blind brute-forcing.

Building the pattern-based wordlist with Crunch — one command:

Rather than running Crunch five separate times, one per keyword, a small shell loop generates the whole list in a single command:

rm -f passlist.txt; for w in Security Excellence Innovation Digital Cloud; do \
len=$((${#w}+5)); crunch $len $len 0123456789! -t "${w}20%%!" >> passlist.txt; done
  • for w in Security Excellence Innovation Digital Cloud; do ... done — loops over all five scraped keywords
  • len=$((${#w}+5)) — computes the total password length on the fly: the keyword itself, plus 20 (2 chars), %% (2 digit placeholders), and ! (1 char) — so min and max never have to be typed by hand per word
  • crunch $len $len 0123456789! -t "${w}20%%!" — same template logic as before (% = digit placeholder, everything else literal), just parameterized on $w
  • >> passlist.txt — every iteration appends to the same file, so it ends up containing all five keyword patterns combined
  • The leading rm -f passlist.txt just makes sure old runs don't get appended on top of a stale file

Same output as running Crunch five times by hand, in one line.

This produces every Keyword20XX!-style candidate for each keyword pulled from the site — a few thousand tightly-scoped guesses instead of millions of blind ones.

Running it against SSH:

hydra -l marco -P passlist.txt -f -V -t4 10.49.179.252 ssh
  • -t4 — kept the task count modest since SSH brute-forcing is heavier per-attempt than an HTTP form, and some SSH daemons rate-limit or drop connections under high concurrency

Hydra worked through the Keyword20XX! candidates and, after a while, landed on a valid SSH credential:

[ssh] host: 10.49.179.252   login: marco   password: Security2024!

Security2024! — the capitalized keyword, the current-feeling year, and the exclamation mark, exactly as Marco described in his own post. That's SSH access, and the final stage of the room.

Writeup for the TryHackMe “Checkmate” room.

--

--

Neelesh Pandey

Written by Neelesh Pandey

Junior penetration tester writing about offensive security, labs, and what I’m learning along the way.