1 SQL注入
- 什么是SQL注入
程序中如果使用了未经校验的外部输入来构造SQL语句访问数据库,就很可能引入SQL注入漏洞。攻击者可以通过构造恶意输入来改变原本的SQL逻辑或者执行额外的SQL语句。如果代码使用存储过程,而这些存储过程作为包含未筛选的用户输入的字符串来传递,也会发生SQL注入。
- 攻击原理
攻击者会将一些恶意代码插入到字符串中。然后会通过各种手段将该字符串传递到数据库的实例中进行分析和执行。只要这个恶意代码符合SQL语句的规则,在代码编译与执行的时候,就不会被系统所发现。攻击者可以通过SQL注入绕过程序原有逻辑执行恶意SQL达到不法的目的。
- 示例
用户名:tom 密码:321
正常输入: name = tom & pwd = 321
SQL:
SELECT * FROM db_user WHERE name = 'tom' AND pwd = '321'
查询结果:
恶意输入: name = tom’ or 1 = 1 & pwd = 111
SQL:
SELECT * FROM db_user WHERE name = 'tom' OR 1 = 1 AND pwd = '111'
查询结果:
我们可以从上面的例子看出,只要tom是有效的用户,SQL注入就可以绕开密码的验证,攻击者还可以通过通过构造恶意输入来执行代码原有逻辑以外的SQL,
比如通过传入
name = tom'; drop table db_user -- & pwd = 111
SQL:
SELECT * FROM db_user WHERE name = 'tom'; drop table db_user -- ' AND pwd = '111'
如果程序执行了该语句,db_user会被直接删掉
后面加两个-,这意味着注释,它将后面的语句注释,让他们不起作用,这样语句永远都能正确执行,用户轻易骗过系统,获取合法身份
- 如何防御
- 检查变量数据类型和格式
固定格式的变量,比如数字,日期,邮箱,手机号等变量在SQL语句执行前严格的校验变量格式,确保变量格式规范,这样很大程度可以避免SQL注入攻击。
- 过滤特殊符号,sql语句 无固定格式的变量在SQL语句执行前可以做一些特殊符号和sql语句的过滤,比如 ;,--,OR,AND等。
- 使用预编译语句
a).使用PrepareStatement和占位符通过预编译sql语句防止sql注入(预编译的sql语句会带着?占位符进行编译,
恶意的攻击参数无法改变已经编译好的sql语句逻辑)。
b). mybatis使用#{ } (#{ }会预编译,${ }则直接进行变量替换操作)。
2 表达式注入
- 什么是表达式
Java统一表达式语言(英语:Unified Expression Language,简称JUEL)是一种特殊用途的编程语言,主要在Java Web应用程序用于将表达式嵌入到web页面。Java规范制定者和Java Web领域技术专家小组制定了统一的表达式语言。JUEL最初包含在JSP 2.1规范JSR-245中,后来成为Java EE 7的一部分,改在JSR-341中定义。
Java中表达式根据框架分为好多种
- OGNL- 一个被WebWork和Apache Struts 2使用的开源的表达式语言。
- MVEL- 一个被众多Java项目使用的开源的表达式语言。
- Apache Commons JEXL - 一个旨在促进Java项目实现动态和脚本功能的开源的表达式语言。
- SpEL - Spring表达式语言,一个开源的EL表达式语言,是Spring Framework的一部分。它主要用于Spring portfolio项目,但也可以用于其他项目。
(摘自维基百科,https://zh.wikipedia.org/wiki/%E7%BB%9F%E4%B8%80%E8%A1%A8%E8%BE%BE%E5%BC%8F%E8%AF%AD%E8%A8%80)
- 什么是表达式注入
表达式根据框架分为好多种,但表达式注入的原理基本一样,表达式全部或部份外部可控从而让使用者可以通过表达式达到程序设计功能以外的能力,恶意攻击者可以通过表达式注入达到一些不法目的。
- 示例
表达式根据框架分为好多种,这里以SpEL表达式为例。
Spring Expression Language(简称SpEL)是一种强大的表达式语言,支持在运行时查询和操作对象图。语言语法类似于Unified EL,但提供了额外的功能,特别是方法调用和基本的字符串模板功能。同时因为SpEL是以API接口的形式创建的,所以允许将其集成到其他应用程序和框架中。SpEL是Spring框架中的一种语言表达式,类似于Struts2中的OGNL。
SpEL使用 #{…} 作为定界符,所有在大括号中的字符都将被认为是 SpEL表达式,我们可以在其中使用运算符,变量以及引用bean,属性和方法如:
引用其他对象:#{car}
引用其他对象的属性:#{car.brand}
调用其它方法 , 还可以链式操作:#{car.toString()}
调用其它方法 , 还可以链式操作:#{car.toString()}
调用静态方法静态属性:#{T(java.lang.Math).PI}
SpEL还支持许多运算符,如:
算术运算符:+,-,*,/,%,^(加号还可以用作字符串连接)
比较运算符:< , > , == , >= , <= , lt , gt , eg , le , ge
逻辑运算符:and , or , not , |
if-else 运算符(类似三目运算符):?:(temary), ?:(Elvis)
正则表达式:#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}'}
@RequestMapping("/spel")
@ResponseBody
public String spel(String input){
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(input);
return expression.getValue().toString();
}
上面接口中的SpEL接口直接将用户输入的参数作为表达式内容进行解析,这就造成了表达式外部可控,用户可以通过input参数进行表达式注入。
输入:input=new ProcessBuilder("calc").start()
系统执行用户传入的命令,打开了计算器。
也可以采用T() 调用一个类的静态方法,它将返回一个 Class Object,然后再调用相应的方法或属性,也是可以实现相同的功能。
输入 :input=T(java.lang.Math).random()
- 常见的命令注入payload
${12*12}
T(java.lang.Runtime).getRuntime().exec("")
T(Thread).sleep(10000)
"".getClass().forName('java.lang.Runtime').getRuntime().exec("")
new java.lang.ProcessBuilder({""}).start()
- 原理
SpEL内部使用了反射,可以直接获得对象的属性信息,如果SpEL表达式外部可控,同样用户可以通过表达式执行精心构造的任意代码,导致命令执行。
- 如何防御
- 尽量避免使用外部输入的内容作为EL表达式内容
- 通过参数白名单或者黑名单控制参数的合法性
如果涉及到执行表达式的方法传入的参数外部可控,就存在表达式注入的安全风险,需要通过白名单参数校验来限制。
- 如果是java程序,需搜索检查如下关键类方法
el.ExpressionFactory.createValueExpression() javax.el.ValueExpression.getValue()
- 指定正确EvaluationContext(针对SpEL表达式注入)
Spring官方推出了SimpleEvaluationContext作为安全类来防御该类漏洞,SimpleEvaluationContext旨在仅支持SpEL语言语法的一个子集。它不包括 Java类型引用,构造函数和bean引用,指定正确EvaluationContext,是防止SpEl表达式注入漏洞产生的首选。
3 命令注入
- 什么是命令注入
是指通过提交恶意构造的参数破坏命令语句结构,从而达到执行恶意命令的目的。在Web应用中,有时候会用到一些命令执行的函数,比如java的java.lang Runtime. Exec,php中system、exec、shell_exec等,当用户能够控制这些函数中的参数时,就可以将恶意参数拼接到正常命令中,从而造成命令注入攻击。
- 示例
@Component
@RequestMapping("/RuntimeTest")
public class RuntimeTest {
@RequestMapping("/execTest")
@ResponseBody
public String execTest(String input) throws IOException{
Runtime run =Runtime.getRuntime();
Process p = run.exec(input);
InputStream ins= p.getInputStream();
return getResByIO(ins);
}
private String getResByIO(InputStream ins){
StringBuilder resBuilder = new StringBuilder();
byte[] b = new byte[100];
int num = 0;
try {
while((num=ins.read(b))!=-1){
System.out.println(new String(b,"gb2312"));
resBuilder.append(new String(b,"gb2312"));
}
} catch (IOException e) {
e.printStackTrace();
}
return resBuilder.toString();
}
}
输入:input = ipconfig
结果:
这是一个非常简单的命令注入的例子,用户通过接口传入的参数没有经过任何处理就直接被Runtime的exec方法执行,实际上发生的命令注入漏洞时命令来源通常比较复杂,可能是程序读取的文件或是系统参数可以被用户修改,也可能是直接或是间接传入的参数,还有根据用户传入的参数进行命令拼接的。但是发生命令注入根本原因是一样的,程序执行的命令是用户可控的,通过命令的执行,应用程序会授予攻击者一种原本不该拥有的特权或能力。
如果攻击者能通过命令注入漏洞获取直接执行系统命令的能力,那他基本可以获取程序拥有的所有权限,危害可想而知。
- 如何防御
- 选择不调用系统命令的实现方法
- 保证所有命令行参数的来源可控,不使用用户传入数据或者用户可以控制的数据做命令来源
- 严格校验参数,可以使用黑白名单校验的方式控制命令的范围
- 降低程序的权限