
Very Easy 문제답게 유저의 입력을 받아 검증하는 간단한 웹 서비스이다.
// index.mjs
const IsPalinDrome = (string) => {
if (string.length < 1000) {
return 'Tootus Shortus';
}
for (const i of Array(string.length).keys()) {
const original = string[i];
const reverse = string[string.length - i - 1];
if (original !== reverse || typeof original !== 'string') {
return 'Notter Palindromer!!';
}
}
return null;
}
// ...
app.post('/', async (c) => {
const {palindrome} = await c.req.json();
const error = IsPalinDrome(palindrome);
if (error) {
c.status(400);
return c.text(error);
}
return c.text(`Hii Harry!!! ${flag}`);
});
유저가 입력한 값이 위의 IsPalinDrome 함수의 필터링을 우회하여 null을 return하기만 하면 flag를 얻을 수 있다.
IsPalinDrome 함수는 첫 번째로 유저의 입력 string의 길이가 1000 이상인지 검사하고 두 번째로 해당 입력이 대칭인지 확인하여 두 조건을 만족하지 않는 입력을 필터링한다. 언듯 보면 그냥 길이가 1000 이상인 대칭되는 문자열을 넣으면 손쉽게 flag를 획득할 수 있을 것처럼 보이지만, nginx.config 파일을 확인해보면
// nginx.conf
server {
listen 80;
server_name 127.0.0.1
client_max_body_size 75;
이와같이 client가 전송할 수 있는 body size를 75로 제한하였다.
JS에서는 중괄호 {} 안에 property와 method를 정의함으로써 literal object를 생성할 수 있다. 그리고 JSON은 JavaScript Object Notation의 약자로 JS에서 literal object를 표현하는 방식으로 데이터를 저장하는 포맷이다. 즉 key-value 쌍으로 데이터를 저장하는 JSON에서는 value로 {}를 줌으러써 JS의 literal object를 담을 수 있다.
// index.mjs
const IsPalinDrome = (string) => {
if (string.length < 1000) {
return 'Tootus Shortus';
}
만약 string이 문자열일 경우 string.length는 해당 문자열의 길이를 나타내는 값일 것이다. 하지만 만약 유저가 literal object를 제공할 경우 string.length는 string object의 length라는 이름의 property값을 가져올 것이다.
JS는 타입 정책이 유연한 언어로 다른 언어의 경우 서로 다른 타입의 데이터끼리의 연산을 만날 경우 에러를 발생시키지만, JS는 대부분의 서로 다른 타입의 데이터끼리의 연산을 수행한다.
{
"palindrome":{
"length":"1000",
}
}
만약 유저가 위와 같은 요청을 보낸다면 이 app은 string.length에서 문자열 "1000"을 가져올 것이고 (string.length < 1000)을 수행할 것이다. JS는 문자열과 숫자의 부등호 연산이 있을 때 문자열을 숫자로 바꿀 수 있는 경우 문자열을 숫자로 바꾸어서 연산을 수행한다. 따라서 (string.length < 1000) 연산 결과로 false가 나오면서 첫 번째 필터링을 우회할 수 있다.
for (const i of Array(string.length).keys()) {
const original = string[i];
const reverse = string[string.length - i - 1];
if (original !== reverse || typeof original !== 'string') {
return 'Notter Palindromer!!';
}
}
다음 필터링은 유저가 전송한 데이터가 좌우 대칭인지를 확인하는 것이다. 이 필터링을 우회하는 방법도 JSON을 통해 문자열이 아닌 literal object를 전달할 수 있다는 것을 이용하는 것이다. string.length을 통해 유저가 전달한 객체의 length property를 가져올 수 있었던 것처럼 string[i]를 통해 객체의 property를 가져올 수 있다.
Array(string.length)는 Array("1000")으로 단순히 문자열 "1000"만을 요소로 가지고 있는 Array이므로 i에는 0이 들어온다. 즉 string[i], string[string.length - i - 1]는 각각string[0], string[999]으로 유저가 전달한 객체의 0, 999 property를 가져온다.
따라서 최종 payload는 다음과 같다.
따라서 최종 payload는 다음과 같다.
{"palindrome":{"length":"1000","0":"a","999":"a"}}