[보안/write-up] magical palindrome

Shadis·2026년 1월 28일

보안/write-up

목록 보기
1/4

About This Wargame

문제 분석

  • 웹 서버: Nginx
  • WAS: node.js
  • Framework: Hono(JS)

application 분석

Very Easy 문제답게 유저의 입력을 받아 검증하는 간단한 웹 서비스이다.

flag 분석

// 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의 필터링 방법

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로 제한하였다.

Exploit

JSON을 통해 문자열이 아닌 객체 전달

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는 타입 정책이 유연한 언어로 다른 언어의 경우 서로 다른 타입의 데이터끼리의 연산을 만날 경우 에러를 발생시키지만, JS는 대부분의 서로 다른 타입의 데이터끼리의 연산을 수행한다.

{
  "palindrome":{
    "length":"1000",
  }
}

만약 유저가 위와 같은 요청을 보낸다면 이 app은 string.length에서 문자열 "1000"을 가져올 것이고 (string.length < 1000)을 수행할 것이다. JS는 문자열과 숫자의 부등호 연산이 있을 때 문자열을 숫자로 바꿀 수 있는 경우 문자열을 숫자로 바꾸어서 연산을 수행한다. 따라서 (string.length < 1000) 연산 결과로 false가 나오면서 첫 번째 필터링을 우회할 수 있다.

JSON을 통해 문자열이 아닌 객체 전달 2

	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

따라서 최종 payload는 다음과 같다.

{"palindrome":{"length":"1000","0":"a","999":"a"}}
profile
HGU 20 김민석

0개의 댓글