VYPR
High severityNVD Advisory· Published Mar 15, 2021· Updated Feb 13, 2025

Apache OpenMeetings: bandwidth can be overloaded with public web service

CVE-2021-27576

Description

If was found that the NetTest web service can be used to overload the bandwidth of a Apache OpenMeetings server. This issue was addressed in Apache OpenMeetings 6.0.0

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

The NetTest web service in Apache OpenMeetings before 6.0.0 lacks client count limits, enabling bandwidth exhaustion via unauthenticated requests.

Vulnerability

The NetTest web service in Apache OpenMeetings, prior to version 5.1.0, had no mechanism to limit the number of concurrent bandwidth test clients [1]. An attacker could exploit this by sending multiple download or upload requests, overwhelming the server's network capacity. The issue was initially addressed in a Jira ticket (OPENMEETINGS-2551) proposing a configurable client limit [2].

Exploitation

The /nettest endpoint is unauthenticated or accessible to any user without restriction. By rapidly initiating many bandwidth test requests (e.g., download tests with large payloads), an attacker can consume all available server bandwidth, degrading service for legitimate users [1][2]. The fix introduces a configurable maximum client count via the property nettest.max.clients (default 100) and enforces a 429 Too Many Requests response when exceeded [3][4].

Impact

A successful denial-of-service (DoS) attack causes severe bandwidth saturation, making the OpenMeetings instance unresponsive or extremely slow for other users. This can disrupt video conferencing, file sharing, and other real-time collaboration features [1].

Mitigation

Apache OpenMeetings version 6.0.0 includes the fix that limits concurrent NetTest clients [1]. Users running earlier versions should upgrade immediately. No other workarounds are documented.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.apache.openmeetings:openmeetings-parentMaven
>= 4.0.0, < 6.0.06.0.0

Affected products

2

Patches

3
afe26c950b12

[OPENMEETINGS-2551] max upload download size is limited

https://github.com/apache/openmeetingsMaxim SolodovnikFeb 17, 2021via ghsa
2 files changed · +10 7
  • openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/NetTestWebService.java+8 3 modified
    @@ -55,7 +55,8 @@ public enum TestType {
     
     	private static final int PING_PACKET_SIZE = 64;
     	private static final int JITTER_PACKET_SIZE = 1024;
    -	private static final int MAX_UPLOAD_SIZE = 16 * 1024 * 1024;
    +	private static final int MAX_DOWNLOAD_SIZE = 5 * 1024 * 1024;
    +	private static final int MAX_UPLOAD_SIZE = 5 * 512 * 1024;
     	public static final AtomicInteger CLIENT_COUNT = new AtomicInteger();
     	private static int maxClients = 100;
     
    @@ -90,6 +91,9 @@ public Response get(@QueryParam("type") String type, @QueryParam("size") int inS
     				size = inSize;
     				break;
     		}
    +		if (size > MAX_DOWNLOAD_SIZE) {
    +			return Response.status(Status.BAD_REQUEST).build();
    +		}
     		return Response.ok()
     				.type(MediaType.APPLICATION_OCTET_STREAM).entity(new InputStream() {
     					int pos = 0;
    @@ -124,9 +128,9 @@ public void close() throws IOException {
     	@POST
     	@Consumes(MediaType.APPLICATION_OCTET_STREAM)
     	@Path("/")
    -	public void upload(@QueryParam("size") int size, InputStream stream) throws IOException {
    +	public int upload(@QueryParam("size") int size, InputStream stream) throws IOException {
     		if (size > MAX_UPLOAD_SIZE) {
    -			return;
    +			return -1;
     		}
     		CLIENT_COUNT.incrementAndGet();
     		byte[] b = new byte[1024];
    @@ -140,6 +144,7 @@ public void upload(@QueryParam("size") int size, InputStream stream) throws IOEx
     		} finally {
     			CLIENT_COUNT.decrementAndGet();
     		}
    +		return totalCount;
     	}
     
     	public static TestType getTypeByString(String typeString) {
    
  • openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-nettest.js+2 4 modified
    @@ -73,7 +73,7 @@ var NetTest = (function() {
     	function __netTest(params, callback) {
     		let tail = '';
     		if (params.size) {
    -			params.curSize = params.multiplier * (params.curSize ? params.curSize : params.size);
    +			params.curSize = params.size + (params.curSize || 0);
     			tail = '&size=' + params.curSize;
     		}
     		setTimeout(() => {
    @@ -133,7 +133,6 @@ var NetTest = (function() {
     							url: '?type=upload'
     							, mode: 'upload'
     							, size: 512 * KB
    -							, multiplier: 2
     							, delay: DELAY
     						}
     						, maxTime: LIMIT
    @@ -149,8 +148,7 @@ var NetTest = (function() {
     						action: __netTest
     						, params: {
     							url: '?type=download'
    -							, size: 2 * MB
    -							, multiplier: 2
    +							, size: 1 * MB
     							, delay: DELAY
     						}
     						, maxTime: LIMIT
    
060a3114ad75

[OPENMEETINGS-2551] more work on client count

https://github.com/apache/openmeetingsMaxim SolodovnikJan 29, 2021via ghsa
3 files changed · +50 44
  • openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/NetTestWebService.java+40 42 modified
    @@ -33,7 +33,6 @@
     import javax.ws.rs.QueryParam;
     import javax.ws.rs.core.MediaType;
     import javax.ws.rs.core.Response;
    -import javax.ws.rs.core.Response.ResponseBuilder;
     import javax.ws.rs.core.Response.Status;
     
     import org.apache.openmeetings.webservice.util.RateLimited;
    @@ -57,10 +56,8 @@ public enum TestType {
     	private static final int PING_PACKET_SIZE = 64;
     	private static final int JITTER_PACKET_SIZE = 1024;
     	private static final int MAX_UPLOAD_SIZE = 16 * 1024 * 1024;
    -	private AtomicInteger clientCount = new AtomicInteger();
    -
    -	@Value("${nettest.max.clients}")
    -	private int maxClients = 100;
    +	public static final AtomicInteger CLIENT_COUNT = new AtomicInteger();
    +	public static int maxClients = 100;
     
     	@PostConstruct
     	private void report() {
    @@ -78,10 +75,6 @@ public Response get(@QueryParam("type") String type, @QueryParam("size") int inS
     		if (TestType.UNKNOWN == testType) {
     			return Response.status(Status.BAD_REQUEST).build();
     		}
    -		if (clientCount.intValue() > maxClients) {
    -			log.error("Download: Max client count reached");
    -			return Response.status(Status.TOO_MANY_REQUESTS).build();
    -		}
     
     		// choose data to send
     		switch (testType) {
    @@ -92,50 +85,50 @@ public Response get(@QueryParam("type") String type, @QueryParam("size") int inS
     				size = JITTER_PACKET_SIZE;
     				break;
     			default:
    -				clientCount.incrementAndGet();
    +				final int count = CLIENT_COUNT.incrementAndGet();
    +				log.info("... download: client count: {}", count);
     				size = inSize;
     				break;
     		}
    -		ResponseBuilder response = Response.ok().type(MediaType.APPLICATION_OCTET_STREAM).entity(new InputStream() {
    -			int pos = 0;
    -
    -			@Override
    -			public int read() throws IOException {
    -				pos++;
    -				return pos > size ? -1 : ThreadLocalRandom.current().nextInt(0, 0xFF);
    -			}
    -
    -			@Override
    -			public int available() throws IOException {
    -				return size - pos;
    -			}
    -
    -			@Override
    -			public void close() throws IOException {
    -				if (TestType.DOWNLOAD_SPEED == testType) {
    -					clientCount.decrementAndGet();
    -				}
    -				super.close();
    -			}
    -		});
    -		response.header("Cache-Control", "no-cache, no-store, no-transform");
    -		response.header("Pragma", "no-cache");
    -		response.header("Content-Length", String.valueOf(size));
    -		return response.build();
    +		return Response.ok()
    +				.type(MediaType.APPLICATION_OCTET_STREAM).entity(new InputStream() {
    +					int pos = 0;
    +
    +					@Override
    +					public int read() throws IOException {
    +						pos++;
    +						return pos > size ? -1 : ThreadLocalRandom.current().nextInt(0, 0xFF);
    +					}
    +
    +					@Override
    +					public int available() throws IOException {
    +						return size - pos;
    +					}
    +
    +					@Override
    +					public void close() throws IOException {
    +						if (TestType.DOWNLOAD_SPEED == testType) {
    +							final int count = CLIENT_COUNT.decrementAndGet();
    +							log.info("... close: client count: {}", count);
    +						}
    +						super.close();
    +					}
    +				})
    +				.header("Cache-Control", "no-cache, no-store, no-transform")
    +				.header("Pragma", "no-cache")
    +				.header("Content-Length", String.valueOf(size))
    +				.build();
     	}
     
    +	@RateLimited
     	@POST
     	@Consumes(MediaType.APPLICATION_OCTET_STREAM)
     	@Path("/")
     	public void upload(@QueryParam("size") int size, InputStream stream) throws IOException {
     		if (size > MAX_UPLOAD_SIZE) {
     			return;
     		}
    -		if (clientCount.intValue() > maxClients) {
    -			log.error("Upload: Max client count reached");
    -			return;
    -		}
    -		clientCount.incrementAndGet();
    +		CLIENT_COUNT.incrementAndGet();
     		byte[] b = new byte[1024];
     		int totalCount = 0;
     		int count;
    @@ -145,7 +138,7 @@ public void upload(@QueryParam("size") int size, InputStream stream) throws IOEx
     			}
     			log.debug("Total bytes read {}", totalCount);
     		} finally {
    -			clientCount.decrementAndGet();
    +			CLIENT_COUNT.decrementAndGet();
     		}
     	}
     
    @@ -162,4 +155,9 @@ public static TestType getTypeByString(String typeString) {
     
     		return TestType.UNKNOWN;
     	}
    +
    +	@Value("${nettest.max.clients}")
    +	private void setMaxClients(int count) {
    +		maxClients = count;
    +	}
     }
    
  • openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/util/RateLimitRequestFilter.java+8 0 modified
    @@ -30,9 +30,12 @@
     
     import org.apache.openmeetings.webservice.NetTestWebService;
     import org.apache.openmeetings.webservice.NetTestWebService.TestType;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
     
     @RateLimited
     public class RateLimitRequestFilter implements ContainerRequestFilter {
    +	private static final Logger log = LoggerFactory.getLogger(RateLimitRequestFilter.class);
     	private static final String ATTR_LAST_ACCESS_TIME = "LAST_ACCESS_TIME";
     	private static final long ALLOWED_TIME = 3000;
     
    @@ -53,6 +56,11 @@ public void filter(ContainerRequestContext context) {
     				return;
     			}
     		}
    +		if (NetTestWebService.CLIENT_COUNT.get() > NetTestWebService.maxClients) {
    +			log.error("Download: Max client count reached");
    +			context.abortWith(Response.status(Status.TOO_MANY_REQUESTS).build());
    +			return;
    +		}
     		Long lastAccessed = (Long)session.getAttribute(ATTR_LAST_ACCESS_TIME);
     		session.setAttribute(ATTR_LAST_ACCESS_TIME, System.currentTimeMillis());
     		if (lastAccessed != null && System.currentTimeMillis() - lastAccessed.longValue() < ALLOWED_TIME) {
    
  • openmeetings-web/src/main/java/org/apache/openmeetings/web/pages/InternalErrorPage.java+2 2 modified
    @@ -20,7 +20,7 @@
     
     import org.apache.openmeetings.web.app.Application;
     import org.apache.wicket.ajax.AjaxRequestTarget;
    -import org.apache.wicket.markup.html.form.Form;
    +import org.apache.wicket.markup.html.form.StatelessForm;
     import org.apache.wicket.model.ResourceModel;
     import org.apache.wicket.request.IRequestParameters;
     
    @@ -33,7 +33,7 @@ public class InternalErrorPage extends BaseInitedPage {
     	@Override
     	protected void onInitialize() {
     		super.onInitialize();
    -		add(new Form<Void>("form").add(
    +		add(new StatelessForm<Void>("form").add(
     				new BootstrapButton("home", new ResourceModel("124"), Buttons.Type.Outline_Primary) {
     					private static final long serialVersionUID = 1L;
     
    
cbdfd2f9731a

[OPENMEETINGS-2551] NetTest client count can be limited

https://github.com/apache/openmeetingsMaxim SolodovnikJan 13, 2021via ghsa
3 files changed · +50 6
  • openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/NetTestWebService.java+44 6 modified
    @@ -22,7 +22,9 @@
     import java.io.IOException;
     import java.io.InputStream;
     import java.util.concurrent.ThreadLocalRandom;
    +import java.util.concurrent.atomic.AtomicInteger;
     
    +import javax.annotation.PostConstruct;
     import javax.ws.rs.Consumes;
     import javax.ws.rs.GET;
     import javax.ws.rs.POST;
    @@ -32,10 +34,12 @@
     import javax.ws.rs.core.MediaType;
     import javax.ws.rs.core.Response;
     import javax.ws.rs.core.Response.ResponseBuilder;
    +import javax.ws.rs.core.Response.Status;
     
     import org.apache.openmeetings.webservice.util.RateLimited;
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
    +import org.springframework.beans.factory.annotation.Value;
     import org.springframework.stereotype.Service;
     
     @Service("netTestWebService")
    @@ -53,15 +57,31 @@ public enum TestType {
     	private static final int PING_PACKET_SIZE = 64;
     	private static final int JITTER_PACKET_SIZE = 1024;
     	private static final int MAX_UPLOAD_SIZE = 16 * 1024 * 1024;
    +	private AtomicInteger clientCount = new AtomicInteger();
    +
    +	@Value("${nettest.max.clients}")
    +	private int maxClients = 100;
    +
    +	@PostConstruct
    +	private void report() {
    +		log.debug("MaxClients: {}", maxClients);
    +	}
     
     	@RateLimited
     	@GET
     	@Produces(MediaType.APPLICATION_OCTET_STREAM)
     	@Path("/")
     	public Response get(@QueryParam("type") String type, @QueryParam("size") int inSize) {
     		final int size;
    -		TestType testType = getTypeByString(type);
    +		final TestType testType = getTypeByString(type);
     		log.debug("Network test:: get, {}, {}", testType, inSize);
    +		if (TestType.UNKNOWN == testType) {
    +			return Response.status(Status.BAD_REQUEST).build();
    +		}
    +		if (clientCount.intValue() > maxClients) {
    +			log.error("Download: Max client count reached");
    +			return Response.status(Status.TOO_MANY_REQUESTS).build();
    +		}
     
     		// choose data to send
     		switch (testType) {
    @@ -72,6 +92,7 @@ public Response get(@QueryParam("type") String type, @QueryParam("size") int inS
     				size = JITTER_PACKET_SIZE;
     				break;
     			default:
    +				clientCount.incrementAndGet();
     				size = inSize;
     				break;
     		}
    @@ -88,6 +109,14 @@ public int read() throws IOException {
     			public int available() throws IOException {
     				return size - pos;
     			}
    +
    +			@Override
    +			public void close() throws IOException {
    +				if (TestType.DOWNLOAD_SPEED == testType) {
    +					clientCount.decrementAndGet();
    +				}
    +				super.close();
    +			}
     		});
     		response.header("Cache-Control", "no-cache, no-store, no-transform");
     		response.header("Pragma", "no-cache");
    @@ -102,13 +131,22 @@ public void upload(@QueryParam("size") int size, InputStream stream) throws IOEx
     		if (size > MAX_UPLOAD_SIZE) {
     			return;
     		}
    +		if (clientCount.intValue() > maxClients) {
    +			log.error("Upload: Max client count reached");
    +			return;
    +		}
    +		clientCount.incrementAndGet();
     		byte[] b = new byte[1024];
    -		int totalCount = 0
    -				, count;
    -		while ((count = stream.read(b)) > -1) {
    -			totalCount += count;
    +		int totalCount = 0;
    +		int count;
    +		try {
    +			while ((count = stream.read(b)) > -1) {
    +				totalCount += count;
    +			}
    +			log.debug("Total bytes read {}", totalCount);
    +		} finally {
    +			clientCount.decrementAndGet();
     		}
    -		log.debug("Total bytes read {}", totalCount);
     	}
     
     	public static TestType getTypeByString(String typeString) {
    
  • openmeetings-web/src/main/webapp/WEB-INF/classes/cxf-servlet.xml+3 0 modified
    @@ -23,6 +23,7 @@
     		xmlns:context="http://www.springframework.org/schema/context"
     		xmlns:jaxrs="http://cxf.apache.org/jaxrs"
     		xmlns:jaxws="http://cxf.apache.org/jaxws"
    +		xmlns:p="http://www.springframework.org/schema/p"
     		xsi:schemaLocation="
     			http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
     			http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    @@ -35,6 +36,8 @@
     
     	<context:annotation-config />
     	<context:component-scan base-package="org.apache.openmeetings.webservice" />
    +	<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
    +		p:location="classpath:openmeetings.properties" />
     
     	<!-- (writeXsiType=false) -->
     	<jaxrs:server id="server" address="/">
    
  • openmeetings-web/src/main/webapp/WEB-INF/classes/openmeetings.properties+3 0 modified
    @@ -49,6 +49,9 @@ kurento.kuid=df992960-e7b0-11ea-9acd-337fb30dd93d
     ## this list can be space and/or comma separated
     kurento.ignored.kuids=
     
    +################## NetTest ##################
    +nettest.max.clients=50
    +
     ################## SIP ##################
     ### Should be updated with real values for Asterisk ###
     sip.hostname=
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.