The Journey of #100DaysOfSecurity (@webchick)

Trying something a bit different for my #100DaysOfCode, which is to make it #100DaysOfSecurity instead. :slight_smile: I’m a member of the MongoDB Security Champions program (not to be confused with the MongoDB Community Champions program, which I run), and thought it might be fun to delve into security topics. has a number of practice hacking challenges. My goal is to beat as many of them as possible, writing about the thought process + tools involved, and hopefully educate both myself and others about security along the way! :smiley:

(Note: These will most definitely NOT be 100 sequential days :wink: But I shall do my best to get a post out once a week or so!)

picoCTF is an example of a Capture The Flag challenge. Somewhere hidden in the challenge is a string that looks like this:


Your goal is to use cunning and curiosity and security smarts to find it. :slight_smile:


#Day01 of #100DaysOfSecurity

Today’s challenge is Mod 26. This is a cryptography challenge.

Cryptography is about modifying a communication in some way to make it harder (or ideally, impossible) to read by snoopy third parties. Its use goes back even to Ancient Rome (a clue on how to solve this one :)).

You’re given the string:


Looks like gibberish, right? How do we approach solving this one?


The hint given by the puzzle itself, “Cryptography can be easy, do you know what ROT13 is?” is actually quite good!

ROT13 (“rotate” by 13 places) refers to a special flavour of the Caesar Cipher, where the alphabet is “shifted” by a number of letters to mask a message’s contents.


This puzzle just uses a straight ROT13 cipher, which shifts the alphabet 13 letters to the right. This means:

  • A → N
  • B → O
  • C → P
  • X → K
  • Y → L
  • Z → M

You could do this by hand with enough time, but it’s a lot easier to use either a web-based tool or use a programming language for this.

For example, this PHP one-liner can solve the puzzle:

<?php echo str_rot13("cvpbPGS{arkg_gvzr_V'yy_gel_2_ebhaqf_bs_ebg13_hyLicInt}"); ?>

For bonus points, the solution contains a joke — do you get it? :wink:

Learn more: picoCTF Primer: Substitution ciphers


#Day02 of #100DaysOfSecurity

Today’s challenge is information. This is a forensics challenge (with some bonus crypto too; there’s a clue ;)).

Digital Forensics is a branch of forensic science encompassing the recovery, investigation, examination and analysis of material found in digital devices.

This challenge will be some of that on a smaller scale: trying to look at a single picture and find the flag that’s somehow hidden within.

You’re given the following ADORABLE :heart_eyes_cat: image: cat.jpg

This image clearly has both fur and tech, but WHERE is the flag…? :thinking:


EXIF ( Exchangeable image file format) is a standard for storing metadata about an image. It’s commonly used to document things like the date and time of its creation, what camera settings were used, and specified copyright information about any given photo.


Interestingly, if you try and view the metadata with a standard EXIF viewer tool such as exif or macOS Finder, it chokes on invalid input. I found two ways around this:

  1. Open the jpg in a text editor such as vi, and it allows you can view the “raw” EXIF data in RDF format.
  2. Upload the image to an online EXIF viewer such as which can extract it regardless.

In any event, you’ll see that the “license” property is set to an interesting-looking string:

<cc:license rdf:resource='cGljb0NURnt0aGVfbTN0YWRhdGFfMXNfbW9kaWZpZWR9'/>

This is suspicious because you’d expect this to be a human-readable string; something like “Public domain” or “CC BY-NC.” This indicates the use of some kind of encoding.

A common type of encoding used on the web, especially for binary objects such as images, is Base64. It encodes binary data into text so it can more easily be sent around (for example, as an email attachment). If you’re ever doing a challenge that has a similar string of gobbledygook (alphanumeric characters, and the number of characters is divisible by 4), and especially if that gobbledygook ends in = or ==, it’s a good bet it’s Base64 encoding.

However, that which can be encoded can also be decoded. Once again, a PHP one-liner can solve this one:

<?php echo base64_decode('cGljb0NURnt0aGVfbTN0YWRhdGFfMXNfbW9kaWZpZWR9'); ?>

Or, you can use a web-based tool such as


#Day03 of #100DaysOfSecurity

Today let’s tackle Insp3ct0r. This is a Web Exploitation challenge, where you go for attacks that are unique to the magic of the World Wide Web. :wink:

This one is pretty chill, and you don’t need any special tools (a hint :slight_smile:) to solve it.

You’re given a URL to a simple website. Can you poke around and find the flag?


Use the source, Luke. :slight_smile:


If you view the page source in your browser, and inspect the code, you’ll find the website consists of three files:

  • index.html (the page you’re looking at)
  • mycss.css (linked from <link rel="stylesheet" type="text/css" href="mycss.css">)
  • myjs.js (linked from <script type="application/javascript" src="myjs.js"></script>)

HTML, CSS, and JavaScript each have the ability to add code comments that don’t show up in the visual view.

Look for those lines, and ye shall find the flag. :slight_smile:

I know some of you out there might roll your eyes at the relative low difficulty level of this challenge, but this type of “hidden in plain sight” exploit happens far more often than you’d think. A couple of prominent examples:


#Day04 of #100DaysOfSecurity

Let’s keep on the Web Exploitation track, and look at Scavenger Hunt.

At first glance you may say to yourself, “Why, self! This looks EXACTLY the same as Day 3’s challenge. This will be a cinch!”

And indeed it starts the same way—with what even looks like the exact same web page!—but this one requires a bit more poking around.


For this one, you’ll need knowledge about other common files found on web servers, not just those embedded in the page itself.

Beyond that, read the clues the puzzle gives you carefully; each one contains a distinct hint to point you in the right direction.


Just like yesterday’s challenge, you can start piecing the flag together by viewing source on the HTML and CSS files and looking at the code comments.

However, you’ll hit a wall when you get to the JS file. Instead of the comment giving you a part of the flag string like before, it will instead ask a cryptic question:

/* How can I keep Google from indexing my website? */

There is a Robots exclusion standard that exists as a means to communicate with (well-behaving, non-malicious) web crawlers about which areas of the website should and should not be processed or scanned.

An example file might look like the following, if it wanted to tell ALL robots not to scan the “private” directory:

User-agent: *
Disallow: /private/

(Ironically, Google’s own documentation states in bold, red letters: " Warning : Don’t use a robots.txt file as a means to hide your web pages from Google search results." :slight_smile: A better approach is a noindex metatag, as that removes the page even if it’s linked to from somewhere else vs. crawled by Google.)

ANYWAY. Once you load that file, you’re given another piece of the flag, as well as another cryptic clue:

# I think this is an apache server... can you Access the next flag?

Apache is a very common web server, and this clue refers to an Apache configuration file that lets you make configuration changes on a per-directory basis, overriding the default Apache configuration found in httpd.conf. You can do things in there such as require a password to access the directory contents or re-write URLs.

Once you load THAT file, you’re given another piece of the flag, as well as another cryptic clue:

# I love making websites on my Mac, I can Store a lot of information there.

Unlike the others, this one isn’t actually a common file found on web servers… at least, not on purpose. :wink: Desktop Services Store files are found inside every directory accessed by macOS Finder, and they contain information about the containing folder, including what file names are inside it [!], which can be parsed and crawled by an attacker to find files they ought not have access to.

They are also the bane of many web developers’ existence, because they are dotfiles, which means they are hidden by default and thus easily accidentally committed to version control or uploaded to a web server. :slightly_frowning_face:

At any rate, throw that file name at the end of the URL and you’ve got the final part of your flag. :slight_smile:


#Day05 of #100DaysOfSecurity

Wave a flag is less of a hacking challenge and more testing your knowledge of Linux commands. (Hint. :))

Your task: extract the flag from this binary file.


First, I’d use picoCTF’s built-in webshell for this; when I tried to execute this on my Mac, I received the error:

zsh: exec format error: ./warm

Second, if you’re not already familiar with running basic Linux commands and how file permissions work, check out the resources at the bottom.


First, you’ll need to download the file to your shell. wget is a useful utility for doing just that! (Failing that, curl can be fun.)

$ wget

Normally, you run an executable file like the following:

$ ./warm

If you try that here, you’ll get the error:

-bash: ./warm: Permission denied

This is because the file won’t actually be able to be executed unless you make it executable.

The easiest way to do that is with chmod to make the file executable by your user:

$ chmod u+x warm

Now if you try ./warm again, you should receive better results. Follow the instructions to receive your flag. :slight_smile:



#Day06 of #100DaysOfSecurity

(Bah. I broke my streak yesterday because I was out with a cold. A COLD. In 2022. After all of this… [gestures broadly at everything]. I am SO ANNOYED. :stuck_out_tongue:)

Python Wrangling is another challenge that’s less about hacking, and more about your knowledge of how Linux commands work. And your ability to get a working Python setup, which can be a challenge all on its own. :wink:

There are three files involved here:

Your task is to combine them together in order to decrypt the flag!


Another one that’s easiest using the webshell, since it already has Python and the required modules all ready to go.

When you run the command the output is quite cryptic:

$ python
Usage: (-e/-d) [file]

However, you can pass the handy -h flag from yesterday’s challenge and get more precise instructions!


What the challenge is asking you to do is put those three files together in a single Linux command.

We want to decrypt a file, so we’ll need to pass the -d flag into Let’s check out that branch of code:

elif sys.argv[1] == "-d":
    if len(sys.argv) < 4:
        sim_sala_bim = input("Please enter the password:")
        sim_sala_bim = sys.argv[3]

    ssb_b64 = base64.b64encode(sim_sala_bim.encode())
    c = Fernet(ssb_b64)

    with open(sys.argv[2], "r") as f:
        data =
        data_c = c.decrypt(data.encode())

A couple of things to point out here:

  1. It looks like the flag is encrypted with Fernet (symmetric encryption).

  2. Though the -h flag doesn’t point this out, you can apparently pass in a 3rd argument to which is the password from pw.txt itself!

Put it all together:

$ python -d flag.txt.en ac9bd0ffac9bd0ffac9bd0ffac9bd0ff

…and you’ve got your flag. :slight_smile:



#Day07 of #100DaysOfSecurity

(Technically this is posted on the same day, but hey, it’s midnight somewhere. :wink:)

Today, let’s get back into some Web Exploitation fun with the login challenge.

You’re given a very simple-looking website with a username and password field.

But hoooowwww to extract the flag from this extremely secure system that your dog-sitter’s brother made? :thinking:


Interestingly, when you submit the form, the error comes back as a JavaScript dialog:

This is a clue that client-side form validation is in use. If this is the only input validation, that is good news for you, intrepid hacker, because that means everything you need to defeat the challenge is right there in your browser. :slight_smile:


The JavaScript alert must be coming from somewhere, so View The Source, Luke, to find a pointer to index.js.

Look at this ugly JavaScript code all-smashed-together-on-one-line. Yuck! :face_vomiting: Let’s run it through a pretty-printer, such as :

(async () => {
    await new Promise((e => window.addEventListener("load", e))), document.querySelector("form").addEventListener("submit", (e => {
        const r = {
                u: "input[name=username]",
                p: "input[name=password]"
            t = {};
        for (const e in r) t[e] = btoa(document.querySelector(r[e]).value).replace(/=/g, "");
        return "YWRtaW4" !== t.u ? alert("Incorrect Username") : "cGljb0NURns1M3J2M3JfNTNydjNyXzUzcnYzcl81M3J2M3JfNTNydjNyfQ" !== t.p ? alert("Incorrect Password") : void alert(`Correct Password! Your flag is ${atob(t.p)}.`)

Ok, that’s more like it. What is this code doing? One important bit is here:

for (const e in r) t[e] = btoa(document.querySelector(r[e]).value).replace(/=/g, "");

This essentially says: for both username (r.u = "input[name=username]") and password ('r.p = "input[name=password]"), run its value through the btoa() function and strip out any = signs (replace them with an empty string "").

Hm. Equal signs? Didn’t we talk about this somewhere before…? :thinking: And what the heck is btoa() anyway?

Why, look, it stands for binary-to-ASCII and uses our good friend, Base64 decoding. :smiley:

These lines then:

return "YWRtaW4" !== t.u ? alert("Incorrect Username") : 
"cGljb0NURns1M3J2M3JfNTNydjNyXzUzcnYzcl81M3J2M3JfNTNydjNyfQ" !== t.p ? alert("Incorrect Password")

…indicate that whatever the username is, it needs to become YWRtaW4 when base 64 encoded. And the password needs to become cGljb0NURns1M3J2M3JfNTNydjNyXzUzcnYzcl81M3J2M3JfNTNydjNyfQ.

Can you use the tools from the other day to solve this one? :slight_smile:


#Day08 of #100DaysOfSecurity

Today, let’s head into our first Reverse Engineering challenge with

If you execute this program you’ll see that it’s quite simple and tells you the bigger of two numbers:

That’s all well and good, but how do we find the flag…?


Peek inside the file, and you will find an interesting surprise. :slight_smile:

# Hiding this really important number in an obscure piece of code is brilliant!

# AND it's encrypted!

# We want our biggest client to know his information is safe with us.

bezos_cc_secret = "A:4@r%uL`M-^M0c0AbcM-MFE07b34c`_6N"

Seems suspicious. All that’s left to do is decode it, right?


Remember learning about ROT-13 back on Day 1? Well here, if the decode_secret() function is to be believed, we appear to be using ROT-47, which is the same deal, except moving ahead 47 places instead of 13.

How is that possible, when the alphabet itself only has 26 letters? Because here, we’re using a special alphabet:

# Reference alphabet
alphabet = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ"+ \

Now. You could painstakingly do the work of taking each character in Bezos’s secret credit card number and counting 47 places ahead in the above string. Or, use an online tool like CyberChef.

Or, you could be super lazy, like me, and just toss the following near the bottom of the file:


…and let our good friend Python do the hard work for you. :wink:


#Day09 of #100DaysOfSecurity

Next on our Reverse Engineering quest, let’s look at This is essentially a “trialware” game, and one of the options is locked unless you enter a valid license key:

Your task: determine the license key to use and you’ll get your flag!


If we take a peek under the hood, there’s a lot more code here than in yesterday’s challenge. There’s code to generate the program menu, do the arcane calculations, deal with license keys, and write out the full version of the program if the key is found to be correct.

Up near the top of the file, we see a few interesting pieces that stand out:

username_trial = "FRASER"
bUsername_trial = b"FRASER"
key_part_static1_trial = "picoCTF{1n_7h3_|<3y_of_"
key_part_dynamic1_trial = "xxxxxxxx"
key_part_static2_trial = "}"
key_full_template_trial = key_part_static1_trial + key_part_dynamic1_trial + key_part_static2_trial

Almost a whole flag right there, we now just need to figure out what the xxxxxs are.


The money seems to be at:

def enter_license():
    user_key = input("\nEnter your license key: ")
    user_key = user_key.strip()
    global bUsername_trial
    if check_key(user_key, bUsername_trial):
        print("\nKey is NOT VALID. Check your data entry.\n\n")

So if check_key(user_key, bUsername_trial) returns true, we’re in business.

Let’s jump over there.

def check_key(key, username_trial):
    global key_full_template_trial
    if len(key) != len(key_full_template_trial):
        return False

First check: is the key we entered equal in length to key_full_template_trial from up above?

If we recall, key_full_template_trial just smooshed together all of those flaggy-looking pieces, so right now it’s:


That’s 32 characters. Which means our key needs to be exactly that long.

Ah, but wait, the next check:

        # Check static base key part --v
        i = 0
        for c in key_part_static1_trial:
            if key[i] != c:
                return False

            i += 1

… indicates that not just ANY 32 character string will do; it has to start with exactly the same characters as there are in key_part_static1_trial, which MEANS the first few characters are picoCTF{1n_7h3_|<3y_of_ That’s a great start!

OK what’s next? A bunch of lines like this:

        if key[i] != hashlib.sha256(username_trial).hexdigest()[4]:
            return False
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[5]:
            return False
            i += 1

This is moving one character at a time through the next part of the key (those Xs), and comparing its value. To what? We recall from above that username_trial is “FRASER”. This code takes that name, runs it through a SHA-256 hash function to turn it into a hexadecimal string, then finds the character in the Nth position. (And actually, N + 1 since indexes in Python, like many other languages, start counting from zero.)

If you run “FRASER” through a tool like SHA256 Online, you’ll see it results in the following string:


(Note: SHA-256 hashing will always result in the same output for any given input. This is why it’s extremely important to “salt” your hashes so that they cannot be easily reverse-engineered, e.g. when used for things like one-way encrypting passwords.)

Reading through the remainder of the check_key() function, you can see that it wants the character in the hash string in the 5th position (“a”), then the 6th (“c”), then the 4th (“7”), … and so on.

Once you’ve figured that out, replace “xxxxx” in key_full_template_trial with what you derived, and you have both your flag and your license key! :slight_smile:


#Day10 of #100DaysOfSecurity

Woohoo! :smiley: Made it to day 10! :smiley:

It’s the weekend, and yesterday’s was a bit of a doozy, so let’s do a bit more chill challenge this time: Nice netcat…

(Pro tip: This is NOT the kind of ”net cat” they’re referring to. :wink:

An adorable brown and white cat, perched atop a cat tree in front of net fencing

But rather, netcat, which is a computer networking utility for reading from and writing to network connections.)

The challenge directs you to connect to on port 21135 and decode the bunch of numbers that get returned:


The numbers have nothing to do with math. :slight_smile: Try and notice patterns. Are the numbers within a certain range? Do any of the numbers repeat? What might those correlate to?


Under the hood, computers can’t inherently deal with text, only with numbers. So when we want to send a text character, such as the letter “a” or the symbol “_”, that needs to be encoded so that the computer can read and transmit it.

A very common method of encoding is ASCII (abbreviated from American Standard Code for Information Interchange). Each character is assigned a numeric value from 32-126. (Why starting at 32? Because the numbers prior to that are for non-printable control characters such as “end of file” or “line break.”)

Once again, this challenge can be solved by either manual conversion of numbers to characters found in an ASCII table, or, you can use an online tool such as Convert ASCII Codes to Characters.

(Warning: The line breaks between the different numbers can really throw off results in automated converters in my experience. If it ends up a garbled mess in one tool, try another.)

And/or, here’s a simple PHP script to get the job done:

// Open a connection to the server and store its response.
$fp = fsockopen("", 21135);
fwrite($fp, "\n");
$numbers = fread($fp, 1000);

// Extract numbers from a long vertical string to an array.
$numbers = explode(PHP_EOL, $numbers);

// Loop through each ASCII value and convert to character.
foreach ($numbers as $ascii) {
  if (is_numeric($ascii)) {
    $ascii = trim($ascii);
    echo chr($ascii);


If you made it through this challenge, also pick up what’s a net cat? for a bonus 100 points! :smiley:


This is awesome @webchick :orange_heart: Many Congratulations on making it to Day 10 :partying_face: You are now a Code Wrangler :wink:

Are you enjoying Cryptic puzzles? I remember @Stennie telling he enjoys cryptography too :smiley:

Are you following the puzzles from the link you shared, At one glance, this looks like a lot to me, I guess maybe for the next relay of 100 I will do Python :smiley:

Wish you a fun, cryptic Sunday :ghost:

Cheers :performing_arts:

1 Like

Woohoo!! Thanks so much!! :smile:

Yeah, I’ve loved playing around with cybersecurity since I was a teenager. One of the first communities I helped manage was a hacking challenge site way back in the day, actually! I find them interesting because they expose you to so many facets of how computers, networks, operating systems, programming languages, encryption, and more work together, and really challenge you to think “outside the box.”

If you’re interested in a similar thing for learning Python programming, Solve Python | HackerRank seems to be a similar setup!


#Day11 of #100DaysOfSecurity

So far we haven’t done a Binary Exploitation challenge. Let’s change that today with CVE-XXXX-XXXX !

This one is more on general security knowledge than actually breaking into anything, but it’s extremely useful knowledge to have!

The challenge is asking you to find a “CVE” for the first recorded remote code execution (RCE) vulnerability in 2021 in the Windows Print Spooler Service.

Can you research this one to find the flag?


CVE is short for “Common Vulnerabilities and Exposures.” Every publicly disclosed cybersecurity vulnerability, dating back to the early days of the Internet, is given a unique CVE ID, to make it easier for cybersecurity professionals to coordinate fixes and ensure they’re discussing the same vulnerability.


There’s a handy keyword search for CVE records. Let’s try searching for “windows print spooler”:

You can see, there have been 58 vulnerabilities reported in Windows Print Spooler since 1999. How can we possibly find the one needle we need in all of that haystack?

It’s useful to know that a CVE ID takes the form of:


Such as: CVE-2022-12345

Since we know that the vulnerability was disclosed in 2021, that reduces our haystack to more like 15 records.

Another clue is that the challenge is asking for a “remote code execution (RCE) vulnerability.” Each CVE comes with a description that summarizes the issue. Other common types of vulnerabilities are Elevation of Privilege, Information Disclosure, and Broken Access Control. See for a comprehensive list.

Looks like 2021 was a bad year for Windows Print Spooler, because there are 4 such CVE Records for 2021. The one with the lowest number is the one that was found first.



Wow… That is soo awesome… :tada: I sometimes wish I was introduced to computers early in the age but I got acquainted when I started going to university… :grimacing:

On Communities, I dint know they existed until 6 years ago when I moved to Ireland :open_mouth: But I have had the best of experiences, so I am good… :orange_heart:

Thank you for sharing your resources for Python :purple_heart: My husband automates using python, I wish I could have 1 common skill with him, as I have none atm :stuck_out_tongue:

Hope to see you kick-starting this again :wink: Happy Sunday… :tada:

1 Like