自己动手实现被动式SQL注入扫描器
字数 1234 2025-08-25 22:58:46
被动式SQL注入扫描器实现教程
一、被动扫描器概述
1.1 被动扫描与主动扫描的区别
被动式自动化安全测试与主动式自动化安全测试的主要区别在于获取测试数据源的方式:
| 测试方式 | 主动式自动化安全测试 | 被动式自动化安全测试 |
|---|---|---|
| 测试覆盖率 | 低,依赖于爬虫爬取质量及范围 | 高,可通过数据源干预测试目标 |
| 速度 | 低,需要主动爬取数据 | 高,无需主动探测数据 |
| 开发难度 | 复杂,需实现爬虫和测试模块 | 相对简单,无需爬虫模块 |
| 测试精准度 | 较低,主要测试通用漏洞 | 较高,可定制复杂扫描项目 |
1.2 本扫描器架构
本被动式SQL注入扫描器包含两大核心模块:
- 数据采集模块:基于Netty实现的HTTP代理服务器
- 测试模块:与SQLMap API交互进行SQL注入检测
二、环境准备
2.1 基础环境
- JDK 1.8.0.201+
- Eclipse IDE
- Tomcat服务器
2.2 依赖库
Maven配置(pom.xml):
<dependencies>
<!-- Netty网络框架 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.37.Final</version>
</dependency>
<!-- FastJSON库 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.59</version>
</dependency>
</dependencies>
2.3 SQLMap API
- 安装Python环境
- 安装SQLMap工具
- 启动SQLMap API服务
三、代理服务器实现
3.1 配置参数
public class Properties {
public static int ProxyPort = 8889; // 代理服务器监听端口
public final static HttpResponseStatus SUCCESS = new HttpResponseStatus(200, "Connection established");
}
3.2 代理服务器主类
public class StartProxy {
public static void main(String[] args) {
startProxy();
}
private static void startProxy() {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup(2);
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast("httpCodec", new HttpServerCodec());
ch.pipeline().addLast("httpObject", new HttpObjectAggregator(65536));
ch.pipeline().addLast("serverHandle", new HttpProxyServerHandle());
}
});
ChannelFuture f = b.bind(Properties.ProxyPort).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
3.3 请求处理类
public class HttpProxyServerHandle extends ChannelInboundHandlerAdapter {
private ChannelFuture cf;
private String host;
private int port;
@Override
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
if (msg instanceof FullHttpRequest) {
forwardAndCheckHttpRequest(ctx, msg); // 处理HTTP请求
} else {
forwardButNoCheck(ctx, msg); // 转发其他消息
}
}
private void forwardButNoCheck(final ChannelHandlerContext ctx, final Object msg) {
if (cf == null) {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(ctx.channel().eventLoop())
.channel(ctx.channel().getClass())
.handler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx0, Object msg) {
ctx.channel().writeAndFlush(msg);
}
});
}
});
cf = bootstrap.connect(host, port);
cf.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) {
if (future.isSuccess()) {
future.channel().writeAndFlush(msg);
} else {
ctx.channel().close();
}
}
});
} else {
cf.channel().writeAndFlush(msg);
}
}
private void forwardAndCheckHttpRequest(final ChannelHandlerContext ctx, final Object msg) throws Exception {
FullHttpRequest request = (FullHttpRequest) msg;
getHostAndPort(request);
if ("CONNECT".equalsIgnoreCase(request.method().name())) {
httpsCONNECT(ctx); // HTTPS连接处理
return;
}
FullHttpRequest payloadRequest = request.copy();
Payload payload = new Payload(payloadRequest);
payload.start(); // 启动测试线程
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(ctx.channel().eventLoop())
.channel(ctx.channel().getClass())
.handler(new HttpProxyInitializer(ctx.channel()));
ChannelFuture cf = bootstrap.connect(host, port);
cf.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) {
if (future.isSuccess()) {
future.channel().writeAndFlush(msg);
} else {
ctx.channel().close();
}
}
});
}
private void httpsCONNECT(ChannelHandlerContext ctx) {
HttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, Properties.SUCCESS);
ctx.writeAndFlush(response);
ctx.pipeline().remove("httpCodec");
ctx.pipeline().remove("httpObject");
}
private void getHostAndPort(FullHttpRequest request) {
String host = request.headers().get("host");
String[] temp = host.split(":");
int port = 80;
if (temp.length > 1) {
port = Integer.parseInt(temp[1]);
} else if (request.uri().indexOf("https") == 0) {
port = 443;
}
this.host = temp[0];
this.port = port;
}
}
四、测试模块实现
4.1 Payload处理类
public class Payload extends Thread {
FullHttpRequest request;
public Payload(FullHttpRequest request) {
this.request = request;
}
public void run() {
try {
FullHttpRequest request = this.request.copy();
SQLPayload sqlpayload = new SQLPayload();
sqlpayload.startSqlInjectTest(request);
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.2 SQL注入测试主类
public class SQLPayload {
public void startSqlInjectTest(FullHttpRequest request) throws Exception {
SQLMAPApi sqlmapapi = new SQLMAPApi();
sqlmapapi.createTask(); // 创建SQLMap任务
sqlmapapi.startScan(request); // 开始扫描
sqlmapapi.status(); // 查询状态
sqlmapapi.result(); // 获取结果
}
}
4.3 SQLMap API交互类
public class SQLMAPApi {
String taskid;
String uri = null;
boolean isEnd = false;
int count = 1;
// 创建新任务
public void createTask() throws Exception {
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) {
socketChannel.pipeline()
.addLast("codec", new HttpClientCodec())
.addLast("httpAggregator", new HttpObjectAggregator(512 * 1024))
.addLast(new NewTaskResponse());
}
});
Channel channel = bootstrap.connect(Properties.SQLMapApiAdr, Properties.SQLMapApiPort).sync().channel();
URI uri = new URI("/task/new");
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri.toASCIIString());
// 设置请求头...
channel.writeAndFlush(request);
channel.closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
}
}
// 开始扫描
public void startScan(FullHttpRequest request) {
String filePath = Properties.requestFileSaveBasePath + taskid;
Result.saveFullHttpRequestToFile(filePath, request);
uri = request.uri();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) {
socketChannel.pipeline()
.addLast("codec", new HttpClientCodec())
.addLast("httpAggregator", new HttpObjectAggregator(512 * 1024))
.addLast(new StartTestResponse());
}
});
Channel channel = bootstrap.connect(Properties.SQLMapApiAdr, Properties.SQLMapApiPort).sync().channel();
Start start = new Start();
start.setUrl("http://" + Properties.TomcatServerIP + ":" + Properties.TomcatPort + Properties.requestFileSavePath + taskid);
String jsonStr = JSON.toJSONString(start);
URI uri = new URI("/scan/" + taskid + "/start");
FullHttpRequest requestToSQLMAPAPI = new DefaultFullHttpRequest(
HttpVersion.HTTP_1_1,
HttpMethod.POST,
uri.toASCIIString(),
Unpooled.wrappedBuffer(jsonStr.getBytes("UTF-8")));
// 设置请求头...
channel.writeAndFlush(requestToSQLMAPAPI).sync();
channel.closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
// 查询状态
public String status() {
while (!isEnd) {
long startTime = System.currentTimeMillis();
do {} while (System.currentTimeMillis() < (startTime + 1000));
count++;
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) {
socketChannel.pipeline()
.addLast("codec", new HttpClientCodec())
.addLast("httpAggregator", new HttpObjectAggregator(512 * 1024))
.addLast(new StatusResponse());
}
});
Channel channel = bootstrap.connect(Properties.SQLMapApiAdr, Properties.SQLMapApiPort).sync().channel();
URI uri = new URI("/scan/" + taskid + "/status");
FullHttpRequest requestToSQLMAPAPI = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri.toASCIIString());
// 设置请求头...
channel.writeAndFlush(requestToSQLMAPAPI).sync();
channel.closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
return "task_Finish";
}
// 获取结果
public void result() {
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) {
socketChannel.pipeline()
.addLast("codec", new HttpClientCodec())
.addLast("httpAggregator", new HttpObjectAggregator(512 * 1024))
.addLast(new DataResponse());
}
});
Channel channel = bootstrap.connect(Properties.SQLMapApiAdr, Properties.SQLMapApiPort).sync().channel();
URI uri = new URI("/scan/" + taskid + "/data");
FullHttpRequest requestToSQLMAPAPI = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri.toASCIIString());
// 设置请求头...
channel.writeAndFlush(requestToSQLMAPAPI).sync();
channel.closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
// 内部响应处理类...
private class NewTaskResponse extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof HttpContent) {
HttpContent httpContent = (HttpContent) msg;
String json = httpContent.content().toString(0, httpContent.content().capacity(), Charset.defaultCharset());
JSONObject obj = JSON.parseObject(json);
taskid = (String) obj.get("taskid");
}
}
}
// 其他响应处理类...
}
五、结果输出模块
5.1 结果处理类
public class Result {
private static BufferedWriter bw;
public static void init() {
try {
Result.bw = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(new File(Properties.resultFilePath)), "UTF-8"));
} catch (Exception e) {
e.printStackTrace();
}
}
public static void writeToFile(String content) {
try {
bw.write(content);
bw.newLine();
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void saveResultToFile(String filePathAndName, String result) {
FileWriter fw = null;
BufferedWriter bufw = null;
try {
File file = new File(filePathAndName);
if (!file.exists()) {
String filePath = filePathAndName.substring(0, filePathAndName.lastIndexOf("/"));
File folder = new File(filePath);
folder.mkdirs();
file.createNewFile();
}
fw = new FileWriter(file, true);
bufw = new BufferedWriter(fw);
bufw.write(result);
bufw.newLine();
bufw.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭流...
}
}
public static void saveFullHttpRequestToFile(String filePathAndName, FullHttpRequest result) {
FileWriter fw = null;
BufferedWriter bufw = null;
try {
File file = new File(filePathAndName);
if (!file.exists()) {
String filePath = filePathAndName.substring(0, filePathAndName.lastIndexOf("/"));
File folder = new File(filePath);
folder.mkdirs();
file.createNewFile();
}
fw = new FileWriter(file, true);
bufw = new BufferedWriter(fw);
String top = "";
String header = "";
String content = "";
String uri = result.uri();
for (int i = 0; i < 2; i++) {
uri = uri.substring(uri.indexOf("/") + 1);
}
uri = uri.substring(uri.indexOf("/"));
top += result.method() + " " + uri + " " + result.protocolVersion();
Set<String> names = result.headers().names();
Iterator<String> it = names.iterator();
while (it.hasNext()) {
String name = it.next();
header += name + ": " + result.headers().get(name) + "\r\n";
}
content = result.content().toString(0, Integer.valueOf(result.headers().get("Content-Length")), Charset.defaultCharset());
String dataPackage = top + "\r\n" + header + "\r\n" + content;
bufw.write(dataPackage);
bufw.newLine();
bufw.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭流...
}
}
}
六、使用流程
-
启动扫描器组件:
- 启动SQLMap API作为测试模块
- 启动Tomcat作为文件服务器及被测试网站
- 运行StartProxy类启动代理服务器
-
配置浏览器:
- 设置浏览器代理为localhost:8889(默认端口)
- 开始浏览目标网站
-
查看结果:
- 扫描结果会自动保存到指定文件
- 包含漏洞类型、问题链接、请求数据包和SQLMap响应
七、关键点总结
- Netty代理服务器:负责拦截和转发HTTP请求,同时将请求副本发送给测试模块
- SQLMap API集成:通过HTTP与SQLMap API交互,实现自动化SQL注入检测
- 异步处理:使用多线程处理测试任务,不影响正常请求的转发
- 结果记录:完整保存请求数据包和测试结果,便于后续分析
- HTTPS支持:能够处理HTTPS连接的CONNECT方法
八、扩展建议
- 增加更多漏洞检测:除SQL注入外,可扩展XSS、CSRF等漏洞检测
- 优化性能:实现请求去重、批量测试等优化
- 增强报告功能:生成更友好的HTML报告
- 增加配置管理:通过配置文件管理代理端口、SQLMap API地址等参数
- 实现分布式扫描:支持多节点协同扫描大型网站
通过本教程实现的被动式SQL注入扫描器,可以在不影响正常浏览的情况下,自动检测网站中的SQL注入漏洞,是Web安全测试的有效工具。