Java安全-注⼊漏洞(SQL注⼊、命令注⼊、表达式注⼊、模板注⼊)
⽂章⽬录
注⼊
SQL注⼊
JDBC拼接不当造成SQL注⼊
JDBC有两种⽅法执⾏SQL语句,分别为PrepareStatement和Statement。两个⽅法的区别在于PrepareStatement会对SQL语句进⾏预编译,⽽Statement⽅法在每次执⾏时都需要编译,会增⼤系统开销。理论上PrepareStatement的效率和安全性会⽐Statement要好,但并不意味着使⽤PrepareStatement就绝对安全,不会产⽣SQL注⼊
PrepareStatement⽅法⽀持使⽤‘?’对变量位进⾏占位,在预编译阶段填⼊相应的值构造出完整的SQL语
句,此时可以避免SQL注⼊的产⽣。但开发者有时为了便利,会直接采取拼接的⽅式构造SQL语句,此时进⾏预编译则⽆法阻⽌SQL注⼊的产⽣。如以下代码所
⽰,PrepareStatement虽然进⾏了预编译,但在以拼接⽅式构造SQL语句的情况下仍然会产⽣SQL注⼊。代码⽰例如下(若使⽤“or
1=1”,仍可判断出这段程序存在SQL注⼊)
String sql ="select * from user where id ="+ Parameter("id");
out.println(sql);
try{
PreparedStatement pstt = con.prepareStatement(sql);
ResultSet re = uteQuery();
()){
out.println("<br>id:"+rs.getObject("id"));
out.println("<br>name:"+re.getObject("name"));
}
catch(SQLException throwables){
throwables.printStackTrace();
}
}
正确地使⽤PrepareStatement可以有效避免SQL注⼊的产⽣,使⽤“?”作为占位符时,填⼊对应字段的值会进⾏严格的类型检查。将前⾯的“拼接构造SQL语句”改为如下“使⽤占位符构造SQL语句”的代码⽚段,即可有效避免SQL注⼊的产⽣
PrintWriter out = Writer();
String sql ="select * from user where id = ?"
out.println(sql);
try{
PreparedStatement pstt = con.prepareStatement(sql);
pstt.setInt(1,Integer.Parameter("id")));
ResultSet rs = uteQuery();
....
}
框架使⽤不当造成SQL注⼊
如今的Java项⽬或多或少会使⽤对JDBC进⾏更抽象封装的持久化框架,如MyBatis和Hibernate。通常,框架底层已经实现了对SQL注⼊的防御,但在研发⼈员未能恰当使⽤框架的情况下,仍然可能存在SQL注⼊的风险
Mybatis框架
MyBatis框架的思想是将SQL语句编⼊配置⽂件中,避免SQL语句在Java程序中⼤量出现,⽅便后续对
SQL语句的修改与配置
MyBatis中使⽤parameterType向SQL语句传参,在SQL引⽤传参可以使⽤#{Parameter}和${Parameter}两种⽅式
使⽤#{Parameter}构造SQL的代码如下所⽰
<select id="getUsername" resultType="an">
select id,name,age from user where name #{name}
<select>
从Debug回显的SQL语句执⾏过程可以看出,使⽤#{Parameter}⽅式会使⽤“?”占位进⾏预编译,因此不存在SQL注⼊的问题。⽤户可以尝试构造“name”值为“z1ng or 1=1”进⾏验证。回显如下,由于程序未查询到结果出现了空指针异常,因此此时不存在SQL注⼊
使⽤${Parameter}构造SQL的代码如下所⽰
<select id ="getUsername" resultType ="an">
select id,name,age from user where name = ${name}
<select>
“name”值被拼接进SQL语句之中,因此此时存在SQL注⼊
${Parameter}采⽤拼接的⽅式构造SQL,在对⽤户输⼊过滤不严格的前提下,此处很可能存在SQL注⼊
Hibernate
Hibernate是⼀种ORM框架,全称为 Object_Relative DateBase-Mapping,Hibernate框架是Java持久化API(JPA)规范的⼀种实现⽅式。Hibernate 将Java 类映射到数据库表中,从 Java 数据类型映射到 SQL 数据类型。Hibernate是⽬前主流的Java数据库持久化框架,采⽤Hibernate查询语⾔(HQL)注⼊
HQL的语法与SQL类似,受语法的影响,HQL注⼊在实际漏洞利⽤上具有⼀定的限制
不安全的反射
利⽤ Java 的反射机制,可以⽆视类⽅法、变量访问权限修饰符,调⽤任何类的任意⽅法、访问并修改成员变量值
Class clazz = Class.forName("java.lang.Runtime");
但是Runtime为单例模式,在其⽣命周期内只能有⼀个对象,因此以上代码是⽆法⽣效的,正确如下
Class clazz = Class.forName("java.lang.Runtime");
这段payload可以拆分为以下代码
Class clazz = Class.forName("java.lang.Runtime");
Method execMethod = Method("exec",String.class);
Method getRuntimeMethod = Method("getRuntime");
Object runtime = getRuntimeMethod.invoke(clazz);
execMethod.invoke(runtime,"");
Java中的Rce, 常见的可执⾏函数如:Runtime().exec(),在审计的时候也要看Process、ProcessBuilder.start()
可能出现的环境
1. 服务器直接存在可执⾏函数(exec()等),且传⼊的参数过滤不严格导致 RCE 漏洞
2. 由表达式注⼊导致的 RCE 漏洞,常见的有:SpEL、OGNL(Struts2中常出现)、MVEL、EL、Fel、JST+EL等
3. 由Java后端模板引擎注⼊导致的RCE漏洞,常见的如:Freemarker、Velocity、Thymeleaf(常⽤在Spring框架)等
4. 由Java⼀些脚本语⾔引起的RCE漏洞,常见的如:Groovy、JavaScriptEngine等
5. 由第三⽅开源组件引起的RCE漏洞,常见的如:Fastjson、Shiro、Xstream、Struts2、Weblogic等
由不安全的输⼊造成的反射命令执⾏Demo
代码对于传⼊的类、传⼊的类⽅法、传⼊类的参数没有做任何限制
@WebServlet("/Rce")
public class Rce extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws IOException {
PrintWriter printWriter = Writer();
// 接收参数
String name = Parameter("command");
String method = Parameter("method");
String str = Parameter("str");
try{
// 获取类的⽆参数构造⽅法
Class getCommandClass = Class.forName(name);
Constructor constructor = DeclaredConstructor();
constructor.setAccessible(true);
// 实例化类
Object getInstance = wInstance();
// 获取类⽅法
Method getCommandMethod = DeclaredMethod(method, String.class);
// 调⽤类⽅法
Object mes = getCommandMethod.invoke(getInstance, str);
printWriter.append("即将执⾏命令");
printWriter.append((Character) mes);
printWriter.flush();
}catch(ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e){ e.printStackTrace();
}
}
}
可以看到代码中存在反射调⽤,当调⽤不安全类时,会造成命令执⾏
localhost:8080/JavaRCE_war_exploded/Rce?command=java.lang.Runtime&method=exec&str=calc
命令注⼊
Java的Runtime类可以提供调⽤系统命令的功能
protected void doGet(HttpServletRequest req,HttpServletResponse resp)throws ServletException,IOException{
String cmd = Parameter("cmd");
Process process = Runtime().exec(cmd);
InputStream in = InputStream();
ByteArrayOutputStream byteArrayOutputStream =new ByteArrayOutputStream();
byte[] b =new byte[1024];
int i =-1;
while((ad(b))!=-1){
byteArrayOutputStream.write(b,0,i);
}
PrintWriter out = Writer();
out.print(new ByteArray()));
}
系统命令连接符有 |、||、&、&&
|:前边命令输出结果作为后边的输⼊
||:前边的命令执⾏失败才执⾏后边的命令
&:前边的命令执⾏后执⾏后边的命令
&&:前边的命令执⾏成功执⾏后边的命令
注意:Java环境下的命令执⾏,& 作为字符拼接,不能命令执⾏
el表达式获取值例:Process process = Runtime().exec("ping"+ url)
Runtime 类中的 exec ⽅法,要执⾏的命令可以通过字符串和数组的⽅式传⼊,当传⼊的参数类型为字符串时,会先经过StringTokenizer 的处理,主要是针对空格以及换⾏符等空⽩字符进⾏处理,后续会分割出⼀个cmdarray数组保存分割后的命令参数,其中cmdarray的第⼀个元素为所要执⾏的命令
代码注⼊
产⽣代码注⼊漏洞的前提条件是将⽤户输⼊的数据作为Java代码进⾏执⾏
由此所见,程序要有相应的功能能够将⽤户输⼊的数据当作代码执⾏,⽽Java反射就可以实现这样的功能:根据传⼊不同的类名、⽅法名和参数执⾏不同的功能
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论