java sql注入包_JDBC(增删改查)与SQL注入攻击、BeanUtils工具包使用

目    录(本篇字数:2841)

SQL注入攻击

SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令,从而利用系统的 SQL 引擎完成恶意行为的做法 。

SQL注入攻击,指对数据库进行攻击的手段之一。例如:在表单提交时候,需要操作数据库去匹配用户信息时,在 sql 语句中注入一些代码,去获取数据库信息或权限。接着,我们来看看在SQL注入代码,成功入侵数据库的案例。

Statement :罪魁祸首

Statement 是 Jdbc 提供的一个可以操作数据库的方法,通常用于增、删、改、查;用它的 statement.executeQuery(sql) 方法可以执行数据库语句。

案例关键代码如下:

public class LoginServlet extends HttpServlet {

@Override

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

req.setCharacterEncoding("utf-8");

Connection conn = null;

Statement statement = null;

ResultSet result = null;

PrintWriter out = resp.getWriter();

String user = req.getParameter("user");

String pwd = req.getParameter("pwd");

System.out.println("请求:user:" + user);

System.out.println("请求:pwd:" + pwd);

try {

conn = JdbcUtils.getConnection();

statement = conn.createStatement();

String sql = "SELECT * FROM user WHERE(user_name='" + user + "' AND password='" + pwd + "')";

System.out.println(sql);

result = statement.executeQuery(sql);

//result 返回 true 说明匹配成功

if (result.next()) {

out.println("user,login succeed");

} else {

out.println("login failed");

}

} catch (Exception e) {

e.printStackTrace();

} finally {

JdbcUtils.release(statement, conn, result);

}

}

}

如上代码中的 sql 语句: SELECT * FROM user WHERE(user_name='" + user + "' AND password='" + pwd + "'),看似没有任何问题,但配合着 Statement 就会出现漏洞了。接着我们来测试 SQL 注入。

测试 SQL 注入攻击

我的数据库有一张 user 表如下:

7e10abe81e6438041b74c8a4b8755ffd.png

我们使用表中的任何 user_name 和 password 都是可以登入的。接着,是用注入代码的方式: user 和 密码 都为

1' OR '1'='1,也是可以登入成功的。

9e6f105c0b4fd734381ad9ce095eb42b.png

d83f8cbeeb41d3ad49e5f52a2f3ba550.png

sql 语句变成如上图控制台打印出来的那样,我们拿到数据库软件中跑一下,也是可以查出来。后面的 ‘1’ = ‘1’ 为真,这句 sql 语句其实变成了 SELECT * FROM user ,结果也就是查询出 user 所有数据信息。

a7b49c9d71bbf30c646472c5f52d7c75.png

漏洞原因

出现这种情况的原因是,每次在请求登入的时候时, Statement 在执行 sql 语句时都会把表单提交来的 用户名 和 密码 都重新拼接到 sql 语句中,然后在执行。这样会导致 sql 语句结构发生改变,攻击者利用这种方式攻击数据库就称为 SQL注入攻击。

PreparedStament 化解 SQL 注入

PreparedStament 是 Statement 的子接口,它最大特点就是预处理 sql 语句。它在实例化时,必须传入 sql 语句,这与Statement 就有所区别。sql 语句中使用占位符 ? 来代替未知参数值,继而通过 preparedStatement.setXXX() 方法添加进 sql 语句中,这样就不会改变 sql 的结构,就能防止 SQL 注入攻击了。

代码修改为:

public class LoginServlet extends HttpServlet {

@Override

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

req.setCharacterEncoding("utf-8");

Connection conn = null;

PreparedStatement preparedStatement = null;

ResultSet result = null;

PrintWriter out = resp.getWriter();

String user = req.getParameter("user");

String pwd = req.getParameter("pwd");

System.out.println("请求:user:" + user);

System.out.println("请求:pwd:" + pwd);

try {

conn = JdbcUtils.getConnection();

String sql = "SELECT * FROM user WHERE(user_name=? AND password=?)";

preparedStatement = conn.prepareStatement(sql);

System.out.println(sql);

preparedStatement.setString(1, user);

preparedStatement.setString(2, pwd);

result = preparedStatement.executeQuery();

//result 返回 true 说明匹配成功

if (result.next()) {

out.println("user,login succeed");

} else {

out.println("login failed");

}

} catch (Exception e) {

e.printStackTrace();

} finally {

JdbcUtils.release(preparedStatement, conn, result);

}

}

}

测试结果,使用 1' OR '1'='1无法登入。

a018e2bbe0b92ed299564e3d799314aa.png

所以,在日常开发中一般都使用 PreparedStatement ,它不仅可以防止 SQL 注入,同时能够预编译 sql  语句,效率更佳。

JDBC 增、删、改、查

数据库为上文 user 表(图在上面)

JDBC 连接工具类public class JdbcUtils {

public static Connection getConnection() throws Exception {

String driver = null;

String jdbcUrl = null;

String user = null;

String password = null;

InputStream inputStream = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");

Properties properties = new Properties();

properties.load(inputStream);

driver = properties.getProperty("driver");

jdbcUrl = properties.getProperty("jdbcUrl");

user = properties.getProperty("user");

password = properties.getProperty("password");

Class.forName(driver);

return (Connection) DriverManager.getConnection(jdbcUrl, user, password);

}

public static void release(Statement statement, Connection conn, ResultSet result) {

try {

if (statement != null) {

statement.close();

}

} catch (Exception e) {

e.printStackTrace();

}

try {

if (conn != null) {

conn.close();

}

} catch (Exception e) {

e.printStackTrace();

}

try {

if (result != null) {

result.close();

}

} catch (Exception e) {

e.printStackTrace();

}

}

}

增、删、改方法public class JdbcTest {

/**

* 提供: 增 、删、改3个功能的通用方法

*

* @param sql

* @param args sql 中占位符的值,可以用多个逗号隔开

*/

public void update(String sql, Object... args) throws Exception {

Connection connection = null;

PreparedStatement preparedStatement = null;

try {

connection = JdbcUtils.getConnection();

System.out.println(sql);

preparedStatement = connection.prepareStatement(sql);

for (int i = 0; i < args.length; i++) {

preparedStatement.setObject(i + 1, args[i]);

}

preparedStatement.executeUpdate();

} catch (Exception e) {

e.printStackTrace();

} finally {

JdbcUtils.release(preparedStatement, connection, null);

}

}

@Test

public void insert() throws Exception {

String sql = "INSERT INTO user(user_name,sex,user_role,password,id_card,register_time)"

+ " VALUES(?,?,?,?,?,?)";

update(sql,"小红","女","VIP用户","xh","654...","2019-01-25 14:02:03");

}

@Test

public void delete() throws Exception {

// 删除 user 表中 user_name 为小红的信息

String sql = "DELETE FROM user WHERE user_name=?";

update(sql,"小红");

}

@Test

public void modify() throws Exception {

// user 表中 user_name 为小红 的用户,将其 user_role 改为 普通用户

String sql = "UPDATE user set user_role=? WHERE user_name=?";

update(sql,"普通用户","小红");

}

}

通用查询方法封装

利用面向对象编程的思想,我们新建了一个 User 类,此类变量为 user 表对应的列名,代码如下:

package com.xww;

public class User {

public String userName;

public String password;

public String registerTime;

public String sex;

public String userRole;

public String idCard;

public User(String userName, String password, String registerTime,

String sex, String userRole, String idCard) {

super();

this.userName = userName;

this.sex = sex;

this.userRole = userRole;

this.password = password;

this.idCard = idCard;

this.registerTime = registerTime;

}

public String getUserName() {

return userName;

}

public void setUserName(String userName) {

this.userName = userName;

}

public String getSex() {

return sex;

}

public void setSex(String sex) {

this.sex = sex;

}

public String getUserRole() {

return userRole;

}

public void setUserRole(String userRole) {

this.userRole = userRole;

}

public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

public String getIdCard() {

return idCard;

}

public void setIdCard(String idCard) {

this.idCard = idCard;

}

public String getRegisterTime() {

return registerTime;

}

public void setRegisterTime(String registerTime) {

this.registerTime = registerTime;

}

@Override

public String toString() {

return "User [userName=" + userName + ", password=" + password + ", registerTime=" + registerTime + ", sex="

+ sex + ", userRole=" + userRole + ", idCard=" + idCard + "]";

}

}

那么查询 user 表的方法就可以写为这样(不够灵活):

public User query(String sql) {

Connection connection = null;

PreparedStatement preparedStatement = null;

ResultSet resultSet = null;

User user = null;

try {

connection = JdbcUtils.getConnection();

System.out.println(sql);

preparedStatement = connection.prepareStatement(sql);

resultSet = preparedStatement.executeQuery();

while (resultSet.next()) {

user = new User(resultSet.getString(2), resultSet.getString(3), resultSet.getString(4),

resultSet.getString(5), resultSet.getString(6), resultSet.getString(7));

System.out.println(user.toString());

}

} catch (Exception e) {

e.printStackTrace();

} finally {

JdbcUtils.release(preparedStatement, connection, resultSet);

}

return user;

}

d8962625631648568d004b9249bb109c.png

这样的确可以获取到 user 表,但是这个方法并不能通用。例如:我想查询 student 表时,student 表中字段名与 user 表肯定不一样,这样就会产生错误。所以,这种情况就可以利用 Java 反射来实现。可是,User 类中的变量相当于 user 表中的列名,如果让 User 类将变量名强制写成 user 表中一样,那就显得很局限,不能够通配。那么,就得用到 JDBC 提供的另一个接口了。

(1)ResultSetMetaData 接口

它用于获取 ResultSet 结果集的元数据,用处是在于从查出的结果集中获取相应的列数、列名等。常用方法如下:

getColumnName(int column):获取指定列的名称

getColumnCount():返回当前 ResultSet 对象中的列数。

getColumnTypeName(int column):检索指定列的数据库特定的类型名称。

getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。

isNullable(int column):指示指定列中的值是否可以为 null。

isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。

(2)利用 sql 语句添加别名的方式

比如我要查询 user 表中 ‘小王’ 的详细信息,原本的 sql 语句:

>  SELECT * FROM user WHERE user_name='小王'

要改为添加别名的方式,注意:别名一定是与User类中定义变量名一致,否则在反射时将找不到字段:

>  SELECT user_name userName,password password,register_time registerTime,sex sex,user_role userRole,id_card idCard FROM user WHERE user_name='小王'

查出来的结果如下图:

70f02a04b14da483e3f8f2a7b61be88c.png

字段名都与 User 类中的变量名相一致了。

(3)利用 Java 反射给 User 类中的每个属性变量赋予值 .

那么,经过这几翻的处理,我们的 User 类终于可以匹配数据库了。这个虽说可以实现通用查询,映射到实体类中,但明显在 sql 语句中那么点缺乏简练。整个查询代码如下:

public class JdbcTest {

/**

* 利用 Java 反射机制,写的一个通用查询方法

*

* @param sql

*/

public T query(Class clazz, String sql, Object... args) {

T entity = null;

Connection connection = null;

PreparedStatement preparedStatement = null;

ResultSet resultSet = null;

User user = null;

try {

connection = JdbcUtils.getConnection();

//System.out.println(sql);

preparedStatement = connection.prepareStatement(sql);

if (args != null) {

for (int i = 0; i < args.length; i++) {

preparedStatement.setObject(i + 1, args[i]);

}

}

resultSet = preparedStatement.executeQuery();

ResultSetMetaData resultSetMetaData = resultSet.getMetaData();

Map values = new HashMap();

if (resultSet.next()) {

for (int i = 0; i < resultSetMetaData.getColumnCount(); i++) {

String columnLable = resultSetMetaData.getColumnLabel(i + 1);

Object columnValue = resultSet.getObject(i + 1);

values.put(columnLable, columnValue);

}

}

System.out.println("--->数据库:"+values);

if (values.size() > 0) {

entity = (T) clazz.newInstance();

for (Entry entry : values.entrySet()) {

String fieldName = entry.getKey();

Object fieldValue = entry.getValue();

Field field = clazz.getDeclaredField(fieldName);

field.setAccessible(true);

field.set(entity, fieldValue);

}

}

} catch (Exception e) {

e.printStackTrace();

} finally {

JdbcUtils.release(preparedStatement, connection, resultSet);

}

return entity;

}

@Test

public void qurey() {

String sql = "SELECT user_name userName,password password,register_time registerTime,"

+ "sex sex,user_role userRole,id_card idCard FROM user WHERE user_name=?";

User user = query(User.class, sql, "小王");

System.out.println("--->反射到 User 类中:"+user.toString());

}

}

(4)BeanUtils 工具包

BeanUtils 工具包是 Apache 出的一个辅助工具,主要方便对于 JavaBean 进行一系列的操作。例如:

可以对 JavaBean 的 getter()/setter() 方法进行赋值操作

可以将Map集合对象直接转为 JavaBean 对象,需要属性名一致

可以实现两个 JavaBean 对象的拷贝操作

BeanUtils 工具类的常用方法如下:

BeanUtils.setProperty(bean, name, value);实现对象的赋值操作

ConvertUtils.register(Converter converter , ..);当需要将String数据转换成引用数据类型(自定义数据类型时),需要使用此方法实现转换

BeanUtils.populate(bean,Map);实现将Map拷贝到对象中

BeanUtils.copyProperties(newObject,oldObject);实现对象之间的拷贝

现在我们导入 beanutils.jar 包和一个依赖的 loggin.jar 包:

203cd2305254a4ad49fe59cadc70668d.png

修改上面部分代码:

将                Field field = clazz.getDeclaredField(fieldName);

field.setAccessible(true);

field.set(entity, fieldValue);

修改为         BeanUtils.setProperty(entity, fieldName, fieldValue);

运行代码,匹配数据库成功,并反射到 User 类中:

03a5dc80937397eb7a8c1b8b5ade7ac7.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值