JAVA代码审计 SQL注入篇

时间:2022-01-18 00:59:51

一、SQL注入步骤

第一步:SQL注入点探测。探测SQL注入点是关键的一步,通过适当的分析应用程序,可以判断什么地方存在SQL注入点。通常只要带有输入提交的动态网页,并且动态网页访问数据库,就可能存在SQL注入漏洞。如果程序员信息安全意识不强,采用动态构造SQL语句访问数据库,并且对用户的输入未进行有效性验证,则存在SQL注入漏洞的可能性很大。一般通过页面的报错信息来确定是否存在SQL注入漏洞。 

第二步:收集后台数据库信息。不同数据库的注入方法、函数都不尽相同,因此在注入之前,我们先要判断一下数据库的类型。判断数据库类型的方法很多,可以输入特殊字符,如单引号,让程序返回错误信息,我们根据错误信息提示进行判断;还可以使用特定函数来判断,比如输入“1 and version()>0”,程序返回正常,说明version()函数被数据库识别并执行,而version()函数是MySQL特有的函数,因此可以推断后台数据库为MySQL。 

第三步:猜解用户名和密码。数据库中的表和字段命名一般都是有规律的。通过构造特殊SQL语句在数据库中依次猜解出表名、字段名、字段数、用户名和密码。 

第四步:查找Web后台管理入口。WEB后台管理通常不对普通用户开放,要找到后台管理的登录网址,可以利用Web目录扫描工具(如:wwwscan、AWVS)快速搜索到可能的登录地址,然后逐一尝试,便可以找到后台管理平台的登录网址。 

第五步:入侵和破坏。一般后台管理具有较高权限和较多的功能,使用前面已破译的用户名、密码成功登录后台管理平台后,就可以任意进行破坏,比如上传mua、篡改网页、修改和窃取信息等,还可以进一步提权,入侵Web服务器和数据库服务器。

二、注入方法

1、数字型注入

当输入的参数为整型时,如ID、年龄、页码等,如果存在注入漏洞,则可以认为是数字型注入。这种数字型注入最多出现在ASP、PHP等弱类型语言中,弱类型语言会自动推导变量类型,例如,参数id=8,PHP会自动推导变量id的数据类型为int类型,那么id=8 and 1=1,则会推导为string类型,这是弱类型语言的特性。而对于Java、C#这类强类型语言,如果试图把一个字符串转换为int类型,则会抛出异常,无法继续执行。所以,强类型的语言很少存在数字型注入漏洞。 [7] 

2、字符型注入

当输入参数为字符串时,称为字符型。数字型与字符型注入最大的区别在于:数字型不需要单引号闭合,而字符串类型一般要使用单引号来闭合。

三、statement执行SQL语句

java.sql.Statement 是 Java JDBC 下执行 SQL 语句的一种原生方式,执行语句时需要通 过拼接来执行。若拼接的语句没有经过过滤,将出现 SQL 注入漏洞。

具体步骤 1、注册驱动 2、获取链接 3、创建 4、执行

//注册驱动 
Class.forName("oracle.jdbc.driver.OracleDriver");
//获取连接
Connection conn = DriverManager.getConnection(DBURL, DBUser, DBPassWord);
//创建
Statement 对象 Statement state = conn.createStatement();
//执行
SQL String sql = "SELECT * FROM user WHERE id = '" + id + "'"; state. executeQuery(sql);

典型代码:

Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://192.168.88.20:3306/iwebsec?&useSSL=false&server
Timezone=UTC","root","root");
String id ="2";
String sql = "select * from user where id = " + id;
ps = conn.createStatement();
rs = ps.executeQuery(sql);
while(rs.next())
{System.out.println("id:"+rs.getInt("id")+" username : "+rs.getString("username")+" password:"+rs.
getString("password"));}

首先将驱动注册完成后实例化Statement对象,SQL查询语句为

"select * from user where id = " + id;

使用拼接的方式传入id值,id值在通过“String ”设置为2,运行此代码后即成功获取user表中的id信息。


四、PrepareStatement执行SQL语句

PrepareStatement为sql预编译方法,包含了已经编译的SQL语句,会预处理SQL语句,SQL语句可具有一个或者多个IN参数。IN参数的值在SQL语句穿件时没有被指定,而是为每个In参数保留了一个?作为占位符(JAVA代码审计Part1​​https://blog.51cto.com/u_16001677/6118278​​)。

每个?的值必须在该语句执行之前通过适当的setXXX方法来提供。

int型则使用setInt方法                  string型则使用setString方法

特征:

1、PrepareStatement方法比Statement方法执行SQL语句更快

2、可有效防止SQL注入????

具体步骤:1、注册驱动 2、获取链接 3、实例化PrepareStatement对象

4、设置占位符为id变量 5、执行

//注册驱动
Class.forName("oracle.jdbc.driver.OracleDriver");
//获取连接
Connection conn =DriverManager.getConnection(DBURL, DBUser, DBPassWord);
//实例化 PreparedStatement 对象
String sql = "SELECT * FROM user WHERE id = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//设置占位符为 id 变量
preparedStatement.setInt(1,id);
//执行 SQL 语句
ResultSet resultSet = preparedStatement.executeQuery();

代码实例:

public class preparedStatement {
public static void main(String[] args) {
String id ="1";
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://192.168.88.20:3306/iwebsec?&useSSL=
false&serverTimezone=UTC","root","root");
String sql = "SELECT * FROM user WHERE id = ?";
PreparedStatement preparedStatement = conn.prepareStatement(sql);
preparedStatement.setString(1, id);
rs = preparedStatement.executeQuery();
while(rs.next())
{System.out.println("id:"+rs.getInt("id")+" username :"+rs.getString("username")+" password :
"+rs.getString("password"));}
}
}

首先将驱动注册完成后实例化PreparedStatement对象,SQL查询语句为

"Select * from user where id = ?"

通过下面语句传入id值(id值通过“String id=1”传入id的值)

PreparedStatement.setInt(1,id)

运行后可成功获取到user表中id的信息

五、常见的SQL注入

1、SQL语句直接进行动态拼接

private static final String DBDriver = "oracle.jdbc.driver.OracleDriver";//驱动
private static final String DBURL = "jdbc:oracle:thin:@127.0.0.1:1521:XE";//URL 命名规则:jdbc:oracle:
thin:@IP 地址:端口号:数据库实例名
private static final String DBUser = "IWEBSEC";
private static final String DBPassWord = "IWEBSEC";
Connection con = null;
Statement st = null;
ResultSet res = null;
try {
//连接
Class.forName(DBDriver);//加载数据库驱动
con = DriverManager.getConnection(DBURL, DBUser, DBPassWord);//连接
st = con.createStatement();
String id=request.getParameter("id");
res = st.executeQuery("SELECT * FROM \"IWEBSEC\".\"user\" WHERE \"id\"="+id);
while(res.next()){
int p = res.getInt("id");
String n = res.getString("username");
String s = res.getString("password");
}
} catch (Exception e) {
out.println(e);
}

看完上面一和二后再看这个例子思路应该会很清晰了。

1、设置好驱动器启动,加载数据库驱动

2、进行数据库连接

3、通过request.getParameter("id")获取传入的id值

4、通过"SELECT * FROM \"IWEBSEC\".\"user\" WHERE \"id\"="+id进行SQL语句拼接并执行

在上述步骤中和代码中可以发现传入id的参数值是可控的存在SQL注入????的风险。

2、预编译错误

JAVA代码审计   SQL注入篇

漏洞代码:

Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://192.168.88.20:3306/iwebsec?&useSSL=false& server
Timeznotallow=UTC","root","root");
String username="user%' or '1'='1'#";
String id ="2";
String sql = "SELECT * FROM user where id = ?";
if (!CommonUtils.isEmptyStr(username))
sql += " and username like '%" + username + "%'";
System.out.println(sql);
PreparedStatement preparedStatement = conn.prepareStatement(sql);
preparedStatement.setString(1, id);
rs = preparedStatement.executeQuery();

该代码为典型的预编席错误编程方式,id虽然使用了PreparedStatement方法进行SQL查询但是该处使用了模糊查询

" and username like '%" + username + "%'"

导致SQL注入????

六、SQL注入????Payload

引用该作者文章

​​​​https://www.cnblogs.com/-xiaopeng1/p/16792468.html​

七、常规SQL注入代码审计(笔记)

1、通过一些关键字可以定位到SQL语句附近

Statement
createStatement
PrepareStatement
like '%${
in (${
select
update
insert

2、通过搜索SQL关键字Statement快速定位存在SQL语句的代码段落

JAVA代码审计   SQL注入篇

3、继续跟进Statement.java文件,可看到SQL语句直接凭借了外部变量id的值

JAVA代码审计   SQL注入篇

4、继续追中代码可发现id是作为UserInfoFoundDao方法的参数被传入,选中id后单击Call Hierarchy,查看调用栈可找到所引用内容

JAVA代码审计   SQL注入篇

5、通过调用栈再selectInfoServiceImpl文件中找到了UserInfoFoundDao的调用

JAVA代码审计   SQL注入篇

6、继续查看调用栈可以在InfoServlet.java中找到selectInfoServiceImpl的调用,可以明显看到id的参数是通过getParameter方法从request中获取到的并没有经过特殊的处理(过滤等方法)直接传入到UserInfoFoundService的过程中进行SQL拼接

JAVA代码审计   SQL注入篇

7、至此弄清楚了整个漏洞产生逻辑,只需通过web.xml文件找到对应的路由绑定关系并通过浏览器传入包含SQL注入Payload的id参数即可出发SQL注入????。

JAVA代码审计   SQL注入篇

可以发现绑定的路径是/,只要传入乡音的路径及参数即可,因为“/”匹配的是所有路径可以用/index?id=1来传入相关数据。当输入 “/index?id=1 and 1=2 union select 1,database(),3”时返回数据信息。

JAVA代码审计   SQL注入篇


摘录于《网络安全JAVA代码审计实战》,真是一本好书!!!强烈推荐!!!