Challenge
Let me start of this post with a small challenge.
Replace // Your code here
with actual code, and print Flag
!
function generateSecret() {
return Date.now() + Math.random() * 10000;
}
const mySecretKey = generateSecret();
// Your code here
if (mySecretKey === 42) {
console.log('Flag!');
} else {
console.log('Bad secret!');
}
Writeup
In order to print the Flag, we need to understand how function hoisting works.
myFunction();
function myFunction() {
console.log('My function was called!');
}
This snippet is valid and will correctly print My function was called!
, even though this function is declared after it has been called.
This works thanks to Hoisting.
Here is a quick definition from MDN:
Conceptually, for example, a strict definition of hoisting suggests that variable and function declarations are physically moved to the top of your code, but this is not in fact what happens. Instead, the variable and function declarations are put into memory during the compile phase, but stay exactly where you typed them in your coding.
This means that the previous code can be understood as:
function myFunction() {
console.log('My function was called!');
}
myFunction();
The function declarations and definitions are moved before the actual code happens, which lets us use functions before they are declared.
But what happens if we declare the same function twice?
function myFunction() {
console.log('My function was called!');
}
myFunction();
function myFunction() {
console.log('My *evil* function was called!');
}
Spoiler alert: The evil function is called!
Once hoisted, the previous code can be understood as:
function myFunction() {
console.log('My function was called!');
}
function myFunction() {
console.log('My *evil* function was called!');
}
myFunction();
As the last declaration of myFunction
is the evil one, all of the calls to myFunction
will be to the evil function!
Solution
In order to solve the challenge, we therefore only need to redeclare the generateSecret
function.
function generateSecret() {
return Date.now() + Math.random() * 10000;
}
const mySecretKey = generateSecret();
// Your code here
function generateSecret() {
return 42;
}
if (mySecretKey === 42) {
console.log('Flag!');
} else {
console.log('Bad secret!');
}
References
MDN: Hoisting
MDN: Function
Medium: Hoist your knowledge of JavaScript hoisting
Top comments (7)
I couldn't understand what is the challenge (to force the value to be 42), maybe is just me
Shouldn't be IDE's and linters handle this mistakes for us? (not double declare something). I guess that is the purpose, to raise awareness of this possible problem.
a better challenge would be that wouldn't require to solve it with a bad practice (redeclare/overwrite a definition).
Hey there, thanks for the feedback!
This is not a "best practice" use case, but one feature of ecmascript that should be known by developers.
The challenge itself is an introduction to a given scenario where you can control only part of the website, such as in a Reflected XSS, yet need to change the behavior of a constant.
In another language, I would expect the secret variable to be safe and tamper-proof, yet it is not thanks to Function Hoisting.
Of course I wouldn't recommend anyone to use this knowledge in clean code, but I definitely can see this being part of a CTF challenge or causing innatention bugs.
Good to know !
But I am afraid why would anyone want to declare function twice to override it. To me code smells ๐คท๐ฝโโ๏ธ
I've run into the situation multiple times with old codes.
E.g.)
Some JS files I dealt with are about 1k+ lines long and many people added the same function named
get*ById
etc.Oh man thats scary, If someone adds another function
getById
later on. Any code which was using oldgetById
would start using newgetById
.To me this sounds like a bug rather than intentional old way of doing things. What do you think it was ?
My guess is that, some devs used function expressions (e.g.
var getById = function()...
) instead of function declarations (function getById()...
)Function declarations are hoisted with body but function expressions are not, causing people who don't know how hoisting work declare same code over and over sometimes.
Thanks Antony for the article ๐
Knowing this, I could debug better with legacy codes ๐