Description
Why search for the flag when I can make a bookmarklet to print it for me?
Setup
Browse to the challenge page and find the flag!
Copy the bookmarklet code displayed on the page to inspect it.
Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
"picoctf") character-by-character (mod 256) to recover the flag, then pops the result via alert().Step 1
Create a bookmarklet manuallyObservationI noticed the challenge page provided a JavaScript snippet starting with 'javascript:' and described it as a bookmarklet, which suggested the intended path was saving that snippet as a browser bookmark and clicking it on the challenge page to trigger in-page decryption.Bookmark any page, edit that bookmark, and replace its URL with the JavaScript snippet you copied. When you click the bookmark, the code decrypts the encrypted flag string embedded on the page (using the key "picoctf" with a repeating subtraction mod 256) and shows the result in an alert.bashjavascript:(function(){ /* bookmarklet code from challenge page */ })();What didn't work first
Tried: Type the bookmarklet URL directly into the browser address bar and press Enter
Modern browsers strip the javascript: scheme from the address bar as a phishing countermeasure and navigate to a blank page or do nothing. The scheme only executes when the browser loads it from a saved bookmark - pasting it into the address bar and hitting Enter is blocked by default in Chrome, Firefox, and Safari. You must save it as a bookmark first and then click the bookmark.
Tried: Open the bookmarklet snippet in a new tab by right-clicking and choosing 'Open in new tab'
The bookmarklet code references the encrypted flag variable that lives in the DOM of the challenge page. Running it in a blank new tab gives a reference error because that variable is not defined there. The snippet must execute in the context of the challenge page itself, where the encrypted bytes are already loaded as a JavaScript variable.
Learn more
A bookmarklet is a browser bookmark whose URL begins with
javascript:instead ofhttps://. When you click it, the browser executes the JavaScript in the context of the currently open page, giving the code access to the page's DOM, cookies, and variables.The IIFE (Immediately Invoked Function Expression) wrapping (the
(function(){})()pattern) is convention rather than strict requirement. For a one-linealert()it changes nothing, but it's a good habit because it isolates any helper variables from the page's globals.In security contexts, bookmarklets demonstrate how powerful in-page JavaScript can be: the same mechanism can read cookies, modify the DOM, or exfiltrate data. This is why Content Security Policy (CSP) headers exist to restrict inline script execution, and why browser extensions go through permission review.
Step 2
Run it in a JS console insteadObservationI noticed the bookmarklet was plain JavaScript with an IIFE pattern and called alert() at the end, which suggested that pasting it directly into the browser DevTools console on the challenge page would execute the decryption loop without needing to create an actual bookmark.Open DevTools (F12) or an online JavaScript runner, paste the snippet, and run it. The script simply calls alert() with the flag, so nothing else is required.js// Paste the full bookmarklet code from the challenge page into the console and press Enter. The decryption loop will run and call alert() with the recovered flag.What didn't work first
Tried: Paste only the inner function body without the IIFE wrapper into the console
Omitting the outer (function(){...})() wrapper means any variable declarations inside use var and leak into the page's global scope, which can shadow existing page variables and produce undefined behavior mid-loop. More critically, if the snippet relies on a return statement at the top level it throws a SyntaxError in the console. Wrapping in an IIFE or using the snippet exactly as written avoids both issues.
Tried: Run the snippet in an isolated online JavaScript sandbox like JSFiddle or CodePen instead of the browser console on the challenge page
The decryption loop reads an encrypted array variable that was embedded in the challenge page's HTML as a global JavaScript variable. An external sandbox starts with a clean global scope and has no access to that variable, so the script throws a ReferenceError immediately. The code must run on the challenge page itself where the encrypted data is already present in the window object.
Learn more
The browser JavaScript console (accessible via F12 or Ctrl+Shift+I) is one of the most powerful tools in web security research. It provides a full JavaScript REPL (Read-Eval-Print Loop) running in the page's security context, meaning it has access to all the same objects the page's own scripts do.
The
alert()function creates a synchronous modal dialog - it pauses all JavaScript execution on the page until dismissed. While basic, alert-based output has been a debugging staple since early JavaScript. In security research,alert(1)is the canonical proof-of-concept payload for demonstrating Cross-Site Scripting (XSS) vulnerabilities because it's harmless but visually confirms code execution.For CTF challenges, the console is invaluable because you can inspect variables, call functions, and read properties that aren't exposed in the visible UI. Many web challenges hide flags in JavaScript variables, HTML attributes, or API responses that are only visible through the console or Network tab.
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.
Flag
Reveal flag
picoCTF{p@g3_turn3r_e8b...}
Running the bookmarklet reveals the alert containing the flag above.