引言
最近,公司中通过AppScan和360两种工具,对系统进行漏洞扫描。由于我们做的是传统行业项目,运用的框架等技术,比较落后。所以扫描结果可想而知,“雪崩”。于是乎,我们就开始了无尽的折磨…
1.代码注入
(1)命令注入
我们的系统,是跑在WinServer上的,里面有部分代码会对操作系统的CPU、内存等进行监控,这时就用到了
System.Runtime.getRuntime().exec();
这个方法,执行对应的cmd命令。由于对入参的String,未做任何关键校验,导致出现此漏洞。
下面我举个栗子:
以下代码来自一个Web应用程序,该段代码通过运行rmanDB.bat脚本启动Oracle数据库备份,然后运行cleanup.bat脚本删除一些临时文件。脚本文件rmanDB.bat接受一个命令行参数,其中指明需要执行的备份类型。
...
String btype = request.getParameter("backuptype");
String cmd = new String("cmd.exe /K \"c:\\util\\rmanDB.bat "+btype+"&& c:\\utl\\cleanup.bat\"");
System.Runtime.getRuntime().exec(cmd);
...
该段代码没有对来自用户请求中的backuptype参数做任何校验。
通常情况下,Runtime.exec()函数不会执行多条命令,但在以上代码中,为了执行多条命令,程序调用Runtime.exec()方法,首先运行了cmd.exe指令,因此能够执行用&&分隔的多条命令了。
如果攻击者传递了一个形式为
"&& del c:\\dbms\\*.*"
的字符串,那么该段代码将会在执行其他指定命令的同时执行这条命令,对系统造成伤害。
(2)SQL注入
什么是SQL注入呢?它是一种数据库攻击手段,用户可以通过输入的参数,对原本的这个sql语句,进行更改,导致该sql执行出其它效果,对系统造成伤害。
例如:
我们在进行系统登陆的场景中,系统会执行这样一条SQL语句,
" SELECT id,username,password FROM db_user WHERE username = ' " + username + " ' AND password = ' " + pwd + " ' ";
如果将username的值写为
张三’ OR ‘1’=1
,这时这个sql语句,就变为:
SELECT id,username,password FROM db_user WHERE username=‘张三’ OR ‘1’=‘1’ AND password=’’;
这样的情况,攻击者会在不知道用户的方式,登入你的系统,进行侵入。
同样
,攻击者可以为password提供如下字符串,
’ OR ‘1’=’1
SELECT id,username,password FROM db_user WHERE username=’’ AND password=’’OR ‘1’=‘1’;
也可以达到入侵系统的目的。
而在我们的项目中,使用公司自己的框架,其中框架中与数据库的操作,大多是对JDBC操作的封装。很多地方使用
java.sql.Statement
拼接出来的sql语句都是类似上面的场景描述的那样,会引起SQL注入。而且,前人在写代码时,也未对具体的入参进行关键校验。
下面说说怎么改进:
就是,将之前的
java.sql.Statement
替换为
java.sql.PreparedStatement
,更换了API,使用后者的好处不少,我来简单罗列一下:
- 很好的,防止SQL注入,具体的原因还是因为有预编译的能力,在后期参入传入时通过替换占位符,将指定参数传入。(SQL注入只会在编译期存在隐患)
- 有预编译效果,使数据库多次执行同一个sql语句,性能更高。
- 使代码更加简洁(强加的优点,哈哈)。
鉴于以上优点,尽量使用“PreparedStatement” 。不过身为21世纪的程序猿,在项目中与数据库的交互,普遍都在用MyBatis、Hibernate框架,这类框架已经很好的屏蔽了这一层次的问题。但是在这些框架中依然存在SQL注入的情况,这个安全问题,值得注意。
MyBatis框架SQL注入
这个框架在我们的项目中,没有用到,我在这里作为知识点,简单总结一下。
其实主要就是通过
#{}
防止SQL注入。
可以通过DeBug得到,它通过代码执行出的sql语句是这样的:
//数据库执行该语句之前,会进行预编译
SELECT id,username,password FROM db_user WHERE username = ? AND password = ?;
在数据库执行时,会动态的将 “?”这个占位符替换为具体参数,执行SQL语句。从而避免SQL注入问题。本质上还是使用了JDBC的PreparedStatement先进行了预编译,这是个关键点。
还有一种表达式是**${}**,它也可以将变量插入sql语句中,但是它未进行预编译,是非安全的,存在SQL注入的隐患。因为它在代编译期的时候,会将参数值传入SQL语句中,与上面的登陆场景效果一样。这种表达式,可以用于动态传入数据库对象,例如传入表名。