Skip to content

Instantly share code, notes, and snippets.

@eligrey
Last active February 15, 2021 00:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eligrey/5f25971ce540a2dee233c8174dbfaafc to your computer and use it in GitHub Desktop.
Save eligrey/5f25971ce540a2dee233c8174dbfaafc to your computer and use it in GitHub Desktop.
Secure user-initiated click isTrusted attestation challenge — https://go.eligrey.com/t/event-isTrusted-challenge
// @ts-nocheck
//
// User-initiated click isTrusted attestation challenge:
//
// Secure this 'click' event listener from synthetic clicks
// while working in a prototype pollution superfund site.
//
// addEventListener() has been been backdoored.
//
// Good luck!
//
// Secure references for backdoor
const apply = Function.prototype.call.bind(Function.prototype.apply);
const create = Object.create.bind(Object);
const { addEventListener } = EventTarget.prototype;
const { MouseEvent } = self;
// Backdoor addEventListener
EventTarget.prototype.addEventListener = function () {
const [type, listener] = arguments;
// Hijack click listeners
if (type === 'click') {
const props = {};
const click = new MouseEvent('click');
// Clone and flatten all enumerable properties in prototype chain
for (const prop in click) {
props[prop] = { value: click[prop] };
}
// Spoof event.isTrusted
props.isTrusted = { value: true };
// Create fake user-initiated click with MouseEvent prototype chain
const fakeClick = create(click, props);
listener(fakeClick);
}
// Pass-through to real addEventListener
return apply(addEventListener, this, arguments);
};
// Backdoor `instanceof MouseEvent`
const MouseEventHasInstance = Object.getOwnPropertyDescriptor(MouseEvent, Symbol.hasInstance);
if (!MouseEventHasInstance || MouseEventHasInstance.configurable) {
Object.defineProperty(MouseEvent, Symbol.hasInstance, {
value: () => true
});
}
// Backdoor `event.type` getter
const getType = Object.getOwnPropertyDescriptor(Event.prototype, 'type').get;
Object.defineProperty(Event.prototype, 'type', {
get() {
return Object.getOwnPropertyDescriptor(this, 'type')?.value || getType.call(this);
}
});
// SOLUTION
// Fix this function without changing code outside of the solution block
const isUserInitiatedClick = (event) => {
try {
return (
event instanceof MouseEvent &&
event.type === 'click' &&
event.isTrusted
);
} catch (ex) {
return false;
}
};
// END SOLUTION
const secret = '[secret value]';
const reciever = (data) => {
console.log('Secret recieved:', data);
};
const container = document.querySelector('ul.nav') || document.body;
const button = container.querySelector('button.challenge-button') || container.appendChild(document.createElement('button'));
button.classList.add('challenge-button');
if (!button.textContent) {
button.append('Share secret (real clicks only!)');
}
// Clear any event listeners on button
const clone = button.cloneNode(true);
button.replaceWith(clone);
// Listen for button clicks
clone.addEventListener('click', (event) => {
if (isUserInitiatedClick(event)) {
console.log('Click trusted:', event);
reciever(secret);
} else {
console.error('Detected fake click:', event);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment