深信服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让我玩就更好了。

posted @ 2020-02-18 15:20  宽字节安全  阅读(2713)  评论(1编辑  收藏