mirror of
https://github.com/gnh1201/caterpillar.git
synced 2025-09-08 02:41:10 +00:00
commit
97f4f42593
|
@ -33,6 +33,7 @@ If you have an ***will be parasitize*** server that you want to proxy, you can i
|
||||||
[settings]
|
[settings]
|
||||||
PORT=5555
|
PORT=5555
|
||||||
SERVER_URL=http://example.org
|
SERVER_URL=http://example.org
|
||||||
|
SERVER_CONNECTION_TYPE=stateful
|
||||||
CA_KEY=ca.key
|
CA_KEY=ca.key
|
||||||
CA_CERT=ca.crt
|
CA_CERT=ca.crt
|
||||||
CERT_KEY=cert.key
|
CERT_KEY=cert.key
|
||||||
|
|
197
index.php
197
index.php
|
@ -1,11 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
// Caterpillar - The simple and parasitic web proxy with spam filter
|
// Caterpillar - The simple and parasitic web proxy with spam filter
|
||||||
// Namhyeon Go <abuse@catswords.net>
|
// Namhyeon Go (Catswords Research) <abuse@catswords.net>
|
||||||
// https://github.com/gnh1201/caterpillar
|
// https://github.com/gnh1201/caterpillar
|
||||||
// Created at: 2022-10-06
|
// Created at: 2022-10-06
|
||||||
// Updated at: 2024-02-20
|
// Updated at: 2024-02-26
|
||||||
|
|
||||||
define("PHP_HTTPPROXY_VERSION", "0.1.4");
|
define("PHP_HTTPPROXY_VERSION", "0.1.5");
|
||||||
|
|
||||||
if (strpos($_SERVER['HTTP_USER_AGENT'], "php-httpproxy/") !== 0) {
|
if (strpos($_SERVER['HTTP_USER_AGENT'], "php-httpproxy/") !== 0) {
|
||||||
exit('<!DOCTYPE html><html><head><title>It works!</title><meta charset="utf-8"></head><body><h1>It works!</h1><p><a href="https://github.com/gnh1201/caterpillar">Download the client</a></p><hr><p>php-httpproxy/' . PHP_HTTPPROXY_VERSION . ' (Server; PHP ' . phpversion() . '; abuse@catswords.net)</p></body></html>');
|
exit('<!DOCTYPE html><html><head><title>It works!</title><meta charset="utf-8"></head><body><h1>It works!</h1><p><a href="https://github.com/gnh1201/caterpillar">Download the client</a></p><hr><p>php-httpproxy/' . PHP_HTTPPROXY_VERSION . ' (Server; PHP ' . phpversion() . '; abuse@catswords.net)</p></body></html>');
|
||||||
|
@ -14,6 +14,34 @@ if (strpos($_SERVER['HTTP_USER_AGENT'], "php-httpproxy/") !== 0) {
|
||||||
ini_set("default_socket_timeout", 1); // must be. because of `feof()` works
|
ini_set("default_socket_timeout", 1); // must be. because of `feof()` works
|
||||||
ini_set("max_execution_time", 0);
|
ini_set("max_execution_time", 0);
|
||||||
|
|
||||||
|
function jsonrpc2_encode($method, $params, $id = '') {
|
||||||
|
$data = array(
|
||||||
|
"jsonrpc" => "2.0",
|
||||||
|
"method" => $method,
|
||||||
|
"params" => $params,
|
||||||
|
"id" => $id
|
||||||
|
);
|
||||||
|
return json_encode($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function jsonrpc2_result_encode($result, $id = '') {
|
||||||
|
$data = array(
|
||||||
|
"jsonrpc" => "2.0",
|
||||||
|
"result" => $result,
|
||||||
|
"id" => $id
|
||||||
|
);
|
||||||
|
return json_encode($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function jsonrpc2_error_encode($error, $id = '') {
|
||||||
|
$data = array(
|
||||||
|
"jsonrpc" => "2.0",
|
||||||
|
"error" => $error,
|
||||||
|
"id" => $id
|
||||||
|
);
|
||||||
|
return json_encode($data);
|
||||||
|
}
|
||||||
|
|
||||||
function parse_headers($str) { // Parses HTTP headers into an array
|
function parse_headers($str) { // Parses HTTP headers into an array
|
||||||
// https://stackoverflow.com/questions/16934409/curl-as-proxy-deal-with-https-connect-method
|
// https://stackoverflow.com/questions/16934409/curl-as-proxy-deal-with-https-connect-method
|
||||||
// https://stackoverflow.com/questions/12433958/how-to-parse-response-headers-in-php
|
// https://stackoverflow.com/questions/12433958/how-to-parse-response-headers-in-php
|
||||||
|
@ -32,39 +60,150 @@ function parse_headers($str) { // Parses HTTP headers into an array
|
||||||
return $headers;
|
return $headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
function read_from_remote_server($remote_address, $remote_port, $scheme, $data = null, $conn = null, $buffer_size = 8192, $id = '') {
|
||||||
|
if (in_array($scheme, array("https", "ssl", "tls"))) {
|
||||||
|
$remote_address = "tls://" . $remote_address;
|
||||||
|
}
|
||||||
|
|
||||||
$buffer_size = $data['chunksize'];
|
$sock = fsockopen($remote_address, $remote_port, $error_code, $error_message, 1);
|
||||||
|
if (!$sock) {
|
||||||
|
$error = array(
|
||||||
|
"status" => 502,
|
||||||
|
"code" => $error_code,
|
||||||
|
"message" => $error_message
|
||||||
|
);
|
||||||
|
|
||||||
$relay_data = base64_decode($data['data']);
|
if ($conn == null) {
|
||||||
$relay_headers = parse_headers($relay_data);
|
echo jsonrpc2_error_encode($error, $id);
|
||||||
$relay_port = intval($data['port']);
|
|
||||||
$relay_scheme = $data['scheme'];
|
|
||||||
$relay_hostname = $data['server'];
|
|
||||||
|
|
||||||
if ($relay_scheme == "https") {
|
|
||||||
$relay_hostname = "tls://" . $relay_hostname;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ($relay_headers['@method'][0]) {
|
|
||||||
case "CONNECT":
|
|
||||||
echo sprintf("%s 200 Connection Established\r\n\r\n", $relay_headers['@method'][2]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
$fp = fsockopen($relay_hostname, $relay_port, $errno, $errstr, 1);
|
|
||||||
|
|
||||||
if (!$fp) {
|
|
||||||
echo "$errstr ($errno)<br />\n";
|
|
||||||
} else {
|
} else {
|
||||||
fwrite($fp, $relay_data);
|
$buf = sprintf("HTTP/1.1 502 Bad Gateway\r\n\r\n");
|
||||||
|
$buf .= jsonrpc2_error_encode($error, $id);
|
||||||
|
fwrite($conn, $buf);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($conn == null) {
|
||||||
|
// send data
|
||||||
|
fwrite($sock, $data);
|
||||||
|
|
||||||
|
// receive data
|
||||||
$buf = null;
|
$buf = null;
|
||||||
while (!feof($fp) && $buf !== false) {
|
while (!feof($sock) && $buf !== false) {
|
||||||
$buf = fgets($fp, $buffer_size);
|
$buf = fgets($sock, $buffer_size);
|
||||||
echo $buf;
|
echo $buf;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// send data
|
||||||
|
$buf = null;
|
||||||
|
while (!feof($conn) && $buf !== false) {
|
||||||
|
$buf = fgets($conn, $buffer_size);
|
||||||
|
fwrite($sock, $buf);
|
||||||
|
}
|
||||||
|
|
||||||
fclose($fp);
|
// receive data
|
||||||
|
$buf = null;
|
||||||
|
while (!feof($sock) && $buf !== false) {
|
||||||
|
$buf = fgets($sock, $buffer_size);
|
||||||
|
fwrite($conn, $buf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fclose($sock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateless mode
|
||||||
|
function relay_request($params, $id = '') {
|
||||||
|
$buffer_size = $params['buffer_size'];
|
||||||
|
$request_data = base64_decode($params['request_data']);
|
||||||
|
$request_header = parse_headers($request_data);
|
||||||
|
$request_length = intval($params['request_length']);
|
||||||
|
$client_address = $params['client_address'];
|
||||||
|
$client_port = intval($params['client_port']);
|
||||||
|
$client_encoding = $params['client_encoding'];
|
||||||
|
$remote_address = $params['remote_address'];
|
||||||
|
$remote_port = intval($params['remote_port']);
|
||||||
|
$scheme = $params['scheme'];
|
||||||
|
$datetime = $params['datetime']; // format: %Y-%m-%d %H:%M:%S.%f
|
||||||
|
|
||||||
|
if (in_array($scheme, array("https", "ssl", "tls"))) {
|
||||||
|
$remote_address = "tls://" . $remote_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($request_header['@method'][0]) {
|
||||||
|
case "CONNECT":
|
||||||
|
$error = array(
|
||||||
|
"status" => 405,
|
||||||
|
"code" => -1,
|
||||||
|
"message" => "Method Not Allowed"
|
||||||
|
);
|
||||||
|
echo jsonrpc2_error_encode($error, $id);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
read_from_remote_server($remote_address, $remote_port, $scheme, $request_data, null, $buffer_size, $id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateful mode
|
||||||
|
function relay_connect($params, $id = '') {
|
||||||
|
$buffer_size = $params['buffer_size'];
|
||||||
|
$client_address = $params['client_address'];
|
||||||
|
$client_port = intval($params['client_port']);
|
||||||
|
$client_encoding = $params['client_encoding'];
|
||||||
|
$remote_address = $params['remote_address'];
|
||||||
|
$remote_port = intval($params['remote_port']);
|
||||||
|
$scheme = $params['scheme'];
|
||||||
|
$datetime = $params['datetime']; // format: %Y-%m-%d %H:%M:%S.%f
|
||||||
|
|
||||||
|
$conn = fsockopen($client_address, $client_port, $error_code, $error_message, 1);
|
||||||
|
if (!$conn) {
|
||||||
|
$error = array(
|
||||||
|
"status" => 502,
|
||||||
|
"code" => $error_code,
|
||||||
|
"message" => $error_message,
|
||||||
|
"_params" => $params
|
||||||
|
);
|
||||||
|
echo jsonrpc2_error_encode($error, $id);
|
||||||
|
} else {
|
||||||
|
$data = jsonrpc2_encode("relay_accept", array(
|
||||||
|
"success" => true
|
||||||
|
), $id);
|
||||||
|
fwrite($conn, $data . "\r\n\r\n");
|
||||||
|
|
||||||
|
read_from_remote_server($remote_address, $remote_port, $scheme, null, $conn, $buffer_size, $id);
|
||||||
|
fclose($conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_client_address() {
|
||||||
|
$client_address = '';
|
||||||
|
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
|
||||||
|
$client_address = $_SERVER['HTTP_CLIENT_IP'];
|
||||||
|
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||||
|
$client_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
|
||||||
|
} else {
|
||||||
|
$client_address = $_SERVER['REMOTE_ADDR'];
|
||||||
|
}
|
||||||
|
return array("client_address" => $client_address);
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse context
|
||||||
|
$context = json_decode(file_get_contents('php://input'), true);
|
||||||
|
|
||||||
|
// check is it jsonrpc
|
||||||
|
if ($context['jsonrpc'] == "2.0") {
|
||||||
|
$method = $context['method'];
|
||||||
|
switch ($method) {
|
||||||
|
case "relay_request":
|
||||||
|
relay_request($context['params'], $context['id']); // stateless mode
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "relay_connect":
|
||||||
|
relay_connect($context['params'], $context['id']); // stateful mode
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "get_client_address":
|
||||||
|
echo jsonrpc2_result_encode(get_client_address(), $context['id']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
129
server.py
129
server.py
|
@ -2,7 +2,7 @@
|
||||||
# Namyheon Go (Catswords Research) <gnh1201@gmail.com>
|
# Namyheon Go (Catswords Research) <gnh1201@gmail.com>
|
||||||
# https://github.com/gnh1201/caterpillar
|
# https://github.com/gnh1201/caterpillar
|
||||||
# Created at: 2022-10-06
|
# Created at: 2022-10-06
|
||||||
# Updated at: 2024-12-24
|
# Updated at: 2024-12-28
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import socket
|
import socket
|
||||||
|
@ -30,6 +30,7 @@ from decouple import config
|
||||||
try:
|
try:
|
||||||
listening_port = config('PORT', cast=int)
|
listening_port = config('PORT', cast=int)
|
||||||
server_url = config('SERVER_URL')
|
server_url = config('SERVER_URL')
|
||||||
|
server_connection_type = config('SERVER_CONNECTION_TYPE')
|
||||||
cakey = config('CA_KEY')
|
cakey = config('CA_KEY')
|
||||||
cacert = config('CA_CERT')
|
cacert = config('CA_CERT')
|
||||||
certkey = config('CERT_KEY')
|
certkey = config('CERT_KEY')
|
||||||
|
@ -56,6 +57,7 @@ parser.add_argument('--buffer_size', help="Number of samples to be used", defaul
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
max_connection = args.max_conn
|
max_connection = args.max_conn
|
||||||
buffer_size = args.buffer_size
|
buffer_size = args.buffer_size
|
||||||
|
accepted_relay = {}
|
||||||
|
|
||||||
# https://stackoverflow.com/questions/25475906/set-ulimit-c-from-outside-shell
|
# https://stackoverflow.com/questions/25475906/set-ulimit-c-from-outside-shell
|
||||||
resource.setrlimit(
|
resource.setrlimit(
|
||||||
|
@ -92,6 +94,27 @@ def start(): #Main Program
|
||||||
print("\n[*] Graceful Shutdown")
|
print("\n[*] Graceful Shutdown")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
def jsonrpc2_create_id(data):
|
||||||
|
return hashlib.sha1(json.dumps(data).encode(client_encoding)).hexdigest()
|
||||||
|
|
||||||
|
def jsonrpc2_encode(method, params = None):
|
||||||
|
data = {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": method,
|
||||||
|
"params": params
|
||||||
|
}
|
||||||
|
id = jsonrpc2_create_id(data)
|
||||||
|
data['id'] = id
|
||||||
|
return (id, json.dumps(data))
|
||||||
|
|
||||||
|
def jsonrpc2_result_encode(result, id = ''):
|
||||||
|
data = {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": result,
|
||||||
|
"id": id
|
||||||
|
}
|
||||||
|
return json.dumps(data)
|
||||||
|
|
||||||
def parse_first_data(data):
|
def parse_first_data(data):
|
||||||
parsed_data = (b'', b'', b'', b'', b'')
|
parsed_data = (b'', b'', b'', b'', b'')
|
||||||
|
|
||||||
|
@ -126,12 +149,23 @@ def parse_first_data(data):
|
||||||
|
|
||||||
parsed_data = (webserver, port, scheme, method, url)
|
parsed_data = (webserver, port, scheme, method, url)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
conn.close()
|
print("[*] Exception on parsing the header. Cause: %s" % (str(e)))
|
||||||
print("[*] Exception on parsing the header of %s. Cause: %s" % (str(addr[0]), str(e)))
|
|
||||||
|
|
||||||
return parsed_data
|
return parsed_data
|
||||||
|
|
||||||
def conn_string(conn, data, addr):
|
def conn_string(conn, data, addr):
|
||||||
|
# check is it JSON-RPC 2.0 request
|
||||||
|
if data.find(b'{') == 0:
|
||||||
|
jsondata = json.loads(data.decode(client_encoding))
|
||||||
|
if jsondata['jsonrpc'] == "2.0" and jsondata['method'] == "relay_accept":
|
||||||
|
id = jsondata['id']
|
||||||
|
accepted_relay[id] = conn
|
||||||
|
while conn.fileno() > -1:
|
||||||
|
time.sleep(1)
|
||||||
|
del accepted_relay[id]
|
||||||
|
print ("[*] relay destroyed: %s" % (id))
|
||||||
|
return
|
||||||
|
|
||||||
# parse first data (header)
|
# parse first data (header)
|
||||||
webserver, port, scheme, method, url = parse_first_data(data)
|
webserver, port, scheme, method, url = parse_first_data(data)
|
||||||
|
|
||||||
|
@ -422,25 +456,94 @@ def proxy_server(webserver, port, scheme, method, url, conn, addr, data):
|
||||||
|
|
||||||
print("[*] Received %s chunks. (%s bytes per chunk)" % (str(i), str(buffer_size)))
|
print("[*] Received %s chunks. (%s bytes per chunk)" % (str(i), str(buffer_size)))
|
||||||
|
|
||||||
else:
|
# stateful mode
|
||||||
|
elif server_connection_type == "stateful":
|
||||||
|
|
||||||
proxy_data = {
|
proxy_data = {
|
||||||
'headers': {
|
'headers': {
|
||||||
"User-Agent": "php-httpproxy/0.1.4 (Client; Python " + python_version() + "; abuse@catswords.net)",
|
"User-Agent": "php-httpproxy/0.1.5 (Client; Python " + python_version() + "; abuse@catswords.net)",
|
||||||
},
|
},
|
||||||
'data': {
|
'data': {
|
||||||
"data": base64.b64encode(data).decode(client_encoding),
|
"buffer_size": str(buffer_size),
|
||||||
"client": str(addr[0]),
|
"client_address": str(addr[0]),
|
||||||
"server": webserver.decode(client_encoding),
|
"client_port": str(listening_port),
|
||||||
"port": str(port),
|
"client_encoding": client_encoding,
|
||||||
|
"remote_address": webserver.decode(client_encoding),
|
||||||
|
"remote_port": str(port),
|
||||||
"scheme": scheme.decode(client_encoding),
|
"scheme": scheme.decode(client_encoding),
|
||||||
"url": url.decode(client_encoding),
|
|
||||||
"length": str(len(data)),
|
|
||||||
"chunksize": str(buffer_size),
|
|
||||||
"datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
"datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
raw_data = json.dumps(proxy_data['data'])
|
|
||||||
|
# get client address
|
||||||
|
try:
|
||||||
|
_, query_data = jsonrpc2_encode('get_client_address')
|
||||||
|
query = requests.post(server_url, headers=proxy_data['headers'], data=query_data, timeout=1)
|
||||||
|
if query.status_code == 200:
|
||||||
|
result = query.json()['result']
|
||||||
|
proxy_data['data']['client_address'] = result['client_address']
|
||||||
|
print ("[*] Resolved IP: %s" % (result['client_address']))
|
||||||
|
except requests.exceptions.ReadTimeout as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# build a tunnel
|
||||||
|
try:
|
||||||
|
id, raw_data = jsonrpc2_encode('relay_connect', proxy_data['data'])
|
||||||
|
relay = requests.post(server_url, headers=proxy_data['headers'], data=raw_data, stream=True, timeout=1)
|
||||||
|
for chunk in relay.iter_content(chunk_size=buffer_size):
|
||||||
|
print (chunk)
|
||||||
|
except requests.exceptions.ReadTimeout as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# wait for the relay
|
||||||
|
print ("[*] waiting for the relay... %s" % (id))
|
||||||
|
while not id in accepted_relay:
|
||||||
|
time.sleep(1)
|
||||||
|
sock = accepted_relay[id]
|
||||||
|
print ("[*] connected the relay. %s" % (id))
|
||||||
|
sendall(sock, conn, data)
|
||||||
|
|
||||||
|
# get response
|
||||||
|
i = 0
|
||||||
|
buffered = b''
|
||||||
|
while True:
|
||||||
|
chunk = sock.recv(buffer_size)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
buffered += chunk
|
||||||
|
if proxy_check_filtered(buffered, webserver, port, scheme, method, url):
|
||||||
|
sock_close(sock, is_ssl)
|
||||||
|
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:]
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
sock_close(sock, is_ssl)
|
||||||
|
|
||||||
|
print("[*] Received %s chunks. (%s bytes per chunk)" % (str(i), str(buffer_size)))
|
||||||
|
|
||||||
|
else:
|
||||||
|
# stateless mode
|
||||||
|
proxy_data = {
|
||||||
|
'headers': {
|
||||||
|
"User-Agent": "php-httpproxy/0.1.5 (Client; Python " + python_version() + "; abuse@catswords.net)",
|
||||||
|
},
|
||||||
|
'data': {
|
||||||
|
"buffer_size": str(buffer_size),
|
||||||
|
"request_data": base64.b64encode(data).decode(client_encoding),
|
||||||
|
"request_length": str(len(data)),
|
||||||
|
"client_address": str(addr[0]),
|
||||||
|
"client_port": str(listening_port),
|
||||||
|
"client_encoding": client_encoding,
|
||||||
|
"remote_address": webserver.decode(client_encoding),
|
||||||
|
"remote_port": str(port),
|
||||||
|
"scheme": scheme.decode(client_encoding),
|
||||||
|
"datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, raw_data = jsonrpc2_encode(proxy_data['data'])
|
||||||
|
|
||||||
print("[*] Sending %s bytes..." % (str(len(raw_data))))
|
print("[*] Sending %s bytes..." % (str(len(raw_data))))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user