Skip to content

Commit 605f28f

Browse files
authored
Merge pull request github#5686 from smowton/haby0/JsonHijacking
Java: JSONP Injection w/cleanups
2 parents 578ce1e + c379940 commit 605f28f

File tree

18 files changed

+790
-9
lines changed

18 files changed

+790
-9
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import java
2+
import semmle.code.java.dataflow.DataFlow
3+
import semmle.code.java.dataflow.FlowSources
4+
import DataFlow::PathGraph
5+
6+
/** Json string type data. */
7+
abstract class JsonStringSource extends DataFlow::Node { }
8+
9+
/**
10+
* Convert to String using Gson library. *
11+
*
12+
* For example, in the method access `Gson.toJson(...)`,
13+
* the `Object` type data is converted to the `String` type data.
14+
*/
15+
private class GsonString extends JsonStringSource {
16+
GsonString() {
17+
exists(MethodAccess ma, Method m | ma.getMethod() = m |
18+
m.hasName("toJson") and
19+
m.getDeclaringType().getASupertype*().hasQualifiedName("com.google.gson", "Gson") and
20+
this.asExpr() = ma
21+
)
22+
}
23+
}
24+
25+
/**
26+
* Convert to String using Fastjson library.
27+
*
28+
* For example, in the method access `JSON.toJSONString(...)`,
29+
* the `Object` type data is converted to the `String` type data.
30+
*/
31+
private class FastjsonString extends JsonStringSource {
32+
FastjsonString() {
33+
exists(MethodAccess ma, Method m | ma.getMethod() = m |
34+
m.hasName("toJSONString") and
35+
m.getDeclaringType().getASupertype*().hasQualifiedName("com.alibaba.fastjson", "JSON") and
36+
this.asExpr() = ma
37+
)
38+
}
39+
}
40+
41+
/**
42+
* Convert to String using Jackson library.
43+
*
44+
* For example, in the method access `ObjectMapper.writeValueAsString(...)`,
45+
* the `Object` type data is converted to the `String` type data.
46+
*/
47+
private class JacksonString extends JsonStringSource {
48+
JacksonString() {
49+
exists(MethodAccess ma, Method m | ma.getMethod() = m |
50+
m.hasName("writeValueAsString") and
51+
m.getDeclaringType()
52+
.getASupertype*()
53+
.hasQualifiedName("com.fasterxml.jackson.databind", "ObjectMapper") and
54+
this.asExpr() = ma
55+
)
56+
}
57+
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import com.alibaba.fastjson.JSONObject;
2+
import com.fasterxml.jackson.databind.ObjectMapper;
3+
import com.google.gson.Gson;
4+
import java.io.BufferedReader;
5+
import java.io.IOException;
6+
import java.io.InputStreamReader;
7+
import java.io.PrintWriter;
8+
import java.util.HashMap;
9+
import javax.servlet.http.HttpServletRequest;
10+
import javax.servlet.http.HttpServletResponse;
11+
import org.springframework.stereotype.Controller;
12+
import org.springframework.web.bind.annotation.GetMapping;
13+
import org.springframework.web.bind.annotation.RequestMapping;
14+
import org.springframework.web.bind.annotation.RequestMethod;
15+
import org.springframework.web.bind.annotation.RequestParam;
16+
import org.springframework.web.bind.annotation.ResponseBody;
17+
import org.springframework.web.multipart.MultipartFile;
18+
19+
@Controller
20+
public class JsonpInjection {
21+
22+
private static HashMap hashMap = new HashMap();
23+
24+
static {
25+
hashMap.put("username","admin");
26+
hashMap.put("password","123456");
27+
}
28+
29+
@GetMapping(value = "jsonp1")
30+
@ResponseBody
31+
public String bad1(HttpServletRequest request) {
32+
String resultStr = null;
33+
String jsonpCallback = request.getParameter("jsonpCallback");
34+
Gson gson = new Gson();
35+
String result = gson.toJson(hashMap);
36+
resultStr = jsonpCallback + "(" + result + ")";
37+
return resultStr;
38+
}
39+
40+
@GetMapping(value = "jsonp2")
41+
@ResponseBody
42+
public String bad2(HttpServletRequest request) {
43+
String resultStr = null;
44+
String jsonpCallback = request.getParameter("jsonpCallback");
45+
resultStr = jsonpCallback + "(" + JSONObject.toJSONString(hashMap) + ")";
46+
return resultStr;
47+
}
48+
49+
@GetMapping(value = "jsonp3")
50+
@ResponseBody
51+
public String bad3(HttpServletRequest request) {
52+
String resultStr = null;
53+
String jsonpCallback = request.getParameter("jsonpCallback");
54+
String jsonStr = getJsonStr(hashMap);
55+
resultStr = jsonpCallback + "(" + jsonStr + ")";
56+
return resultStr;
57+
}
58+
59+
@GetMapping(value = "jsonp4")
60+
@ResponseBody
61+
public String bad4(HttpServletRequest request) {
62+
String resultStr = null;
63+
String jsonpCallback = request.getParameter("jsonpCallback");
64+
String restr = JSONObject.toJSONString(hashMap);
65+
resultStr = jsonpCallback + "(" + restr + ");";
66+
return resultStr;
67+
}
68+
69+
@GetMapping(value = "jsonp5")
70+
@ResponseBody
71+
public void bad5(HttpServletRequest request,
72+
HttpServletResponse response) throws Exception {
73+
String jsonpCallback = request.getParameter("jsonpCallback");
74+
PrintWriter pw = null;
75+
Gson gson = new Gson();
76+
String result = gson.toJson(hashMap);
77+
String resultStr = null;
78+
pw = response.getWriter();
79+
resultStr = jsonpCallback + "(" + result + ")";
80+
pw.println(resultStr);
81+
}
82+
83+
@GetMapping(value = "jsonp6")
84+
@ResponseBody
85+
public void bad6(HttpServletRequest request,
86+
HttpServletResponse response) throws Exception {
87+
String jsonpCallback = request.getParameter("jsonpCallback");
88+
PrintWriter pw = null;
89+
ObjectMapper mapper = new ObjectMapper();
90+
String result = mapper.writeValueAsString(hashMap);
91+
String resultStr = null;
92+
pw = response.getWriter();
93+
resultStr = jsonpCallback + "(" + result + ")";
94+
pw.println(resultStr);
95+
}
96+
97+
@RequestMapping(value = "jsonp7", method = RequestMethod.GET)
98+
@ResponseBody
99+
public String bad7(HttpServletRequest request) {
100+
String resultStr = null;
101+
String jsonpCallback = request.getParameter("jsonpCallback");
102+
Gson gson = new Gson();
103+
String result = gson.toJson(hashMap);
104+
resultStr = jsonpCallback + "(" + result + ")";
105+
return resultStr;
106+
}
107+
108+
@RequestMapping(value = "jsonp11")
109+
@ResponseBody
110+
public String good1(HttpServletRequest request) {
111+
JSONObject parameterObj = readToJSONObect(request);
112+
String resultStr = null;
113+
String jsonpCallback = request.getParameter("jsonpCallback");
114+
String restr = JSONObject.toJSONString(hashMap);
115+
resultStr = jsonpCallback + "(" + restr + ");";
116+
return resultStr;
117+
}
118+
119+
@RequestMapping(value = "jsonp12")
120+
@ResponseBody
121+
public String good2(@RequestParam("file") MultipartFile file,HttpServletRequest request) {
122+
if(null == file){
123+
return "upload file error";
124+
}
125+
String fileName = file.getOriginalFilename();
126+
System.out.println("file operations");
127+
String resultStr = null;
128+
String jsonpCallback = request.getParameter("jsonpCallback");
129+
String restr = JSONObject.toJSONString(hashMap);
130+
resultStr = jsonpCallback + "(" + restr + ");";
131+
return resultStr;
132+
}
133+
134+
public static JSONObject readToJSONObect(HttpServletRequest request){
135+
String jsonText = readPostContent(request);
136+
JSONObject jsonObj = JSONObject.parseObject(jsonText, JSONObject.class);
137+
return jsonObj;
138+
}
139+
140+
public static String readPostContent(HttpServletRequest request){
141+
BufferedReader in= null;
142+
String content = null;
143+
String line = null;
144+
try {
145+
in = new BufferedReader(new InputStreamReader(request.getInputStream(),"UTF-8"));
146+
StringBuilder buf = new StringBuilder();
147+
while ((line = in.readLine()) != null) {
148+
buf.append(line);
149+
}
150+
content = buf.toString();
151+
} catch (IOException e) {
152+
e.printStackTrace();
153+
}
154+
String uri = request.getRequestURI();
155+
return content;
156+
}
157+
158+
public static String getJsonStr(Object result) {
159+
return JSONObject.toJSONString(result);
160+
}
161+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>The software uses external input as the function name to wrap JSON data and returns it to the client as a request response.
7+
When there is a cross-domain problem, this could lead to information leakage.</p>
8+
9+
</overview>
10+
<recommendation>
11+
12+
<p>Adding <code>Referer</code>/<code>Origin</code> or random <code>token</code> verification processing can effectively prevent the leakage of sensitive information.</p>
13+
14+
</recommendation>
15+
<example>
16+
17+
<p>The following examples show the bad case and the good case respectively. Bad cases, such as <code>bad1</code> to <code>bad7</code>,
18+
will cause information leakage when there are cross-domain problems. In a good case, for example, in the <code>good1</code>
19+
method and the <code>good2</code> method, When these two methods process the request, there must be a request body in the request, which does not meet the conditions of Jsonp injection.</p>
20+
21+
<sample src="JsonpInjection.java" />
22+
23+
</example>
24+
<references>
25+
26+
<li>
27+
OWASPLondon20161124_JSON_Hijacking_Gareth_Heyes:
28+
<a href="https://owasp.org/www-chapter-london/assets/slides/OWASPLondon20161124_JSON_Hijacking_Gareth_Heyes.pdf">JSON hijacking</a>.
29+
</li>
30+
<li>
31+
Practical JSONP Injection:
32+
<a href="https://securitycafe.ro/2017/01/18/practical-jsonp-injection">
33+
Completely controllable from the URL (GET variable)
34+
</a>.
35+
</li>
36+
</references>
37+
</qhelp>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* @name JSONP Injection
3+
* @description User-controlled callback function names that are not verified are vulnerable
4+
* to jsonp injection attacks.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @precision high
8+
* @id java/JSONP-Injection
9+
* @tags security
10+
* external/cwe/cwe-352
11+
*/
12+
13+
import java
14+
import JsonpInjectionLib
15+
import semmle.code.java.dataflow.FlowSources
16+
import semmle.code.java.deadcode.WebEntryPoints
17+
import DataFlow::PathGraph
18+
19+
/** Taint-tracking configuration tracing flow from get method request sources to output jsonp data. */
20+
class RequestResponseFlowConfig extends TaintTracking::Configuration {
21+
RequestResponseFlowConfig() { this = "RequestResponseFlowConfig" }
22+
23+
override predicate isSource(DataFlow::Node source) {
24+
source instanceof RemoteFlowSource and
25+
any(RequestGetMethod m).polyCalls*(source.getEnclosingCallable())
26+
}
27+
28+
override predicate isSink(DataFlow::Node sink) {
29+
sink instanceof XssSink and
30+
any(RequestGetMethod m).polyCalls*(sink.getEnclosingCallable())
31+
}
32+
33+
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
34+
exists(MethodAccess ma |
35+
isRequestGetParamMethod(ma) and pred.asExpr() = ma.getQualifier() and succ.asExpr() = ma
36+
)
37+
}
38+
}
39+
40+
from DataFlow::PathNode source, DataFlow::PathNode sink, RequestResponseFlowConfig conf
41+
where
42+
conf.hasFlowPath(source, sink) and
43+
exists(JsonpInjectionFlowConfig jhfc | jhfc.hasFlowTo(sink.getNode()))
44+
select sink.getNode(), source, sink, "Jsonp response might include code from $@.", source.getNode(),
45+
"this user input"

0 commit comments

Comments
 (0)