Java 代码审计笔记

常见漏洞审计

1.SQL 注入漏洞

Java 执行 SQL 语句常见方式:

  • 使用 JDBC 的 java.sql.Statement 执行 SQL 语句
  • 使用 JDBC 的 java.sql.PreparedStatement 执行 SQL 语句
  • 使用 Hibernate 执行 SQL 语句
  • 使用 MyBatis 执行 SQL 语句

其中 Statement 需要通过拼接来执行语句且每次都要进行编译,若不对输入做过滤会导致 SQL 注入漏洞;而 PrepareStatement 进行预编译参数化查询能够有效防止 SQL 注入。通过搜索这两个类的调用并进行审计,从而检查出漏洞是否存在。

PreparedStatement 是使用占位符传入参数,因此在执行 order by 时会出错(具体原因不详细说明,自行百度),只能使用字符串拼接的方式,因此只能使用 Statement。

Java 预编译查询中不会对 % 和_进行转义处理,而 % 和_是 like 查询的通配符,不做相关过滤可能导致恶意模糊查询。

MyBatis 中的 #{} 在底层上使用 “?” 作为占位符来生成 PreparedStatement,是参数化查询预编译的机制,较安全,也因此在使用 order by、like、in 查询时会报错,需要转用 ${} 进行查询;${} 将传入的数据直接显示生成在 SQL 语句中,类似字符串拼接,因此不对输入进行检查、过滤将可能导致 SQL 注入。

审计总结:(留意 getParameter 函数来找到可控参数)

  • Statement
  • createStatement
  • PreparedStatement
  • like ‘%${
  • in (${
  • select
  • update
  • insert

2. 任意文件上传漏洞

常见文件上传方式:

public String fileUpload(@RequestParam("file") CommonsMulpartFile file) throws IOException{
long startTime = System.currentTimeMillis();
System.out.println("fileName: "+file.getOriginalFilename());
try{
OutputStream os = new FileOutputStream("/tmp"+newDate().getTime()+file.getOriginalFilename());
InputStream is = file.getInputStream();
int temp;
while((temp = is.read())!=(-1))
{
os.write(temp);
}
os.flush();
os.close();
is.close();
} catch(FileNotFoundException e){
e.printStackTrace();
}
return "/success";
}
String realPath = this.getServletContext().getRealPath("/upload");
String tempPath = "/tmp";
File f = new File(realPath);
if(!f.exists()&&!f.isDirectory()){
f.mkdir();
}
File f1 = new File(tempPath);
if(!f1.isDirectory)){
f1.mkdir();
}
DiskFileUploadItemFactory factory = new DiskFileItemFactory();
factory.setRepository(f1);
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setHeaderEncoding("UTF-8");
if(!ServletFileUpload.isMultipartContent(req)){ return; }
List<FileItem> items = upload.parseRequest(req);
for(FileItem item:items){
if(item.isFormField()){
String filedName = item.getFieldName();
String filedValue = item.getString("UTF-8");
}
else{
String filedName = item.getName();
if(fileName == null || "".equals(fileName.trim())){ continue; }
fileName = fileName.substring(fileName.lastIndexOf("/")+1);
String filePath = realPath+"/"+fileName;
InputStream in = item.getInputStream();
OutputStream out = new FileOutputStream(filePath);
byte b[] = new byte[1024];
int len = -1;
while((len=in.read(b)))!=-1){
out.write(b,0,len);
}
out.close();
in.close();
try{
Thread.sleep(3000);
} catch(InterruptedException e){
e.printStackTrace();
}
item.delete();
public String handleFileUpload(@RequestParam("file") MultipartFile file){
if(file.isEmpty()){
return "请上传文件";
}
//获取文件名
String fileName = file.getOriginalFilename();
String suffixName = fileName.substring(fileName.indexOf("."));
String filePath = "/tmp";
File dest = new File(filePath+fileName);
if(!dest.getParentFile().exists()){
dest.getParentFile().mkdirs();
}
try{
file.transferTo(dest);
return "上传成功";
} catch(IllegalStateException e){
e.printStackTrace();
} catch(IOException e){
e.printStackTrace();
}
return "上传失败";
}

审计总结:(检查前后端是否存在后缀过滤不全和读取后缀方式错误等)

  • org.apache.commons.fileupload
  • java.io.File
  • MultipartFile
  • RequestMethod
  • MultipartHttpServletRequest
  • CommonsMultipartResolver

3.XSS 漏洞

XXS 漏洞的产生必然存在相关的输入 / 输出:Java 输入通常使用 request.getParameter (param) 或 ${param} 输入信息。输出表现为前端的渲染,可通过定位前端中一些标识来找。

XSS 常见触发位置

JSP 表达式

  • <%= 变量 %>
  • <% out.println (变量); %>
  • <% String msg = request.getParameter (‘变量’);%>

EL(Expression Language,表达式语言)

  • <c:out> 标签:显示一个表达式的结果
  • <c:if> 标签:判断表达式的值
  • <c:forEach> 标签:迭代输出标签内部的内容

ModelAndView 类用来存储处理完成后的结果数据,以及显示该数据的视图,其前端 JSP 页面可以使用 ${参数} 的方法来获取值:

package com.dgr.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@RequestMapping("mvc")
@Controller
public class TestRequestMapping{
@RequestMapping(value="/getMessage")
public ModelAndView getMessage(){
ModelAndView modelandview = new ModelAndView();
modelandview.setViewName("message");
modelandview.addObject("message","Hello World");
return modelandview;
}
}

ModelMap 类的使用

//Spring也提供了ModelMap类,此处用java.util.Map实现,可根据模型属性的具体类型自动生成模型属性的名称
public String testmethod(String someparam,ModelMap model){
//省略方法处理逻辑
//将数据放置到ModelMap类的model对象中,第二个参数可以是任何Java类型
Model.addAttribute("key",someparam);
return "success";
}

Model 类的使用(Model 类是一个接口类,通过 attribute () 添加数据,存储的数据范围是 requestScope)

public String index1(Model model){
model.addAttribute("result","后台返回");
return "result";
}

审计总结:

  • <%=
  • ${
  • <c:out
  • <c:if
  • <c:forEach
  • ModelAndView
  • ModelMap
  • Model
  • request.gerParameter
  • request.setAttribute
  • response.getWriter().print()
  • response.getWriter().writer()

4. 目录穿越漏洞

目录穿越的本质是路径可控,一旦涉及文件的读取问题便会涉及 java.io.File 类,审计时优先查找 java.io.File 类的调用,判断 Paths、path、System.getProperty (“user.dir”) 等可能会用来构造路径的关键字

5.URL 跳转漏洞

检查用于 URL 重定向的方法:

  • 通过 ModelAndView
  • 通过返回 String
  • 使用 sendRedirect
  • 使用 RedirectAttributes
  • 通过设置 Header

6. 命令执行漏洞

Java.lang.ProcessBuilder 类用于创建操作系统进程,每个 ProcessBuilder 实例管理一个进程属性集。start () 方法利用这些属性创建一个新的 Process 进程实例,从而利用 ProcessBuilder 执行命令:

ProcessBuilder pb = new ProcessBuilder("mycommand","myArg");
Process process = pb.start();

java.lang.Runtime 公共类中的 exec () 方法同样也可以执行系统命令:

  • public Process exec(String command)
  • public Process exec(String[] cmdarray)
  • public Process exec(String[] cmdarray,String[] envp)
  • public Process exec(String[] cmdarray,String[] envp,File dir)
  • public Process exec(String command,String[] envp)
  • public Process exec(String command,String[] envp,File dir)

7.XXE 漏洞

搜索常见的能够解析 XML 的方法:

  • XMLReader
  • SAXBuilder
  • SAXReader
  • SAXParserFactory
  • Digester // 通常没有回显
  • DocumentBuilderFactory

8.SSRF 漏洞

与 PHP 不同,在 Java 中 SSRF 仅支持 sun.net.www.protocol 下所有的协议:http、https、file、ftp、mailto、jar 及 netdoc 协议,因此不能像 PHP 一样使用 gopher 协议来扩展攻击面

在 Java 中可以通过 file 或 netdoc 协议进行列目录操作以读取更多敏感信息,对于无回显的文件读取可以利用 ftp 协议进行外带攻击(部分版本的 Java 即使使用 ftp 协议也无法读取多行文件

注意能够发起 HTTP 请求的类及函数:

  • HttpURLConnection.getInputStream
  • URLConnection.getInputStream
  • HttpClient.execute
  • OkHttpClient.newCall.execute
  • Request.Get.execute
  • Request.Post.execute
  • [url].openStream () 方法
  • ImageIO.read

若想支持 sun.net.www.protocol 中的所有协议则只能使用方法:URLConnection、URL

若发起的网络请求是带 HTTP 的,那么其将只支持 HTTP、HTTPS 协议:HttpURLConnection、HttpClient、OkHttpClient.newCall.execute

9.SpEL 表达式注入漏洞

SpEL 表达式语法:

  • 使用量表达式语法:”#{‘Hello World’}”
  • 使用 Java 代码 new/instanceof:Expression exp = parser.parseExpression (“new Spring (‘Hello World’)”);
  • 使用 T (Type) 来表示 java.lang.Class 实例:parser.parseExpression (“T (Integer).MAX_VALUE”);

SpEL 中可以使用 #bean_id 来获取容器内的变量。同时,存在两个特殊的变量 #this、#root,分别用来表示使用当前的上下文和引用容器的 root 对象

SpEL 可以使用 T () 操作符声明特定的 Java 类型,一般用来访问 Java 类型中的静态属性或静态方法。括号中需要包含类名的全限定名,也就是包名加上类名。唯一例外的是 SpEL 内置了 java.lang 包下的类声明,例如 java.lang.String 可以通过 T (String) 访问而不需要使用全限定名

使用 new 可以直接在 SpEL 中创建实例,创建实例的类要通过全限定名进行访问:

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new.java.util.Date()");
Date value = (Date)exp.getValue();
System.out.println(value);
  • org.springframework.expression.spel.standard
  • SpelExpressionParser
  • parseExpression
  • expression.getValue()
  • expression.setValue()

SpEL 漏洞成因:Spring 为解析 SpEL 提供了两套接口,分别是 SimpleEvaluationContext 和 StandardEvaluationContext。SimpleEvaluationContext 仅支持 SpEL 语法的子集,抛弃了 Java 类型引用、构造函数及 beam 引用相对安全。而 StandardEvaluationContext 包含了 SpEL 所有功能,且在不指定 EvaluationContext 情况下默认采用 StandardEvaluationContext;很大一部分开发人员未对用户输入进行处理就直接通过解析引擎对 SpEL 继续解析

通过插入以下 POC 检查是否存在 SpEL 表达式注入:

  • ${4*4}
  • T(Thread).sleep(10000)
  • T(java.lang.Runtime).getRuntime().exec(‘command’)
  • T(java.lang.Runtime).getRuntime().exec(“nslookup baidu.com”)
  • new java.lang.ProcessBuilder(“command”).start()
  • new java.lang.ProcessBuilder({‘nslookup baidu.com’}).start()
  • #this.getClass().forName(‘java.lang.Runtime’).getRuntime().exec(‘nslookup baidu.com’)

对于常见黑名单过滤可以利用两种方式构造 payload:利用反射与拆分关键字构造、利用 ScriptEngineManager 构造

10.Java 反序列化

审计关键字:

  • ObjectInputStream.readObject
  • ObjectInputStream.readUnshared
  • XMLDecoder.readObject
  • Yaml.load
  • XStream.fromXML
  • ObjectMapper.readValue
  • JSON.parseObject

搜索关键字找到存在反序列化操作的文件时,考虑参数是否可控,以及引用的 Class Path 是否包含 Apache Commons Collections 等危险库(ysoserial 所支持的其他库亦可)。同时满足以上条件则通过 ysoserial 工具生成 payload 直接打。不支持危险库则尝试通过构造利用链接实现任意代码执行

11.SSTI 模板注入漏洞

Java 中常见模板引擎:XMLTemplate、Velocity、CommonTemplate、FreeMarker、Smarty4j、TemplateEngine 等,Velocity 使用较多

在 Velocity 中以 #来标识 Velocity 的脚本语句例如 #set、#if、#else、# 恩典、#foreach、#iinclude、#parse 等

#if($msg.img)
<img src="$msg.imgs" border=0>
#else
<img src="qccp.jpg">
#end

$ 用来标识一个对象,一旦可以调用对象则有办法构造命令执行语句:$e.getClass ().forName (“java.lang.Runtime”).getMethod (“getRuntime”,null).invoke (null,null).exec ()

在 Velocity 模板注入中,如果无法进行命令执行,往往可以通过修改 Cookie 来进行特权升级,例如:$session.setAttribute (“IS_ADMIN”,”1″)

在漏洞不存在回显的情况且容器为 Tomcat7 时,可通过以下方法构造一个有回显的命令执行:

#set($str=$class.inspect("java.lang.String").type)
#set($chr=$class.inspect("java.lang.Character").type)
#set($ex=$class.inspect("java.lang.Runtime").type.getRuntime().exec("whoami"))
$ex.waitFor()
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])
$str.valueOf($chr.toChars($out.read()))
#end

审计关键字:搜索对于模板引擎的关键字,例如 velocity 对应的 org.apache.velocity

12. 整数溢出漏洞

java 中的 int 是 32 位有符号整数类型,其最大值是 0x7fffffff,最小值是 0x80000000,即 - 2147483648~2147483647 之间。当运算结果超出这个范围则发生了溢出,而且不会有任何异常抛出

13. 硬编码密码漏洞

硬编码密码指在系统中采用明文的形式存储密码(密码直接存储在源代码甚至是前端代码中),通常会导致严重的身份验证失败。

14. 不安全的随机数生成器

计算机产生的随机数具有周期性、可预测性,例如 java.util.Random 工具类没带参数构造函数生成的 Random 对象的种子默认是当前系统时间的毫秒数,故进入到 Random 类中查看其种子默认是当前的系统时间,只要种子一样,其输出的随机序列也是一样的

欢迎评论区中交流
No Comments

Send Comment Edit Comment


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
Previous