Description
My dog-sitter's brother made this website but I can't log in; can you help?
Setup
Open the challenge URL in your browser.
Open the browser developer tools (F12) and navigate to the Sources or Debugger tab.
Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Read the page sourceObservationI noticed the login form submitted with no visible network request to a backend server, which suggested that all authentication logic was running client-side in JavaScript and would be visible in the page source or loaded scripts.View the page source or open the Network tab and find index.js. All authentication logic is written in client-side JavaScript - there is no server-side validation.Learn more
Everything a web browser renders is sent to the client - HTML, CSS, and JavaScript are all downloadable and readable. When authentication logic is implemented entirely in client-side JavaScript (as opposed to a server-side check), the credentials and validation rules are necessarily included in that code. An attacker simply reads the script to understand exactly what the application checks.
Browser developer tools (F12) are the primary instrument for client-side web analysis. The Sources tab shows all loaded JavaScript files, the Network tab shows every HTTP request and response, and the Console lets you run arbitrary JavaScript in the page's context. Pressing Ctrl+U shows the raw HTML source including inline scripts.
Real applications should always perform authentication on the server, where the code is not exposed to users. Client-side checks are appropriate only for UI feedback (like disabling a submit button before all fields are filled) - they must never be the only gate protecting sensitive resources.
Step 2
Decode the usernameObservationI noticed the script compared btoa(username) against the string 'YWRtaW4=' with its telltale '=' padding, which identified it as base64 encoding and indicated the plaintext username could be recovered by simply decoding it.The script compares btoa(username) === 'YWRtaW4=' - decode this base64 string to get the required username.pythonpython3 -c "import base64; print(base64.b64decode('YWRtaW4=').decode())"Expected output
admin
What didn't work first
Tried: Use atob() in the browser console to decode the username instead of Python
atob('YWRtaW4=') works fine in the browser and returns 'admin' - the issue is that some learners confuse btoa (encode) with atob (decode) and call btoa('YWRtaW4=') instead, which double-encodes the string and produces 'WVdSdGFXND0=' rather than the plaintext. Always use atob() to go from base64 to plaintext.
Tried: Treat the base64 string as an MD5 or SHA hash and try to crack it with a tool like hashcat
Hash outputs are fixed-length hex strings (32 hex chars for MD5, 64 for SHA-256) and are not reversible. Base64 strings end in = padding and decode instantly because they are an encoding, not a one-way function. Feeding 'YWRtaW4=' to hashcat produces no result because base64 is not a hash algorithm.
Learn more
Base64 is an encoding scheme that converts binary data (or arbitrary text) to a string using only 64 printable ASCII characters (A-Z, a-z, 0-9, +, /). It is used for data transmission contexts that require text-safe encoding, such as HTTP Basic Auth headers, data URIs, and email attachments. Critically, it is not encryption - it is trivially reversible by anyone, as this step demonstrates.
btoa()is a browser JavaScript function (Base64 encode) andatob()is its inverse (Base64 decode). The names derive from the historical Unix programsbtoa(binary to ASCII) andatob(ASCII to binary). Encoding a password or username in base64 and comparing it provides zero security - it is security theater that only stops someone who has never heard of base64.The
=or==at the end of a base64 string is padding: base64 encodes 3 bytes into 4 characters, so strings whose length is not a multiple of 3 are padded to reach the next boundary. Recognizing this padding is one of the simplest ways to identify base64-encoded data in the wild.Step 3
Decode the password / flagObservationI noticed the same btoa-comparison pattern applied to the password field, with a longer base64 string that matched the length of a picoCTF flag, which suggested decoding it directly would yield the flag without needing to submit the form.The password is compared against a base64 string. Decoding it in the browser console reveals the flag directly - the password itself is the flag.bash# In browser console: atob('cGljb0NURns1M3J2M3JfNTNydjNyXzUzcnYzcl81M3J2M3JfNTNydjNyfQ==')What didn't work first
Tried: Try to log in using the decoded username and password through the actual form to get the flag
Submitting the form is not required because the password itself is the flag - decoding the base64 string gives you picoCTF{...} directly. If you do try the form, it may still work, but you have already captured the flag by decoding the value, so the form submission step is unnecessary.
Tried: Attempt SQL injection on the login form (e.g. entering ' OR 1=1 --) hoping to bypass authentication
SQL injection only works when user input is interpolated into a database query server-side. This login has no server component at all - the comparison is done by JavaScript running entirely in your browser. SQL injection payloads are passed as strings to btoa() which encodes them, and the resulting base64 will never match the hardcoded expected value.
Learn more
The browser console is itself a full JavaScript REPL (Read-Eval-Print Loop) running in the page's security context. Calling
atob()there is equivalent to calling it in the application code - you get the same decoded string the application would compare against. This makes the console the fastest way to evaluate JavaScript expressions you find in the page source without writing a separate script.The password being the flag itself is a common CTF shortcut that avoids the need to log in at all - once you decode the password, you have the flag regardless of whether the login form actually works. In real-world penetration testing, hardcoded credentials found in client-side JavaScript are a critical finding even when they are obfuscated beyond simple base64, because automated tools and reverse engineering can always extract them.
This pattern - client-side-only authentication with hardcoded or client-visible credentials - falls under CWE-798 (Use of Hard-coded Credentials) and CWE-602 (Client-Side Enforcement of Server-Side Security). Both appear regularly in real-world web application security assessments, particularly in older or hobby-built sites.
Interactive tools
- Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
- Regex TesterTest regular expressions against a string with live match highlighting, flag toggles, and common CTF pattern shortcuts.
Flag
Reveal flag
picoCTF{53rv3r_53rv3r_53rv3r_53rv3r_53rv3r}
Client-side authentication is trivially bypassable - all validation logic and credentials are sent to the user's browser and can be read directly from the JavaScript source.