Skip to content

Commit dda0e03

Browse files
committed
pac4j: untrustated data
- don't allow to set encoded data outside of pac4j internals
1 parent 1ab0210 commit dda0e03

File tree

6 files changed

+293
-50
lines changed

6 files changed

+293
-50
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package io.jooby.internal.pac4j;
2+
3+
import io.jooby.*;
4+
import io.jooby.pac4j.Pac4jUntrustedDataFound;
5+
6+
import javax.annotation.Nonnull;
7+
import javax.annotation.Nullable;
8+
import java.time.Instant;
9+
import java.util.Map;
10+
11+
class Pac4jSession implements Session {
12+
public static final String PAC4J = "p4j~";
13+
14+
public static final String BIN = "b64~";
15+
16+
private final Session session;
17+
18+
public Pac4jSession(@Nonnull Session session) {
19+
this.session = session;
20+
}
21+
22+
@Nullable
23+
public String getId() {
24+
return session.getId();
25+
}
26+
27+
@Nonnull
28+
public Value get(@Nonnull String name) {
29+
return session.get(name);
30+
}
31+
32+
@Nonnull
33+
public Instant getLastAccessedTime() {
34+
return session.getLastAccessedTime();
35+
}
36+
37+
public void destroy() {
38+
session.destroy();
39+
}
40+
41+
@Nonnull
42+
public Session setId(String id) {
43+
session.setId(id);
44+
return this;
45+
}
46+
47+
@Nonnull
48+
public Value remove(@Nonnull String name) {
49+
return session.remove(name);
50+
}
51+
52+
public boolean isNew() {
53+
return session.isNew();
54+
}
55+
56+
@Nonnull
57+
public Session setNew(boolean isNew) {
58+
session.setNew(isNew);
59+
return this;
60+
}
61+
62+
@Nonnull
63+
public Session setLastAccessedTime(@Nonnull Instant lastAccessedTime) {
64+
session.setLastAccessedTime(lastAccessedTime);
65+
return this;
66+
}
67+
68+
public boolean isModify() {
69+
return session.isModify();
70+
}
71+
72+
@Nonnull
73+
public Session setCreationTime(@Nonnull Instant creationTime) {
74+
session.setCreationTime(creationTime);
75+
return this;
76+
}
77+
78+
@Nonnull
79+
public Session setModify(boolean modify) {
80+
session.setModify(modify);
81+
return this;
82+
}
83+
84+
public Session renewId() {
85+
session.renewId();
86+
return this;
87+
}
88+
89+
@Nonnull
90+
public Instant getCreationTime() {
91+
return session.getCreationTime();
92+
}
93+
94+
@Nonnull
95+
public Map<String, String> toMap() {
96+
return session.toMap();
97+
}
98+
99+
public Session clear() {
100+
session.clear();
101+
return this;
102+
}
103+
104+
public Session getSession() {
105+
return session;
106+
}
107+
108+
public static Context create(Context ctx) {
109+
return new ForwardingContext(ctx) {
110+
@Nonnull
111+
@Override
112+
public Session session() {
113+
return new Pac4jSession(super.session());
114+
}
115+
116+
@Override
117+
public Session sessionOrNull() {
118+
Session session = super.sessionOrNull();
119+
return session == null ? null : new Pac4jSession(session);
120+
}
121+
};
122+
}
123+
124+
@Nonnull
125+
@Override
126+
public Session put(@Nonnull String name, @Nonnull String value) {
127+
if (value != null) {
128+
if (value.startsWith(PAC4J) || value.startsWith(BIN)) {
129+
throw new Pac4jUntrustedDataFound(name);
130+
}
131+
}
132+
return session.put(name, value);
133+
}
134+
}

modules/jooby-pac4j/src/main/java/io/jooby/internal/pac4j/SessionStoreImpl.java

+23-33
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
package io.jooby.internal.pac4j;
77

88
import io.jooby.Session;
9-
import io.jooby.SneakyThrows;
109
import io.jooby.Value;
1110
import io.jooby.pac4j.Pac4jContext;
1211
import org.pac4j.core.context.session.SessionStore;
@@ -21,13 +20,9 @@
2120
import org.pac4j.core.exception.http.UnauthorizedAction;
2221
import org.pac4j.core.exception.http.WithContentAction;
2322
import org.pac4j.core.exception.http.WithLocationAction;
23+
import org.pac4j.core.util.JavaSerializationHelper;
2424

25-
import java.io.ByteArrayInputStream;
26-
import java.io.ByteArrayOutputStream;
27-
import java.io.IOException;
28-
import java.io.ObjectInputStream;
29-
import java.io.ObjectOutputStream;
30-
import java.util.Base64;
25+
import java.io.*;
3126
import java.util.Optional;
3227

3328
import static io.jooby.StatusCode.BAD_REQUEST_CODE;
@@ -42,16 +37,19 @@
4237
public class SessionStoreImpl
4338
implements org.pac4j.core.context.session.SessionStore<Pac4jContext> {
4439

45-
private static final String PAC4J = "p4j~";
46-
47-
private static final String BIN = "b64~";
48-
4940
private Session getSession(Pac4jContext context) {
50-
return context.getContext().session();
41+
return session(context.getContext().session());
42+
}
43+
44+
private Session session(Session session) {
45+
if (session instanceof Pac4jSession) {
46+
return ((Pac4jSession) session).getSession();
47+
}
48+
return session;
5149
}
5250

5351
private Optional<Session> getSessionOrEmpty(Pac4jContext context) {
54-
return Optional.ofNullable(context.getContext().sessionOrNull());
52+
return Optional.ofNullable(session(context.getContext().sessionOrNull()));
5553
}
5654

5755
@Override public String getOrCreateSessionId(Pac4jContext context) {
@@ -94,7 +92,7 @@ private Optional<Session> getSessionOrEmpty(Pac4jContext context) {
9492
}
9593

9694
@Override public boolean renewSession(Pac4jContext context) {
97-
//getSessionOrEmpty(context).ifPresent(session -> session.renewId());
95+
getSessionOrEmpty(context).ifPresent(Session::renewId);
9896
return true;
9997
}
10098

@@ -103,15 +101,11 @@ static Optional<Object> strToObject(final Value node) {
103101
return Optional.empty();
104102
}
105103
String value = node.value();
106-
if (value.startsWith(BIN)) {
107-
try {
108-
byte[] bytes = Base64.getDecoder().decode(value.substring(BIN.length()));
109-
return Optional.of(new ObjectInputStream(new ByteArrayInputStream(bytes)).readObject());
110-
} catch (Exception x) {
111-
throw SneakyThrows.propagate(x);
112-
}
113-
} else if (value.startsWith(PAC4J)) {
114-
return Optional.of(strToAction(value.substring(PAC4J.length())));
104+
if (value.startsWith(Pac4jSession.BIN)) {
105+
JavaSerializationHelper helper = new JavaSerializationHelper();
106+
return Optional.of(helper.deserializeFromBase64(value.substring(Pac4jSession.BIN.length())));
107+
} else if (value.startsWith(Pac4jSession.PAC4J)) {
108+
return Optional.of(strToAction(value.substring(Pac4jSession.PAC4J.length())));
115109
}
116110
return Optional.of(value);
117111
}
@@ -121,21 +115,17 @@ static String objToStr(final Object value) {
121115
return value.toString();
122116
} else if (value instanceof HttpAction) {
123117
return actionToStr((HttpAction) value);
124-
}
125-
try {
126-
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
127-
ObjectOutputStream stream = new ObjectOutputStream(bytes);
128-
stream.writeObject(value);
129-
stream.flush();
130-
return BIN + Base64.getEncoder().encodeToString(bytes.toByteArray());
131-
} catch (IOException x) {
132-
throw SneakyThrows.propagate(x);
118+
} else if (value instanceof Serializable) {
119+
JavaSerializationHelper helper = new JavaSerializationHelper();
120+
return Pac4jSession.BIN + helper.serializeToBase64((Serializable) value);
121+
} else {
122+
throw new UnsupportedOperationException("Unsupported type: " + value.getClass().getName());
133123
}
134124
}
135125

136126
private static String actionToStr(HttpAction action) {
137127
StringBuilder buffer = new StringBuilder();
138-
buffer.append(PAC4J).append(action.getCode());
128+
buffer.append(Pac4jSession.PAC4J).append(action.getCode());
139129
if (action instanceof WithContentAction) {
140130
buffer.append(":").append(((WithContentAction) action).getContent());
141131
} else if (action instanceof WithLocationAction) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.jooby.internal.pac4j;
2+
3+
import io.jooby.Route;
4+
import io.jooby.Session;
5+
6+
import javax.annotation.Nonnull;
7+
8+
public class UntrustedSessionDataDetector implements Route.Decorator {
9+
@Nonnull
10+
@Override
11+
public Route.Handler apply(@Nonnull Route.Handler next) {
12+
return ctx -> {
13+
Session session = ctx.sessionOrNull();
14+
if (session instanceof Pac4jSession) {
15+
return session;
16+
}
17+
return session == null ? next.apply(ctx) : next.apply(Pac4jSession.create(ctx));
18+
};
19+
}
20+
}

modules/jooby-pac4j/src/main/java/io/jooby/pac4j/Pac4jModule.java

+4-17
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,8 @@
55
*/
66
package io.jooby.pac4j;
77

8-
import io.jooby.Context;
9-
import io.jooby.Extension;
10-
import io.jooby.Jooby;
11-
import io.jooby.Route;
12-
import io.jooby.Router;
13-
import io.jooby.StatusCode;
14-
import io.jooby.internal.pac4j.ActionAdapterImpl;
15-
import io.jooby.internal.pac4j.CallbackFilterImpl;
16-
import io.jooby.internal.pac4j.ClientReference;
17-
import io.jooby.internal.pac4j.DevLoginForm;
18-
import io.jooby.internal.pac4j.ForwardingAuthorizer;
19-
import io.jooby.internal.pac4j.LogoutImpl;
20-
import io.jooby.internal.pac4j.NoopAuthorizer;
21-
import io.jooby.internal.pac4j.Pac4jCurrentUser;
22-
import io.jooby.internal.pac4j.SavedRequestHandlerImpl;
23-
import io.jooby.internal.pac4j.SecurityFilterImpl;
24-
import io.jooby.internal.pac4j.UrlResolverImpl;
8+
import io.jooby.*;
9+
import io.jooby.internal.pac4j.*;
2510
import org.pac4j.core.authorization.authorizer.Authorizer;
2611
import org.pac4j.core.client.Client;
2712
import org.pac4j.core.client.Clients;
@@ -488,6 +473,8 @@ public Pac4jModule client(@Nonnull Class<? extends Authorizer> authorizer,
488473
pac4j.setSecurityLogic(newSecurityLogic(excludes));
489474
}
490475

476+
application.decorator(new UntrustedSessionDataDetector());
477+
491478
/** For each client to a specific path, add a security handler. */
492479
for (Map.Entry<String, List<ClientReference>> entry : allClients.entrySet()) {
493480
String pattern = entry.getKey();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.jooby.pac4j;
2+
3+
import io.jooby.StatusCode;
4+
import io.jooby.exception.StatusCodeException;
5+
6+
/**
7+
* Occurs when sensitive encoded data is set outside pac4j internals.
8+
*
9+
* @since 2.16.6
10+
*/
11+
public class Pac4jUntrustedDataFound extends StatusCodeException {
12+
public Pac4jUntrustedDataFound(String message) {
13+
super(StatusCode.FORBIDDEN, message);
14+
}
15+
}

0 commit comments

Comments
 (0)