Author:颖奇L’Amore

Blog:www.gem-love.com

我不相信只有我一个人是从头到尾都没成功打开过平台。。。


WEB1

别人发来的题目链接:http://183.129.189.60:55200/ ,题目其他信息就不知道了

在/source得到源码

const express = require("express");
const cors = require("cors");
const app = express();
const uuidv4 = require("uuid/v4");
const md5 = require("md5");
const jwt = require("express-jwt");
const jsonwebtoken = require("jsonwebtoken");
const server = require("http").createServer(app);

const { flag, secret, jwtSecret } = require("./flag");

const config = {
  port: process.env.PORT || 8081,
  adminValue: 1000,
  message: "Can you get flag?",
  secret: secret,
  adminUsername: "kirakira_dokidoki",
  whitelist: ["/", "/login", "/init", "/source"],
};

let users = {
  0: {
    username: config.adminUsername,
    isAdmin: true,
    rights: Object.keys(config)
  }
};

app.use(express.json());

app.use(cors());

app.use(
  jwt({ secret: jwtSecret }).unless({
    path: config.whitelist
  })
);

app.use(function(error, req, res, next) {
  if (error.name === "UnauthorizedError") {
    res.json(err("Invalid token or not logged in."));
  }
});

function sign(o) {
  return jsonwebtoken.sign(o, jwtSecret);
}

function ok(data = {}) {
  return { status: "ok", data: data };
}

function err(msg = "Something went wrong.") {
  return { status: "error", message: msg };
}

function isValidUser(u) {
  return (
    u.username.length >= 6 &&
    u.username.toUpperCase() !== config.adminUsername.toUpperCase() && u.username.toUpperCase() !== config.adminUsername.toLowerCase()
  );
}

function isAdmin(u) {
  return (u.username.toUpperCase() === config.adminUsername.toUpperCase() && u.username.toUpperCase() === config.adminUsername.toLowerCase()) || u.isAdmin;
}

function checkRights(arr) {
  let blacklist = ["secret", "port"];

  if(blacklist.includes(arr)) {
    return false;
  }
  
  for (let i = 0; i < arr.length; i++) {
    const element = arr[i];
    if (blacklist.includes(element)) {
      return false;
    }
  }
  return true;
}

app.get("/", (req, res) => {
  res.json(ok({ hint:  "You can get source code from /source"}));
});

app.get("/source", (req, res) => {
    res.sendFile( __dirname + "/" + "app.js");
});

app.post("/login", (req, res) => {
  let u = {
    username: req.body.username,
    id: uuidv4(),
    value: Math.random() < 0.0000001 ? 100000000 : 100,
    isAdmin: false,
    rights: [
      "message",
      "adminUsername"
    ]
  };
  if (isValidUser(u)) {
    users[u.id] = u;
    res.send(ok({ token: sign({ id: u.id }) }));
  } else {
    res.json(err("Invalid creds"));
  }
});

app.post("/init", (req, res) => {
  let { secret } = req.body;
  let target = md5(config.secret.toString());

  let adminId = md5(secret)
    .split("")
    .map((c, i) => c.charCodeAt(0) ^ target.charCodeAt(i))
    .reduce((a, b) => a + b);

  res.json(ok({ token: sign({ id: adminId }) }));
});


// Get server info
app.get("/serverInfo", (req, res) => {
  let user = users[req.user.id] || { rights: [] };
  let info = user.rights.map(i => ({ name: i, value: config[i] }));
  res.json(ok({ info: info }));
});

app.post("/becomeAdmin", (req, res) => {
  let {value} = req.body;
  let uid = req.user.id;
  let user = users[uid];

  let maxValue = [value, config.adminValue].sort()[1];
  if(value >= maxValue && user.value >= value) {
    user.isAdmin = true;
    res.send(ok({ isAdmin: true }));
  }else{
    res.json(err("You need pay more!"));
  }
});

// only admin can update user
app.post("/updateUser", (req, res) => {
  let uid = req.user.id;
  let user = users[uid];
  if (!user || !isAdmin(user)) {
    res.json(err("You're not an admin!"));
    return;
  }
  let rights = req.body.rights || [];
  if (rights.length > 0 && checkRights(rights)) {
    users[uid].rights = user.rights.concat(rights).filter((value, index, self)=>{
      return self.indexOf(value) === index;
    });
  }
  res.json(ok({ user: users[uid] }));
});

// only uid===0 can get the flag
app.get("/flag", (req, res) => {
  if (req.user.id == 0) {
    res.send(ok({ flag: flag }));
  } else {
    res.send(err("Unauthorized"));
  }
});

server.listen(config.port, () =>
  console.log(`Server listening on port ${config.port}!`)
);

Step 1:称为管理员

在/becomeAdmin路由下使用了Arraysort()方法:

let maxValue = [value, config.adminValue].sort()[1];

JS比较坑的是,sort()方法实际上是把number转成了string再排序的,所以就会出现这种情况:

所以只要利用这个特性就可以成功满足value>=maxValue && 100 >= value而成为管理员

Step 2:得到secret

首先在updateUser路由下可以为用户的rights数组加东西:

之后在serverInfo路由下,将rights数组内的元素作为config对象的属性并返回对应的值

我们希望得到secret的值,然而secret是被checkRights()给禁掉的,无论req.body.rights是数组还是字符串都会被ban:

这里又用到一个js的特性:

所以提交[["secret"]]即可

之后得到secret

Step 3:修改uid为0

因为jwt用的jwtSecret是不会泄露的,伪造jwt的思路就失败了。注意到init处可以修改id:

md5(secret)现在已知了,然后有一个异或操作,所以直接让它们相等就能返回0了。注意需要提交secret为字符串形式,然后得到新token:

jwt的payload部分直接解base64就可以看到明文,可以看下id是否为0了

然后带上新jwt去访问flag即可


后记:

赛后发现,是原题,无语

https://xz.aliyun.com/t/7177

等第四届BJDCTF,请大家吃我出了整整一个星期的NodeJS奥

结账下机,准备今晚9点半打InCTF了

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

本文链接地址:https://www.gem-love.com/ctf/2537.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