Headline
CVE-2023-33176: Merge pull request #18052 from paultrudel/ssrf-fix-25 · bigbluebutton/bigbluebutton@43394da
BigBlueButton is an open source virtual classroom designed to help teachers teach and learners learn. In affected versions are affected by a Server-Side Request Forgery (SSRF) vulnerability. In an insertDocument
API request the user is able to supply a URL from which the presentation should be downloaded. This URL was being used without having been successfully validated first. An update to the followRedirect
method in the PresentationUrlDownloadService
has been made to validate all URLs to be used for presentation download. Two new properties presentationDownloadSupportedProtocols
and presentationDownloadBlockedHosts
have also been added to bigbluebutton.properties
to allow administrators to define what protocols a URL must use and to explicitly define hosts that a presentation cannot be downloaded from. All URLs passed to insertDocument
must conform to the requirements of the two previously mentioned properties. Additionally, these URLs must resolve to valid addresses, and these addresses must not be local or loopback addresses. There are no workarounds. Users are advised to upgrade to a patched version of BigBlueButton.
Expand Up @@ -3,15 +3,16 @@ import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Stream;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; Expand All @@ -24,6 +25,7 @@ import org.apache.http.impl.nio.client.HttpAsyncClients; import org.apache.http.nio.client.methods.HttpAsyncMethods; import org.apache.http.nio.client.methods.ZeroCopyConsumer; import org.apache.commons.validator.routines.InetAddressValidator; import org.bigbluebutton.api.Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; Expand All @@ -38,6 +40,8 @@ public class PresentationUrlDownloadService { private String presentationBaseURL; private String presentationDir; private String BLANK_PRESENTATION; private List<String> insertDocumentSupportedProtocols; private List<String> insertDocumentBlockedHosts;
private ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
Expand Down Expand Up @@ -176,6 +180,8 @@ private String followRedirect(String meetingId, String redirectUrl, return null; }
if(!isValidRedirectUrl(redirectUrl)) return null;
URL presUrl; try { presUrl = new URL(redirectUrl); Expand Down Expand Up @@ -215,6 +221,64 @@ private String followRedirect(String meetingId, String redirectUrl, } }
private boolean isValidRedirectUrl(String redirectUrl) { URL url;
try { url = new URL(redirectUrl); String protocol = url.getProtocol(); String host = url.getHost();
if(insertDocumentSupportedProtocols.stream().noneMatch(p -> p.equalsIgnoreCase(protocol))) { if(insertDocumentSupportedProtocols.size() == 1 && insertDocumentSupportedProtocols.get(0).equalsIgnoreCase(“all”)) { log.warn(“Warning: All protocols are supported for presentation download. It is recommended to only allow HTTPS.”); } else { log.error("Invalid protocol [{}]", protocol); return false; } }
if(insertDocumentBlockedHosts.stream().anyMatch(h -> h.equalsIgnoreCase(host))) { log.error("Attempted to download from blocked host [{}]", host); return false; } } catch(MalformedURLException e) { log.error("Malformed URL [{}]", redirectUrl); return false; }
try { InetAddress[] addresses = InetAddress.getAllByName(url.getHost()); InetAddressValidator validator = InetAddressValidator.getInstance();
boolean localhostBlocked = insertDocumentBlockedHosts.stream().anyMatch(h -> h.equalsIgnoreCase(“localhost”));
for(InetAddress address: addresses) { if(!validator.isValid(address.getHostAddress())) { log.error("Invalid address [{}]", address.getHostAddress()); return false; }
if(localhostBlocked) { if(address.isAnyLocalAddress()) { log.error("Address [{}] is a local address", address.getHostAddress()); return false; }
if(address.isLoopbackAddress()) { log.error("Address [{}] is a loopback address", address.getHostAddress()); return false; } } } } catch(UnknownHostException e) { log.error("Unknown host [{}]", url.getHost()); return false; }
return true; }
public boolean savePresentation(final String meetingId, final String filename, final String urlString) {
Expand Down Expand Up @@ -282,4 +346,12 @@ public void setBlankPresentation(String blankPresentation) { this.BLANK_PRESENTATION = blankPresentation; }
public void setInsertDocumentSupportedProtocols(String insertDocumentSupportedProtocols) { this.insertDocumentSupportedProtocols = new ArrayList<>(Arrays.asList(insertDocumentSupportedProtocols.split(“,”))); }
public void setInsertDocumentBlockedHosts(String insertDocumentBlockedHosts) { this.insertDocumentBlockedHosts = new ArrayList<>(Arrays.asList(insertDocumentBlockedHosts.split(“,”))); }
}