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 1Read the page source
ObservationThe form submits with no network request to any backend, so the whole authentication runs client-side in JavaScript, where the page source will show it.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 2Decode the username
ObservationThe script compares the encoded username against a string ending in equals padding, which is base64. Decode it and the plaintext username is right there.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 works fine in the browser and returns the plaintext. The trap is the near-identical name beside it: btoa encodes, so calling it here double-encodes the string instead of decoding it. atob goes from base64 back to text.
Tried: Treat the base64 string as an MD5 or SHA hash and try to crack it with a tool like hashcat
A hash is a fixed-length hex string, 32 characters for MD5 and 64 for SHA-256, and it does not reverse. Base64 ends in equals padding and decodes instantly, because it is an encoding rather than a one-way function. hashcat has nothing to do here.
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 3Decode the password / flag
ObservationThe password field uses the same pattern with a longer string, about the length of a flag. Decode it and you never need 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
The password is the flag, so decoding the string gives it to you directly. Submitting the form works too and tells you nothing new.
Tried: Attempt SQL injection on the login form (e.g. entering ' OR 1=1 --) hoping to bypass authentication
SQL injection needs user input reaching a database query on a server. This login has no server at all; the comparison happens in your browser. An injection payload is just a string that gets encoded, and the result never matches the hardcoded 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.