summaryrefslogtreecommitdiffstats
path: root/iv/orodja
diff options
context:
space:
mode:
authorAnton Luka Šijanec <anton@sijanec.eu>2024-09-01 00:15:18 +0200
committerAnton Luka Šijanec <anton@sijanec.eu>2024-09-01 00:15:18 +0200
commitb94bef6e820fec0ffde7971d3134d5738c1521d1 (patch)
tree2ae275e51629ae9979a7303f5605bb4988b30234 /iv/orodja
parenthacker gets hacked, double fdput vuln (diff)
downloadr-b94bef6e820fec0ffde7971d3134d5738c1521d1.tar
r-b94bef6e820fec0ffde7971d3134d5738c1521d1.tar.gz
r-b94bef6e820fec0ffde7971d3134d5738c1521d1.tar.bz2
r-b94bef6e820fec0ffde7971d3134d5738c1521d1.tar.lz
r-b94bef6e820fec0ffde7971d3134d5738c1521d1.tar.xz
r-b94bef6e820fec0ffde7971d3134d5738c1521d1.tar.zst
r-b94bef6e820fec0ffde7971d3134d5738c1521d1.zip
Diffstat (limited to 'iv/orodja')
-rw-r--r--iv/orodja/ldmitm/Makefile5
-rw-r--r--iv/orodja/ldmitm/ldmitm.c153
-rw-r--r--iv/orodja/ldmitm/tcp_times.c1
-rw-r--r--iv/orodja/ldmitm/tcp_times_example.c32
-rw-r--r--iv/orodja/napad/.gitignore1
-rw-r--r--iv/orodja/napad/config44
-rwxr-xr-xiv/orodja/napad/exploit.sh23
-rwxr-xr-xiv/orodja/napad/submission.py64
-rw-r--r--iv/orodja/waf/Dockerfile6
-rw-r--r--iv/orodja/waf/regexes.txt2
-rw-r--r--iv/orodja/waf/waf.py85
11 files changed, 363 insertions, 53 deletions
diff --git a/iv/orodja/ldmitm/Makefile b/iv/orodja/ldmitm/Makefile
index 4010899..1ef8a92 100644
--- a/iv/orodja/ldmitm/Makefile
+++ b/iv/orodja/ldmitm/Makefile
@@ -14,4 +14,7 @@ clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
rm ldmitm.so tcp_times_example
-.PHONY: allmods clean
+prepare:
+ apt install libsqlite3-dev linux-headers-generic
+
+.PHONY: allmods clean prepare
diff --git a/iv/orodja/ldmitm/ldmitm.c b/iv/orodja/ldmitm/ldmitm.c
index 66cba41..2b8b815 100644
--- a/iv/orodja/ldmitm/ldmitm.c
+++ b/iv/orodja/ldmitm/ldmitm.c
@@ -5,7 +5,7 @@ Kako deluje: https://tbrindus.ca/correct-ld-preload-hooking-libc/
Prevedi z: gcc -shared -fPIC -ldl ldmitm.c -o ldmitm.so
Poženi z: LD_PRELOAD=$PWD/ldmitm.so php -S 0:1234
A je treba beležit execve in fopen? Po mojem ne. Odtekanje flagov itak vidimo v TCP sessionu.
-TODO: add mutex locks!!!
+TODO: add mutex locks, openssl bio mitm, read, write, rtt, timestamps (if possible), namesto trenutnega mtu rajši dobi advertised mss iz tcp_info, dobi last ack timestamp option value: https://elixir.bootlin.com/linux/v6.11-rc3/source/include/linux/tcp.h#L302 <= tu notri je mogoče z BPF???
*/
#include <stdio.h>
#include <dlfcn.h>
@@ -18,20 +18,25 @@ TODO: add mutex locks!!!
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
+#include <sys/param.h>
+#include <netinet/ip.h>
#if SQLITE_VERSION_NUMBER < 3037000
#error "we require sqlite newer than 3037000, yours is " #SQLITE_VERSION_NUMBER " --- because of STRICT!"
#endif
#define LOG(x, ...) if (getenv("LDMITM_LOG")) printf("[ldmitm %s] " x "\n", __func__ __VA_OPT__(,) __VA_ARGS__)
struct stream {
- bool active;
- bool silenced;
- int id; // id in sqlite3 database
+ bool active; // only stream connections are true, listening sockets and inactive fds are false
+ bool silenced; // only makes sense for stream connections, true to silence traffic
+ int id; // id in sqlite3 database -- only makes sense for stream connections
+ int bound; // for listening sockets: port, for stream sockets: fd of listening socket
};
typedef int (*accept_t)(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
typedef int (*close_t)(int fd);
-static accept_t real_accept;
-static close_t real_close;
-static __thread sqlite3 * db = NULL;
+typedef int (*bind_t)(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
+static accept_t real_accept = NULL;
+static close_t real_close = NULL;
+static bind_t real_bind = NULL;
+static sqlite3 * db = NULL;
struct stream * streams = NULL;
int streams_sizeof = 0;
static void setup_db () { // should be able to ignore being called in case db is already set up on this thread
@@ -74,8 +79,8 @@ static void setup_db () { // should be able to ignore being called in case db is
LOG("failed to step create " name ": %s", sqlite3_errstr(ret)); \
goto fail; \
}
- CREATE_TABLE("connections", "id INTEGER PRIMARY KEY, peer TEXT NOT NULL, accepted TEXT DEFAULT (strftime('%FT%R:%f', 'now')) NOT NULL, closed TEXT, silenced TEXT"); // accepted, closed and silenced are iso datestrings
- CREATE_TABLE("messages", "connection INTEGER NOT NULL, direction INTEGER NOT NULL CHECK (direction IN (0, 1)), time TEXT DEFAULT (strftime('%FT%R:%f', 'now')) NOT NULL, FOREIGN KEY(connection) REFERENCES connections(id)");
+ CREATE_TABLE("connections", "id INTEGER PRIMARY KEY, bound INTEGER NOT NULL, peer TEXT NOT NULL, accepted TEXT DEFAULT (strftime('%FT%R:%f', 'now')) NOT NULL, closed TEXT, silenced TEXT, mtu INTEGER NOT NULL"); // accepted, closed and silenced are iso datestrings, bound is listening port
+ CREATE_TABLE("messages", "connection INTEGER NOT NULL, direction INTEGER NOT NULL CHECK (direction IN (0, 1)), time TEXT DEFAULT (strftime('%FT%R:%f', 'now')) NOT NULL, rtt INTEGER NOT NULL, FOREIGN KEY(connection) REFERENCES connections(id)");
return;
fail:
if (db)
@@ -114,6 +119,74 @@ static void end_stream (int fd) { // cleanup function, should be able to handle
fail:
memset(&(streams[fd]), 0, sizeof streams[fd]);
}
+static bool more_fds (int largest_fd) {
+ if (largest_fd >= streams_sizeof) {
+ int streams_sizeof_new;
+ if (streams_sizeof == 0)
+ streams_sizeof_new = 128;
+ else
+ streams_sizeof_new = streams_sizeof * 2;
+ struct stream * streams_new = realloc(streams, streams_sizeof_new*sizeof *streams);
+ if (!streams_new) {
+ LOG("ENOMEM realloc streams!");
+ return false;
+ }
+ memset(streams_new+streams_sizeof, 0, (streams_sizeof_new-streams_sizeof)*sizeof *streams);
+ streams = streams_new;
+ streams_sizeof = streams_sizeof_new;
+ }
+ if (largest_fd < streams_sizeof)
+ return true;
+ return more_fds(largest_fd);
+}
+static int port_from_sa (const struct sockaddr * sa) { // gets port from sockaddr
+ switch (sa->sa_family) {
+ case AF_INET:
+ return ntohs(((struct sockaddr_in *) sa)->sin_port);
+ case AF_INET6:
+ return ntohs(((struct sockaddr_in6 *) sa)->sin6_port);
+ default:
+ return -1;
+ }
+}
+static void str_from_sa (char * peeraddress, const struct sockaddr * address) {
+ switch (address->sa_family) {
+ case AF_INET:
+ if (!inet_ntop(AF_INET, &(((struct sockaddr_in *) address)->sin_addr), peeraddress, *address_len)) {
+ int myerrno = errno;
+ strcpy(peeraddress, "!");
+ strcat(peeraddress, strerror(myerrno));
+ }
+ sprintf(peeraddress+strlen(peeraddress), "/%d", ntohs(((struct sockaddr_in *) address)->sin_port));
+ break;
+ case AF_INET6:
+ if (!inet_ntop(AF_INET6, &(((struct sockaddr_in6 *) address)->sin6_addr), peeraddress, *address_len)) {
+ int myerrno = errno;
+ strcpy(peeraddress, "!");
+ strcat(peeraddress, strerror(myerrno));
+ }
+ sprintf(peeraddress+strlen(peeraddress), "/%d", ntohs(((struct sockaddr_in6 *) address)->sin6_port));
+ break;
+ default:
+ strcpy(peeraddress, "unknown family");
+ break;
+ }
+}
+int bind (int sockfd, const struct sockaddr * addr, socklen_t addrlen) {
+ if (!real_bind)
+ real_bind = dlsym(RTLD_NEXT, "bind");
+ LOG("called bind()");
+ int ret = real_bind(sockfd, addr, addrlen);
+ int old_errno = errno;
+ if (ret == -1)
+ goto fail;
+ if (!more_fds(sockfd))
+ goto fail; // enomem
+ streams[sockfd].bound = port_from_sa(addr);
+fail:
+ errno = old_errno;
+ return ret;
+}
int close (int fd) {
if (!real_close)
real_close = dlsym(RTLD_NEXT, "close");
@@ -130,56 +203,42 @@ int accept (int socket, struct sockaddr *restrict address, socklen_t *restrict a
LOG("called accept()");
int ret = real_accept(socket, address, address_len);
int saved_errno = errno;
- if (ret >= streams_sizeof) {
- int streams_sizeof_new;
- if (streams_sizeof == 0)
- streams_sizeof_new = 128;
- else
- streams_sizeof_new = streams_sizeof * 2;
- struct stream * streams_new = realloc(streams, streams_sizeof_new*sizeof *streams);
- if (!streams_new) {
- LOG("ENOMEM realloc streams!");
- goto fail;
- }
- memset(streams_new+streams_sizeof, 0, (streams_sizeof_new-streams_sizeof)*sizeof *streams);
- streams = streams_new;
- streams_sizeof = streams_sizeof_new;
- }
+ if (ret == -1)
+ goto fail;
+ if (!more_fds(MAX(ret, socket))
+ goto fail; // enomem
end_stream(ret);
streams[ret].active = true;
+ streams[ret].bound = socket;
setup_db();
if (db) {
sqlite3_stmt * stmt = NULL;
- int sqlret = sqlite3_prepare_v3(db, "insert into connections (peer) values (:p);", -1, 0, &stmt, NULL);
+ int sqlret = sqlite3_prepare_v3(db, "insert into connections (peer, bound, mtu) values (:p, :b, :m);", -1, 0, &stmt, NULL);
if (sqlret != SQLITE_OK) {
LOG("failed to prepare insert connections: %s", sqlite3_errstr(sqlret));
goto sqlfail;
}
char peeraddress[INET_ADDRSTRLEN+128];
- switch (address->sa_family) {
- case AF_INET:
- if (!inet_ntop(AF_INET, &(((struct sockaddr_in *) address)->sin_addr), peeraddress, *address_len)) {
- int myerrno = errno;
- strcpy(peeraddress, "!");
- strcat(peeraddress, strerror(myerrno));
- }
- sprintf(peeraddress+strlen(peeraddress), "/%d", ntohs(((struct sockaddr_in *) address)->sin_port));
- break;
- case AF_INET6:
- if (!inet_ntop(AF_INET6, &(((struct sockaddr_in6 *) address)->sin6_addr), peeraddress, *address_len)) {
- int myerrno = errno;
- strcpy(peeraddress, "!");
- strcat(peeraddress, strerror(myerrno));
- }
- sprintf(peeraddress+strlen(peeraddress), "/%d", ntohs(((struct sockaddr_in6 *) address)->sin6_port));
- break;
- default:
- strcpy(peeraddress, "unknown family");
- break;
- }
+ str_from_sa(peeraddress, address);
sqlret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":p"), peeraddress, -1, SQLITE_STATIC);
if (sqlret != SQLITE_OK) {
- LOG("failed to bind insert connection: %s", sqlite3_errstr(sqlret));
+ LOG("failed to bind insert connection text: %s", sqlite3_errstr(sqlret));
+ goto sqlfail;
+ }
+ sqlret = sqlite3_bind_int64(stmt, sqlite3_bind_parameter_index(stmt, ":b"), streams[streams[ret].bound].bound);
+ if (sqlret != SQLITE_OK) {
+ LOG("failed to bind insert connection bound: %s", sqlite3_errstr(sqlret));
+ goto sqlfail;
+ }
+ int mtu;
+ int mtusize = sizeof mtu;
+ if (getsockopt(socket, IPPROTO_IP, IP_MTU, &mtu, &mtusize) == -1) {
+ mtu = -errno;
+ LOG("failed to get MTU: %s", -mtu);
+ }
+ sqlret = sqlite3_bind_int64(stmt, sqlite3_bind_parameter_index(stmt, ":m"), mtu);
+ if (sqlret != SQLITE_OK) {
+ LOG("failed to bind insert connection mtu: %s", sqlite3_errstr(sqlret));
goto sqlfail;
}
sqlret = sqlite3_step(stmt);
diff --git a/iv/orodja/ldmitm/tcp_times.c b/iv/orodja/ldmitm/tcp_times.c
index 082d2d8..02a067a 100644
--- a/iv/orodja/ldmitm/tcp_times.c
+++ b/iv/orodja/ldmitm/tcp_times.c
@@ -28,7 +28,6 @@ if (ioctl(tcp_times, 0, &tt) == -1) {
#include <linux/tcp.h>
#include <linux/version.h>
#include "tcp_times.h"
-#define MIN(a,b) (((a)<(b))?(a):(b))
MODULE_AUTHOR("Anton Luka Šijanec <anton@sijanec.eu>");
MODULE_DESCRIPTION("tcp last received tsval, rtt procfs ioctl driver");
MODULE_LICENSE("");
diff --git a/iv/orodja/ldmitm/tcp_times_example.c b/iv/orodja/ldmitm/tcp_times_example.c
index c98b29a..707bfce 100644
--- a/iv/orodja/ldmitm/tcp_times_example.c
+++ b/iv/orodja/ldmitm/tcp_times_example.c
@@ -12,7 +12,24 @@ Posluša na TCP vratih 6969, prejme eno povezavo, vsako sekundo nanjo izpiše LF
#include <stdbool.h>
#include <sys/ioctl.h>
#include <fcntl.h>
+#include <signal.h>
+int samomor = 0;
+void handler (int sig __attribute__((unused))) {
+ samomor = 1;
+}
int main (void) {
+ if (signal(SIGINT, handler) == SIG_ERR) {
+ perror("signal");
+ return 1;
+ }
+ if (signal(SIGHUP, handler) == SIG_ERR) {
+ perror("signal");
+ return 1;
+ }
+ if (signal(SIGTERM, handler) == SIG_ERR) {
+ perror("signal");
+ return 1;
+ }
int tcp_socket = socket(AF_INET6, SOCK_STREAM, 0);
if (tcp_socket == -1) {
perror("socket");
@@ -29,12 +46,12 @@ int main (void) {
}
if (listen(tcp_socket, 1 /* only one client is handled*/) == -1) {
perror("listen");
- return 1;
+ goto die;
}
int flow = accept(tcp_socket, NULL, NULL);
if (flow == -1) {
perror("accept");
- return 1;
+ goto die;
}
int tcp_times = open("/proc/tcp_times", O_RDWR);
struct tcp_times tt = {
@@ -44,13 +61,20 @@ int main (void) {
while (true) {
if (ioctl(tcp_times, 0, &tt) == -1) {
perror("ioctl");
- return 1;
+ break;
}
printf(TCP_TIMES_PRINTF_FORMAT "\n", TCP_TIMES_PRINTF_VARIABLES(tt.));
if (send(flow, &buf, 1, MSG_NOSIGNAL) == -1) {
perror("write");
- return 1;
+ break;
}
+ if (samomor)
+ break;
sleep(1);
}
+die:
+ close(tcp_times);
+ close(flow);
+ close(tcp_socket);
+ return 1;
}
diff --git a/iv/orodja/napad/.gitignore b/iv/orodja/napad/.gitignore
new file mode 100644
index 0000000..e417f8f
--- /dev/null
+++ b/iv/orodja/napad/.gitignore
@@ -0,0 +1 @@
+flags.db
diff --git a/iv/orodja/napad/config b/iv/orodja/napad/config
new file mode 100644
index 0000000..371faed
--- /dev/null
+++ b/iv/orodja/napad/config
@@ -0,0 +1,44 @@
+# Common config for exploit.sh and submission.py.
+# It is to be sourced. It only sets environment variables.
+
+# ==========================
+# ========= COMMON =========
+
+export FLAG_REGEX="^[A-Z0-9]{31}=$"
+export SUBMISSION_PORT=21502
+
+# ==========================
+# ======= EXPLOIT.SH =======
+
+# Where can exploit.sh find submission.py. Port is a common setting.
+export SUBMISSION_HOST=k.4a.si
+
+# Must be precise, not less than round duration. Used to calculate round id.
+export ROUND_DURATION=120
+
+# When does the game start (in UTC). Used to calculate current round id.
+export GAME_START=2024-09-01T07:00:00
+
+# ==========================
+# ====== SUBMISSION.PY =====
+
+export SUBMISSION_DB=flags.db
+
+# How much flags to send in one request.
+# With 2560, if it takes 37 bytes per flag, 2560*37=94720
+# Ostane nam torej še dobrih 5280 za headerje,
+# če je request limited na 100 kB
+export SUBMISSION_MAX_FLAGS=2560
+
+# PUT request, ECSC 2024 AD style
+export SUBMISSION_URL=http://10.10.0.1:8080/flags
+
+# How many seconds to delay after a successful submission.
+# With 15, we send at most 4 requests per minute out of 15 allowed.
+export SUBMISSION_DELAY=15
+
+# This is sent in X-Team-Token in requests to SUBMISSION_URL
+export SUBMISSION_TEAM_TOKEN=e5152d70a4d18093cae8844f4e959cf1
+
+# Where to bind to. Use SUBMISSION_PORT in common settings for port.
+export SUBMISSION_BIND=::
diff --git a/iv/orodja/napad/exploit.sh b/iv/orodja/napad/exploit.sh
new file mode 100755
index 0000000..1111b00
--- /dev/null
+++ b/iv/orodja/napad/exploit.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+if [ x$1 = x ]
+then
+echo >&2 <<EOF
+No command. Subcommands:
+ $0 once <exploit> # runs an exploit once, print captured flags
+ $1 loop <exploit> # runs an exploit in a loop once per round
+<exploit> is an executable file. Flags, grepped from stdout, are submitted.
+It is called for every target. Args are target IP and flag IDs JSON object.
+ Example: <exploit> 10.1.2.3 '{"user": "root", "pass": "hunter2"}'
+Flag IDs are also available in the environment as variables FLAG_ID_<key>:
+ {"user": "root", "pass": "hunter2"} will be in environment as vars
+ FLAG_ID_user=root and FLAG_ID_pass=hunter2
+In loop mode, exploit is first exec'd rapidly for still valid old rounds.
+Max execution time is $EXPLOIT_TIMEOUT seconds (EXPLOIT_TIMEOUT in config)
+Exploits are NOT executed in parallel.
+Make sure that your system time is set CORRECTLY TO THE SECOND, it's used
+ to get the current round id. Current time: `date`.
+Configuration values are also available in environment of exploits.
+EOF
+ exit 1
+fi
+set -xeuo pipefail
diff --git a/iv/orodja/napad/submission.py b/iv/orodja/napad/submission.py
new file mode 100755
index 0000000..dcf75ca
--- /dev/null
+++ b/iv/orodja/napad/submission.py
@@ -0,0 +1,64 @@
+#!/usr/bin/python3
+import os
+import asyncio
+import re
+import sqlite3
+import aiohttp
+db = sqlite3.connect(os.getenv("SUBMISSION_DB", "flags.db"))
+db.execute("CREATE TABLE IF NOT EXISTS flags (id INTEGER PRIMARY KEY, flag TEXT NOT NULL UNIQUE, sent INTEGER NOT NULL DEFAULT 0, date TEXT DEFAULT (strftime('%FT%R:%f', 'now')) NOT NULL, status TEXT, msg TEXT) STRICT")
+flag_regex = re.compile(os.getenv("FLAG_REGEX", "^[A-Z0-9]{31}=$").encode(), re.ASCII | re.DOTALL | re.VERBOSE)
+async def submitter ():
+ while True:
+ print("submitter loop")
+ flags = []
+ for row in db.execute("SELECT flag FROM flags WHERE sent == 0 ORDER BY date DESC"):
+ if len(flags) < int(os.getenv("SUBMISSION_MAX_FLAGS", "2560")):
+ flags.append(row[0])
+ if len(flags) == 0:
+ await asyncio.sleep(1)
+ for i in [1]:
+ async with aiohttp.ClientSession(headers={"X-Team-Token": os.getenv("SUBMISSION_TEAM_TOKEN")}) as session:
+ async with session.put(os.getenv("SUBMISSION_URL", 'http://10.10.0.1:8080/flags'), json=flags) as response:
+ if response.status // 100 != 2:
+ print("submitter error: " + await response.text())
+ break
+ cursor = db.cursor()
+ for obj in await response.json():
+ cursor.execute("UPDATE flags SET sent=?, status=?, msg=? WHERE flag=?", [int(obj.get("status") != "RESUBMIT"), obj.get("status"), obj.get("msg"), obj.get("flag")])
+ db.commit()
+ await asyncio.sleep(int(os.getenv("SUBMISSION_DELAY", "15")))
+async def handle_client (reader, writer):
+ while True:
+ incoming = await reader.readuntil(b'\n')
+ if len(incoming) == 0:
+ break
+ buffer = incoming.replace(b'\r', b'').replace(b'\n', b'')
+ if buffer.startswith(b' '):
+ for row in db.execute(buffer[1:].decode()):
+ writer.write(str(row).encode() + b'\n')
+ continue
+ if buffer.startswith(b'@'):
+ writer.write(str(db.execute(buffer[1:].decode()).fetchall()).encode() + b'\n')
+ continue
+ if buffer.startswith(b'#'):
+ writer.write(str(len(db.execute(buffer[1:].decode()).fetchall())).encode() + b'\n')
+ continue
+ if re.match(flag_regex, buffer) == None:
+ writer.write(b'BAD_FLAG\n')
+ continue
+ flag = buffer.decode()
+ try:
+ db.execute("INSERT INTO flags (flag) VALUES (?)", [flag])
+ except sqlite3.IntegrityError:
+ status, msg, date = [x for x in db.execute("SELECT status, msg, date FROM flags WHERE flag=?", [flag])][0]
+ writer.write(b"OLD_FLAG " + date.encode() + b" " + str(status).encode() + b" " + str(msg).encode() + b"\n")
+ else:
+ writer.write(b'NEW_FLAG\n')
+ writer.close()
+async def run_server ():
+ server = await asyncio.start_server(handle_client, os.getenv("SUBMISSION_BIND", "::"), os.getenv("SUBMISSION_PORT", "21502"))
+ event_loop = asyncio.get_event_loop()
+ event_loop.create_task(submitter())
+ async with server:
+ await server.serve_forever()
+asyncio.run(run_server())
diff --git a/iv/orodja/waf/Dockerfile b/iv/orodja/waf/Dockerfile
new file mode 100644
index 0000000..bc87aaf
--- /dev/null
+++ b/iv/orodja/waf/Dockerfile
@@ -0,0 +1,6 @@
+FROM alpine:latest
+RUN apk add python3
+WORKDIR /waf
+COPY ./waf.py .
+RUN chmod +x ./waf.py
+CMD ["./waf.py"]
diff --git a/iv/orodja/waf/regexes.txt b/iv/orodja/waf/regexes.txt
new file mode 100644
index 0000000..301a41c
--- /dev/null
+++ b/iv/orodja/waf/regexes.txt
@@ -0,0 +1,2 @@
+HACKERS
+HACKERS
diff --git a/iv/orodja/waf/waf.py b/iv/orodja/waf/waf.py
new file mode 100644
index 0000000..701b4dd
--- /dev/null
+++ b/iv/orodja/waf/waf.py
@@ -0,0 +1,85 @@
+#!/usr/bin/python3
+import asyncio
+import os
+import ssl
+import re
+
+def manyregex (string, patterns):
+ for pattern in patterns:
+ if re.search(pattern, string) != None:
+ return True
+ return False
+
+async def cevovod (reader, writer, writer2, compiled):
+ buffer = b''
+ while True:
+ prebral = await reader.read(65536)
+ buffer += prebral
+ if manyregex(buffer, compiled):
+ break ## hacker detected
+ if len(buffer) > 65536:
+ buffer = buffer[-32768:]
+ if len(prebral) == 0:
+ break
+ writer.write(prebral)
+ writer.close()
+ writer2.close()
+
+async def handle_client (reader, writer):
+ try:
+ compiled = []
+ reflags = re.ASCII | re.MULTILINE | re.DOTALL | re.VERBOSE
+ with open(os.getenv("WAF_REGEXES"), "rb") as rulesfile:
+ regexes = rulesfile.read().split(b"\n")
+ for regex in regexes:
+ if len(regex) == 0:
+ continue
+ compiled.append(re.compile(regex, reflags))
+ write_to_backend = b''
+ if os.getenv("WAF_HTTP"):
+ headers = []
+ while True:
+ line = (await reader.readuntil(b'\n')).replace(b'\r', b'').replace(b'\n', b'')
+ if not line.lower().startswith(b"accept-encoding:"):
+ write_to_backend += line + b"\r\n"
+ if len(line) == 0:
+ break
+ headers.append(line)
+ if headers[0].startswith(b"GET /waf HTTP/"):
+ writer.write(b"HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\nwaf works (: PID " + str(os.getpid()).encode() + b"\r\n")
+ writer.close()
+ return
+ context = None
+ if os.getenv("WAF_TLS"):
+ context = ssl.create_default_context()
+ context.check_hostname = False
+ context.verify_mode = ssl.CERT_NONE
+ backendr, backendw = await asyncio.open_connection(os.getenv("WAF_BACKEND_HOST"), os.getenv("WAF_BACKEND_PORT"), ssl=context)
+ if manyregex(write_to_backend, compiled):
+ writer.write(b'HTTP/1.0 469 Hacking\r\nContent-Type: text/plain\r\n\r\nHacking is illegal according to ZEKom-2.\r\n')
+ backendw.close() ## hacker
+ writer.close()
+ return
+ backendw.write(write_to_backend)
+ except Exception as e:
+ writer.write(b"HTTP/1.0 569 Exception\r\nContent-Type: text/plain\r\n\r\n")
+ writer.write(str(e).encode())
+ writer.write(b"\r\n")
+ backendw.close()
+ writer.close()
+ raise e
+ else:
+ event_loop = asyncio.get_event_loop()
+ event_loop.create_task(cevovod(reader, backendw, writer, compiled))
+ event_loop.create_task(cevovod(backendr, writer, backendw, compiled))
+
+async def run_server ():
+ context = None
+ if os.getenv("WAF_TLS"):
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+ context.load_cert_chain(certfile=os.getenv("WAF_TLS"), keyfile=os.getenv("WAF_TLS_KEY"))
+ server = await asyncio.start_server(handle_client, os.getenv("WAF_LISTEN_ADDR"), os.getenv("WAF_LISTEN_PORT"), ssl=context)
+ async with server:
+ await server.serve_forever()
+
+asyncio.run(run_server())