freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

如何成为一个牛逼的脚本小子日记之0x001-JAVA 代码审计 Top half (2023829-...
2023-09-01 23:15:50

如何成为一个牛逼的脚本小子日记之

·0x001-JAVA 代码审计 Top half (2023/8/29-2023/9/1)

此记录是在拥有一定的java基础下进行的,java基础类,反射,继承,filter,servlet,calssLoader,Dynamic agent等基础知识点将不再进行阐述,TopHalf主要集中描述除了java反序列化之外的基础漏洞,BottomHalf会集中描述java反序列化,CC/CB,weblogic,fastjson反序列化等各种反序列化审计和kWAF/绕过的姿势

·从0到什么都会点的JAVA审计

· Inject


·SQL-Inject 老生常谈的sql注入

​ 导致注入的原因:

​ 1.经典的sql语句拼接不当

String sql_command="select * from db_name where id = " + req.getParamenter("id") // inject on id

​ 1.1.Statement 的拼接

String sql_command ="select * from db_name where id = " + req.getParamenter("id");
try{
Statement st = con.createStatement(); // inject on id
ResultSet rs = st.executeQuery(sql_command);
while (rs.next()){
out....... // rs.getObject("id");
}
}

​ 1.2.带有 prepareStatement 预编译 的 拼接不当

String sql_command ="select * from db_name where id = " + req.getParamenter("id");
try{
	PreparedStatement pstt = con.prepareStatement(sql_command);
	ResultSet rs = pstt.executeQuery(); //Inject on id 
	while(rs.next()){
		out ....... // rs.getObkect("id");
		}
}catch (SqlException throables) {
throables.printStackTrace();
}

​ 2.框架使用不当

​ 如MyBatis,Hibernate

​ 2.1 MyBaits ${parameter} 使用不当导致的注入

<select id ="getUsername" resultType = "com.sqltest.bean.user">
select * from user where name = ${name} //inject on name
</select>

​ 2.2 HQL 参数语法使用不当

List user = session.createQuery("from user where name= '" + req.getParament("insert") + "'",User.class).getResultList(); //inject on insert 

那么如何审计java中的SQL-Inject?

​ 跟踪关键字sql sql_command sql_query 寻找sql拼接的入口点

​ 跟踪sql链条寻找sql拼接的入口点

​ 寻找 con.prepareStatement , req.getParamenter , st.executeQuery , pstt.executeQuery 等 回溯构造链


·command-Inject

​ 命令注入的常见情况基本为直接拼接exec导致命令注入

String Cmd = req.getParament("cmd");
Process process = Runtime.getRuntime().exec(Cmd);

​ 命令注入需要注意的雷坑

​ 1.1 Runtime.exec 中 StringTokenizer 对 空白字符 的截取导致命令注入失效

ping 127.0.0.1&whoami
```
String[1] = "ping"
String[2] = "127.0.0.1&whoami"

```

​ 可见 Runtime.exec 中 StringTokenizer 对 空白字符 的截取 导致 String[2] 成为 ping 后续的参数而非继续执行的命令导致命令注入失效

​ 当然 可以使用多个空格拼接参数进行绕过

ping 127.0.0.1 & whoami
```
String[1] = "ping"
String[2] = "127.0.0.1"
String[3] = "&"
String[4] = "whoami"
```

·code-Inject



·EL-Inject

​ 表达式注入

​ EL 表达式可以从JSP的四大作用域 page/request/session/application 中获取数据

​ 可以在JSP页面中执行一些基本的关系运算,逻辑运算和算数运算

​ 可以获取web开发常用对象

​ 可以调用java方法 允许开发者自定义EL函数 在自定义的JSP中调用java方法

​ JSP四大作用域如下:

​ page:only save data on one page [Javax.servlet.jsp.PageContext]

​ request:only save data on one request [ Javax.servlent.httpServletRequest]

​ session:save data on one dialogue , only support to one user [Javx.servlet.http.HttpSesssion]

​ application:save data in hole server and all user shared [Javax.servlet.ServletContext]

​ EL 在JSP中 以{} 表示EL表达式 例如 ${name} 来获取name 变量 当没有指定作用范围的时候 默认在page的作用域查找

<@page contentType="text/html;charset=UTF-8" language="Java" %>
<html>
<center>
<h3>name is : ${param.name} </h3>
</center>
<html>

​ Springboot中的EL表达式注入

​ Spring表达式语言(简称SpEl)是一个支持查询和操作运行时对象导航图功能的强大的表达式语言. 它的语法类似于传统EL,但提供额外的功能,最出色的就是函数调用和简单字符串的模板函数。

​ SpEL使用 #{...} 作为定界符,所有在大括号中的字符都将被认为是 SpEL表达式,我们可以在其中使用运算符,变量以及引用bean,属性和方法如:

​ 1.引用其他对象:#{car} 引用其他对象的属性:#{car.brand} 调用其它方法 , 还可以链式操作:#{car.toString()}

​ 其中属性名称引用还可以用符号 如:{someProperty}

​ 除此以外在SpEL中,使用T()运算符会调用类作用域的方法和常量,例如,在SpEL中使用Java的Math类,我们可以像下面的示例这样使用T()运算符:#{T(java.lang.Math)} 1 #{T(java.lang.Math)}

​ T()运算符的结果会返回一个java.lang.Math类对象。

​ 该漏洞的漏洞形态类似于命令注入,因此之前该漏洞归为命令注入类

@RequestMapping("/test")
    @ResponseBody
    public String test(String input){
        SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(input);
        return expression.getValue().toString();
    }

​ 将输入的参数直接当作表达式解析的参数,在解析过程中将造成命令执行。

http://127.0.0.1:8080/test?input=new%20java.lang.ProcessBuilder(%22/Applications/Calculator.app/Contents/MacOS/Calculator%22).start()

​ 当然也可以采用T() 调用一个类的静态方法,它将返回一个 Class Object,然后再调用相应的方法或属性,也是可以是想相同的功能。


·SSTI

​ freemark模板注入

​ 成因依旧是未经过滤的模板渲染,一个道理

​ freemarker.template.utility.Execute 需要着重寻找,或者根据调用链反推入口位置

​ 从 2.3.17版本以后,官方版本提供了三种TemplateClassResolver对类进行解析:

​ 1、UNRESTRICTED_RESOLVER:可通过ClassUtil.forName(className)获取任何类。

​ 2、SAFER_RESOLVER:不能加载freemarker.template.utility.JythonRuntimefreemarker.template.utility.Executefreemarker.template.utility.ObjectConstructor这三个类。 3、ALLOWS_NOTHING_RESOLVER:不能解析任何类。 可通过freemarker.core.Configurable#setNewBuiltinClassResolver方法设置TemplateClassResolver,从而限制通过new()函数对freemarker.template.utility.JythonRuntimefreemarker.template.utility.Executefreemarker.template.utility.ObjectConstructor这三个类的解析。

​ 但是依旧不排除可以bypass的可能(后续可能会看看代码吧


·失效的身份认证

·这部分主要集中在JWT的认证上,关于JWT的部份不难,所以精简描述

​ 基本审计方法为倒推cookieORsession的create方法然后结合已经拿到的jwt反解(解密网站一百度一大堆)后越权


·XXE

​ XXE(XML外部实体注入,XML External Entity) ,漏洞在对不安全的外部实体数据进行处理时,可能存在恶意行为导致读取任意文件、探测内网端口、攻击内网网站、发起DoS拒绝服务攻击、执行系统命令等问题。简单来说,如果系统能够接收并解析用户的XML,但未禁用DTD和Entity时,可能出现XXE漏洞,常见场景如pdf在线解析、word在线解析、定制协议或者其他可以解析xml的API接口。

​ 较容易产生的漏洞点如下

javax.xml.parsers.DocumentBuilder
javax.xml.parsers.DocumentBuildFactory
org.xml.sax.EntityResolver
org.dom4j.*
javax.xml.parsers.SAXParser
javax.xml.parsers.SAXParserFactory
TransformerFactory
SAXReader
DocumentHelper
SAXBuilder
SAXParserFactory
XMLReaderFactory
XMLInputFactory
SchemaFactory
DocumentBuilderFactoryImpl
SAXTransformerFactory
DocumentBuilderFactoryImpl
XMLReader
Xerces: DOMParser, DOMParserImpl, SAXParser, XMLParser
·XML

​ XML(eXtensible Markup Language)叫做可扩展的标记语言,所有的标签都可以自定义。通常xml被用于信息的记录和传递,所以xml经常用于配置文件。

<?xml version="1.0" encoding="UTF-8"?>
</source1>
<source id="1"> 
<name>asd</name>
</source1>
·TDT

​ DTD(Document Type Definition,文档类型定义),DTD用于约束xml的文档格式,保证xml是一个有效的xml。DTD可以分为内部DTD和外部DTD。

·内部TDT,随便举个例子
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE scores [
        <!ELEMENT scores (student*) >
        <!ELEMENT student (name, course, score)>
        <!ATTLIST student id CDATA #REQUIRED>
        <!ELEMENT name (#PCDATA)>
        <!ELEMENT course (#PCDATA)>
        <!ELEMENT score (#PCDATA)>
        ]>
<scores>
    <student id="1">
        <name>张三</name>
        <course>java</course>
        <score>90</score>
    </student>
    <student id="2">
        <name>李四</name>
        <course>xml</course>
        <score>99</score>
    </student>
</scores>
·外部TDT
<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE scores SYSTEM "scores.dtd" >
<scores>
//中间部分省略
</scores>

有/无回显的利用方法就不说了,SSRF那套+dnslog/server那套可以直接搬过来用,网上一搜一大堆,没什么好讲的


·敏感信息泄露

​ 输出数据的时候没对用户权限进行验证(也算是变相的权限控制不当)

一个很简单的例子(抄的,因为真的太典了)

public static void show(boolean bAjax, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/* 38 */     PMInterface pm = null;
/*    */     
/* 40 */     StringBuffer sb = new StringBuffer();
/*    */     
/* 42 */     for (int i = 0; i < alPM.size(); i++) {
/* 43 */       pm = alPM.get(i);
/* 44 */       sb.append(pm.PM());
/*    */       
/* 46 */       sb.append("\r\n");
/*    */     } 
/*    */ 
/*    */     
/* 50 */     String str = sb.toString();
/* 51 */     response.getOutputStream().write(str.getBytes(SysConts.New_InCharSet));
/*    */   }
/*    */ }

·失效的访问控制

·横向/纵向越权 这部份计划放到后续的实战审计中进行描述,越权还是结合代码看清晰一些,成因基本上也都是没有对用户身份进行校验,审计的点也是关注http传参构造链和pom.xml对传入数据的处理即可


·CSRF

·对referer控制不严格导致CSRF

​ 下面是一眼典中典的环节 getReferer > check referer "www.example.com"

public class RefererInterceptor extends HandlerInterceptorAdapter {
	private Bollen check = true;
	@override
  public bollen preHandle(HttpServlentRequest req),
  HttpServlentRequest resp, Object handler) throws Exception{
    if(!check){
      return true;
    }
  String referer=request.getHeader("Referer");
    if((referer!=null)&&(referer.trim().startsWith("www.example.com"))){
      chain.doFilter(request, response);
    }else {
      request.getRequestDispatcher("index.jsp").forward(request,response);
    }
}

·Token可重用导致CSRF

​ 还是一眼典的环节 make Token (generateToken()) > 未删除 如果导致Token泄漏那么可直接对Token进行重用

String sToken = generateToken();
String pToken = req.getPrameter("csrf-token");
if(sToken!=null && pToken !=null && sToken.equals(pToken)){
  chain.doFilter(request,response)
}else{
  request.getRequestDispatcher("index.jsp").forward(request,response);
}	

·SSRF

需要关注的敏感函数

HttpClient.execute()
HttpClitne.executeMethod()
HttpURLConnection.connect()
HttpUrlConnection.getInputStream()
URL.openStream()
HttpServerRequest()
BasicHttpEntityEnclosingRequest()
DefaultBHttpClientConnection()
BasicHttpRequest()

​ 审计的思路点基本是寻找下面函数的接口点看看是否可以借住接口点回调内网资源 这边的例子会在后续审计实战打组合拳中描述 其实从审计+实战的角度来讲 盲打内网存活信息和读取内网文件是比较常用的手段

​ 关于SSRF怎么打内网存活和读文件这里就不多说了,渗透的东西一搜一堆


·文件操作

·文件包含

<%@include file="test.jsp"%> //静态包含
<jsp:include page="<%=file%>"></jsp:include>
<jsp:include page ="<%=file%>"/>  //动态包含

​ 来看看第一种形式,这种形式相对静态包含来讲, 要复杂一点,因为在静态包含中其只属于一个include指令元素, 并且只有一个file的属性, 只是写上路径就行了, 路径可以是相对路径也可以是绝对路径, 但不能是<%=...%>代表的表达式,但在这里,file 属性可以是<%=...%>代表的表达式。

​ 第二种形式其实和第一种形式并无本质上的区别,core 库 的<c:import><jsp:include>一样,也是一种请求时操作,它的目的就是将其它一些 Web 资源的内容插入到当前的 JSP 页面中,这些 Web 资源就是通过url 属性来指定的,这也是<c:import>的唯一一个必选属性。值得一提的是,这里允许使用相对 URL,并且根据当前页面的 URL 来解析这个相对 URL。

​ 举个例子,如果我们当前页面的 URL 地址是http://127.0.0.1/admin/index.jsp,那么如果我们引用的 URL 属性值为/user/edit.jsp,那么其实最终解析的 URL 就是http://127.0.0.1/admin/user/edit.jsp

​ 所以,如果 url 属性的值以斜杠开始,那么它就被解释成本地 JSP 容器内的绝对 URL。如果没有为context属性指定值,那么就认为这样的绝对 URL 引用当前 servlet 上下文内的资源。如果通过context属性显式地指定了上下文,那么就根据指定的 servlet 上下文 解析绝对(本地)URL。

​ 当然,<c:import>操作并不仅仅限于访问本地内容,也可以为具体协议和主机名的完整 URI 。并且实际上,协议甚至不仅局限于 HTTP。<c:import>的 url 属性值可以使用 java.net.URL类所支持的任何协议(也就是http,https,ftp,file,jar,mailto,netdoc)。

​ 由于这些特性,导致动态包含可能会出现文件包含漏洞, 但这种包含和 PHP 中的包含存在很大的差别,对于 Java 的本地文件包含来说,造成的危害只有文件读取或下载,一般情况下不会造成命令执行或代码执行。因为一般情况下 Java 中对于文件的包含并不是将非 jsp 文件当成 Java 代码去执行,如果这个 JSP 文件是一个一句话木马文件,我们可以直接去访问利用,并不需要多此一举去包含它来使用了,除非在某些特殊场景下,如某些目录下权限不够可以尝试利用包含来绕过(理论上)。

​ 通常情况下 Java 并不会把非 jsp 文件当成 Java 去解析执行,但是可以利用服务容器本身的一些特性(如将指定目录下的文件全部作为 jsp 文件解析),来实现任意后缀的文件包含,如 Apache Tomcat Ajp(CVE-2020-1938)漏洞,利用 Tomcat 的 AJP(定向包协议)协议实现了任意后缀名文件当成 jsp 文件去解析,从而导致 RCE 漏洞。

​ 除此之外,另外提一点,静态包含和动态包含在执行时间上有很大的区别。静态包含是翻译阶段执行的,即被包含的文件和被插入到的页面会被 JSP 编译器合成编译,最终编译后的文件实际上只有一个。而动态包含实际是在请求处理阶段执行的,JSP程序会将请求转发到(注意不是重定向)被包含页面,并将执行结果输出到浏览器中,然后返回页面继续执行后面的代码,即被包含的文件和被插入到的页面会被JSP编译器单独编译。

比较另类的远程文件包含

一眼典环节 没有对url传入参数进行限制导致可以直接请求远程服务器下的各种文件 一般情况下可以配合来读文件

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head> 
 <title>远程文件包含测试</title>
 <%  String url = request.getParameter("url"); %>
 <c:import url="<%=url%>"></c:import>  
 
 <meta http-equiv="pragma" content="no-cache">
 <meta http-equiv="cache-control" content="no-cache">
 <meta http-equiv="expires" content="0">
</head>
<body>
This is my JSP page. <br>
</body>
</html>

·文件上传

​ 这里就不讲怎么打怎么绕限制了文件上传需要注意审计的函数有

File
lastIndexOf
indexOf
FileUpload
getRealPath
getServletPath
getPathInfo
getContentType
equalsLgnoreCase
FileUtils
MutilpartFile
MutilpartRequestEntiry
UploadHandleServlet
FileLoadServlet
FileOutputStream
getInputStream
DiskFileItemFactory

·任意文件读取/下载

​ 主要关注FileInputStream反溯构造链即可


# web安全 # java # 网络安全技术 # 审计 # Java代码审计
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录