Skip to content

Commit 27b4af2

Browse files
authored
Merge pull request #1368 from imeszaros/xss-fix
Avoiding possible XSS attack through the default error handler
2 parents 72642c3 + 395dab7 commit 27b4af2

File tree

4 files changed

+59
-13
lines changed

4 files changed

+59
-13
lines changed

jooby/src/main/java/org/jooby/Err.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,10 @@
203203
*/
204204
package org.jooby;
205205

206-
import java.util.LinkedHashMap;
207-
import java.util.Map;
208-
import java.util.Optional;
206+
import java.util.*;
207+
import java.util.function.BiFunction;
208+
import java.util.function.Function;
209+
import java.util.function.Supplier;
209210

210211
import com.typesafe.config.Config;
211212
import org.jooby.funzy.Try;
@@ -287,12 +288,21 @@ public void handle(final Request req, final Response rsp, final Err ex) throws T
287288
log.error("execution of: {}{} resulted in exception\nRoute:\n{}\n\nStacktrace:",
288289
req.method(), req.path(), req.route().print(6), ex);
289290
Config conf = req.require(Config.class);
291+
Env env = req.require(Env.class);
290292
boolean stackstrace = Try.apply(() -> conf.getBoolean("err.stacktrace"))
291-
.orElse(req.require(Env.class).name().equals("dev"));
293+
.orElse(env.name().equals("dev"));
294+
295+
Function<Object, String> xssFilter = env.xss("html").compose(Objects::toString);
296+
BiFunction<String, Object, String> escaper = (k, v) -> xssFilter.apply(v);
297+
298+
Map<String, Object> details = ex.toMap(stackstrace);
299+
details.compute("message", escaper);
300+
details.compute("reason", escaper);
301+
292302
rsp.send(
293303
Results
294-
.when(MediaType.html, () -> Results.html(VIEW).put("err", ex.toMap(stackstrace)))
295-
.when(MediaType.all, () -> ex.toMap(stackstrace)));
304+
.when(MediaType.html, () -> Results.html(VIEW).put("err", details))
305+
.when(MediaType.all, () -> details));
296306
}
297307

298308
}

jooby/src/main/java/org/jooby/internal/HttpHandlerImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ private static Route[] routes(final Set<Route.Definition> routeDefs, final Strin
627627
// default /favicon.ico handler:
628628
rsp.status(Status.NOT_FOUND).end();
629629
} else {
630-
throw new Err(Status.NOT_FOUND, req.path(true));
630+
throw new Err(Status.NOT_FOUND, req.path());
631631
}
632632
}
633633
}, method, path, "err", accept));

jooby/src/main/java/org/jooby/internal/RouteImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ public class RouteImpl implements RouteWithFilter {
233233
public static RouteWithFilter notFound(final String method, final String path) {
234234
return new FallbackRoute("404", method, path, MediaType.ALL, (req, rsp, chain) -> {
235235
if (!rsp.status().isPresent()) {
236-
throw new Err(Status.NOT_FOUND, req.path(true));
236+
throw new Err(Status.NOT_FOUND, req.path());
237237
}
238238
});
239239
}

jooby/src/test/java/org/jooby/DefaultErrHandlerTest.java

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.jooby;
22

33
import com.google.common.collect.ImmutableList;
4+
import com.google.common.escape.Escapers;
5+
import com.google.common.html.HtmlEscapers;
46
import com.typesafe.config.Config;
57
import static org.easymock.EasyMock.expect;
68
import org.jooby.test.MockUnit;
@@ -15,6 +17,7 @@
1517

1618
import java.io.PrintWriter;
1719
import java.io.StringWriter;
20+
import java.util.LinkedHashMap;
1821
import java.util.Map;
1922

2023
@RunWith(PowerMockRunner.class)
@@ -66,6 +69,7 @@ private MockUnit.Block handleErr(Throwable ex, boolean stacktrace) {
6669
expect(conf.getBoolean("err.stacktrace")).andReturn(stacktrace);
6770
Env env = unit.get(Env.class);
6871
expect(env.name()).andReturn("dev");
72+
expect(env.xss("html")).andReturn(HtmlEscapers.htmlEscaper()::escape);
6973

7074
Request req = unit.get(Request.class);
7175

@@ -112,13 +116,45 @@ public void handleWithErrMessage() throws Exception {
112116
});
113117
}
114118

119+
@SuppressWarnings({"unchecked"})
120+
@Test
121+
public void handleWithHtmlErrMessage() throws Exception {
122+
Err ex = new Err(500, "Something something <em>dark</em>");
123+
124+
StringWriter writer = new StringWriter();
125+
ex.printStackTrace(new PrintWriter(writer));
126+
String[] stacktrace = writer.toString().replace("\r", "").split("\\n");
127+
128+
new MockUnit(Request.class, Response.class, Route.class, Env.class, Config.class)
129+
.expect(handleErr(ex, true))
130+
.run(unit -> {
131+
132+
Request req = unit.get(Request.class);
133+
Response rsp = unit.get(Response.class);
134+
135+
new Err.DefHandler().handle(req, rsp, ex);
136+
},
137+
unit -> {
138+
Result result = unit.captured(Result.class).iterator().next();
139+
View view = (View) result.ifGet(ImmutableList.of(MediaType.html)).get();
140+
assertEquals("err", view.name());
141+
checkErr(stacktrace, "Server Error(500): Something something &lt;em&gt;dark&lt;/em&gt;",
142+
(Map<String, Object>) view.model()
143+
.get("err"));
144+
145+
Object hash = result.ifGet(MediaType.ALL).get();
146+
assertEquals(4, ((Map<String, Object>) hash).size());
147+
});
148+
}
149+
115150
private void checkErr(final String[] stacktrace, final String message,
116151
final Map<String, Object> err) {
117-
assertEquals(message, err.remove("message"));
118-
assertEquals("Server Error", err.remove("reason"));
119-
assertEquals(500, err.remove("status"));
120-
assertArrayEquals(stacktrace, (String[]) err.remove("stacktrace"));
121-
assertEquals(err.toString(), 0, err.size());
152+
final Map<String, Object> copy = new LinkedHashMap<>(err);
153+
assertEquals(message, copy.remove("message"));
154+
assertEquals("Server Error", copy.remove("reason"));
155+
assertEquals(500, copy.remove("status"));
156+
assertArrayEquals(stacktrace, (String[]) copy.remove("stacktrace"));
157+
assertEquals(copy.toString(), 0, copy.size());
122158
}
123159

124160
}

0 commit comments

Comments
 (0)