前言

文章的灵感来自于刚刚结束的 DefCamp CTF 2018 上的一道题目,主要的考点是 Node.js 的 prototype pollution attack。因为在 CTF 中 Node.js 的题型较少,同时本人也恰好对其比较感兴趣,所以特地来分析一下这道题的前因后果。

题目

题目是一个由 Node.js 编写的基于 socket.io 的聊天应用,运行在 https://chat.dctfq18.def.camp 的 80 端口上,我们可以从 https://dctf.def.camp/dctf-18-quals-81249812/chat.zip 下载源码

客户端的代码非常简单,分析 client.js 我们可以发现其只是向服务端注册用户并发送消息:

const io = require('socket.io-client')
const socket = io.connect('https://chat.dctfq18.def.camp')

if(process.argv.length != 4) {
  console.log('name and channel missing')
   process.exit()
}
console.log('Logging as ' + process.argv[2] + ' on ' + process.argv[3])
var inputUser = {
  name: process.argv[2],
};

socket.on('message', function(msg) {
  console.log(msg.from,"[", msg.channel!==undefined?msg.channel:'Default',"]", "says:\n", msg.message);
});

socket.on('error', function (err) {
  console.log('received socket error:')
  console.log(err)
})

socket.emit('register', JSON.stringify(inputUser));
socket.emit('message', JSON.stringify({ msg: "hello" }));
socket.emit('join', process.argv[3]);//ps: you should keep your channels private
socket.emit('message', JSON.stringify({ channel: process.argv[3], msg: "hello channel" }));
socket.emit('message', JSON.stringify({ channel: "test", msg: "i own you" }));

所以我们需要继续审计服务端的代码,可以看到 server.js 中存在着很一个敏感的函数 getAscii,在分析了其对应的代码后,可以发现其中存在着一个很明显的命令注入问题:

getAscii: function(message) {
    var e = require('child_process');
    return e.execSync("cowsay '" + message + "'").toString();
}

只要我们构造 message = "aaa';ls -al; echo 'xxx",服务器就会将命令 cowsay 'aaa'; ls -al; echo 'xxx' 执行后的结果发送给我们。

那么我们需要关注的下一个问题则是哪里会调用 getAscii 函数,可以发现服务端会在监听到 join 和 leave 两个事件的时候触发该函数:

client.on('join', function(channel) {
    try {
        clientManager.joinChannel(client, channel);
        sendMessageToClient(client,"Server", 
            "You joined channel", channel)

        var u = clientManager.getUsername(client);
        var c = clientManager.getCountry(client);

        sendMessageToChannel(channel,"Server", 
            helper.getAscii("User " + u + " living in " + c + " joined channel"))
    } catch(e) { console.log(e); client.disconnect() }
});

client.on('leave', function(channel) {
    try {
        client .join(channel);
        clientManager.leaveChannel(client, channel);
        sendMessageToClient(client,"Server", 
            "You left channel", channel)
点击收藏 | 3 关注 | 2
  • 动动手指,沙发就是你的了!
登录 后跟帖