关注PHP漏洞的朋友一定知道LFI+phpinfo可以搞出一个webshell。具体点击此处可看。 LFI这个条件还算正常,但phpinfo这个还是比较难凑的,所以有点鸡肋。接下来,我分享一个……同样鸡肋的思路……大家先把屠刀放下!虽然鸡肋,但思路还是值得探讨的。

起因是一篇老外写的文章,读完后发现他的思路或许可以和PHP LFI结合。 文章前面写Java那段简直就是意淫,但是后来提到当linux进程访问一个文件的时候会在/proc/{PID}/fd/下创建一个文件描述符来指向被访问的文件。比如/proc/1234/fd/5会指向/tmp/tmpXXX。而如果你cat proc/1234/fd/5,就会看到/tmp/tmpXXX的内容。而我们知道,向任何php脚本发送上传文件的请求,php都会创建一个临时文件来保存上传的当内容,当php脚本执行结束才删除这个临时文件。那么当创建临时文件的时候,一定会创建相应的文件描述符。如果我们能找到对应的文件描述,应该就能通过文件包含实现webshell了。一开始我想难点是猜出PID和文件描述符编号,PID一般是几千,apache进程的文件描述符编号一般就只在1-20之间,所以理论上暴力破解并不难。后来我发现其实搞定PID和文件描述符编号要比想象中简单得多。

实验步骤我就写得简洁一点了。

首先,我们需要不断上传文件到任意php脚本,这个文件最好还大一些。我用的是一个大概4mb的文件,似乎上传文件有大小限制,太大的文件我没有实验成功,也没有深究。然后就是写个程序不断上传这个文件就好,我用的是jquery+HTML。更理想一点应该是用python/java跑多线程。以下是源代码:

<html> <body>  <script src="https://code.jquery.com/jquery-1.10.2.js"></script>  <form id="form" action="http://192.168.174.128/Default.php" method="post" enctype="multipart/form-data"> <input type="file" name="test"> <input type="submit"> </form>  <script> $( "form" ).submit(function( event ) {     uploadFiles(event);    return; }); var files;  $('input[type=file]').on('change', prepareUpload);  function prepareUpload(event) {  files = event.target.files; } function uploadFiles(event) {  event.stopPropagation();      event.preventDefault();      var data = new FormData();     $.each(files, function(key, value)    {         data.append(key, value);     });  var i=0;  for(;i<1000;i++){   $.ajax({    url: 'http://192.168.174.128/Default.php',    type: 'POST',    data: data,    cache: false,    dataType: 'json',    processData: false,     contentType: false,     success: function(data, textStatus, jqXHR)    {     if(typeof data.error === 'undefined')     {      submitForm(event, data);     }     else     {      console.log('ERRORS: ' + data.error);     }    },    error: function(jqXHR, textStatus, errorThrown)    {     console.log('ERRORS: ' + textStatus);    }   });   //wait(100);  } } function wait(ms){    var start = new Date().getTime();    var end = start;    while(end < start + ms) {      end = new Date().getTime();  } } </script> </body> </html>

运行截图:

把PHP LFI漏洞变成Webshell的思路

运行后可以在服务器上看到如下结果:

把PHP LFI漏洞变成Webshell的思路

可以看到确实有临时文件创建了,而且也有文件描述符被创建,最重要的是文件描述符编号全部都是13,可能不同的机器上这个编号不同,但只要它是一个恒定的数字,那么就可以省去相当的多的猜解时间。比如只暴力猜测pid,运行多次,而每次只使用一个文件描述符编号就行。

接下来我就测试了一下是否能真的读到内容,我挑了3个pid,随便cat了几次,还真就成功了:

把PHP LFI漏洞变成Webshell的思路

有人会说猜测pid也是一件很费劲的事,毕竟文件描述符只存在于读写文件的时候,即使php脚本执行时间很长,文件描述符也不会一直存在。且即使几个apache进程会不断处理上传的文件,但短时间内猜测一千到一两万的数字还是很难中的。后来经过我不断探索,发现apache的第一个进程的pid是可以直接读到的。用LFI直接读/var/run/apache2/apache2.pid就行,这个文件是所有用户可读的:

把PHP LFI漏洞变成Webshell的思路

而apache创建的其他进程的pid会在这个编号上递增。而apache也就创建几个进程而已,所以猜测范围可能都不超过20。会有一两个进程的pid不按这个规律,我也不知道为啥,但并不碍事。

看到cat出内容的时候我那个激动啊,说明离成功只有一步之遥了,要是成功了就变成个大杀器了!然而当我通过浏览器进行暴力破解的时候怎么也不成功,我那个郁闷啊。后来看到错误日志的时候才彻底懵逼:

把PHP LFI漏洞变成Webshell的思路

。。。我不是专搞php的,后来经过学习才发现虽然apache会以root运行,但它会创建子进程以www-data来运行php。而文件描述符是属于服务器权限内也就是root的,所以www-data读不了。我当时那个沮丧啊。。。。这就是这个思路最终沦为鸡肋的原因。文件描述符应该只有owner可读,也就是说要么apache和php都配置为同一个用户运行,要么就是用别的服务器运行php了。毕竟根据老外文章中给的python例子,服务器的权限一般和应用程序的一样,tomcat+Java也是同权限,只有apache+php这个组合有点奇葩。

*本文原创作者:jfeiyi