CVE-2019-5101 - CVE-2019-5102
An exploitable information leak vulnerability exists in the ustream-ssl library of
OpenWrt, versions 18.06.4 and 15.05.1. When connecting to a remote server, the server’s
SSL certificate is checked but no action is taken when the certificate is invalid. An
attacker could exploit this behavior by performing a man-in-the-middle attack, providing
any certificate, leading to the theft of all the data sent by the client during the first
request.
### Tested Versions
OpenWrt 18.06.4, via wget (uclient-fetch)
OpenWrt 15.05.1, via wget (busybox)
### Product URLs
https://openwrt.org/
https://git.openwrt.org/project/ustream-ssl.git
### CVSSv3 Score
4.0 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:N/A:N
### CWE
CWE-295: Improper Certificate Validation
### Details
OpenWrt is a Linux-based OS, primarily used on embedded devices to route network traffic.
OpenWrt is highly customizable, and ships with a set of tools and libraries that have been
optimized to run on hardware with limited resources.
Among these tools, OpenWrt uses wget
to allow scripts to download files from the web. In
OpenWrt 18.06.4, wget
is a symbolic link to uclient-fetch
, while it’s a symbolic link
to busybox
in OpenWrt 15.05.1. In both cases, the SSL support is provided by the
ustream-ssl
library, which is an SSL wrapper for OpenSSL
, mbed TLS
, and wolfSSL
.
When the underlying SSL library used is OpenSSL
(package libustream-openssl
) or mbed
TLS
(package libustream-mbedtls
), ustream-ssl
has an issue that could be exploited by
an attacker to bypass the server’s certificate check and reveal the whole contents of the
client’s request.
### CVE-2019-5101 - OpenSSL (libustream-openssl)
After an SSL connection is initialized via _ustream_ssl_init
, and after any data (e.g.
the client’s HTTP request) is written to the stream using ustream_printf
, the code
eventually enters the function __ustream_ssl_poll
, which is used to dispatch the
read/write events.
static bool __ustream_ssl_poll(struct ustream *s)
{
struct ustream_ssl *us = container_of(s->next, struct ustream_ssl, stream);
char *buf;
int len, ret;
bool more = false;
ustream_ssl_check_conn(us);
if (!us->connected || us->error)
return false;
…
// [1]
The first action taken is to check the SSL connection by calling ustream_ssl_check_conn
at [1]:
static void ustream_ssl_check_conn(struct ustream_ssl *us)
{
if (us->connected || us->error)
return;
if (__ustream_ssl_connect(us) == U_SSL_OK) {
us->connected = true;
if (us->notify_connected)
us->notify_connected(us);
ustream_write_pending(&us->stream);
This function, in turn, calls __ustream_ssl_connect
[2], and if the return is
U_SSL_OK
, then the connection is assumed to be established [3] and any pending write
operations are executed [4].
Because of the write happening at [4], the function __ustream_ssl_connect
should only
return U_SSL_OK
when the SSL connection has been checked and the server’s certificate is
verified:
_hidden enum ssl_conn_status __ustream_ssl_connect(struct ustream_ssl *us)
{
void *ssl = us->ssl;
int r;
if (us->server)
r = SSL_accept(ssl);
else
r = SSL_connect(ssl);
if (r == 1) {
#ifndef CYASSL_OPENSSL_H
#endif }
ustream_ssl_verify_cert(us);
return U_SSL_OK;
// [5] // [6]
// [2]
// [3] connected
// [4] write to the stream
r = SSL_get_error(ssl, r);
if (r == SSL_ERROR_WANT_READ || r == SSL_ERROR_WANT_WRITE)
return U_SSL_PENDING;
ustream_ssl_error(us, r);
return U_SSL_ERROR;
However, while the function will call ustream_ssl_verify_cert
at [5], U_SSL_OK
will be
returned in any case [6].
Indeed, ustream_ssl_verify_cert
checks the connection and returns early [7], without
setting us->valid_cert
(which will stay false
).
static void ustream_ssl_verify_cert(struct ustream_ssl *us)
void *ssl = us->ssl;
X509 *cert;
int res;
res = SSL_get_verify_result(ssl);
if (res != X509_V_OK) {
if (us->notify_verify_error)
us->notify_verify_error(us, res,
X509_verify_cert_error_string(res));
return; }
cert = SSL_get_peer_certificate(ssl);
if (!cert)
return;
us->valid_cert = true;
us->valid_cn = ustream_ssl_verify_cn(us, cert);
X509_free(cert);
// [7] At this point, the SSL connection is established [3] (from the point of view of `ustream- ssl`), and the pending writes are executed on the stream [4], allowing a man-in-the-middle attacker, by suppling any certificate, to read the data written into the stream. Despite this, the code in `__ustream_ssl_poll` will terminate a few calls later:
static bool __ustream_ssl_poll(struct ustream *s)
{
struct ustream_ssl *us = container_of(s->next, struct ustream_ssl, stream);
char *buf;
int len, ret;
bool more = false;
ustream_ssl_check_conn(us);
if (!us->connected || us->error)
return false;
do {
buf = ustream_reserve(&us->stream, 1, &len);
if (!len)
break;
ret = __ustream_ssl_read(us, buf, len); // [9]
// [8]
switch (ret) {
case U_SSL_PENDING:
return more;
case U_SSL_ERROR:
return false;
case 0:
us->stream.eof = true;
ustream_state_change(&us->stream);
return false;
default:
ustream_fill_read(&us->stream, ret);
more = true;
continue;
while (1);
return more;
[10] At [9], the stream is read, but the underlying `SSL_read` function (defined in the `OpenSSL` library) will error out and the function will exit at [10]. ### CVE-2019-5102 - mbed TLS (libustream-mbedtls) The same issue exists in the `libustream-mbedtls` package, the affected code [11] is similar:
__hidden enum ssl_conn_status __ustream_ssl_connect(struct ustream_ssl *us)
void *ssl = us->ssl;
int r;
r = mbedtls_ssl_handshake(ssl);
if (r == 0) {
ustream_ssl_verify_cert(us);
return U_SSL_OK;
if (ssl_do_wait(r))
return U_SSL_PENDING;
ustream_ssl_error(us, r);
return U_SSL_ERROR;
2019-09-11 - Vendor disclosure
2019-11-13 - Vendor patched
2019-11-15 - Public release
Discovered by Claudio Bozzato of Cisco Talos.