SCF SCF全称(Serverless Cloud Function)即云函数,是腾讯云为企业和开发者们提供的无服务器执行环境,帮助您在无需购买和管理服务器的情况下运行代码。 您只需使用平台支持的语言编写核心代码并设置代码运行的条件,即可在腾讯云基础设施上弹性、安全地运行代码。
如何开发基于云函数的代理扫描? 首先需要明白通过云函数扫描的原理即我们本地的扫描器通过云函数代理转发HTTP请求(事实上云函数上也是可以直接run一些轻型扫描器)。将我们本地扫描器的请求通过MITMPROXY转发给云端函数由它发起真正的请求即可实现 本地 -> 云 -> 服务器
这一过程。
安装 & 使用 Mitmproxy
通过pip可以直接安装mitmproxy并通过如下命令启动:
开启mitmproxy后你可以设置监听的端口为HTTP代理并安装证书(否则你将无法转发HTTPS流量)
接下来我们需要编写一个代理客户端脚本,不熟悉mitmproxy脚本开发的师傅可移步官方文档及Github示例 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 from base64 import b64encode, b64decodefrom urllib.parse import urlparsefrom mitmproxy import ctxfrom random import choiceimport mitmproxy.httpimport jsonTOKEN = 'zz123000' SERVER_URL = [] class Proxy : def __init__ (self) : pass def request (self, flow : mitmproxy.http.HTTPFlow) : request = flow.request datas = { 'method' : request.method, 'url' : request.pretty_url, 'headers' : dict(request.headers), 'params' : dict(request.query), 'cookies' : dict(request.cookies), 'data' : b64encode(request.raw_content).decode() } PROXY_URL = choice(SERVER_URL) flow.request = flow.request.make( method = 'POST' , url = PROXY_URL, content = json.dumps(datas), headers = { 'Host' : urlparse(PROXY_URL).netloc, 'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36' , 'Token' : TOKEN } ) def response (self, flow : mitmproxy.http.HTTPFlow) : try : if flow.response.status_code == 200 : datas = flow.response.content datas = json.loads(b64decode(datas).decode()) res = flow.response.make( status_code = datas['status_code' ], content = datas['content' ], headers = datas['headers' ] ) flow.response = res else : ctx.log.info(flow.response.status_code) except Exception as e: print(e) addons = [ Proxy() ]
如上就是使用SCF代理所需的客户端脚本, 将请求数据包的内容重新封装后发往SCF即可,接着我们需要编写SCF服务端脚本, 来解析数据内容并重新封装请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 from base64 import b64decode, b64encodeimport requestsimport jsonSCF_TOKEN = "zz123000" def authorization () : return { "isBase64Encoded" : False , "statusCode" : 401 , "headers" : {}, "body" : "Please provide correct Token" , } def main_handler (event : dict, context : dict) : try : token = event['headers' ]['Token' ] if token != SCF_TOKEN: return authorization() except KeyError: return authorization() data = event["body" ] data = json.loads(data) data['data' ] = b64decode(data['data' ]).decode() data.pop(list(data.keys())[-1 ]) try : res = requests.request(**data, verify=False ) text = { 'status_code' : res.status_code, 'headers' : dict(res.headers), 'content' : res.text } return { 'isBase64Encoded' : False , 'statusCode' : 200 , 'headers' : {}, 'body' : b64encode(json.dumps(text).encode('utf-8' )).decode() } except (requests.exceptions.ConnectionError, Exception) as err: error = str(err) return { 'isBase64Encoded' : False , 'statusCode' : 408 , 'headers' : {}, 'body' : b64encode(json.dumps(error).encode('utf-8' )).decode() }
在编写SCF脚本时需要注意, 首先需要从请求体中取出封装好的数据包, 将请求体的数据进行base64解码并转为字符串, 然后使用requests模块的request方法将 data 变量作为关键字参数传入。
踩坑
在将本地封装的请求数据转发至SCF时发现 mitmproxy
自动在请求体中添加了一个随机参数, 暂时不知道是什么原因, 期待解答, 这里简单粗暴的做法就是在SCF中将最后一个参数移除。
实现效果
参考 SCFProxy