- Book mode https://hackmd.io/@ncnu-opensource/book
[TOC]
# 什麼是防火牆?
:::info
- 在學校內就相當於警衛的角色
- 確認只有學生或是教職人員可以進入學校
:::


- 讓使用者設定規則/策略,保護自己的網路環境
主要的功能有:
- **阻擋**不要的封包
- **放行**需要的封包
- **轉發**封包
:::danger
## 如果沒有防火牆

- 任何使用者都能直接連線到我的主機
:::
:::success
## 如果有防火牆

- 確定只有合法的使用者能夠連線進來
:::
# Netfilter
## 介紹
- Linux 內建的封包過濾框架
- 由 Rusty Russell 在 1998 年開始主導開發
- Linux 2.4 版本時,正式取代舊的 ipchains
- 目前 Linux 上主流的防火牆軟體,都是基於此框架去實作
- 主要提供以下功能
- 封包選擇跟過濾
- 網路位址轉譯 (NAT, Network Address Translation)
- Packet mangling
- Connection tracking
## Hooks
:::info
在系統設計中,Hook 是一種機制,用來在特定的事件發生時啟動額外的自訂的行為。防火牆的規則,就是透過在以下這些 Hooks 上註冊一些檢查以及相對應的行為去達成的
:::
```mermaid
flowchart TB
A((封包進入)) --> B[NF_INET_PRE_ROUTING]
B --> C{路由判斷}
C -->|封包送往本機| D[NF_INET_LOCAL_IN]
C -->|轉發封包| E[NF_INET_FORWARD]
D --> |交給需要的本機資源| F[[本機 process]]
E --> |發送封包| I[NF_INET_POST_ROUTING]
G[[本機 process]] --> |產生封包| H[NF_INET_LOCAL_OUT]
H -->|發送封包| I
I --> J((封包傳出))
```
當封包進到 Linux 的網路系統中時,會按照一定的順序去觸發這些 Hooks,Netfilter 主要提供以下 5 種 Hook 點:
### `NF_INET_PRE_ROUTING`
接收到的封包第一個會碰到的 Hook,會在進行路由判斷 (routing decision) 之前觸發
### `NF_INET_LOCAL_IN`
經過路由判斷後,若目的地是本機,就會觸發這個 Hook
### `NF_INET_FORWARD`
經過路由判斷後,若是需要轉發出去的封包(目的地不是本機),就會觸發這個 Hook
### `NF_INET_LOCAL_OUT`
本機產生並要送往外部的封包,會觸發這個 Hook
### `NF_INET_POST_ROUTING`
任何送往外部的封包 (包含轉發) 最終都會觸發的 Hook
# iptables
## 介紹
- iptables 是 Linux 的一種防火牆軟體
- 透過使用 iptables 提供的指令可以操作 Netfilter,進而實現防火牆的功能
- 都需要超級使用者權限 (`sudo`) 才能使用
## 架構

- 由 **table**、**chain**、**rule** 組成
- iptable"s"/nftable"s",可知有多個 table
- 一個 table 內會有多個 chain
- 一個 chain 內會有多個 rule
## Tables
根據**用途** iptables 有**預先定義**幾個 Table:

### `raw`
- **不常用**的 table
- 主要用於控制哪些封包需要進行連線追蹤 (connection tracking)
### `filter`
- **常用**的 table
- 專門用於封包過濾,根據定義的規則來允許、阻擋封包,是防火牆功能的核心
### `nat`
- **常用**的 table
- 專門用於網路位址轉譯 (NAT) 的 table。主要是修改封包的來源或目的地 IP 地址和通訊埠號 (port),以便在內外網之間進行網路通訊
### `mangle`
- **不常用**的 table
- 主要用來修改封包的標頭或屬性
### `security`
- **不常用**的 table
- 專門與 SELinux (Security-Enhanced Linux) 或 AppArmor 等 Linux 安全模組 (Linux Security Modules, LSM) 配合使用,進行更細粒度的封包控制
> 接下來的內容會聚焦在 `filter` 跟 `nat` 這兩個**常用**的 table 上
## Chains

iptables 中有五個預先定義好的 chain:
- `PREROUTING`
- `INPUT`
- `FORWARD`
- `OUTPUT`
- `POSTROUTING`
:::warning
Q&A
聰明的你有發現可以直接對應到之前講的什麼東西嗎?
:::
### `filter` table 的 chains

| chain | 用途 |
|:--------- |:------------------ |
| `INPUT` | 過濾**進入**的封包 |
| `OUTPUT` | 過濾**送出**的封包 |
| `FORWARD` | 過濾要轉送的封包 |
### `nat` table 的 chains

| chain | 用途 |
|:------------- |:------------------------------------------------------------------------------------- |
| `PREROUTING` | 在封包進入 routing decision 之前,對其進行目的地位址轉譯 (DNAT) |
| `INPUT` | 因為已經過了 routing decision,所以僅能對封包進行本機的 port 轉發,僅在特殊情況使用 |
| `OUTPUT` | 特殊情況下,修改本機生成封包的目的地位址 (DNAT),可用來做透明代理 (transparent proxy) |
| `POSTROUTING` | 在封包離開系統後、發送到網路之前,對其進行來源地位址轉譯 (SNAT) |
## Rules
- 使用者使用 iptables 設定的規則,若符合規則就會對封包執行對應的行為
- rule 是以**有序的**方式進行判斷
- rule 在判斷的時候是短路 (short circuit) 的,意即只要有一條規則被匹配到就不會繼續往接下來的規則進行判斷
### Policy
封包預設的處理方式,若沒有 rule 被匹配到的時候,就會執行 policy 設定的行為
### Target
符合規則的時候,要對封包執行的操作,以下是常見的 target:
| Target | 用途 |
|:------------ |:---------------------------------------------------------------------------------------------------------- |
| `ACCEPT` | 允許封包 |
| `REJECT` | 拒絕封包,且會透過回傳 RST 封包通知發送封包的人連線被拒絕,也因為會有回應所以發送者會知道主機存在 |
| `DROP` | 丟棄封包,不會對發送封包的人有任何回應,會讓連線自己 timeout,因為沒有回應所以發送者也無法判定主機是否存在 |
| `DNAT` | 修改封包的目的地位址 |
| `SNAT` | 修改封包的來源地位址 |
| `REDIRECT` | 修改封包目的地的 port |
| `MASQUERADE` | 動態的 SNAT |
| `LOG` | 記錄到 `syslog` |
:::warning
Q&A
覺得哪些 target 跟 `filter` table 相關? 哪些跟 `nat` table 相關?
:::
:::success
好文分享
- [DROP vs. REJECT](https://www.chiark.greenend.org.uk/~peterb/network/drop-vs-reject) ([中文翻譯](https://www.jyh.im/2024/11/01/Drop-versus-Reject/))
- [iptables REDIRECT vs. DNAT vs. TPROXY](https://gsoc-blog.ecklm.com/iptables-redirect-vs.-dnat-vs.-tproxy/)
:::
## 運作流程
```mermaid
flowchart TB
subgraph NF_INET_PRE_ROUTING
nat_PREROUTING[nat: PREROUTING]
end
subgraph NF_INET_LOCAL_IN
direction TB
nat_INPUT[nat: INPUT] --> filter_INPUT[filter: INPUT]
end
subgraph NF_INET_FORWARD
filter_FORWARD[filter: FORWARD]
end
subgraph NF_INET_LOCAL_OUT
direction TB
nat_OUTPUT[nat: OUTPUT] --> filter_OUTPUT[filter: OUTPUT]
end
subgraph NF_INET_POST_ROUTING
nat_POSTROUTING[nat: POSTROUTING]
end
routing_decision_1{路由判斷}
routing_decision_2{路由判斷}
routing_decision_3{路由判斷}
packet_in((封包進入))
local_process_1[[本機 process]]
local_process_2[[本機 process]]
packet_out((封包傳出))
packet_in --> NF_INET_PRE_ROUTING
NF_INET_PRE_ROUTING --> routing_decision_1
routing_decision_1 -->|封包送往本機| NF_INET_LOCAL_IN
routing_decision_1 -->|轉發封包| NF_INET_FORWARD
NF_INET_LOCAL_IN --> |交給需要的本機資源| local_process_1
NF_INET_FORWARD --> |發送封包| NF_INET_POST_ROUTING
local_process_2 --> |產生封包| routing_decision_2
routing_decision_2 --> NF_INET_LOCAL_OUT
NF_INET_LOCAL_OUT --> routing_decision_3
routing_decision_3 --> |發送封包| NF_INET_POST_ROUTING
NF_INET_POST_ROUTING --> packet_out
```
:::info
這邊主要在基於 Netfilter hooks 再加上 `filter` 跟 `nat` table 的流程,涉及到更多 table 可以看[這邊](https://www.frozentux.net/iptables-tutorial/chunkyhtml/images/tables_traverse.jpg)
:::
### `NF_INET_PRE_ROUTING`
1. `nat` table `PREROUTING` chain
修改封包的目的位址 (e.g. IP, port),來達到**轉送封包**的功能 (DNAT)
### 路由判斷 (routing decisions)
根據 routing table 設定的規則,決定封包要**進入系統**還是要**轉發出去**
:::warning
Q&A
可以使用什麼命令查看 routing table
:::
### `NF_INET_LOCAL_IN`
> 路由判斷後,若封包要**進入本機**就會進到這個 hook
1. `nat` table 的 `INPUT` chain
是否針對進入本機的流量進行 port 轉發
2. `filter` table 的 `INPUT` chain
判斷是否要允許封包進入本機
### `NF_INET_FORWARD`
> 路由判斷後,若封包要**轉發出去**就會進到這個 hook
1. `filter` table 的 `FORWARD` chain
判斷封包是否要允許封包轉發出去
### `NF_INET_LOCAL_OUT`
> 本機產生的封包要從本機送出時會到這個 hook
1. `nat` table 的 `OUTPUT` chain
修改封包的目的地,但不常用
2. `filter` table 的 `OUTPUT` chain
判斷封包是否可以送出
### `NF_INET_POST_ROUTING`
> 只要是要從本機送出的封包,最終都會碰到這個 hook
1. `nat` table 的 `POSTROUTING` chain
修改封包的來源地位址 (SNAT),來讓外部網路回傳的封包能夠正確找到系統的對外位址
## 指令操作
`iptables [-t table] {-A|-C|-D...} chain rule [options...]`
### 查看 `filter` table 內有哪些 chains 跟 rules
`sudo iptables -t filter -L`
:::info
補充一些好用的選項
- `-n` 不嘗試把 IP 位址解析成域名,執行速度會較快
- `-v` 列出更多資訊
- `--line-numbers` 顯示 rule 的編號,在新增或是刪除時可能需要
:::


:::warning
Q&A
查看 `mangle` table 的 `PREROUTING` chain 內的規則的指令
:::
### 修改 `filter` table `INPUT` chain 的 policy
`sudo iptables -t filter -P INPUT DROP`

:::danger
某些情況為了系統安全,會將 policy 設為 DROP,設定的 rule 就會變成哪些封包是能夠被允許的,變成像是**白名單**,但會建議說在 rule 都確定沒問題之後,在規則的最下方再加上一 DROP 的規則,可以有效的避免一些操作失誤
像是在 ssh 到遠端主機進行 iptables 設定時,要是手殘 flush 了所有規則,此時若 policy 為 DROP,ssh 的 connection 也會直接被切斷
:::
:::warning
Q&A
把 policy 改回 `ACCEPT` 的指令
:::
### 設定 rule
`sudo iptables [-t table] {-A|-I} chain -j target`
- `-A, --append`: 會將規則加在舊有規則的**後面**
- `-I, --insert`: 會將規則加在舊有規則的**前面**
- `-D, --delete`: 刪除指定的 rule
- `-R, --replace`: 取代指定的 rule
- `-F, --flush`: 刪除指定的 chain 或是 table 內的所有 rules
- `-j, --jump`: 指定符合規則時會執行哪個 target
#### 針對網路介面卡設定規則
:::warning
Q&A
用什麼指令可以列出電腦網卡跟 IP 的資訊
:::
- `-i, --in-interface`: 封包進入系統時經過的網卡名稱
`sudo iptables -t filter -I INPUT -i eth0 -j ACCEPT`
- `-o, --out-interface`: 封包傳出系統時經過的網卡名稱
`sudo iptables -t filter -I OUTPUT -o eth0 -j ACCEPT`
#### 針對 IP 位址設定規則
- `-s, --source`: 封包的來源 IP 位址
`sudo iptables -t filter -I INPUT -s 192.168.56.11 -j DROP`
:::warning
Q&A
想想為什麼我這邊擋了 INPUT 但我嘗試去 `ping 192.168.56.11` 的時候也是會沒有反應
:::
- `-d, --destination`: 封包的目的 IP 位址
`sudo iptables -t filter -I OUTPUT -d 192.168.56.11 -j DROP`
- `!`: 用在 `-s` 跟 `-d` 之前,指不是這個 IP 的時候
`sudo iptables -t filter -I INPUT ! -d 192.168.56.11 -j DROP`
#### 針對協定設定規則
- `-p, --protocol`: 封包使用的協定
`sudo iptables -t filter -I INPUT -p ICMP -j DROP`
#### 針對 port 設定規則
> 在 `-p` 指定是 `tcp` 或是 `udp` 時可以使用
- `--sport`: 封包來源的 port
`sudo iptables -I OUTPUT -p tcp --sport 80 -j ACCEPT`
- `--dport`: 封包目的的 port
`sudo iptables -I INPUT -p tcp --dport 80 -j ACCEPT`
#### 刪除 rule
- 刪除 `filter` table `OUTPUT` chain 的第 1 個 rule
`sudo iptables -t filter -D OUTPUT 1`
- 清空 `filter` table `INPUT` chain 所有的 rule
`sudo iptables -t filter -F INPUT`
> 若後面不加 chain 會清空整張 table 內的所有規則
:::info
**conntrack**
內建在 netfilter subsystem 用於**記錄連線狀態**的模組,有以下幾種狀態
| 名稱 | 記錄的狀態 |
| ------------- |:----------------------------------- |
| `NEW` | 新建立的連線 |
| `ESTABLISHED` | 已建立的連線、NEW 封包的回應封包 |
| `RELATED` | 新的連線,但與已建立的連線相關 |
| `INVALID` | 無效,無法識別狀態的封包 |
| `UNTRACKED` | 在 `raw` table 被標示關閉追蹤的封包 |

但因為需要載入額外的模組,以及儲存這些連線的狀態,所以會有額外的記憶體開銷
**demo**
情境: 我們想阻止來自特定 IP(192.168.56.11) 的所有請求,但希望主機還是能夠對它發送請求,並成功接收到它的回應

如之前所述,可以先使用這個 iptables 規則擋住來自 192.168.56.11 的所有流量
`sudo iptables -t filter -I INPUT -s 192.168.56.11 -j DROP`
但這樣也會同時阻擋掉 192.168.56.11 的回應,這個情況就可以利用 conntrack 的狀態追蹤功能,單獨允許回應的封包
`sudo iptables -t filter -I INPUT -m conntrack --ctstate ESTABLISHED -j ACCEPT`
封包的流程如下:
1. 當我們向 192.168.56.11 發送 ping 時,conntrack 會記錄這條連線的狀態。
2. 當 192.168.56.11 回應我們的 ping 時,回應流量會被標記為 `ESTABLISHED` 狀態
3. 防火牆會檢查這個狀態,發現回應是我們允許的 `ESTABLISHED` 狀態,因此允許其通過
:::warning
Q&A
若是不想用 conntrack,我們可以使用
`sudo iptables -t filter -I INPUT -s 192.168.56.11 -p icmp --icmp-type 0 -j ACCEPT`
這條規則去允許 192.168.56.11 對於 ping 的回覆,但這樣會有什麼樣的問題?
:::
:::info
Stateful Firewall vs. Stateless Firewall
Stateless Firewall 無狀態防火牆,**不會記錄封包的狀態**,就會造成前面 QA 關於 ping 的狀況
而 Stateful Firewall 有狀態防火牆,就是**會記錄封包狀態**的防火牆,可以根據連線的狀態設定更靈活的防火牆規則
:::
### 儲存 iptables 的設定
直接使用 `iptables` 的指令不會自動儲存在系統中,關機後就會消失,`iptables` 有提供幾個工具可以使用
#### `iptables-save`
儲存目前的 `iptables` 的設定
`iptables-save > 檔案路徑`
#### `iptables-restore`
使用設定檔恢復 `iptables` 的設定
`iptables-restore < 檔案路徑`
#### `iptables-apply`
用來測試新的 iptables 規則,套用新規則時若太長時間 (預設是 10 秒) 沒有回應就會自動恢復成舊的設定
`iptables-apply 檔案路徑`
> 預設的路徑是: `/etc/network/iptables.up.rules`
# Lab
## NAT

- 網路位址轉譯 (Network Address Translation)
- 改變網路封包中的 IP 位址資訊,使不同網路之間能夠進行通訊
- 可以節省 IP 位置
- 主要有 **SNAT (Source Network Address Translation)** 跟 **DNAT (Destination Network Address Translation)**
### SNAT
#### 情境: 內網主機 (192.168.1.100) 要連線到 yahoo.com.tw

1. 內網主機 192.168.1.100 發送封包給 Firewall (NAT 主機)
2. Firewall 尋找路由表,發現目的地不在直接連接的網路,也沒有紀錄指定該往哪裡送,所以透過路由表找到「default gateway」並使用對應的網路介面卡
3. 因目前封包的 source IP 是內網 IP,直接送出去的話會造成對方的錯亂,誤會來源位置,所以,會在 `POSTROUTING` chain 把來源 IP 修改成 Firewall 對外的 IP,這時 **NAT translation table** 把這兩個來源 IP、port 的對應關係記錄起來,之後回應封包傳回來時就能知道要傳給內網的哪台主機


4. 封包從外部網路傳回來,目的地是 Firewall 的外網 IP
5. 在 `PREROUTING` chain 根據 **NAT translation table** 的紀錄更改封包目的地
`sudo iptables -t nat -I PREROUTING -o {外網網卡} -j SNAT --to {NAT 主機的外網網卡 IP}`
6. 封包轉發給內網主機 (192.168.1.100)
:::info
`MASQUERADE`
有時候 Firewall 取得的外網 IP 不會是固定的 IP,可能會隨時間變動,而若是這種情況有可能會需要頻繁地去更動 SNAT 的規則,`MASQUERADE` 就是為了因應這樣的情況,他會自動找尋能連接上外網的網路介面卡,然後使用那張網卡的 IP 位置當作 SNAT 的來源 IP
:::
### DNAT
#### 情境: 在內網主機架了 Web Server,要讓外網能夠連進來

1. 外網主機先向 Firewall 發送請求
2. Firewall 收到封包後,在 `PREROUTING` chain 將目的 IP 從 Firewall 的外網 IP 改成內網 Web Server 的 IP,然後也會在 **NAT translation table** 把這兩個目的 IP、port 的對應關係記錄起來
`sudo iptables -t nat -I PREROUTING -p tcp --dport 80 -j DNAT --to 192.168.1.210:80`
3. 封包會路由到內網網卡 (192.168.1.2),然後再傳送到目的地 (192.168.1.210)

### 統整
| | SNAT | DNAT |
|:-------- |:---------------------- |:------------------------ |
| 功用 | 更改封包的來源 IP | 更改封包的目的 IP |
| 使用場景 | 讓內網主機可以連線外網 | 讓外網能主動連到內網主機 |
### Demo

#### 準備環境
**Firewall (NAT 主機)**
要先開啟 Linux 轉發封包的功能
1. `sudo vim /etc/sysctl.conf`
2. 把這行的註解取消
```
# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward=1
```
3. `sudo sysctl -p`
**Server (內網的 Web Server)**
1. `sudo ip route del default`
2. `sudo ip route add default via 10.0.0.10 dev eth1`
#### iptables 規則設定
**功能要求**
1. 將內網主機要送往外網的封包,透過 Firewall 轉送到外網,且回應的封包,能夠回傳給內網主機
`sudo iptables -t nat -A POSTROUTING -o eth1 -j SNAT --to-source 192.168.56.21`
或使用 `MASQUERADE`
`sudo iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE`
2. 將外網連線到 Firewall 80 port 的封包,轉發到內網主機 8080 port 上的 Web Server
`sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 10.0.0.11:8080`

## DMZ

**功能要求**
- 外網可以直接連線 DMZ,但不能直接連線 LAN
- DMZ 可以直接連線外網,但不能直接連線 LAN
- LAN 可以直接連線外網,也可以直接連線 DMZ
**環境**
- Firewall 主機上設定防火牆
- DMZ 中有一台**對外開放**的 Web Server 主機,80 port 上有 web service
- LAN 中有一台**不對外開放**的 Student 主機
- 外網透過連線到 Firewall 主機的 8080 port,可以連線到 Web Server 主機的 80 port
- `INPUT`、`FORWARD`、`OUPUT` 的 policy 都是 `ACCEPT`
### 準備環境
**Firewall (NAT 主機)**
要先開啟 Linux 轉發封包的功能
1. `sudo vim /etc/sysctl.conf`
2. 把這行的註解取消
```
# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward=1
```
3. `sudo sysctl -p`
**DMZ 和 LAN 的 default gateway (設成 Firewall 主機 IP)**
1. `sudo route del default`
2. `sudo route add default gw {Firewall IP}`
### 設定規則
- 外網能透過 Firewall 連到 DMZ 的 web server
```shell
sudo iptables -t nat -A PREROUTING -i {外網網卡} -p tcp --dport 8080 -j DNAT --to {Web Server IP}:80
```
- 同意該封包轉送
```shell
sudo iptables -A FORWARD -i {外網網卡} -d {Web Server IP} -p tcp --dport 80 -j ACCEPT
```
- DMZ 能夠連到外網
```shell
sudo iptables -I FORWARD -i {DMZ 網卡} -o {外網網卡} -j ACCEPT
sudo iptables -t nat -I POSTROUTING -o {外網網卡} -j MASQUERADE
```
- LAN 能夠連到外網
```shell
sudo iptables -I FORWARD -i {LAN 網卡} -o {外網網卡} -j ACCEPT
# 出去的封包做 MASQUERADE 上面已經設過,這裡就不再設定
```
- LAN 能夠連到 DMZ
```shell
sudo iptables -I FORWARD -i {LAN 網卡} -o {DMZ 網卡} -j ACCEPT
```
- 允許轉送已建立或相關的連線
```shell
sudo iptables -I FORWARD -m conntrack --ctstate "ESTABLISHED,RELATED" -j ACCEPT
```
- 拒絕其他所有進入 Firewall 主機的連線
```shell
sudo iptables -A INPUT -j DROP
```
- 拒絕轉送其他所有連線
```shell
sudo iptables -A FORWARD -j DROP
```
:::warning
Q&A
這裡為什麼要用 `-A`
:::
<!--
# nftables
:::info
nftables 是作為 iptables 的替代,較新的 Linux 發行版都逐漸向 nftables 靠攏,但這學期上課使用的 Ubuntu 22.04 (Jammy Jellyfish) 還是預設使用 iptables (但到 24.04 就預設使用 nftables),所以這次課程內容以 iptables 為準
:::
:::info
nftables 補充
nftables 維持了 tables、chains、rules 的架構,但已捨棄預先定義的上述的 iptables 的資源,只依照自訂的 chain 要註冊在哪個 netfilter hook 上,還有設定的優先級來決定規則順序
:::
-->
# UFW (Uncomplicated Firewall)
使用 iptables 或是 nftables 作為後端,提供比較簡單的介面
### 安裝 (通常 Ubuntu 已經預設安裝)
`sudo apt install ufw`
### 開/關
`sudo ufw enable`
`sudo ufw disable`
### 查看防火牆狀態
`sudo ufw status`
`sudo ufw status verbose`
`sudo ufw status numbered`
### 設定預設規則
`sudo ufw default allow`
`sudo ufw default deny`
### 針對服務或是 port 設定規則
`sudo ufw allow ssh`
`sudo ufw deny 80`
> 針對一個範圍的 port & protocol
`sudo ufw allow 6000:6007/tcp`
### 針對 IP
`sudo ufw allow from 192.168.11.10`
> 針對網段
`sudo ufw deny from 192.168.11.0/24`
> 拒絕某 IP 連到某 port
`sudo ufw deny from 192.168.11.7 to any port 22`
### 刪除規則
`sudo ufw delete 3`
> 重設
`sudo ufw reset`