diff --git a/base.py b/base.py index b10f138..a205e6b 100644 --- a/base.py +++ b/base.py @@ -23,6 +23,7 @@ from typing import Union, List client_encoding = 'utf-8' + def extract_credentials(url): pattern = re.compile(r'(?P\w+://)?(?P[^:/]+):(?P[^@]+)@(?P.+)') match = pattern.match(url) @@ -35,10 +36,12 @@ def extract_credentials(url): else: return None, None, url + def jsonrpc2_create_id(data): return hashlib.sha1(json.dumps(data).encode(client_encoding)).hexdigest() -def jsonrpc2_encode(method, params = None): + +def jsonrpc2_encode(method, params=None): data = { "jsonrpc": "2.0", "method": method, @@ -48,7 +51,8 @@ def jsonrpc2_encode(method, params = None): data['id'] = id return (id, json.dumps(data)) -def jsonrpc2_result_encode(result, id = ''): + +def jsonrpc2_result_encode(result, id=''): data = { "jsonrpc": "2.0", "result": result, @@ -56,7 +60,8 @@ def jsonrpc2_result_encode(result, id = ''): } return json.dumps(data) -def jsonrpc2_error_encode(error, id = ''): + +def jsonrpc2_error_encode(error, id=''): data = { "jsonrpc": "2.0", "error": error, @@ -64,6 +69,7 @@ def jsonrpc2_error_encode(error, id = ''): } return json.dumps(data) + class Extension(): extensions = [] protocols = [] @@ -124,14 +130,14 @@ class Extension(): return None @classmethod - def send_accept(cls, conn, method, success = True): + def send_accept(cls, conn, method, success=True): if 'tcp' in cls.protocols: _, message = jsonrpc2_encode(f"{method}_accept", { "success": success }) conn.send(message.encode(client_encoding)) - print (f"Accepted request with {cls.protocols[0]} protocol") + print(f"Accepted request with {cls.protocols[0]} protocol") @classmethod def readall(cls, conn): @@ -147,7 +153,7 @@ class Extension(): pass return data - + elif 'http' in cls.protocols: # empty binary when an file not exists if 'file' not in conn.request.files: @@ -156,7 +162,7 @@ class Extension(): # read an uploaded file with binary mode file = conn.request.files['file'] return file.read() - + def __init__(self): self.type = None self.method = None @@ -166,7 +172,7 @@ class Extension(): def test(self, filtered, data, webserver, port, scheme, method, url): raise NotImplementedError - def dispatch(self, type, id, params, method = None, conn = None): + def dispatch(self, type, id, params, method=None, conn=None): raise NotImplementedError def connect(self, conn, data, webserver, port, scheme, method, url): @@ -176,7 +182,7 @@ class Extension(): class Logger(logging.Logger): def __init__(self, name: str, level: int = logging.NOTSET): super().__init__(name, level) - self.formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + self.formatter = logging.Formatter('[%(asctime)s] %(levelname)s %(module)s: %(message)s') if not os.path.isdir("logs"): os.mkdir("logs") diff --git a/server.py b/server.py index 375f8ae..aca6dd5 100644 --- a/server.py +++ b/server.py @@ -76,6 +76,7 @@ auth = None if _username: auth = HTTPBasicAuth(_username, _password) + def parse_first_data(data): parsed_data = (b'', b'', b'', b'', b'') @@ -84,12 +85,12 @@ def parse_first_data(data): method, url = first_line.split()[0:2] - http_pos = url.find(b'://') #Finding the position of :// + http_pos = url.find(b'://') #Finding the position of :// scheme = b'http' # check http/https or other protocol if http_pos == -1: temp = url else: - temp = url[(http_pos+3):] + temp = url[(http_pos + 3):] scheme = url[0:http_pos] port_pos = temp.find(b':') @@ -103,7 +104,7 @@ def parse_first_data(data): port = 80 webserver = temp[:webserver_pos] else: - port = int((temp[(port_pos+1):])[:webserver_pos-port_pos-1]) + port = int((temp[(port_pos + 1):])[:webserver_pos - port_pos - 1]) webserver = temp[:port_pos] if port == 443: scheme = b'https' @@ -114,6 +115,7 @@ def parse_first_data(data): return parsed_data + def conn_string(conn, data, addr): # JSON-RPC 2.0 request def process_jsonrpc2(data): @@ -136,8 +138,8 @@ def conn_string(conn, data, addr): if path == "/proxy-cgi/jsonrpc2": conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n') pos = data.find(b'\r\n\r\n') - if pos > -1 and process_jsonrpc2(data[pos+4:]): - conn.close() # will be close by the server + if pos > -1 and process_jsonrpc2(data[pos + 4:]): + conn.close() # will be close by the server return # if it is reverse proxy @@ -151,6 +153,7 @@ def conn_string(conn, data, addr): proxy_server(webserver, port, scheme, method, url, conn, addr, data) + def jsonrpc2_server(conn, id, method, params): if method == "relay_accept": accepted_relay[id] = conn @@ -165,6 +168,7 @@ def jsonrpc2_server(conn, id, method, params): #return in conn_string() + def proxy_connect(webserver, conn): hostname = webserver.decode(client_encoding) certpath = "%s/%s.crt" % (certdir.rstrip('/'), hostname) @@ -177,7 +181,9 @@ def proxy_connect(webserver, conn): if not os.path.isfile(certpath): epoch = "%d" % (time.time() * 1000) p1 = Popen([openssl_binpath, "req", "-new", "-key", certkey, "-subj", "/CN=%s" % hostname], stdout=PIPE) - p2 = Popen([openssl_binpath, "x509", "-req", "-days", "3650", "-CA", cacert, "-CAkey", cakey, "-set_serial", epoch, "-out", certpath], stdin=p1.stdout, stderr=PIPE) + p2 = Popen( + [openssl_binpath, "x509", "-req", "-days", "3650", "-CA", cacert, "-CAkey", cakey, "-set_serial", epoch, + "-out", certpath], stdin=p1.stdout, stderr=PIPE) p2.communicate() except Exception as e: logger.error("[*] Skipped generating the certificate.", exc_info=e) @@ -193,6 +199,7 @@ def proxy_connect(webserver, conn): return (conn, data) + def proxy_check_filtered(data, webserver, port, scheme, method, url): filtered = False @@ -203,6 +210,7 @@ def proxy_check_filtered(data, webserver, port, scheme, method, url): return filtered + def proxy_server(webserver, port, scheme, method, url, conn, addr, data): try: logger.info("[*] Started the request. %s" % (str(addr[0]))) @@ -213,18 +221,19 @@ def proxy_server(webserver, port, scheme, method, url, conn, addr, data): while True: try: conn, data = proxy_connect(webserver, conn) - break # success + break # success #except OSError as e: # print ("[*] Retrying SSL negotiation... (%s:%s) %s" % (webserver.decode(client_encoding), str(port), str(e))) except Exception as e: - raise Exception("SSL negotiation failed. (%s:%s) %s" % (webserver.decode(client_encoding), str(port), str(e))) + raise Exception( + "SSL negotiation failed. (%s:%s) %s" % (webserver.decode(client_encoding), str(port), str(e))) # override data if is_ssl: _, _, _, method, url = parse_first_data(data) # https://stackoverflow.com/questions/44343739/python-sockets-ssl-eof-occurred-in-violation-of-protocol - def sock_close(sock, is_ssl = False): + def sock_close(sock, is_ssl=False): #if is_ssl: # sock = sock.unwrap() #sock.shutdown(socket.SHUT_RDWR) @@ -253,8 +262,8 @@ def proxy_server(webserver, port, scheme, method, url, conn, addr, data): sock_close(sock, is_ssl) raise Exception("Filtered request") sock.send(chunk) - if len(buffered) > buffer_size*2: - buffered = buffered[-buffer_size*2:] + if len(buffered) > buffer_size * 2: + buffered = buffered[-buffer_size * 2:] except: break @@ -292,8 +301,8 @@ def proxy_server(webserver, port, scheme, method, url, conn, addr, data): add_filtered_host(webserver.decode(client_encoding), '127.0.0.1') raise Exception("Filtered response") conn.send(chunk) - if len(buffered) > buffer_size*2: - buffered = buffered[-buffer_size*2:] + if len(buffered) > buffer_size * 2: + buffered = buffered[-buffer_size * 2:] i += 1 # when blocked @@ -319,7 +328,8 @@ def proxy_server(webserver, port, scheme, method, url, conn, addr, data): if is_ssl and method == b'GET': logger.info("[*] Trying to bypass blocked request...") - remote_url = "%s://%s%s" % (scheme.decode(client_encoding), webserver.decode(client_encoding), url.decode(client_encoding)) + remote_url = "%s://%s%s" % ( + scheme.decode(client_encoding), webserver.decode(client_encoding), url.decode(client_encoding)) requests.get(remote_url, stream=True, verify=False, hooks={'response': bypass_callback}) else: conn.sendall(b"HTTP/1.1 403 Forbidden\r\n\r\n{\"status\":403}") @@ -351,7 +361,8 @@ def proxy_server(webserver, port, scheme, method, url, conn, addr, data): while len(resolved_address_list) == 0: try: _, query_data = jsonrpc2_encode('get_client_address') - query = requests.post(server_url, headers=proxy_data['headers'], data=query_data, timeout=1, auth=auth) + query = requests.post(server_url, headers=proxy_data['headers'], data=query_data, timeout=1, + auth=auth) if query.status_code == 200: result = query.json()['result'] resolved_address_list.append(result['data']) @@ -364,14 +375,17 @@ def proxy_server(webserver, port, scheme, method, url, conn, addr, data): def relay_connect(id, raw_data, proxy_data): try: # The tunnel connect forever until the client destroy it - relay = requests.post(server_url, headers=proxy_data['headers'], data=raw_data, stream=True, timeout=None, auth=auth) + relay = requests.post(server_url, headers=proxy_data['headers'], data=raw_data, stream=True, + timeout=None, auth=auth) for chunk in relay.iter_content(chunk_size=buffer_size): jsondata = json.loads(chunk.decode(client_encoding, errors='ignore')) if jsondata['jsonrpc'] == "2.0" and ("error" in jsondata): e = jsondata['error'] - logger.error("[*] Error received from the relay server: (%s) %s" % (str(e['code']), str(e['message']))) + logger.error("[*] Error received from the relay server: (%s) %s" % ( + str(e['code']), str(e['message']))) except requests.exceptions.ReadTimeout as e: pass + id, raw_data = jsonrpc2_encode('relay_connect', proxy_data['data']) start_new_thread(relay_connect, (id, raw_data, proxy_data)) @@ -405,8 +419,8 @@ def proxy_server(webserver, port, scheme, method, url, conn, addr, data): add_filtered_host(webserver.decode(client_encoding), '127.0.0.1') raise Exception("Filtered response") conn.send(chunk) - if len(buffered) > buffer_size*2: - buffered = buffered[-buffer_size*2:] + if len(buffered) > buffer_size * 2: + buffered = buffered[-buffer_size * 2:] i += 1 sock_close(sock, is_ssl) @@ -445,8 +459,8 @@ def proxy_server(webserver, port, scheme, method, url, conn, addr, data): add_filtered_host(webserver.decode(client_encoding), '127.0.0.1') raise Exception("Filtered response") conn.send(chunk) - if len(buffered) > buffer_size*2: - buffered = buffered[-buffer_size*2:] + if len(buffered) > buffer_size * 2: + buffered = buffered[-buffer_size * 2:] i += 1 logger.info("[*] Received %s chunks. (%s bytes per chunk)" % (str(i), str(buffer_size))) @@ -467,6 +481,7 @@ def proxy_server(webserver, port, scheme, method, url, conn, addr, data): conn.sendall(b"HTTP/1.1 403 Forbidden\r\n\r\n{\"status\":403}") conn.close() + # journaling a filtered hosts def add_filtered_host(domain, ip_address): hosts_path = './filtered.hosts' @@ -479,7 +494,8 @@ def add_filtered_host(domain, ip_address): with open(hosts_path, 'w') as file: file.writelines(lines) -def start(): #Main Program + +def start(): #Main Program try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('', listening_port)) @@ -491,15 +507,16 @@ def start(): #Main Program while True: try: - conn, addr = sock.accept() #Accept connection from client browser - data = conn.recv(buffer_size) #Recieve client data - start_new_thread(conn_string, (conn, data, addr)) #Starting a thread + conn, addr = sock.accept() #Accept connection from client browser + data = conn.recv(buffer_size) #Recieve client data + start_new_thread(conn_string, (conn, data, addr)) #Starting a thread except KeyboardInterrupt: sock.close() logger.info("[*] Graceful Shutdown") sys.exit(1) -if __name__== "__main__": + +if __name__ == "__main__": # load extensions for s in use_extensions.split(','): Extension.register(s) diff --git a/web.py b/web.py index 5458503..46fd801 100644 --- a/web.py +++ b/web.py @@ -10,30 +10,25 @@ # Updated at: 2024-07-10 # -from flask import Flask, request, redirect, url_for, render_template import os import sys -import json -import importlib - -import hashlib from decouple import config - -from base import Extension, jsonrpc2_create_id, jsonrpc2_result_encode, jsonrpc2_error_encode, Logger +from flask import Flask, request, render_template +from base import Extension, jsonrpc2_error_encode, Logger # TODO: 나중에 Flask 커스텀 핸들러 구현 해야 함 logger = Logger(name="web") app = Flask(__name__) app.config['UPLOAD_FOLDER'] = 'data/' - - if not os.path.exists(app.config['UPLOAD_FOLDER']): os.makedirs(app.config['UPLOAD_FOLDER']) + @app.route('/') def upload_form(): return render_template('upload.html') + @app.route('/upload', methods=['POST']) def process_upload(): # make connection profile from Flask request @@ -45,10 +40,11 @@ def process_upload(): params = { 'filename': filename } - + # just do it return Extension.dispatch_rpcmethod(method, 'call', '', params, conn) + @app.route('/jsonrpc2', methods=['POST']) def process_jsonrpc2(): # make connection profile from Flask request @@ -64,9 +60,11 @@ def process_jsonrpc2(): 'message': "Not vaild JSON-RPC 2.0 request" }) + def jsonrpc2_server(conn, id, method, params): return Extension.dispatch_rpcmethod(method, "call", id, params, conn) + class Connection(): def send(self, data): self.messages.append(data) @@ -81,11 +79,13 @@ class Connection(): self.messages = [] self.request = req + if __name__ == "__main__": - # initalization + # initialization try: listening_port = config('PORT', default=5555, cast=int) client_encoding = config('CLIENT_ENCODING', default='utf-8') + use_extensions = config('USE_EXTENSIONS', default='') except KeyboardInterrupt: logger.warning("[*] User has requested an interrupt") logger.warning("[*] Application Exiting.....") @@ -96,8 +96,12 @@ if __name__ == "__main__": # set environment of Extension Extension.set_protocol('http') - # load extensions - for s in use_extensions.split(','): - Extension.register(s) + # Fix Value error + if use_extensions: + # load extensions + for s in use_extensions.split(','): + Extension.register(s) + else: + logger.warning("[*] No extensions registered") app.run(debug=True, host='0.0.0.0', port=listening_port)