Skip to content

Commit

Permalink
UNDERTOW-827 WFLY-6760 CVE-2016-4993 wildfly: HTTP header injection /…
Browse files Browse the repository at this point in the history
… response splitting
  • Loading branch information
stuartwdouglas committed Sep 12, 2016
1 parent 4491bf4 commit 834496f
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 3 deletions.
6 changes: 5 additions & 1 deletion core/src/main/java/io/undertow/UndertowMessages.java
Expand Up @@ -476,6 +476,10 @@ public interface UndertowMessages {
@Message(id = 148, value = "Invalid HPack encoding. First byte: %s")
HpackException invalidHpackEncoding(byte b);

@Message(id = 149, value = "Pseudo header %s received after receiving normal headers. Pseudo headers must be the first headers in a HTTP/2 header block.")
@Message(id = 149, value = "HttpString is not allowed to contain newlines. value: %s")
IllegalArgumentException newlineNotSupportedInHttpString(String value);

@Message(id = 150, value = "Pseudo header %s received after receiving normal headers. Pseudo headers must be the first headers in a HTTP/2 header block.")
IllegalArgumentException pseudoHeaderInWrongOrder(HttpString header);

}
Expand Up @@ -155,6 +155,13 @@ public State encode(HeaderMap headers, ByteBuffer target) {
int required = 11 + headerName.length(); //we use 11 to make sure we have enough room for the variable length itegers

String val = values.get(i);
for(int v = 0; v < val.length(); ++v) {
char c = val.charAt(v);
if(c == '\r' || c == '\n') {
val = val.replace('\r', ' ').replace('\n', ' ');
break;
}
}
TableEntry tableEntry = findInTable(headerName, val);

required += (1 + val.length());
Expand Down
Expand Up @@ -151,7 +151,12 @@ private static void putString(final ByteBuffer buf, String value) {
final int length = value.length();
putInt(buf, length);
for (int i = 0; i < length; ++i) {
buf.put((byte) value.charAt(i));
char c = value.charAt(i);
if(c != '\r' && c != '\n'){
buf.put((byte) c);
} else {
buf.put((byte)' ');
}
}
buf.put((byte) 0);
}
Expand Down
Expand Up @@ -289,7 +289,12 @@ private void bufferDone() {
private static void writeString(ByteBuffer buffer, String string) {
int length = string.length();
for (int charIndex = 0; charIndex < length; charIndex++) {
buffer.put((byte) string.charAt(charIndex));
char c = string.charAt(charIndex);
if(c != '\r' && c != '\n') {
buffer.put((byte) c);
} else {
buffer.put((byte) ' ');
}
}
}

Expand Down
12 changes: 12 additions & 0 deletions core/src/main/java/io/undertow/util/HttpString.java
Expand Up @@ -30,6 +30,8 @@
import static java.lang.System.arraycopy;
import static java.util.Arrays.copyOfRange;

import io.undertow.UndertowMessages;

/**
* An HTTP case-insensitive Latin-1 string.
*
Expand Down Expand Up @@ -116,13 +118,23 @@ public HttpString(final String string) {
this.bytes = bytes;
this.hashCode = calcHashCode(bytes);
this.string = string;
checkForNewlines();
}

private void checkForNewlines() {
for(byte b : bytes) {
if(b == '\r' || b == '\n') {
throw UndertowMessages.MESSAGES.newlineNotSupportedInHttpString(string);
}
}
}

private HttpString(final byte[] bytes, final String string) {
this.bytes = bytes;
this.hashCode = calcHashCode(bytes);
this.string = string;
this.orderInt = 0;
checkForNewlines();
}

/**
Expand Down
@@ -0,0 +1,78 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.undertow.server;

import java.io.IOException;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import io.undertow.io.Receiver;
import io.undertow.testutils.DefaultServer;
import io.undertow.testutils.HttpClientUtils;
import io.undertow.testutils.TestHttpClient;
import io.undertow.util.HttpString;
import io.undertow.util.StatusCodes;

/**
* @author Stuart Douglas
*/
@RunWith(DefaultServer.class)
public class NewlineInHeadersTestCase {

private static final String RESPONSE = "response";
private static final String ECHO = "echo";

@Test
public void testNewlineInHeaders() throws IOException {
DefaultServer.setRootHandler(new HttpHandler() {
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
exchange.getRequestReceiver().receiveFullString(new Receiver.FullStringCallback() {
@Override
public void handle(HttpServerExchange exchange, String message) {
exchange.getResponseHeaders().put(HttpString.tryFromString(ECHO), message);
exchange.getResponseSender().send(RESPONSE);
}
});
}
});
final TestHttpClient client = new TestHttpClient();
try {
HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL());
post.setEntity(new StringEntity("test"));
HttpResponse result = client.execute(post);
Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
Assert.assertEquals("test", result.getFirstHeader(ECHO).getValue());
Assert.assertEquals(RESPONSE, HttpClientUtils.readResponse(result));

post = new HttpPost(DefaultServer.getDefaultServerURL());
post.setEntity(new StringEntity("test\nnewline"));
result = client.execute(post);
Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
Assert.assertEquals("test newline", result.getFirstHeader(ECHO).getValue());
Assert.assertEquals(RESPONSE, HttpClientUtils.readResponse(result));
} finally {
client.getConnectionManager().shutdown();
}
}
}

0 comments on commit 834496f

Please sign in to comment.