Another web challenge, this time served at http://d2b24dd8.quals2018.oooverflow.io. After logging in with the provided credentials `admin@oooverflow.io:admin`, we are redirected to the following [browser test page](http://d2b24dd8.quals2018.oooverflow.io/browsertest.php):

<title>Testing the browser...</title>
#animated_div {
background: #92B901;
color: #ffffff;
position: relative;
-webkit-animation:animated_div 5s 1;

@-webkit-keyframes animated_div
0% {-webkit-transform: rotate(0deg);left:0px;}
25% {-webkit-transform: rotate(20deg);left:0px;}
50% {-webkit-transform: rotate(0deg);left:500px;}
55% {-webkit-transform: rotate(0deg);left:500px;}
70% {-webkit-transform: rotate(0deg);left:500px;background:#1ec7e6;}
100% {-webkit-transform: rotate(-360deg);left:0px;}


<video id="v" autoplay> </video>

Click me to print the page.

<link rel="prerender" href="https://news.ycombinator.com/">
<div id="animated_div">OOOverflow Securityyy</div>
var f = "";
if (navigator.onLine)
f += "o";
f += navigator.vendor;
function p() {
/* incompatible
window.onbeforeprint = function () {
// some code
f += navigator.mimeTypes.length;
x=0; for ( i in navigator ) { x += 1; } f += x;
x=0; for ( i in window ) { x += 1; } f += x;
// hash
function str2ab(str) {
var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
var bufView = new Uint16Array(buf);
for (var i=0, strLen=str.length; i<strLen; i++) {
bufView[i] = str.charCodeAt(i);
return buf;
function sha256(str) {
// We transform the string into an arraybuffer.
var buffer = str2ab(str);
return crypto.subtle.digest({name:"SHA-256"}, buffer).then(function (hash) {
return hex(hash);

function hex(buffer) {
var hexCodes = [];
var view = new DataView(buffer);
for (var i = 0; i < view.byteLength; i += 4) {
// Using getUint32 reduces the number of iterations needed (we process 4 bytes each time)
var value = view.getUint32(i)
// toString(16) will give the hex representation of the number without padding
var stringValue = value.toString(16)
// We use concatenation and slice for padding
var padding = '00000000'
var paddedValue = (padding + stringValue).slice(-padding.length)

// Join all the hex strings into one
return hexCodes.join("");
f += navigator.plugins[0].filename;
f += navigator.plugins[1].description;

sha256(f).then(function(digest) {
if (digest == "31c6b7c46ff55afc8c5e64f42cc9b48dde6a04b5ca434038cd2af8bd3fd1483a") {
window.location = "flag.php?f=" +btoa(f);
} else {
x = document.getElementById("animated_div");
z = document.createElement("div");
z.innerHTML = "Test Failed";
setTimeout(function() {x.appendChild(z)}, 5000);

A quick overview of the inline script suggests that the flag can be accessed by the admin iff a matching browser is used. The client is checked for the presence of certain plugins, the ability to execute selected APIs outside secure-contexts such as `SubtleCrypto` and lack of support for `window.onbeforeprint` (see the commented out code in the js). Moreover, several `-webkit-` prefixed CSS rules wink at WebKit-powered user agents.

By cross-checking all the conditions, the list of possible candidates reduced to Google Chrome between versions 37 and 39 (included). At this point it was just a matter of running these [old browsers](https://google-chrome.en.uptodown.com/ubuntu/old) inside a VM, dump the list of plugins along with other features exploited by the fingerprinting function and use a bit of (brute) force.

import hashlib
import sys

h = '31c6b7c46ff55afc8c5e64f42cc9b48dde6a04b5ca434038cd2af8bd3fd1483a'

mime_types = range(0, 10)
navigator = range(0, 46)
windows = range(150, 231)

vendors = ['Google Inc.']
plugin0_filenames = ['', 'libwidevinecdmadapter.so', 'libppGoogleNaClPluginChrome.dll', 'undefined', 'libpepflashplayer.dll', 'libppGoogleNaClPluginChrome.so', 'internal-remoting-viewer', 'libpepflashplayer.so', 'internal-nacl-plugin', 'internal-pdf-viewer', 'mhjfbmdgcfjbbpaeojofohoefgiehjai', 'libwidevinecdmadapter.dll', 'libpdf.so', 'libpdf.dll']
plugin1_descr = ['', 'This plugin allows you to securely access other computers that have been shared with you. To use this plugin you must first install the
Chrome Remote Desktop webapp.', 'undefined', 'Shockwave Flash 14.0 r0', 'Enables Widevine licenses for playback of HTML audio/video content.', 'Portable Document Format', 'Enables Widevine licenses for playback of HTML audio/video content. (version: Something fresh)'] + ['Shockwave Flash {}.{} r0'.format(maj, mino) for maj in range(18) for mino in range(5)]

for ven in vendors:
for mt in mime_types:
for nav in navigator:
for win in windows:
for p0 in plugin0_filenames:
for p1 in plugin1_descr:
# simulate utf-16 encoding
data = ''.join(c+'\x00' for c in 'o{}{}{}{}{}{}'.format(ven, mt, nav, win, p0, p1))
if h == hashlib.sha256(data).hexdigest():
print('[FOUND] {}'.format(data))

The script returns the string `oGoogle Inc.828186libpepflashplayer.soThis plugin allows you to securely access other computers that have been shared with you. To use this plugin you must first install the Chrome Remote Desktop webapp.` that can be used, in a base64-encoded form, to access the flag:

$ curl 'http://d2b24dd8.quals2018.oooverflow.io/flag.php?f=b0dvb2dsZSBJbmMuODI4MTg2bGlicGVwZmxhc2hwbGF5ZXIuc29UaGlzIHBsdWdpbiBhbGxvd3MgeW91IHRvIHNlY3VyZWx5IGFjY2VzcyBvdGhlciBjb21wdXRlcnMgdGhhdCBoYXZlIGJlZW4gc2hhcmVkIHdpdGggeW91LiBUbyB1c2UgdGhpcyBwbHVnaW4geW91IG11c3QgZmlyc3QgaW5zdGFsbCB0aGUgPGEgaHJlZj0iaHR0cHM6Ly9jaHJvbWUuZ29vZ2xlLmNvbS9yZW1vdGVkZXNrdG9wIj5DaHJvbWUgUmVtb3RlIERlc2t0b3A8L2E+IHdlYmFwcC4='

Flag: `OOO{th3r3c@nb30nly0n3br0ws3r!}`

Original writeup (https://mhackeroni.it/archive/2018/05/20/defconctfquals-2018-all-writeups.html#geckome).