CSAW Finals - Grande

I recently played CSAW CTF 2021 with PPP. I worked with Anish on a cool challenge, grande, by @itszn13. This was an Express node application which exposed some interesting behavior about Express’s query parsing.


There were two steps in the challenge.

The first was a XSS. This required creating an array-like object for which Array.isArray(obj) === false, which can be done with ?next[__proto__]=first&next[__proto__]=second.

The second was a CSP bypass. In summary, the nonce got set to undefined when the nonce cookie is unset but the user session was not null. We could then abuse logout CSRF to force this condition, due to the SameSite properties of the cookies.

Read More

5 RCEs in npm for $15,000

I found and reported these vulnerabilities with @ginkoid.

In this post, I will discuss the root cause of these vulnerabilities, as well as briefly walk through the exploitation process. I’ll also include some thoughts about bug bounty in general at the end.

These are the associated CVEs and payouts:

CVE-2021-39134 affects @npmcli/arborist. The others affect node-tar.

Read More

Empires and Deserts

This is an author writeup for the paired Chrome sandbox escape I made for redpwnCTF 2021, Empires and Deserts.

When writing this challenge, I wanted to create a sandbox escape where the solution is not obvious. In other words, the difficulty of the challenge arises not from exploiting the vulnerability - but in finding the vulnerability itself.

Read More

SBX Intro

Lately, I’ve been getting into Chrome sandbox exploitation. Having found and exploited a few sandbox escape vulnerabilities, I thought it would be fun to include these in a CTF. Unfortunately, one issue I faced while learning SBX is lack of online resources.

I think conceptually, this attack surface is not exceedingly complex - at least compared to the renderer. In this blog post, I aim to provide a high level overview of SBX concepts, which will hopefully speed along the learning process.

Read More

Breaking GitHub Private Pages for $35k

I found and reported this vulnerability with @ginkoid.

This was actually the first report that paid out for me on HackerOne. At $35,000, it’s also the highest bounty I’ve received so far from HackerOne (and I believe the highest GitHub has paid out to date).

A lot of bugs seem to be a mix of both luck and intuition. In this blog post, I’ll illustrate my thought processes in approaching such a target.

Read More


Over the summer of my junior year, I decided to do some router pentesting. Embedded security always seemed very fun to me. I liked the idea of breaking things that are found in our everyday lives, and what better place to start than my router. I was also inspired by my friend @arinerron who had some success with his router previously.

I dumped the firmware and started analyzing the binaries in Ghidra. I also found a GitHub repository containing a modified version of the source which greatly helped with the reversing process.

Read More

College Applications

This is a bit of a departure from the norm on this blog. I thought it might be interesting to switch it up a bit, and talk about my experience with the American college application process. This is in part inspired by my friend @arinerron’s post on school sponsored extracurriculars.

I think college applications have quite a lot of symbolic value. They’re the culmination of a twelve-year scholastic journey, a period in our sentence to pre-collegiate learning. Their finality is perhaps best exemplified by the seasonal afflictions of “senioritis”. After submitting applications, students have a tradition of slacking on classwork.

Of course, I too am afflicted. A testament to the fact that in the end, college applications underlie much of our scholastic life.

Read More

Adult CSP

This is an author writeup for Adult CSP, a Chromium sandbox escape that I wrote for DiceCTF 2021.


I wanted to write a pseudo-realistic Chromium sandbox escape. In particular, this meant no helper functions to give leaks.

There were a total of two intended vulnerabilities:

  • UAF on Cat* for leaks
  • UAF on CATServiceImpl* for controlled vcall

Interestingly enough, both of these showed up in one line of code.

    FROM_HERE, {content::BrowserThread::UI},
    base::BindOnce(&CATServiceImpl::ProcessCATOnUI, base::Unretained(this), base::Unretained(it->second.get()), std::move(callback))

base::Unretained means that it is the caller’s responsibility to ensure the object lives past the call. Usually these should be replaced with weak pointers, unless it’s obvious that it’s impossible for the object to be destructed before the function call - for example, an owned child member.

These show up pretty commonly as real Chrome vulnerabilities too, making it quite suitable for a CTF challenge.

Read More


This was a very interesting challenge.


The libc version is 2.29. This implies the use of tcache bins, as well as additional protections against double-free.

As usual, the first thing we do is run checksec.

$ checksec shattered
[*] '/home/robert/writeups/binexp/hsctf20/shattered/shattered'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

All protections enabled - it’s a typical heap exploit challenge. Then again, this is to be expected. The challenge author, poortho, is notorious for only writing glibc heap problems.

The exploit path will probably involve getting a libc leak, and then overwriting one of the hooks - __malloc_hook or __free_hook.

Read More

SSD November

I recently won the SSD November challenge, the challenge itself was actually quite interesting.

The first vulnerability was improper hashing.

  while (i < size) {
    tmp_value._0_1_ = input[i];
    if ((int)(char)tmp_value * 10 < 0x20) {
      tmp_value._0_1_ = (char)tmp_value % '\x05';
    else {
      tmp_value._0_1_ = (char)(((int)(char)tmp_value * 0x124343) % 0xef);
    input[i] = (char)tmp_value;
    i = i + 1;

Upon seeing this, I was immediately suspicious. Most hash functions, even handrolled ones, have both addition and multiplication? I didn’t really understand what was going on, but analysis through GDB showed that the values tended towards very high values (ie 0xfc, 0xfd, 0xfe, 0xff, 0x00).

Because the hash is done per character, I handpicked a few characters with distinct hash values after running them through the max repetition of hashes. From here, we can easily brute force the 4 byte long admin password.

Read More


The week before early admission deadline, perfect time to do a CTF and burn a weekend.


Libc version is 2.27, we get tcache without the security checks.

As usual, the first thing we do is run checksec.

[+] checksec for '/home/robert/writeups/binexp/meta20/dmzf/dmzf'
Canary                        : ✓
NX                            : ✓
PIE                           : ✓
Fortify                       : ✘
RelRO                         : Full

Canary, NX, PIE, Full RelRO - it’s a typical heap exploit challenge. The exploit path will probably involve getting a libc leak, and then overwriting one of the hooks - __malloc_hook or __free_hook.

There’s also seccomp which makes exploitation a bit more tricky.

Read More

House Of Red

House of Red

This is an author writeup for house-of-red and its sibling problem zero-the-hero, both of which appeared in the 2020 redpwnCTF. Their respective challenge repositories are also open sourced.

This writeup presents a relatively novel exploitation technique, that can be applied to a wide range of challenges.


There are two prerequisites to execute this attack.

  1. Libc address leak
  2. Constrained write in libc

The most common scenario for the second constraint is mallocing a chunk, and then providing a negative size value. If we are able to perform such a write, we can almost always overwrite some part of _IO_2_1_stdin_ to escalate to shell. A similar attack scenario appeared in justCTF 2019’s ATM Service. Although FSOP wasn’t the intended scenario, the relative overwrite in libc allowed me to bypass the buffer overflow.

Read More


This was one of the more interesting challenges I’ve done in a while. We were one of 4 teams who solved this binary exploitation challenge in DawgCTF.


This was the only challenge where we were given a libc. Looks like it’s going to be a heap pwn.

As usual, the first thing we do is look at the protections on the binary.

$ checksec tiktok
[*] '/home/robert/writeups/binexp/umbccd/tiktok/tiktok'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

All protections are enabled except for PIE, implying that we’ll probably have to use either FSOP or write to one of the malloc hooks to win.

Dumping the binary into Ghidra, I immediately noted something suspicious. The fd is stored but never validated again after creation.


Null byte overwrites are really common, especially when dealing with off by one errors or C string functions. If we could somehow overwrite the fd to null, we could control the heap!

Read More


I recently competed in Codegate CTF 2020 under the junior category. This was the one of the more interesting challenges that I solved.


This was more of a reversing problem than pwn. The llvm served only to obfuscated the code, as opposed to raising any challenges itself.


Three observations are needed to solve the problem.

  1. The data pointer can be out of bounds after a codeblock finishes executing.
  2. During branched execution, the security checks don’t ever get called. 3.
  3. During linear execution, ptrBoundCheck won’t get called if you don’t adjust rel_pos.

Read More

Tasteless CTF 2019

I competed in Tasteless CTF this weekend with redpwn. We solved one challenge, House of Bad Taste, which was an interesting glibc heap pwn.

House of Bad Taste




As usual, the first step is to run checksec against the binary.

$ checksec chall
[*] '/pwn/tasteless19/house-of-bad-taste/chall'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

$ strings libc.so.6 | grep GNU
GNU C Library (Ubuntu GLIBC 2.29-0ubuntu2) stable release version 2.29.
Compiled by GNU CC version 8.3.0.

With all protections enabled, it’ll probably be another heap exploit. GLIBC 2.29 means tcache protections are enabled.

The next step is to decompile the binary with Ghidra.

Read More

pico19 Secret Hitler


The binary uses glibc version 2.29, which patches the double free vulnerability.


We are given the libc base address, so no leaks are needed. All we have to do is get a write.

House of Poortho

The vulnerability is that read() overwrites the first byte of the next chunk header with a null byte. If the next chunk has a size header with least significant byte not equal to 0x00, the size of the next chunk changes.

Read More

pico19 Sice Cream




$ strings libc.so.6 | grep GNU
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11) stable release version 2.23, by Roland McGrath et al.
Compiled by GNU CC version 5.4.0 20160609.
        GNU Libidn by Simon Josefsson

$ checksec sice_cream
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    RUNPATH:  './'

The libc version is 2.23, meaning that tcache has not been implemented yet. We could also try House of Orange because of less strict checks. Only PIE is disabled, meaning that we will probably have to go for either FSOP or an overwrite on __malloc_hook or __free_hook.

Decompiling the binary with Ghidra, there are no checks on the pointer that we free, leading to a double free vulnerability. We also have the ability to print an array in .bss, name. Finally, we get a total of 20 mallocs, with a constrained size (size < 0x58).

Read More

pico19 Ghost Diary




$ ldd ghostdiary
    linux-vdso.so.1 (0x00007ffcabdd4000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff81dd18000)
    /lib64/ld-linux-x86-64.so.2 (0x00007ff81e30c000)
$ strings /lib/x86_64-linux-gnu/libc.so.6 | grep GNU
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1) stable release version 2.27.
Compiled by GNU CC version 7.3.0.

$ checksec ghostdiary
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

The libc version is 2.27 which implies the use of tcache with very little security checks. All protections are enabled, implying a heap only exploit.

We can only malloc 20 chunks at a time (which is not a really big concern). There is also a rather interesting constraint on malloc size, (size <= 0xf0 || (size >= 0x110 && size <= 0x1e0). There is a null byte overflow in the edit function. In addition, we can print any chunk, regardless of if it’s freed or not, which we will use to get a libc leak.

Read More

CSAW Red 19 Tumbler

I was one of the two teams that solved Tumbler from CSAW Red.


Pwn 500

No way that cryptocurrency is a scam, that would NEVER happen

nc pwn.chal.csaw.io 1000


The libc provided was 2.23.

$ strings libc.so.6 | grep GNU
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu10) stable release version 2.23, by Roland McGrath et al.
Compiled by GNU CC version 5.4.0 20160609.
        GNU Libidn by Simon Josefsson

Decompiling the binary, it is immediately apparent that we have an arbitrary write primitive in one of the later functions called.

void arb_write(void)

  void *__buf;

  puts("Which coin do you want to edit?");
  __buf = (void *)get_number();
  puts("What are you writing?");

Read More

Seating Charts

Creating a seating chart for a class is an extremely interesting problem that delves into the realms of both web design and competitive programming.


There are two questions I attempted to answer.

  1. How to create an easy to use interface that minimizes the cognitive load on the user
  2. How to best create the seating chart given a list of preferences for each student

Read More

Java Tricks

Honestly, you should switch to c++. But if you insist on using Java, here are some cool tricks.


Memory allocation is extremely cheap (~1e6 bytes per 1ms) compared to everything else - don’t be afraid to allocate huge arrays. That being said, be careful you don’t hit a MLE.

Be careful of the dimensional order of 2D arrays.

Read More

Secret Hitler Account Takeover

This is an account takeover attack I discovered on the open source Secret Hitler game.

By submitting crafted parameters to the /password-reset endpoint, attackers are able to takeover arbitrary non-staff accounts.

This vulnerability can be mitigated by disabling JSON parsing.

We control all of the parameters passed through req.body.

const { username, password, password2, tok } = req.body;

Read More

Secret Hitler Vulns

Two low-moderate vulnerabilites on the open source Secret Hitler game. Note that a lot of the vulnerabilites are due to the use of JSON parsing, which allows attackers to submit arbitrary objects to the endpoints.

Obfuscated IP Leakage

The check in the /profile endpoint is unnecessarily complex, and forgets an edge case.

if (req && req.user && requestingUser && requestingUser !== 'undefined' && req.user.username && requestingUser !== req.user.username) {
  // Error 

Read More

pico18 jbr

Cryptography - 700

Problem Statement

Dr. Xernon has finally approved an update to James Brahm’s spy terminal. (Someone finally told them that ECB isn’t secure.) Fortunately, CBC mode is safe! Right? Connect with nc 2018shell1.picoctf.com 22666.

Hint: What killed SSL3?

Read More

pico18 Dog or Frog

Misc - 900


This is a classic machine learning problem.

We came across this article and copy much of the template code from there. I wrote my code in a Jupyter notebook.

Read More

tj18 Abyss

Written by nthistle

Problem Statement

If you stare into the abyss, the abyss stares back.

nc problem1.tjctf.org 8006


The first observation is that this is a Python environment.

However, further experimentation reveals that all our errors are eaten up with a cute message.

>>> blahblop
The Abyss consumed your error.

Read More