sql注入
在常见的网络应用漏洞中,SQL注入漏洞较为常见,危害大。攻击者一旦利用系统中存在的SQL注入漏洞来发起攻击,在条件允许的情况下,不仅可以获取整站数据,还可通过进一步的渗透 来获取服务器权限,从而进入内网,大家也许都听过某某学长通过攻击学校数据库修改自己成绩的事情,这些学长们一般用的就是SQL注入方法。注入攻击的本质,是把用户输入的数据当做代码执行。
SQL注入条件
1、是用户能够控制输入
2、是原本程序 要执行的代码,拼接了用户输入的数据
案例一
当用户发送GET请求:
http://www.xxx.com/news.jsp?id=1
这是一个新闻详情页面,会显示出新闻的title和content,程序内部 会接收这个id参数传递给SQL语句,SQL如下:
SELECT title,content FROM news WHERE id = 1
这是SQL的原义,也是程序员想要得到的结果,但是如果用户改变了id 的内容,修改成如下:
http://www.jd.com/news.jsp?id=1 and 1=2 UNION SELECT
userna-me, password FROM admin
此时内部程序执行的SQL语句为:
SELECT title,content FROM news WHERE id = 1 and 1=2
UNION SELECT username, password FROM admin
这条SQL的原义就会被改变,导致将管理员数据表中的用户名显示 在页面title位置,密码显示在页面content位置,攻击成功。
java-JDBC
以前程序员往往这么从数据库获取数据。
// 程序宇数据库的连接方式:先连接、构造sql语句、执行
public User getUserById(String id) throws //String字符串类型或者 id
SQLException {
Connection connection =
JDBCTOOLS.getConnection();
String sql = "select id,username from user where id=" + id; //直接拼接sql语句
Statement statement =
connection.createStatement();
ResultSet resultSet =
statement.executeQuery(sql);
resultSet.next();
int userId = resultSet.getInt(1);
String username = resultSet.getString(2);
User user = new User(userId, username);
return user;
}
通过拼接字符串来构建sql语句,其中又有用户可控的部分,很容易 出现问题
直接拼接实例代码
http://localhost:8080/sqli/jdbc/vul?username=joychou
源码分析
@GetMapping("/jdbc/vuln")
// 访问了 /jdbc/vuln 就执行以下方法
//public String jdbc_sqli_vul(@RequestParam("username") String username) {
public String jdbc_sqli_vul( String username) {
StringBuilder result = new StringBuilder();
try {
Class.forName(driver);
Connection con = DriverManager.getConnection(url, user, password);
if (!con.isClosed())
System.out.println("Connect to database successfully.");
// sqli vuln code
Statement statement = con.createStatement();
String sql = "select * from users where username = '" + username + "'";
logger.info(sql);
ResultSet rs = statement.executeQuery(sql);
预编译处理后的代码(修复后代码)
http://localhost:8080/sqli/jdbc/sec?username=joychou
后来,出现了预编译机制,但是预编译只能处理查询参数,很多场 景下仅仅使用预编译是不够的。
// fix code
String sql = "select * from users where username = ?"; // 这里使用了? 用来做预处理编译
// sql: "select *from users where userbane=?"
PreparedStatement st = con.prepareStatement(sql); //con.prepareStatement java 预编译代码函数
// st "com.mysql.cj.jdbc.clientPerparedStatement: select *from users where username= 'joychou ' or '1'='1'" com:ConnectionImapl@8805 sql: "select *from users where username =?"
st.setString(1, username); // 预编译完成后占位上输入占位符
// username: "joychou ' or '1'='1"
logger.info(st.toString()); // sql after prepare statement
ResultSet rs = st.executeQuery();
// st "com.mysql.cj.jdbc.clientPerparedStatement: select *from users where username= 'joychou ' or '1'='1'"
//从以上代码可以看出 con.prepareStatement 是做了预编译 预处理,相当于出现单引号就加/ 这样 攻击者输入的语句就无法
//被当初sql语句执行了
like
like拼接实现代码:
http://localhost:8080/sqli/jdbc/like?username=joy
在使用模糊查询的场景中
String sql = "select * from user where username
like '%?%'";
这种写法是无法进行预编译的,程序会报错
like预处理报错
http://localhost:8080/sqli/jdbc/likesec?username=joy
这里报错的原因是 jbc 预处理方法是针对于前面大众形式的,防止注入:字符串,数字型等 := ..但是对于 like 搜索的 有 等号= ?号的 是不太适用
like预处理
http://localhost:8080/sqli/jdbc/likesec?username=joy
order by
需要按照时间、id等信息进行排序的时候,也是无法使用预编译 的。sort=123
String sort = req.getParameter("sort");
String sql = "select * from user order by ?";
PreparedStatement preparedStatement =
connection.prepareStatement(sql); //预编译
preparedStatement.setString(1, sort); //绑定参数
ResultSet resultSet =
preparedStatement.executeQuery();
如果像上面这样强行使用预编译,数据库会将字段名解析为字符 串,即实际执行的sql为
select * from user order by 'sort';
总结
jdbc方式进行拼接的,可以直接使用预处理来规避sql注入, 但是如果有like、order by 进行参数拼接不能直接使用预处理来解 决,必须在set处把%拼接上。
原文始发于微信公众号(漏洞404):java-SQL注入(java sec code)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论