Description
We intercepted a suspicious file from a system, but instead of the password itself, it only contains its SHA-1 hash. Using OSINT techniques, you are provided with personal details about the target. Generate a custom password list and recover the original password by matching its hash. Download: userinfo.txt, hash.txt, and check_password.py.
cat userinfo.txtcat hash.txtcat check_password.pySolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Profile the target
ObservationThe challenge is OSINT-driven and ships a userinfo.txt full of personal details. Profile the target before generating any password candidates.Read userinfo.txt carefully. It contains personal details like name, birthdate, favourite things, pet names, or other information people commonly use in passwords.bashcat userinfo.txtWhat didn't work first
Tried: Trying to crack the hash directly with hashcat or john using a generic rockyou.txt wordlist.
rockyou.txt holds 14 million common passwords and none of them are built from this person's initials and birthdate. The challenge is designed around profiling, so the password comes from the target's own details rather than a public leak. CUPP generates candidates no generic list contains.
Tried: Skimming userinfo.txt quickly and only entering the name and birthdate into CUPP, ignoring fields like nickname or pet name.
CUPP mutates every field you give it, combining them with leet substitutions, separators, and year suffixes. Leave fields blank and the wordlist shrinks, possibly dropping the exact combination you need. Read every line of userinfo.txt and enter all of it.
Learn more
Password profiling is an OSINT (Open Source Intelligence) technique used in penetration testing and red team engagements. Instead of brute-forcing all possible passwords, you generate a targeted wordlist based on information specific to the target: their name, birthday, spouse's name, pet's name, favourite sports team, employer, and common patterns like appending birth years or "123" suffixes.
Research shows that people overwhelmingly choose passwords based on memorable personal information. A 2019 study found that over 50% of users choose passwords containing their name or birthdate. Attackers who know a target's personal details can often crack their password with a few hundred candidates, whereas a brute-force attack against the same password might take millions of years.
In real engagements, profiling information comes from LinkedIn profiles, social media, company websites, and data breaches. This challenge simulates receiving that information in a file - in practice, gathering it requires careful OSINT research using tools like Maltego, SpiderFoot, or manual social media enumeration.
Step 2Generate a custom wordlist with CUPP and crack the SHA-1
ObservationThere is a SHA-1 hash and a pile of personal details, not a generic password list. CUPP's interactive mode builds a targeted wordlist from the name, nickname, and birthdate.Clone and run CUPP in interactive mode. Enter the personal details from userinfo.txt when prompted. CUPP generates a targeted wordlist (saved as alice.txt or similar). Copy it to passwords.txt, then run check_password.py to find the matching SHA-1 hash.bashgit clone https://github.com/Mebus/cupp.gitbashcd cupp && python3 cupp.py -ibash# Fill in the details from userinfo.txt interactively:bash# Name: Alice Johnsonbash# Nickname: A.J.bash# Birthdate: 15071990 (CUPP asks for DDMMYYYY, no separators)bash# (press Enter for fields you don't know)bash# CUPP saves the wordlist as alice.txt (or similar)bashcp alice.txt ../passwords.txtbashcd .. && python3 check_password.pybash# The correct password is something like Aj_15901990Expected output
Password found: Aj_15901990 picoCTF{Aj_...}What didn't work first
Tried: Running CUPP without the -i flag (e.g. python3 cupp.py -l) to download a pre-built wordlist instead of generating a custom one.
The -l flag downloads generic wordlists, none of which contain a password built from this person's initials and birthdate. The -i flag is the one that prompts for personal details and generates a targeted list.
Tried: Copying the wordlist to a filename other than passwords.txt (e.g. wordlist.txt) and running check_password.py.
check_password.py opens the wordlist by a hardcoded name, so a differently named file raises a not-found error or finishes silently with no match. That is why the solution copies the generated list to the expected name.
Learn more
SHA-1 is a cryptographic hash function that produces a 160-bit (40 hex character) digest. It is no longer considered secure for digital signatures (broken in 2017 by Google's SHAttered attack), but understanding it is important because it's still found in older systems and CTF challenges. SHA-1's main weakness for password storage is that it's fast: a GPU can compute billions of SHA-1 hashes per second, making dictionary attacks trivial.
The correct tool for password hashing is a purpose-built slow hash function like bcrypt, scrypt, Argon2, or PBKDF2 with a high iteration count. These deliberately take milliseconds to compute, making dictionary attacks tens of thousands of times slower. They also include a salt (a random value mixed into the hash) to prevent pre-computed rainbow table attacks. For a deeper tour of the cracking workflow see Hash cracking for CTF; for the Python idioms used by check_password.py see Python for CTF.
Step 3Run check_password.py to retrieve the flag
Observationcheck_password.py ships alongside the hash and the userinfo file, so it exists to run the comparison loop over your wordlist and print the flag on a match.Copy your generated wordlist to passwords.txt (if not already done), then run check_password.py. The script reads from passwords.txt automatically and prints the flag when it finds the matching password.pythonpython3 check_password.pyWhat didn't work first
Tried: Running check_password.py from a different working directory than where passwords.txt was saved.
The script opens the wordlist by relative path, meaning the current working directory. Run it from the cupp subdirectory with the list in the parent and it raises a not-found error and exits. Put both in the same place.
Tried: Writing a manual Python loop to hash each line of the wordlist and compare it to the SHA-1 from hash.txt instead of using check_password.py.
That works in principle and usually fails in practice, because each wordlist line carries a trailing newline and hashing the candidate with it produces a different digest. check_password.py strips whitespace before hashing, which is exactly the step hand-rolled scripts forget.
Learn more
Hash cracking fundamentally works by preimage attack: given a hash H, find any input m such that hash(m) = H. For targeted attacks like this, you compute hash(candidate) for each candidate in your wordlist and compare against the target hash. This is conceptually simple but the quality of the wordlist determines success.
From a defensive standpoint, this challenge illustrates why password complexity rules alone are insufficient. "Fluffy123!" satisfies most complexity requirements (upper, lower, number, special character, 9+ characters) but is trivially cracked by any attacker who knows your pet's name. Password managers generating random strings are the correct solution because they make password profiling attacks impossible.
Interactive tools
- Hash IdentifierIdentify unknown hash types by length and prefix. Covers MD5, SHA-1, SHA-256, SHA-512, bcrypt, NTLM, and more.
- Checksum CalculatorCompute CRC32, MD5, SHA-1, SHA-256, SHA-384, and SHA-512 hashes for text or uploaded files. Verify against known hashes.
- Regex TesterTest regular expressions against a string with live match highlighting, flag toggles, and common CTF pattern shortcuts.
Flag
Reveal flag
picoCTF{Aj_...}
Use CUPP (Common User Passwords Profiler) in interactive mode with the target's personal details from userinfo.txt to generate a wordlist. Run check_password.py against it to find the matching SHA-1 hash. The password combines initials and birthdate (e.g. Aj_15901990).