0ctf-ezdoor-復現分析

在學習opcache的時候,看到了這個題目,恰好有環境,就來複現一下,這個題目也讓我學到了不少。php

建立鏡像:html

docker build -t 0ctf-ezdoor .
git

啓動容器:github

docker run -itd -p 9010:80 --name 0ctf-ezdoor 0ctf-ezdoorweb

源碼以下:docker

<?php

error_reporting(0);

$dir = 'sandbox/' . sha1($_SERVER['REMOTE_ADDR']) . '/';  //建立一個用戶沙盒
if(!file_exists($dir)){  
  mkdir($dir);
}  //每次訪問頁面不存在該目錄時都將從新建立,
if(!file_exists($dir . "index.php")){
  touch($dir . "index.php"); //若是index.php不存在,則直接用touch建立
} 
function clear($dir) { if(!is_dir($dir)){ unlink($dir); return; } //若是不是目錄,則直接刪除 foreach (scandir($dir) as $file) { //若是是目錄,則刪除該目錄下的全部文件 if (in_array($file, [".", ".."])) { continue; } unlink($dir . $file); } rmdir($dir); //而後刪除目錄 } switch ($_GET["action"] ?? "") { case 'pwd': echo $dir; //顯示沙盒路徑 break; case 'phpinfo': echo file_get_contents("phpinfo.txt"); //顯示phpinfo信息 break; case 'reset': clear($dir); break; case 'time': echo time(); break; case 'upload': if (!isset($_GET["name"]) || !isset($_FILES['file'])) { break; } if($_FILES['file']['size'] > 100000) { clear($dir); break; } $name = $dir . $_GET["name"]; if (preg_match("/[^a-zA-Z0-9.\/]/", $name) || stristr(pathinfo($name)["extension"], "h")) { //取文件的後綴而且過濾了h,
則全部php後綴都不能上傳後面的stristr(pathinfo)是用來判斷以「.」隔斷後的字符串中是否含有「h」字符,在這裏pathinfo是以字符串中最後一個「.」來進行隔斷。
break; } move_uploaded_file($_FILES['file']['tmp_name'], $name); $size = 0; foreach (scandir($dir) as $file) { if (in_array($file, [".", ".."])) { continue; } $size += filesize($dir . $file); } if ($size > 100000) { clear($dir); } break; case 'shell': ini_set("open_basedir", "/var/www/html/$dir:/var/www/html/flag"); include $dir . "index.php"; break; default: highlight_file(__FILE__); break; }

最終包含的是$dir."index.php",而且沒法上傳php後綴,最後有include,那麼應該是包含shell,因此咱們若是可以經過上傳覆蓋index.php,就可以getshell,而後根據給出的flag路徑去讀flagshell

此時首先讀一下phpinfo,這個是個txt的phpinfo信息,而且裏面刪除了一些配置項, 在裏面發現opcache是開啓的,apache

預期解法:

opcache突破口

A網站的網頁index.php具備緩存文件index.php.bin
而訪問index.php的時候加載緩存index.php.bin
假若這時候具備上傳,咱們能夠覆蓋index.php.bin
是否是就會加載咱們的惡意文件了呢?
題目中雖然過濾php類型的結尾,可是卻未過濾bin的結尾緩存

 

經過opcache.file_cache能夠看到opcache的存儲路徑信息在/tmp/cache下bash

執行docker exec -it bash_name bash 進入docker容器發現實際上目錄是cache/systemid,就是每一個用戶都會有一個id,來鑑別的

因此咱們的目的很明確,就是去覆蓋此index.php.bin來上傳咱們本身的index.php.bin,那麼當再次訪問index.php.bin時實際上就是訪問的咱們的惡意index.php文件

因此首先要知道服務器緩存文件的目錄,計算一下服務器端的systemid,利用https://github.com/GoSecure/php7-opcache-override,由於代碼須要指定一個phpinfo的頁面,可是其最終解析出來進行計算的一些配置項纔是最重要的,所以找到目標服務器的這些配置項

 

php_version=7.0.28

zend_extension_id=API320151012,NTS

zend_bin_id由這兩部分組成

即zend_bin_id=BIN_SIZEOF_CHAR48888

接下來利用公式計算一下就能獲得:

systemid=7badddeddbd076fe8352e80d8ddf3e73

 可是這個systemid計算出來的跟我在docker容器裏面看到的名字不同,這裏查看一下php的版本,題目給的是7.0.28,可是此時我復現的時候從hub庫拖過來的php7版本是7.0.33,所以這裏計算systemid時要和題目的php版本一致,這裏把php的版本改爲7.0.33計算一下就好了,獲得system_id爲0b8bd94e9858e5d32d058dc0acf75014

 

和我docker是相符的,說明沒問題,此時已經獲得了服務器端的opcache路徑,那麼下一步就是經過上傳去覆蓋此index.php.bin

opcache文件生成

首先要在本地搭一個根目標服務器同樣的環境,因此pull一個環境下來:

sudo docker pull php:7.0.33-apache

而後經過鏡像建立容器:

docker run -itd -p 9010:80 --name php:7.0.33-apache opcache

此時容器已經起來了,進入配置與服務器相同的路徑

docker exec -it opcache bash

由於咱們的index.php在用戶的沙盒中,所以可使用pwd先看看路徑

 

能夠看到路徑爲sandbox/fac849dc498b60000e200f3f2a2712b54da39b92/,因此首先新建一個文件夾吧,而後開一個index.php,先看看能不能成功

此時文件生成了,須要配置一下php.ini的opcache

直接從docker hub拉來的鏡像我發現裏面沒有加載php.ini,可是看到加載php.ini的路徑爲/usr/loca/etc/php,因此去這個目錄看看,

應該是給了兩個ini,選定一個改名爲php.ini讓apache去加載,因此cp拷貝一份就好了,我配置了以下選項:

zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20151012/opcache.so //默認存在該擴展,只要把so文件引進來便可
opcache.file_cache => /tmp/cache => /tmp/cache opcache.enable => On => On opcache.validate_timestamps => On => On opcache.file_cache_only => 1 => 1

而後重啓一下apache,這裏不能用service apache2 restart,容器會斷掉,由於容器至關於一個cmd環境,重啓本身會斷,這裏用reload從新加載一下配置文件,此時刷新phpinfo看看

 

 

opcache擴展也打開了,訪問咱們模擬環境的index.php試試生成bin文件:

 

此時,由於目標服務器timestamps爲on,所以咱們不只要置換bin裏面的systemid還要置換一下timestamps

運行如下代碼就能得到最新的文件時間戳:

import requests
print requests.get('http://127.0.0.1:8585/index.php?action=time').content
print requests.get('http://127.0.0.1:8585/index.php?action=reset').content
print requests.get('http://127.0.0.1:8585/index.php?action=time').content

 

 

此時只要用010修改一些systemid和timestamps,直接使用https://github.com/GoSecure/php7-opcache-override中提供的模板文件來幫助咱們解析bin文件,此時就能看到要修改的兩個字段

 修改之後保存,而後本地構造上傳表單,進行上傳,由於咱們想要覆蓋目標服務器的bin文件,那麼路徑必須與其相同才行,這裏直接將$_GET['name']與沙盒路徑拼接在了一塊兒,沒有對變量進行過濾

因此此時肯定路徑能夠進行路徑穿越,能夠穿越到任意目錄,因此能夠直接經過systemid來構造路徑爲:

../../../../../tmp/cache/0b8bd94e9858e5d32d058dc0acf75014/var/www/html/sandbox/fac849dc498b60000e200f3f2a2712b54da39b92/index.php.bin
<html>
<body>
 <form action="http://127.0.0.1:8585/?action=upload&name=../../../../../tmp/cache/0b8bd94e9858e5d32d058dc0acf75014/var/www/html/sandbox/fac849dc498b60000e200f3f2a2712b54da39b92/index.php.bin" method="post" enctype="multipart/form-data">
 <input type="file" name="file" />
 <input type="submit" value="upload" />
 </form>
</body>
</html>

而後上傳咱們的bin文件,此時再訪問action=shell,來觸發index.php加載咱們bin文件

 

由上圖能夠看到此時已經成功加載了咱們的bin文件,咱們繼續讀一下/var/www/html和/var/www/html/flag目錄,能夠看到服務器作了限制,只能讀到flag目錄有個奇怪的文件,能夠讀一讀它

 

 接下來讀一下這個文件

將其base64解碼之後存到本地的flag.php.bin文件中,拖到010進行分析,發現解析出來其systemid出現了錯誤,對比一下正常的bin文件發現其頭部少了一個字節00

因此在其magic頭部補充一個字節就好了,此時systemid還原正常,其它字段的值也正常了,接下來就要讓bin文件還原成咱們能夠閱讀的代碼或者語言,作到這web部分結束,暫時不往下看了==

非預期解: 

1.經過條件競爭

由於pathinfo會獲取最後一個點以後的擴展,經過index.php/. 就能夠繞過pathinfo,可是move_uploaded_file這個函數在調用stat檢測到index.php存在時就不會進行覆蓋,也就是咱們可以上傳可是卻不能

進行覆蓋,可是這裏關注這一段代碼

function clear($dir)
{
  if(!is_dir($dir)){
    unlink($dir);
    return;
  } //若是不是目錄,則直接刪除
  foreach (scandir($dir) as $file) {  //若是是目錄,則刪除該目錄下的全部文件
    if (in_array($file, [".", ".."])) {
      continue;
    }
    unlink($dir . $file);
  }
  rmdir($dir); //而後刪除目錄
}

刪除時,先刪除目錄中的文件,再刪除目錄,那麼咱們知道若是目錄裏面有文件調用rmdir將沒法刪除,因此咱們能夠給要刪除的目錄傳遞大量沒用的文件,那麼在還在scandir的for循環結束時,原有的正常的index.php

也被刪除了,此時沙盒中有還有無用文件,不能刪除此沙盒目錄,所以咱們能夠再上傳本身的index.php,而後以此來getshell

2.繞過php底層文件操做函數

x/../index.php/. 直接將路徑修改成該路徑,就能夠覆蓋原來的index.php,由於首先php調用tsrm_realpath去掉/.將其轉換爲一個標準路徑,而後調用lstst獲取文件屬性,也就是判斷文件存不存在,不存在將寫文件,x/../index.php將繞過lstat讓其認爲index.php不存在因此從新寫入,因此能夠getshell

參考:

 https://www.kingkk.com/2018/04/2018-0ctf-ezdoor%E5%88%86%E6%9E%90/#%E5%8F%A6%E4%B8%80%E7%A7%8D%E9%AA%9A%E6%93%8D%E4%BD%9C

https://www.cdxy.me/?p=790

http://pupiles.com/%E7%94%B1%E4%B8%80%E9%81%93ctf%E9%A2%98%E5%BC%95%E5%8F%91%E7%9A%84%E6%80%9D%E8%80%83.html

https://lorexxar.cn/2016/05/27/opcache-jcfx/

http://wonderkun.cc/index.html/?p=626

https://skysec.top/2018/04/11/0ctf-ezdoor/#%E9%A2%84%E6%9C%9F%E8%A7%A3

http://elssm.top/2018/05/04/2018-0ctf-ezdoor%E5%A4%8D%E7%8E%B0/

https://altman.vip/2018/10/10/0ctf-Ezdoor/#%E6%89%A7%E8%A1%8C%E5%91%BD%E4%BB%A4

https://www.angelwhu.com/blog/?p=438

相關文章
相關標籤/搜索