深信服vpnweb登录逆向学习
深信服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让我玩就更好了。