Skip to content

Commit 4df8bc5

Browse files
committed
1 parent b249698 commit 4df8bc5

File tree

8 files changed

+136
-12
lines changed

8 files changed

+136
-12
lines changed

components/blitz/resources/ome/services/blitz-servantDefinitions.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@
6464
<bean class="ome.services.blitz.impl.ScriptI">
6565
<constructor-arg ref="throttlingStrategy"/>
6666
<constructor-arg ref="scriptRepoHelper"/>
67+
<constructor-arg ref="aclVoter"/>
68+
<constructor-arg ref="omeroInterceptor"/>
6769
<constructor-arg ref="checksumProviderFactory"/>
6870
<constructor-arg ref="paramsCache"/>
6971
</bean>

components/blitz/resources/omero/api/ISession.ice

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
/*
2-
* $Id$
3-
*
42
* Copyright 2010 Glencoe Software, Inc. All rights reserved.
53
* Use is subject to license terms supplied in LICENSE.txt
6-
*
74
*/
85

96
#ifndef OMERO_API_ISESSION_ICE
@@ -51,8 +48,6 @@ module omero {
5148
/**
5249
* Allows a user to open up another session for him/herself with the given
5350
* defaults without needing to re-enter password.
54-
*
55-
* TODO Review the security of this method.
5651
*/
5752
omero::model::Session createUserSession(long timeToLiveMilliseconds, long timeToIdleMilliseconds, string defaultGroup)
5853
throws ServerError, Glacier2::CannotCreateSessionException;

components/blitz/src/ome/services/blitz/impl/ScriptI.java

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2008 University of Dundee. All rights reserved.
2+
* Copyright 2008-2016 University of Dundee. All rights reserved.
33
* Use is subject to license terms supplied in LICENSE.txt
44
*/
55

@@ -15,6 +15,8 @@
1515
import ome.api.RawFileStore;
1616
import ome.model.core.OriginalFile;
1717
import ome.model.enums.ChecksumAlgorithm;
18+
import ome.security.ACLVoter;
19+
import ome.security.basic.OmeroInterceptor;
1820
import ome.services.blitz.util.BlitzExecutor;
1921
import ome.services.blitz.util.BlitzOnly;
2022
import ome.services.blitz.util.ParamsCache;
@@ -31,6 +33,7 @@
3133
import omero.RInt;
3234
import omero.RType;
3335
import omero.ResourceError;
36+
import omero.SecurityViolation;
3437
import omero.ServerError;
3538
import omero.ValidationException;
3639
import omero.api.AMD_IScript_canRunScript;
@@ -89,12 +92,18 @@ public class ScriptI extends AbstractAmdServant implements _IScriptOperations,
8992

9093
protected final ScriptRepoHelper scripts;
9194

95+
protected final ACLVoter aclVoter;
96+
97+
protected final OmeroInterceptor interceptor;
98+
9299
protected final ChecksumProviderFactory cpf;
93100

94-
public ScriptI(BlitzExecutor be, ScriptRepoHelper scripts,
101+
public ScriptI(BlitzExecutor be, ScriptRepoHelper scripts, ACLVoter aclVoter, OmeroInterceptor interceptor,
95102
ChecksumProviderFactory cpf, ParamsCache cache) {
96103
super(null, be);
97104
this.scripts = scripts;
105+
this.aclVoter = aclVoter;
106+
this.interceptor = interceptor;
98107
this.cpf = cpf;
99108
this.cache = cache;
100109
}
@@ -193,6 +202,38 @@ public Long call() {
193202
});
194203
}
195204

205+
/**
206+
* Check that the user can write files in the current context.
207+
* @param __current Ice method invocation context
208+
* @throws SecurityViolation if the user may not write files in the current context
209+
*/
210+
private void assertCanWriteFiles(final Current __current) throws SecurityViolation {
211+
boolean allowCreation = false;
212+
try {
213+
allowCreation = (Boolean) factory.executor.execute(
214+
__current.ctx, factory.principal, new Executor.SimpleWork(this, "uploadScript-prep") {
215+
@Transactional(readOnly = true)
216+
public Boolean doWork(Session session, ServiceFactory sf) {
217+
final OriginalFile file = new OriginalFile();
218+
/* check with interceptor */
219+
try {
220+
interceptor.newTransientDetails(file);
221+
} catch (ome.conditions.SecurityViolation sv) {
222+
return false;
223+
}
224+
/* check with ACL voter */
225+
return aclVoter.allowCreation(file);
226+
}
227+
});
228+
} catch (ome.conditions.SecurityViolation sv) {
229+
/* cannot even access the current context */
230+
}
231+
if (!allowCreation) {
232+
throw new SecurityViolation(null, null,
233+
"No permission to upload script");
234+
}
235+
}
236+
196237
/**
197238
* Upload script to the server.
198239
*
@@ -203,6 +244,7 @@ public Long call() {
203244
*/
204245
public void uploadScript_async(final AMD_IScript_uploadScript __cb,
205246
final String path, final String scriptText, final Current __current) throws ServerError {
247+
assertCanWriteFiles(__current);
206248
safeRunnableCall(__current, __cb, false, new Callable<Long>() {
207249
public Long call() throws Exception {
208250
OriginalFile file = makeFile(path, scriptText, __current);
@@ -216,6 +258,7 @@ public Long call() throws Exception {
216258
public void uploadOfficialScript_async(
217259
AMD_IScript_uploadOfficialScript __cb, final String path,
218260
final String scriptText, final Current __current) throws ServerError {
261+
assertCanWriteFiles(__current);
219262
safeRunnableCall(__current, __cb, false, new Callable<Long>() {
220263
public Long call() throws Exception {
221264
EventContext ec = factory.getEventContext();
@@ -528,11 +571,22 @@ public void deleteScript_async(final AMD_IScript_deleteScript cb,
528571
safeRunnableCall(__current, cb, true, new Callable<Object>() {
529572
public Object call() throws Exception {
530573

531-
OriginalFile file = getOriginalFileOrNull(id, __current);
574+
final OriginalFile file = getOriginalFileOrNull(id, __current);
532575
if (file == null) {
533576
throw new ApiUsageException(null, null,
534577
"No script with id " + id + " on server.");
535578
}
579+
final boolean allowDelete = (Boolean) factory.executor.execute(
580+
__current.ctx, factory.principal, new Executor.SimpleWork(this, "deleteScript-prep") {
581+
@Transactional(readOnly = true)
582+
public Boolean doWork(Session session, ServiceFactory sf) {
583+
return aclVoter.allowDelete(file, file.getDetails());
584+
}
585+
});
586+
if (!allowDelete) {
587+
throw new SecurityViolation(null, null,
588+
"No permission to delete script with id " + id);
589+
}
536590

537591
deleteOriginalFile(file, __current);
538592
return null; // void

components/blitz/src/ome/services/blitz/repo/RepositoryDaoImpl.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import ome.io.nio.FileBuffer;
1717
import ome.model.IObject;
1818
import ome.model.fs.FilesetJobLink;
19+
import ome.model.internal.Permissions.Right;
20+
import ome.model.internal.Permissions.Role;
1921
import ome.model.meta.Experimenter;
2022
import ome.parameters.Parameters;
2123
import ome.services.RawFileBean;
@@ -1063,7 +1065,11 @@ protected void canWriteParentDirectory(ServiceFactory sf, SqlAction sql,
10631065

10641066
if (parentObjectOwnerId != roles.getRootId() || parentObjectGroupId != roles.getUserGroupId()) {
10651067
final LocalAdmin admin = (LocalAdmin) sf.getAdminService();
1066-
if (!admin.canAnnotate(parentObject)) {
1068+
final EventContext ec = admin.getEventContext();
1069+
if (!(admin.canAnnotate(parentObject) ||
1070+
parentObjectGroupId == roles.getUserGroupId() &&
1071+
ec.getCurrentGroupPermissions().isGranted(
1072+
parentObjectOwnerId == ec.getCurrentUserId() ? Role.USER : Role.GROUP, Right.ANNOTATE))) {
10671073
throw new ome.conditions.SecurityViolation(
10681074
"No annotate access for parent directory: "
10691075
+ parentId);

components/common/src/ome/api/ISession.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@ public interface ISession extends ServiceInterface {
4343
/**
4444
* Allows a user to open up another session for him/herself with the given
4545
* defaults without needing to re-enter password.
46-
*
47-
* TODO Review the security of this method.
4846
*/
4947
Session createUserSession(long timeToLiveMilliseconds,
5048
long timeToIdleMillisecond, String defaultGroup);

components/server/src/ome/security/basic/BasicACLVoter.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import ome.conditions.InternalException;
1818
import ome.conditions.SecurityViolation;
1919
import ome.model.IObject;
20+
import ome.model.core.OriginalFile;
2021
import ome.model.internal.Details;
2122
import ome.model.internal.Permissions;
2223
import ome.model.internal.Permissions.Right;
@@ -31,6 +32,7 @@
3132
import ome.system.EventContext;
3233
import ome.system.Roles;
3334

35+
import org.hibernate.LazyInitializationException;
3436
import org.hibernate.Session;
3537
import org.slf4j.Logger;
3638
import org.slf4j.LoggerFactory;
@@ -339,6 +341,20 @@ private int allowUpdateOrDelete(BasicEventContext c, IObject iObject,
339341
}
340342

341343
Permissions grpPermissions = c.getCurrentGroupPermissions();
344+
final ExperimenterGroup grp = d.getGroup();
345+
if (!(sysType || grp == null)) {
346+
try {
347+
if (grp.isLoaded()) {
348+
/* allow current group context to apply for adjusting directories in "user" group */
349+
if (!(iObject instanceof OriginalFile && iObject.isLoaded() && grp.getId() == roles.getUserGroupId() &&
350+
"Directory".equals(((OriginalFile) iObject).getMimetype()))) {
351+
grpPermissions = grp.getDetails().getPermissions();
352+
}
353+
}
354+
} catch (LazyInitializationException lie) {
355+
/* cannot check if group is loaded */
356+
}
357+
}
342358
if (grpPermissions == null || grpPermissions == Permissions.DUMMY) {
343359
if (d.getGroup() != null) {
344360
Long gid = d.getGroup().getId();
@@ -396,7 +412,7 @@ public Set<String> restrictions(IObject object) {
396412
public void postProcess(IObject object) {
397413
if (object.isLoaded()) {
398414
Details details = object.getDetails();
399-
// Sets context values.s
415+
// Sets context values.
400416
this.currentUser.applyContext(details,
401417
!(object instanceof ExperimenterGroup));
402418

components/server/src/ome/security/basic/OmeroInterceptor.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import org.hibernate.type.Type;
2929
import org.springframework.util.Assert;
3030

31+
import com.google.common.base.Splitter;
32+
3133
import ome.conditions.ApiUsageException;
3234
import ome.conditions.GroupSecurityViolation;
3335
import ome.conditions.InternalException;
@@ -40,6 +42,7 @@
4042
import ome.model.IMutable;
4143
import ome.model.IObject;
4244
import ome.model.core.Image;
45+
import ome.model.core.OriginalFile;
4346
import ome.model.core.Pixels;
4447
import ome.model.display.RenderingDef;
4548
import ome.model.display.Thumbnail;
@@ -59,6 +62,7 @@
5962
import ome.system.Roles;
6063
import ome.tools.hibernate.ExtendedMetadata;
6164
import ome.tools.hibernate.HibernateUtils;
65+
import ome.tools.lsid.LsidUtils;
6266

6367
/**
6468
* implements {@link org.hibernate.Interceptor} for controlling various aspects
@@ -81,6 +85,10 @@ public class OmeroInterceptor implements Interceptor {
8185

8286
private static Logger log = LoggerFactory.getLogger(OmeroInterceptor.class);
8387

88+
/* array indices for OriginalFile "path" and "name" properties */
89+
private static final String IDX_FILE_PATH = LsidUtils.parseField(OriginalFile.PATH);
90+
private static final String IDX_FILE_NAME = LsidUtils.parseField(OriginalFile.NAME);
91+
8492
private final Interceptor EMPTY = EmptyInterceptor.INSTANCE;
8593

8694
private final SystemTypes sysTypes;
@@ -164,6 +172,22 @@ public boolean onSave(Object entity, Serializable id, Object[] state,
164172
return true; // transferDetails ALWAYS edits the new entity.
165173
}
166174

175+
/**
176+
* Do not allow <q>.</q> and <q>..</q> components in file paths.
177+
* @param filepath a file path
178+
* @return if the file path contains any prohibited components
179+
*/
180+
private static boolean isProblemFilepath(String filepath) {
181+
for (final String component : Splitter.on('/').split(filepath)) {
182+
switch (component) {
183+
case ".":
184+
case "..":
185+
return true;
186+
}
187+
}
188+
return false;
189+
}
190+
167191
/**
168192
* calls back to
169193
* {@link BasicSecuritySystem#checkManagedDetails(IObject, Details)} for
@@ -181,6 +205,18 @@ public boolean onFlushDirty(Object entity, Serializable id,
181205

182206
Details newDetails = evaluateLinkages(iobj);
183207

208+
if (previousState != null && iobj instanceof OriginalFile && !currentUser.current().isCurrentUserAdmin()) {
209+
final int pathIndex = HibernateUtils.index(IDX_FILE_PATH, propertyNames);
210+
final int nameIndex = HibernateUtils.index(IDX_FILE_NAME, propertyNames);
211+
final String currentPath = (String) currentState[pathIndex];
212+
final String currentName = (String) currentState[nameIndex];
213+
if (currentPath != null && currentName != null &&
214+
!(currentPath.equals(previousState[pathIndex]) && currentName.equals(previousState[nameIndex])) &&
215+
isProblemFilepath(currentPath + currentName)) {
216+
throw new SecurityViolation("only administrators may introduce non-canonical OriginalFile path or name");
217+
}
218+
}
219+
184220
altered |= resetDetails(iobj, currentState, previousState, idx,
185221
newDetails);
186222

history.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
OMERO version history
22
=====================
33

4+
5.2.8 (March 2017)
5+
------------------
6+
7+
This is a security release including three security vulnerability fixes.
8+
9+
:secvuln:`2017-SV1-filename` prevents users from accessing and manipulating
10+
other people's data by creating an original file and changing its path to
11+
point to another user's file on the underlying filesystem.
12+
13+
:secvuln:`2017-SV2-edit-rw` prevents users in read-write groups from
14+
editing official scripts.
15+
16+
:secvuln:`2017-SV3-delete-script` prevents the deletion of official
17+
scripts by users without the correct permissions to do so.
18+
19+
It is highly recommended that you upgrade your server.
20+
421
5.2.7 (December 2016)
522
---------------------
623

0 commit comments

Comments
 (0)