JavaScript原型链污染
大力学习js然后学到原型链把人学傻了,专门理了一下
__proto__和prototype
每个对象都存在一个__proto__属性,而每个类(js并没有很正统的类这个说法)则拥有prototype,由该类构造出来的对象的__proto__就指向该类的prototype,这都是网上可以搜到的内容,不想讲,看这个
https://www.cnblogs.com/shuiyi/p/5305435.html
大力学习js然后学到原型链把人学傻了,专门理了一下
每个对象都存在一个__proto__属性,而每个类(js并没有很正统的类这个说法)则拥有prototype,由该类构造出来的对象的__proto__就指向该类的prototype,这都是网上可以搜到的内容,不想讲,看这个
https://www.cnblogs.com/shuiyi/p/5305435.html
又是一个js题,原型链污染和toUppercase,最后发现居然还有express框架的SSTI
源码在www.zip下
主要逻辑是/route/index.js
var express = require('express');
var router = express.Router();
const isObject = obj => obj && obj.constructor && obj.constructor === Object;
const merge = (a, b) => {
for (var attr in b) {
if (isObject(a[attr]) && isObject(b[attr])) {
merge(a[attr], b[attr]);
} else {
a[attr] = b[attr];
}
}
return a
}
const clone = (a) => {
return merge({}, a);
}
function safeKeyword(keyword) {
if(keyword.match(/(admin)/is)) {
return keyword
}
return undefined
}
.......
router.post('/login', function (req, res) {
if(req.body.Submit=="register"){
if(safeKeyword(req.body.userid)){
res.end("<script>alert('forbid word');history.go(-1);</script>")
}
req.session.user={
'user':req.body.userid.toUpperCase(),
'passwd': req.body.pwd,
'isLogin':false
}
res.redirect('/');
}
else if(req.body.Submit=="login"){
if(!req.session.user){res.end("<script>alert('register first');history.go(-1);</script>")}
if(req.session.user.user==req.body.userid&&req.body.pwd==req.session.user.passwd){
req.session.user.isLogin=true;
}
else{
res.end("<script>alert('error passwd');history.go(-1);</script>")
}
}
res.redirect('/');
});
router.post('/action', function (req, res) {
if(req.session.user.user!="ADMIN"){res.end("<script>alert('ADMIN is asked');history.go(-1);</script>")}
req.session.user.data = clone(req.body);
res.end("<script>alert('success');history.go(-1);</script>");
});
router.get('/info', function (req, res) {
res.render('index',data={'user':res.outputFunctionName});
})
module.exports = router;
上来就自己定义了一个clone函数,怎么看怎么原型链污染,题目意图非常明显,而看到login,首先不允许你是admin,又需要你用admin登录,没找到源码的时候题目有说只支持大写用户名就能猜到是怎么回事了,用一个奇怪的字符绕过一下就好
最近把JavaScript入了一下门,就顺便做了个js题目
app.get('/', function(req, res) {
const action = req.query.action ? req.query.action : "index";
if( action.includes("/") || action.includes("\\") ){
res.send("Errrrr, You have been Blocked");
}
let file = path.join(__dirname + '/template/' + action + '.pug');
const html = pug.renderFile(file);
res.send(html);
});
app.post('/file_upload', function(req, res){
const ip = req.connection.remoteAddress;
let obj = {
msg: '',
};
if (!ip.includes('127.0.0.1')) {
obj.msg="only admin's ip can use it"
res.send(JSON.stringify(obj));
return
}
fs.readFile(req.files[0].path, function(err, data){
if(err){
obj.msg = 'upload failed';
res.send(JSON.stringify(obj));
}else{
const file_path = '/uploads/' + req.files[0].mimetype + "/";
const file_name = req.files[0].originalname;
const dir_file = __dirname + file_path + file_name;
if(!fs.existsSync(__dirname + file_path)){
try {
fs.mkdirSync(__dirname + file_path)
} catch (error) {
obj.msg = "file type error";
res.send(JSON.stringify(obj));
return
}
}
try {
fs.writeFileSync(dir_file,data)
obj = {
msg: 'upload success',
filename: file_path + file_name
}
} catch (error) {
obj.msg = 'upload failed';
}
res.send(JSON.stringify(obj));
}
})
})
app.get('/source', function(req, res) {
res.sendFile(path.join(__dirname + '/template/source.txt'));
});
app.get('/core', function(req, res) {
const q = req.query.q;
const resp = "";
if (q) {
const url = 'http://localhost:8081/source?' + q;
console.log(url)
const trigger = blacklist(url);
if (trigger === true) {
res.send("<p>error occurs!</p>");
} else {
try {
http.get(url, function(resp) {
resp.setEncoding('utf8');
resp.on('error', function(err) {
if (err.code === "ECONNRESET") {
console.log("Timeout occurs");
return;
}
});
resp.on('data', function(chunk) {
try {
resps = chunk.toString();
res.send(resps);
}catch (e) {
res.send(e.message);
}
}).on('error', (e) => {
res.send(e.message);});
});
} catch (error) {
console.log(error);
}
}
} else {
res.send("search param 'q' missing!");
}
})
function blacklist(url) {
const evilwords = ["global", "process", "mainModule", "require", "root", "child_process", "exec", "\"", "'", "!"];
const arrayLen = evilwords.length;
for (var i = 0; i < arrayLen; i++) {
const trigger = url.includes(evilwords[i]);
if (trigger === true) {
return true
}
}
}
就简单贴几个路由和过滤函数
根目录是解析一个template目录下的pug文件并返回,查了一下pug就是一个渲染模板一类的东西,可以在里面写一些代码进行渲染,命令执行估计是靠这个
file_upload路由需要remote_address是localhost,必然需要找一个ssrf点,上传一个文件到对应的mimetype文件夹下,mimetype即为文件上传部分的content-type(注意不是请求头中的content-type)
source路由展示源码,写死了无法控制
core路由是一个ssrf点,但是路由写死了source,后面的参数q可控,但需要通过blacklist的检测
看了两天题目,做出来的都是一开始的非预期,马上就上了一个打了补丁的v2.0然后不会做了呜呜呜,base64这题还是个phppwn,好不容易找到了.so文件还需要pwn爷爷去做,webweb代码审计看到头痛也看不出名堂,太菜了
算不上题解,只能说是找到.so文件的步骤,呜呜呜
题目给了一个base64解码功能,简单测试之后发现当输入字符长度不是4的倍数,或者出现不能解码的字符的时候,就会把能解码的部分阶段解码返回,顺便输出一个看不懂的字符画(师傅说那个画的是’base64decodefail’)
查看源码可以看到一个hint,输入参数filename=hint.php,得到
全新的知识,PHP7.4中的全新玩法
上来直接给一个shell,直接执行phpinfo看一眼,open_basedir写上了,disable_function超级过滤,把平常用的mail和unsetenv都给禁了,常用的bypass_disable_function via RD_PRELOAD都不能用了,得寻找新的突破口
可以先var_dump(scandir(‘.’));看一眼有没有其他的可利用文件,发现一个preload.php,里面写了一个类
<?php
final class A implements Serializable {
protected $data = [
'ret' => null,
'func' => 'print_r',
'arg' => '1'
];
private function run () {
$this->data['ret'] = $this->data['func']($this->data['arg']);
}
public function __serialize(): array {
return $this->data;
}
public function __unserialize(array $data) {
array_merge($this->data, $data);
$this->run();
}
public function serialize (): string {
return serialize($this->data);
}
public function unserialize($payload) {
$this->data = unserialize($payload);
$this->run();
}
public function __get ($key) {
return $this->data[$key];
}
public function __set ($key, $value) {
throw new \Exception('No implemented');
}
public function __construct () {
throw new \Exception('No implemented');
}
}
说实话我是没看懂这个类能怎么利用的,虽然我印象中析构函数会将工作目录切换到根目录,但是这里显然也没有析构函数,而尝试了一下用这个类的反序列化去执行命令,显然还是PHP函数,无法绕过disable_function