initial commit

This commit is contained in:
Felix Kehrer 2021-08-17 13:40:17 +02:00
commit 78c6150d59
7 changed files with 342 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

72
Cargo.lock generated Normal file
View File

@ -0,0 +1,72 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765"
[[package]]
name = "memchr"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]]
name = "nfq"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf3fd1239e9f2acc4d80da3ecb17f95c8714fb07cddbf64abb8bcc339ac7a4b"
dependencies = [
"libc",
]
[[package]]
name = "pdu"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0091978bc0658e5ea8bfd61339429ddb0489e91160bfb9a9c5b7726ccba4551"
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "zunft"
version = "0.1.0"
dependencies = [
"lazy_static",
"nfq",
"pdu",
"regex",
]

14
Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
cargo-features = ["edition2021"]
[package]
name = "zunft"
version = "0.1.0"
authors = ["Felix Kehrer <felix.kehrer@gmail.com>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lazy_static = "1.4.0"
nfq = "0.2.4"
pdu = "1.4.1"
regex = "1.5.4"

16
README.md Normal file
View File

@ -0,0 +1,16 @@
# README
Zunft - Zauberei und nftables
Now using the new Rust 2021 edition :)
## Useful commands
`cargo +nightly build --release && sudo setcap cap_net_admin+ep ./target/release/zunft && cargo +nightly run --release`
`sudo iptables -t filter -A INPUT -j NFQUEUE --queue-num 0 --queue-bypass`
`sudo iptables -L INPUT --line-numbers`
`sudo iptables -D INPUT 1`
## Testing the filter
While you can run whatever you want, the test case I made is for you to serve the assets folder with something like `python -m http.server 8000`, and then try to have a lookg at the files. Normally you should be able to get both files, but once the firewall runs, trying to access `/secret`should not work anymore.

4
assets/public Normal file
View File

@ -0,0 +1,4 @@
you should be able to read this.
if not, you probably filtered too much.
but then again, if that happens you won't be able to read this.
so, erm, good luck, i guess.

2
assets/secret Normal file
View File

@ -0,0 +1,2 @@
lol, you should not be able to see this.
is the firewall actually running?

233
src/main.rs Normal file
View File

@ -0,0 +1,233 @@
#[allow(unused_imports)]
use lazy_static::lazy_static;
use nfq::{Queue, Verdict};
use pdu::{Gre, GrePdu, Icmp, IcmpPdu, Ip, Ipv4, Ipv4Pdu, Ipv6Pdu, Tcp, TcpPdu, Udp, UdpPdu};
#[allow(unused_imports)]
use regex::bytes::Regex;
fn main() {
let mut queue = Queue::open().expect("Could not open queue!");
queue.bind(0).expect("Could not bind to queue 0!");
loop {
let mut msg = queue.recv().expect("Could not receive message from queue!");
let packet = msg.get_payload();
let verdict = match Ip::new(packet) {
Ok(Ip::Ipv4(ipv4_pdu)) => {
print!("IPv4 -> ");
//print_ipv4(ipv4_pdu);
match ipv4_pdu.inner() {
#[allow(unused_variables)]
Ok(Ipv4::Raw(raw)) => {
print!("RAW");
Verdict::Accept
}
Ok(Ipv4::Tcp(tcp_pdu)) => {
print!("TCP -> ");
//print_tcp(tcp_pdu);
match tcp_pdu.inner() {
Ok(Tcp::Raw(raw)) => {
print!("RAW");
filter_ipv4_tcp_raw(ipv4_pdu, tcp_pdu, raw)
}
Err(e) => panic!("Could not get inner payload of TCP PDU: {}", e),
}
}
Ok(Ipv4::Udp(udp_pdu)) => {
print!("UDP -> ");
//print_udp(udp_pdu);
match udp_pdu.inner() {
#[allow(unused_variables)]
Ok(Udp::Raw(raw)) => {
print!("RAW");
}
Err(e) => panic!("Could not get inner payload of UDP PDU: {}", e),
}
Verdict::Accept
}
Ok(Ipv4::Icmp(icmp_pdu)) => {
print!("ICMP -> ");
//print_icmp(icmp_pdu);
match icmp_pdu.inner() {
#[allow(unused_variables)]
Ok(Icmp::Raw(raw)) => {
print!("RAW");
}
Err(e) => panic!("Could not get inner payload of ICMP PDU: {}", e),
}
Verdict::Accept
}
Ok(Ipv4::Gre(gre_pdu)) => {
print!("GRE -> ");
//print_gre(gre_pdu);
match gre_pdu.inner() {
#[allow(unused_variables)]
Ok(Gre::Raw(raw)) => {
print!("RAW");
}
Ok(_) => print!("Eth/Ipv4/Ipv6"),
Err(e) => panic!("Could not get inner payload of GRE PDU: {}", e),
}
Verdict::Accept
}
Err(e) => panic!("Could not decode inner packet: {}", e),
}
}
#[allow(unused_variables)]
Ok(Ip::Ipv6(ipv6_pdu)) => {
println!("IPv6!");
// TODO same as above
Verdict::Accept
}
Err(e) => panic!("Could not decode IP packet: {}", e),
};
println!();
msg.set_verdict(verdict);
queue
.verdict(msg)
.expect("Could not set verdict for message!");
}
}
#[allow(dead_code)]
fn print_gre(gre_pdu: GrePdu) {
print!(
"GRE {:10} {:3} {:5} {:5} {:5} {:5} {:?} {:?} {:?} {:?} ",
gre_pdu.computed_ihl(),
gre_pdu.version(),
gre_pdu.ethertype(),
gre_pdu.has_checksum(),
gre_pdu.has_key(),
gre_pdu.has_sequence_number(),
gre_pdu.checksum(),
gre_pdu.computed_checksum(),
gre_pdu.key(),
gre_pdu.sequence_number()
);
}
#[allow(dead_code)]
fn print_icmp(icmp_pdu: IcmpPdu) {
print!(
"ICMP {:3} {:3} {:5} {:10}",
icmp_pdu.message_type(),
icmp_pdu.message_code(),
icmp_pdu.checksum(),
//icmp_pdu.computed_checksum(ip: &crate::Ip)
icmp_pdu.computed_data_offset()
);
}
#[allow(dead_code)]
fn print_ipv4(ipv4_pdu: Ipv4Pdu) {
let sa = ipv4_pdu.source_address();
let da = ipv4_pdu.destination_address();
print!(
"IPv4 {} {} {} {:2} {} {:4} {:5} {:5} {:5} {} {} {:3} {:2} {:5} {:5} {:03}:{:03}:{:03}:{:03} {:03}:{:03}:{:03}:{:03} ",
ipv4_pdu.version(),
ipv4_pdu.ihl(),
ipv4_pdu.computed_ihl(),
ipv4_pdu.dscp(),
ipv4_pdu.ecn(),
ipv4_pdu.total_length(),
ipv4_pdu.identification(),
ipv4_pdu.dont_fragment(),
ipv4_pdu.more_fragments(),
ipv4_pdu.fragment_offset(),
ipv4_pdu.computed_fragment_offset(),
ipv4_pdu.ttl(),
ipv4_pdu.protocol(),
ipv4_pdu.checksum(),
ipv4_pdu.computed_checksum(),
//ipv4_pdu.source_address(),
sa[0],
sa[1],
sa[2],
sa[3],
//ipv4_pdu.destination_address(),
da[0],
da[1],
da[2],
da[3]
//ipv4_pdu.options()
);
}
#[allow(dead_code)]
fn print_ipv6(ipv6_pdu: Ipv6Pdu) {
print!(
"IPv {:3} {:3} {:3} {:10} {:5} {:3} {:10} {:3} {:?} {:?} {:?} {:3} {:?} {:?}",
// the {:10} could maybe be shorter
// {:?} should be replaced with "proper" hexdecoding, aka a lib to do the displaying
ipv6_pdu.version(),
ipv6_pdu.dscp(),
ipv6_pdu.ecn(),
ipv6_pdu.flow_label(),
ipv6_pdu.payload_length(),
ipv6_pdu.next_header(),
ipv6_pdu.computed_ihl(),
ipv6_pdu.computed_protocol(),
ipv6_pdu.computed_identification(),
ipv6_pdu.computed_more_fragments(),
ipv6_pdu.computed_fragment_offset(),
ipv6_pdu.hop_limit(),
ipv6_pdu.source_address(),
ipv6_pdu.destination_address(),
//ipv6_pdu.extension_headers()
);
}
#[allow(dead_code)]
fn print_tcp(tcp_pdu: TcpPdu) {
print!(
"TCP {:5} {:5} {:10} {:10} {:2} {:2} {:2} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {} {} ",
tcp_pdu.source_port(),
tcp_pdu.destination_port(),
tcp_pdu.sequence_number(),
tcp_pdu.acknowledgement_number(),
tcp_pdu.data_offset(),
tcp_pdu.computed_data_offset(),
tcp_pdu.flags(),
tcp_pdu.fin(),
tcp_pdu.syn(),
tcp_pdu.rst(),
tcp_pdu.psh(),
tcp_pdu.ack(),
tcp_pdu.urg(),
tcp_pdu.ecn(),
tcp_pdu.cwr(),
tcp_pdu.window_size(),
//tcp_pdu.computed_window_size(shift: u8),
tcp_pdu.checksum(),
//tcp_pdu.computed_checksum(ip: &crate::Ip),
tcp_pdu.urgent_pointer(),
//tcp_pdu.options(),
);
}
#[allow(dead_code)]
fn print_udp(udp_pdu: UdpPdu) {
print!(
"UDP {:5} {:5} {:5} {:5} {} ",
udp_pdu.source_port(),
udp_pdu.destination_port(),
udp_pdu.length(),
udp_pdu.checksum(),
//udp_pdu.computed_checksum(ip: &crate::Ip),
udp_pdu.computed_data_offset()
);
}
#[allow(unused_variables)]
fn filter_ipv4_tcp_raw(ipv4_pdu: Ipv4Pdu, tcp_pdu: TcpPdu, raw: &[u8]) -> Verdict {
// example filter that drops GET requests to /secret
lazy_static! {
static ref RE: Regex = Regex::new("^GET /secret ").expect("Could not compile Regex!");
}
if RE.is_match(raw) {
Verdict::Drop
} else {
Verdict::Accept
}
}