diff --git a/documentation/documentation.pdf b/documentation/documentation.pdf index 200bcb7..85ab092 100644 Binary files a/documentation/documentation.pdf and b/documentation/documentation.pdf differ diff --git a/documentation/tex/src/crc.patch b/documentation/tex/src/crc.patch index f8196af..6e69f63 100644 --- a/documentation/tex/src/crc.patch +++ b/documentation/tex/src/crc.patch @@ -1,5 +1,5 @@ diff --git a/service/backend/controller/repl.go b/service/backend/controller/repl.go -index 0b0f782..69f75be 100644 +index 0b0f782..bd7e2e7 100644 --- a/service/backend/controller/repl.go +++ b/service/backend/controller/repl.go @@ -1,6 +1,8 @@ @@ -11,7 +11,7 @@ index 0b0f782..69f75be 100644 "fmt" "net/http" "replme/service" -@@ -44,8 +46,11 @@ func (repl *ReplController) Create(ctx *gin.Context) { +@@ -44,8 +46,10 @@ func (repl *ReplController) Create(ctx *gin.Context) { util.SLogger.Debugf("[%-25s] Creating new REPL user", fmt.Sprintf("UN:%s..", createReplRequest.Username[:5])) @@ -20,7 +20,6 @@ index 0b0f782..69f75be 100644 + hasher := sha256.New224() + hasher.Write([]byte(createReplRequest.Username)) + hash := hasher.Sum(nil) -+ + name := hex.EncodeToString(hash) util.SLogger.Debugf("[%-25s] Created new REPL user", fmt.Sprintf("UN:%s.. | NM:%s..", createReplRequest.Username[:5], name[:5])) diff --git a/documentation/tex/src/dir-structure.txt b/documentation/tex/src/dir-structure.txt new file mode 100644 index 0000000..18f52cd --- /dev/null +++ b/documentation/tex/src/dir-structure.txt @@ -0,0 +1,18 @@ +service/ + backend/ - MAIN-BACKEND + controller/ - api controller for auth, devenvs, and repls + database/ - database init + model/ - database models + server/ - server and router + service/ - cleanup, docker interfacing, proxying, and repls + types/ + util/ + frontend/ - Next.js frontend + image/ - CHILD-BACKEND + controller/ - api controller for auth and terms + server/ - server and router + service/ - user management and terms management + types/ + util/ + nginx/ - reverse proxy config + postgres/ - database config diff --git a/documentation/tex/src/main.tex b/documentation/tex/src/main.tex index 8cc23dc..4f9793f 100644 --- a/documentation/tex/src/main.tex +++ b/documentation/tex/src/main.tex @@ -26,7 +26,6 @@ inputencoding=utf8/latin1, backgroundcolor=\color{gray!10}, frame=single, - numbers=left, numberstyle=\tiny, } @@ -39,6 +38,7 @@ morecomment=[f][\color{red}]{-}, morecomment=[f][\color{olive}]{+}, captionpos=b, + numbers=left, } \lstdefinestyle{go}{ @@ -70,6 +70,7 @@ breakatwhitespace=true, basicstyle=\ttfamily, captionpos=b, + numbers=left, } \lstdefinestyle{python}{ @@ -81,7 +82,8 @@ showstringspaces=false, tabsize=4, breaklines=true, - captionpos=b + captionpos=b, + numbers=left, } \lstdefinestyle{bash}{ @@ -93,7 +95,8 @@ showstringspaces=false, tabsize=4, breaklines=true, - captionpos=b + captionpos=b, + numbers=left, } \usepackage{hyperref} @@ -161,6 +164,11 @@ \section{Overview} To spawn a REPL or compile code in a DEVENV the MAIN-BACKEND interfaces with a docker-in-docker instance (DIND) to create Alpine Linux containers (hereafter ALPINEs). The IO of the command running on the aplines is proxied to the web client by the MAIN-BACKEND. \\ The ALPINEs themselves have another golang+gin backend running on them (hereafter CHILD-BACKEND). The MAIN-BACKEND interfaces with the CHILD-BACKEND to create/authenticate users and spawn commands on the ALPINEs. + +\section{Directory Structure} + +\lstinputlisting[]{src/dir-structure.txt} + \section{Vulnerability \#1 -- CRC} \subsection{How it works} @@ -357,15 +365,15 @@ \subsection{How it works} \begin{center} \robotomonoRegular{GET /api/devenv//files/} \end{center} -However, all \robotomonoRegular{/api/devenv/...} endpoints also accept a query parameter \robotomonoRegular{?uuid=} which overloads the \robotomonoRegular{} when specified. +However, all \robotomonoRegular{/api/devenv/...} endpoints also accept a query parameter \robotomonoRegular{?uuid=} which, when specified, overloads the \robotomonoRegular{}. \lstinputlisting[caption=service/backend/server/router.go\ (lines\ 110-148), style=go, label={lst:devenv-uuid-snippet}]{src/devenv-uuid-snippet.go} -The extracted DEVENV and \robotomonoRegular{uuid} are saved in the current requests context. We can see, that \robotomonoRegular{id} is seemingly sanitized via \robotomonoRegular{util.ExtractUuid}. Instead of using the \robotomonoRegular{devenv} from the context (that definitely belongs to the current user), the eventually executed controller function \robotomonoRegular{GetFileContent} uses the \robotomonoRegular{uuid} to compute the path of the requested file content. +The extracted DEVENV and \robotomonoRegular{uuid} are saved in the current requests context. We can see, that \robotomonoRegular{id} is seemingly sanitized via \robotomonoRegular{util.ExtractUuid}. Instead of using the DEVENV from the context (that definitely belongs to the current user), the eventually executed controller function \robotomonoRegular{GetFileContent} uses the \robotomonoRegular{uuid} to compute the path of the requested file content. \lstinputlisting[caption=service/backend/controller/devenv.go\ (lines\ 239-262), style=go, label={lst:devenv-file-content-snippet}]{src/devenv-file-content-snippet.go} -Herein lies the second vulnerability. Due to the \robotomonoRegular{util.ExtractUuid} function (seen in Listing \ref{lst:devenv-uuid-snippet}) which does not sanitize user input correctly, the adversary is able use path traversal to retrieve the contents of files belonging to DEVENVs owned by other users. +Herein lies the second vulnerability. Due to the \robotomonoRegular{util.ExtractUuid} function which does not sanitize user input correctly, the adversary is able to use path traversal to retrieve the contents of files belonging to DEVENVs owned by other users. \lstinputlisting[caption=service/backend/util/encoding.go\ (lines\ 21-29), style=go, label={lst:extract-uuid-snippet}]{src/extract-uuid-snippet.go} @@ -397,7 +405,7 @@ \section{Vulnerability \#3 -- Unintended RCE} \subsection{How it works} -All the ALPINEs share the same DIND network. That means an adversary can send requests to an ALPINEs CHILD-BACKEND from another ALPINE. But, the CHILD-BACKENDs API is secured with an API key, that is randomly generated once for all ALPINEs per service. This API key lies in the environment of the root user of an ALPINE. To register a user on an ALPINE the client sends some username and password key-pair. Without being properly sanitized, the password is forwarded by the MAIN-BACKEND to the target ALPINEs CHILD-BACKEND. The following code is then executed by the CHILD-BACKEND. +All the ALPINEs share the same DIND network. That means an adversary can send requests to an ALPINEs CHILD-BACKEND from another ALPINE. But, the CHILD-BACKENDs API is secured with an API key that is randomly generated once for all ALPINEs per service. This API key lies in the environment of the root user of an ALPINE. To register a user on an ALPINE the client sends some username and password key-pair. Without being properly sanitized, the password is forwarded by the MAIN-BACKEND to the target ALPINEs CHILD-BACKEND. The following code is then executed by the CHILD-BACKEND. \lstinputlisting[caption=service/image/service/user.go\ (lines\ 148-188), style=go]{src/create-user-snippet.go} diff --git a/documentation/tex/src/path-traversal.patch b/documentation/tex/src/path-traversal.patch index c6e3efa..a29a0fd 100644 --- a/documentation/tex/src/path-traversal.patch +++ b/documentation/tex/src/path-traversal.patch @@ -1,5 +1,5 @@ diff --git a/service/backend/controller/devenv.go b/service/backend/controller/devenv.go -index cd56dbc..eb91a0b 100644 +index 90e4940..3cb438e 100644 --- a/service/backend/controller/devenv.go +++ b/service/backend/controller/devenv.go @@ -7,7 +7,6 @@ import ( diff --git a/documentation/tex/src/rce.patch b/documentation/tex/src/rce.patch index ecdee05..11a980a 100644 --- a/documentation/tex/src/rce.patch +++ b/documentation/tex/src/rce.patch @@ -20,4 +20,3 @@ index 161560f..f4fecc6 100644 ctx.JSON(http.StatusBadRequest, gin.H{"error": "Illegal username"}) return } -