내 친구가 이 부족한 툴을 만들었다; 쿠키 값을 훔칠수 있겠어? 링크를 보내주면 내가 전달할수 있어.

 

this dumb tool

툴이라고 소개된 url로 접속하게 되면 2가지 기능을 마주하게 된다.

- Link Shortener

직역하면 링크 단축키라고 한다. 

URL 풀주소를 입력해주면 web-utils.dicec.tf를 도메인으로 사용하는 URL주소를 건네준다.

 

ex) http://www.google.com -> http://web-utils.dicec.tf/~~

실제로 단축 주소를 입력해주면 해당 사이트로 이동하는 것을 확인 할수있다.

https://web-utils.dicec.tf/view/pEr848xN

 

- Pastebin

값을 입력하면 해당 데이터 값을 출력(?) 해주는 URL을 건네준다.

ex) test -> https://web-utils.dicec.tf/~~ 

마찬가지로 실제 단축 주소로 이동해보면 입력한 값을 그대로 출력해주는 페이지를 마주할 수 있다.

https://web-utils.dicec.tf/view/TRTO7I1W

 

 

I can pass it along

ravidusash.tistory.com/139

web/Babier CSP에서 나왔던 Admin Bot인 것 같다. URL을 입력해주면 Admin Bot 접속해주는 방식인 것 같다.

 

우리는 이 문제의 목표를 찾았다.

쿠키 값을 얻기 위해서 어떠한 URL 주소를 찾아내 입력해 주어야 한다.

 

다음으로 제공된 app.zip 파일을 열어 분석을 진행해 보았다.

아래는 dumb tool에 관한 구조이다.

-app
| + modules
|  - database.js
|
| + Public
|  - view
|  - style
|  - index
|  + pastes
|   - index (pastebin)
|   - script
|   - style
|  + links
|   - index (link shortener)
|   - script
|   - style
|
| + routes
|  - api
|  - view
|
| - Dockerfile
| - index.js (server)
| - package.jsaon

 

크게 server -> linkshortener -> pastebin 순으로 진행하겠다.

const fastify = require('fastify')();

const path = require('path');

fastify.register(require('fastify-static'), {
  root: path.join(__dirname, 'public'),
  redirect: true,
  prefix: '/'
});

fastify.register(require('./routes/api'), {
  prefix: '/api/'
});

fastify.register(require('./routes/view'), {
  prefix: '/view/'
});

const start = async () => {
  console.log(`listening on ${await fastify.listen(3000, '0.0.0.0')}`)
}

start()

index.js (server)의 코드이다.

코드를 통해 route를 사용하여 api, view를 정의한 것을 확인 할수 있다.

 

linkshortener와 pastebin을 확인하기 전에 route을 먼저 보도록 하겠다.

 

server -> route -> linkshortener -> pastebin

순서대로 api, view이다.

const database = require('../modules/database');

module.exports = async (fastify) => {
  fastify.post('createLink', {
    handler: (req, rep) => {
      const uid = database.generateUid(8);
      const regex = new RegExp('^https?://');
      if (! regex.test(req.body.data))
        return rep
          .code(200)
          .header('Content-Type', 'application/json; charset=utf-8')
          .send({
            statusCode: 200,
            error: 'Invalid URL'
          });
      database.addData({ type: 'link', ...req.body, uid });
      rep
        .code(200)
        .header('Content-Type', 'application/json; charset=utf-8')
        .send({
          statusCode: 200,
          data: uid
        });
    },
    schema: {
      body: {
        type: 'object',
        required: ['data'],
        properties: {
          data: { type: 'string' }
        }
      }
    }
  });

  fastify.post('createPaste', {
    handler: (req, rep) => {
      const uid = database.generateUid(8);
      database.addData({ type: 'paste', ...req.body, uid });
      rep
        .code(200)
        .header('Content-Type', 'application/json; charset=utf-8')
        .send({
          statusCode: 200,
          data: uid
        });
    },
    schema: {
      body: {
        type: 'object',
        required: ['data'],
        properties: {
          data: { type: 'string' }
        }
      }
    }
  });

  fastify.get('data/:uid', {
    handler: (req, rep) => {
      if (!req.params.uid) {
        return;
      }
      const { data, type } = database.getData({ uid: req.params.uid });
      if (!data || !type) {
        return rep
          .code(200)
          .header('Content-Type', 'application/json; charset=utf-8')
          .send({
            statusCode: 200,
            error: 'URL not found',
          });
      }
      rep
        .code(200)
        .header('Content-Type', 'application/json; charset=utf-8')
        .send({
          statusCode: 200,
          data,
          type
        });
    }
  });
}
module.exports = async (fastify) => {
  fastify.get(':id', {
    handler: (req, rep) => {
      rep.sendFile('view.html');
    }
  })
}

 

route의 api 소스코드를 분석해보면  8개의 랜덤 값을 생성하는 것과 url이 https:// 로 시작되는지 확인하는 과정을 볼 수 있다.

당장은 눈에 보이는게 없어 이 정도 정보만 얻고 넘어가도록 하겠다.

 

server -> route-> linkshortener -> pastebin

 

Public/link/index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Link Shortener</title>
    <link rel="stylesheet" href="style.css">
    <script src="script.js"></script>
  </head>
  <body>
    <div class="parent">
      <div class="content">
        <form id="url-form">
          <input id="url-input" type="text" placeholder="Link..." spellcheck="false"/>
          <input id="submit-button" type="submit" value="Shorten"/>
          <div id="output" class="display"></div>
        </form>
      </div>
    </div>
  </body>
</html>

html소스코드를 보고 script.js 분석이 필요하다고 느꼈다.

 

Public/link/script.js

(async () => {

  await new Promise((resolve) => {
    window.addEventListener('load', resolve);
  });

  document.getElementById('url-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const url = document.getElementById('url-input').value;
    const res = await (await fetch('/api/createLink', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        data: url
      })
    })).json();

    if (res.error) {
      return;
    }

    document.getElementById('output').textContent =
      `${window.origin}/view/${res.data}`
  });

})();

index.html에서 입력 받은 값을 api/createLink로 전달하여 특정 값을 전달 받는 것을 확인 할 수 있다.

아까 지나오면서 본 그 랜덤 8자리 값인 것으로 보인다.

 

server -> route-> linkshortener -> pastebin

 

Public/pastes/index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Pastebin</title>
    <link rel="stylesheet" href="style.css">
    <script src="script.js"></script>
  </head>
  <body>
    <div class="parent">
      <div class="content">
        <form id="text-form">
          <textarea id="text-input" placeholder="Paste..." spellcheck="false" rows="5"></textarea>
          <input id="submit-button" type="submit" value="Shorten"/>
          <div id="output" class="display"></div>
        </form>
      </div>
    </div>
  </body>
</html>

마찬가지로 script.js를 확인해 보아야 할 것 같다.

 

(async () => {

  await new Promise((resolve) => {
    window.addEventListener('load', resolve);
  });

  document.getElementById('text-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const text = document.getElementById('text-input').value;
    const res = await (await fetch('/api/createPaste', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        data: text
      })
    })).json();

    if (res.error) {
      return;
    }

    document.getElementById('output').textContent =
      `${window.origin}/view/${res.data}`
  });

})();

같은 내용이다. index.html에서 받은 URL을 api/createPaste에 전달하여 값을 받는다. 가장 뒷부분에 사용되고 이는 아까 랜덤 값 8개라 지칭하였던 그 값이다.

 

위에서 살펴 봤던 route/api를 다시 한번 살펴봐야 될 것 같다.

server -> route-> linkshortener -> pastebin -> route

const database = require('../modules/database');

//linkshortener에서 사용됨
module.exports = async (fastify) => {
  fastify.post('createLink', {
    handler: (req, rep) => {
      const uid = database.generateUid(8); // 8자리의 랜덤 값을 뽑아냄(database.js 분석 결과)
      const regex = new RegExp('^https?://');
      if (! regex.test(req.body.data))
        return rep
          .code(200)
          .header('Content-Type', 'application/json; charset=utf-8')
          .send({
            statusCode: 200,
            error: 'Invalid URL'
          });
      database.addData({ type: 'link', ...req.body, uid }); 
      rep //랜덤한 8자리 값을 반환
        .code(200)
        .header('Content-Type', 'application/json; charset=utf-8')
        .send({
          statusCode: 200,
          data: uid
        });
    },
    schema: {
      body: {
        type: 'object',
        required: ['data'],
        properties: {
          data: { type: 'string' }
        }
      }
    }
  });

//createPaste에서 사용됨
  fastify.post('createPaste', {
    handler: (req, rep) => {
      const uid = database.generateUid(8); // 8자리의 랜덤 값을 뽑아냄
      database.addData({ type: 'paste', ...req.body, uid });
      rep
        .code(200)
        .header('Content-Type', 'application/json; charset=utf-8')
        .send({
          statusCode: 200,
          data: uid
        });
    },
    schema: {
      body: {
        type: 'object',
        required: ['data'],
        properties: {
          data: { type: 'string' }
        }
      }
    }
  });

  fastify.get('data/:uid', {
    handler: (req, rep) => {
      if (!req.params.uid) {
        return;
      }
      const { data, type } = database.getData({ uid: req.params.uid });
      if (!data || !type) {
        return rep
          .code(200)
          .header('Content-Type', 'application/json; charset=utf-8')
          .send({
            statusCode: 200,
            error: 'URL not found',
          });
      }
      rep //랜덤한 8자리 값을 반환
        .code(200)
        .header('Content-Type', 'application/json; charset=utf-8')
        .send({
          statusCode: 200,
          data,
          type
        });
    }
  });
}

server -> route-> linkshortener -> pastebin ->route -> modules

const Database = require('better-sqlite3')
const db = new Database('db.sqlite3')

const init = () => {
  db.prepare(`CREATE TABLE IF NOT EXISTS data(
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        uid TEXT,
        data TEXT,
        type TEXT
        );`).run();
}

init();

const statements = {
  getData: db.prepare(`SELECT data, type FROM data WHERE uid = ?;`),
  addData: db.prepare(`INSERT INTO data (uid, data, type) VALUES (?, ?, ?);`)
}

module.exports = {
  getData: ({ uid }) => { // 값 조회
    return statements.getData.get(uid);
  },
  addData: ({ uid, data, type }) => { // 값 추가
    statements.addData.run(uid, data, type);
  },
  generateUid: (length) => {// 값 8개 가챠
    const characters =
      'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    const arr = [];
    for (let i = 0; i < length; i++) {
      arr.push(
        characters.charAt(Math.floor(Math.random() * characters.length))
      );
    }
    return arr.join('');
  }
}

소스코드 분석을 통해 당장 얻을 수 있는 정보는 다 얻어냈다.

 

 

'Challenge > CTF' 카테고리의 다른 글

[2021 hspace] Baby_Crypto  (0) 2021.05.07
[DiceCTF] web/Web Utils (2)  (0) 2021.03.09
[DiceCTF] web/Missing Flavortext  (0) 2021.02.16
[DiceCTF] web/Babier CSP  (0) 2021.02.09
CYBRICS CAPTURE THE FLAG  (0) 2020.07.28

+ Recent posts