Teacher's Pinboard Write-up

I found the address of the teacher's pinboard! Can you try to get in and read all teachers' notes? Maybe you need to attack the admin account as well.

The fluxfingers (again) hosted the Capture The Flag (CTF) event for the hack.lu security conference in Luxembourg. It's long since I've left the university but I'm still fond of both the hack.lu CTF and fluxfingers, so I contributed two tasks for the event.

Since nobody bothered to publish their solution for the challenge, I have decided to release my own. By the way, you can still access the challenge in the archive and I recommend you take a look yourself, before you continue reading this spoiler.

screenshot

The teacher's pinboard is a website that provides you a log-in form that accepts any kind of username and password combination. After logging in, you are provided with a typical pinboard and you might notice that the website has provided you three cookies of the following format:

session=7341e6fa2ab3b37f26e0cac6fedc25f3438e5fb9b12a9daed890459aa21f349b12676b3d;
accountinfo=(dp0%0AS'username'%0Ap1%0AS'foo'%0Ap2%0AsS'password'%0Ap3%0AS'bar'%0Ap4%0AsS'admin'%0Ap5%0AI00%0As.;
signature=81a380499ae8a9801c1b791da88d93bb5c74224ce59c60ae4a5010bc943ad85f

The accountinfo looks interesting!

Lets decode it:

(dp0\nS'username'\np1\nS'foo'\np2\nsS'password'\np3\nS'bar'\np4\nsS'admin'\np5\nI00\ns.

With some research (or experience), one might notice that this is the output of Python's pickle module. Unpickling this thus leads the following dictionary:

{'admin': False, 'password': 'bar', 'username': 'foo'}

Setting admin to True does not work, as the other cookie (signature) seems to enforce integrity of the accountinfo data. Another idea would be to attempt generic pickle exploits (like so), which did not work out as well.

After some further close inspection, the user might notice that the JavaScript that comes with the pinboard does not only provide the nice user interface for playing with the notes. It also deals with the cookies! First it reads the cleartext password in your cookies and uses that to derive an encryption key. This key is then used to store the current notes in localStorage. Why? Mostly decoy, actually.

The interesting part is how it looks a the cookies. It does so by executing pickle.loads() in JavaScript.

"Pickle in JavaScript??" you say. Yes, in JavaScript. For this challenge, I have re-implemented Python's pickle module in JavaScript. Pickle is not actually a storage format but a nice little stack machine, that is easy to understand and fun to read. Take a look at /usr/lib/python2.7/pickle.py for example. The JavaScript pickle code is worth a read too, but the concept is generally the same: If you want to execute a function, you load a reference to a global on the stack and call it using R (reduce). Parameters of said function should be a tuple on the stack just below the function.

So what would we do in JavaScript, if we wanted to execute arbitrary code but not write a pickle thing by hand? Well, throw our source code on the stack and call eval() of course. Turns out, the pickle.js author (me) thought of that too and implemented a blacklist, that disallows certain things:

  const BLACKLIST = ["require", "eval", "setTimeout", "setInterval", "setImmediate"];

So that still leaves us with the Function constructor. I also know that Teams who solved this challenge used process.mainModule.require, which should also work. In general, the blacklist was more of a hint that one was going in the right direction than a real blocker.

I ended up writing a compiler that takes JavaScript source code and produces a pickle object that executes said code:

function compiler(s) {
  // c= load global "Function" on stack
  // ( = set marker on stack, S = create String
  // t = create tuple from marker to top of stack
  //     (i.e. put string in a tuple)
  // R = call top-of-stack - 1 with top-of-stack as the argument
  //
  // i.e., call Function(), with s as param  
  var pickle = "cglobal\nFunction\n(S'"+s+"'\ntR";
  pickle +=    "(tR."
  return pickle;
};

Executing arbitrary code means you can now go through the directory and either find the file with the flag or find the source code and the secret key to produce an HMAC for a cookie that has admin set to True.

All posts

  1. Reference Sheet for Principals in Mozilla Code (Mon 03 August 2020)
  2. Hardening Firefox against Injection Attacks – The Technical Details (Tue 07 July 2020)
  3. Understanding Web Security Checks in Firefox (Part 1) (Wed 10 June 2020)
  4. Help Test Firefox's built-in HTML Sanitizer to protect against UXSS bugs (Fri 06 December 2019)
  5. Remote Code Execution in Firefox beyond memory corruptions (Sun 29 September 2019)
  6. XSS in The Digital #ClimateStrike Widget (Mon 23 September 2019)
  7. Chrome switching the XSSAuditor to filter mode re-enables old attack (Fri 10 May 2019)
  8. Challenge Write-up: Subresource Integrity in Service Workers (Sat 25 March 2017)
  9. Finding the SqueezeBox Radio Default SSH Passwort (Fri 02 September 2016)
  10. New CSP directive to make Subresource Integrity mandatory (`require-sri-for`) (Thu 02 June 2016)
  11. Firefox OS apps and beyond (Tue 12 April 2016)
  12. Teacher's Pinboard Write-up (Wed 02 December 2015)
  13. A CDN that can not XSS you: Using Subresource Integrity (Sun 19 July 2015)
  14. The Twitter Gazebo (Sat 18 July 2015)
  15. German Firefox 1.0 ad (OCR) (Sun 09 November 2014)
  16. My thoughts on Tor appliances (Tue 14 October 2014)
  17. Subresource Integrity (Sun 05 October 2014)
  18. Revoke App Permissions on Firefox OS (Sun 24 August 2014)
  19. (Self) XSS at Mozilla's internal Phonebook (Fri 23 May 2014)
  20. Tales of Python's Encoding (Mon 17 March 2014)
  21. On the X-Frame-Options Security Header (Thu 12 December 2013)
  22. html2dom (Tue 24 September 2013)
  23. Security Review: HTML sanitizer in Thunderbird (Mon 22 July 2013)
  24. Week 29 2013 (Sun 21 July 2013)
  25. The First Post (Tue 16 July 2013)