[Dreamhack] baby-nextjs
문제 설명
- use nextjs 👶
풀이
문제에 처음 접속하면 give me the flag라는 버튼이 있어, 눌러 보면 unavailable in production 문자열이 나온다.


page.js 페이지 소스 코드를 살펴보면 use client를 통해서 클라이언트에서 실행되는 컴포넌트라고 선언하고 있고, actions.js에서 getFlag() 함수를 가져온다. IS_PROD에서는 환경변수로 현재 실행 중인 환경이 production 인지 확인하고 true면 배포버전 false면 개발 모드이다. 개발 모드이면 getFlag() 함수를 통해서 flag를 출력해주는 코드이다.
'use client';
import { useState } from 'react';
import { getFlag } from './actions';
const IS_PROD = process.env.NODE_ENV === 'production';
export default function Home() {
const [message, setMessage] = useState('');
return (
<>
{message && <p>{message}</p>}
<button
onClick={async () => {
if (IS_PROD) {
setMessage('unavailable in production');
return;
}
const { message } = await getFlag();
setMessage(message);
}}
>
give me the flag
</button>
</>
);
}
actions.js 파일의 소스 코드도 살펴보면, 서버 컴포넌트라는걸 선언하였고, import 문으로 js, path, url 모듈을 가져와서 사용하고 있다. __dirname 변수에는 /app/action.js 라면 /app 이 저장된다. 그리고 flag를 읽어오는 getFlag() 함수가 존재한다.
'use server';
import { readFileSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
export async function getFlag() {
return { message: readFileSync(resolve(__dirname, '../../flag.txt'), 'utf8') };
}
layout.js 파일에서는 딱히 볼게 없다.
export const metadata = {
title: "Baby Next.js",
description: "👶",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
</body>
</html>
);
}
이번 문제에서 해당 버튼을 클릭하고 개발자 도구의 네트워크 및 burp에서 history를 분석해보면, js 파일을 불러오는 거 말고는 버튼을 눌렀을 때의 요청이 남아있지 않다. 따라서 처음엔 개발 모드 production을 어떻게 조작하여 우회할지 고민해보았지만, 검색하다보니 next.js의 13~14 버전에서 클라이언트 코드에서 백엔드 함수를 직접 호출하는 방식이 가능하다는 것을 알게 되었다.
next.js에서는 서버 함수마다 내부적으로 고유한 identifier를 생성하고 _rsc 또는 _actions 등의 비공개 API 엔드포인트로 호출한다. 또한 함수를 호출할 때 구조는 보통 아래와 같이 json을 사용하여 호출이 되고, Next-Action이라는 헤더에 Action_ID 값을 넣어 요청하게 된다. 따라서 번들에서 getFlag() 함수를 호출하는 Action ID가 존재함으로 이를 분석하여 찾아야 한다. 참고로 $$는 Next.js 내부에서 이 값이 Server Action ID임을 식별하기 위한 접두사이다.
POST /_rsc HTTP/1.1
Content-Type: application/json
Next-Action: Action_ID
...
["$$ACTION_ID", null]
burp 히스토리에서 요청된 js 소스 코드에서 Action_ID를 찾기 위해서 getFlag, flag, callServer 등 문자열로 찾아봐야 한다. 또한 이 Action_ID는 길이가 40인 유니크 문자열이며, callServer는 클라이언트에서 서버 함수를 직접 호출할 때 사용되는 함수로 이를 검색하여 찾을 수 있다.

이제 burp 요청에서 아래와 같이 _rsc 경로로 Action_ID를 가져와 요청하면 flag 값을 획득할 수 있다.

댓글남기기