scieee AI-readable full text Open interactive document viewer

Compositional analysis of vulnerabilities in microservice-based applications

Loureiro, Nelson Diogo Santos

Abstract

Nowadays, software applications are becoming larger, more expensive, and more complex due to high market demands and adoption of new technologies that have emerged in recent years. Several companies, in order to remain competitive and solve problems resulting from traditional architectural styles, have started to develop their applications following the microservice architectural style. Microservices are an approach to distributed systems, where a single application is developed as a collection of small services, each running its own process and communicating over a network using lightweight mechanisms, such as the HTTP protocol. All types of applications, including those based on microservices, may contain vulnerabilities, which when exploited by a malicious user can cause problems for the organization developing the application and/or the other users. One way to prevent such problems is through the detection and correction of vulnerabilities. However, due to the way microservice-based applications are developed, there exists an additional challenge on how to detect vulnerabilities resulting from service-toservice communications, since these type of interactions are entirely different from those that occur within traditional applications. In collaboration with Checkmarx, we developed a solution that detects vulnerabilities resulting from service-to-service interactions. This was achieved with the help of CxSAST, a static analyzer of Checkmarx built to discover vulnerabilities in source code, and CxQL queries, which implement a technique of data-ow analysis. We applied a compositional approach, which analyses one service at a time, and then connects the results. Our work is directed at services that communicate through the HTTP protocol and interact in a way that satisfies the REST architectural style. Our solution consists of a collection of queries and of an external tool. The queries produce results, searching for traces of vulnerabilities in the source code of services. The external tool, implemented in the C# language, connects these results and defines paths between them. This is done with the use of an adapted depth-first search algorithm over a graph where each vertex is a result, or a combination of results produced by the queries. In order to only provide relevant information, some decisions have been taken about the type of paths to be produced by the external tool, such as the decision to avoid subpaths, which in this context are a source of redundancy.

Full text

DIREITOS DE AUTOR E CONDIÇÕES DE UTILIZAÇÃO DO TRABALHO POR TERCEIROS Este é um trabalho académico que pode ser utilizado por terceiros desde que respeitadas as regras e boas práticas internacionalmente aceites, no que concerne aos direitos de autor e direitos conexos. Assim, o presente trabalho pode ser utilizado nos termos previstos na licença abaixo indicada. Caso o utilizador necessite de permissão para poder fazer um uso do trabalho em condições não previstas no licenciamento indicado, deverá contactar o autor, através do RepositóriUM da Universidade do Minho. Licença concedida aos utilizadores deste trabalho Atribuição CC BY https://creativecommons.org/licenses/by/4.0/ Acknowledgments First I would like to thank my parents and my brother for all the support they given me this year. Without their encouragement, this dissertation would not have been possible. I would like to thank my supervisors, Professor Lu´ıs Pinto and Professor Maria Jo˜ao Frade, for their help, patience, and guidance throughout this year. I learned a lot from them in this dissertation. To all my colleagues from Checkmarx. Especially the two who helped me, throughout this year, understand the concepts needed for this work, and to those from my internship that were there to answer my stupid questions. I also would like to thank all my friends that were there me in the good times and the bad times. Special thanks to the three that gave me motivation to continue on, and complete my academic journey. v DECLARAÇÃO DE INTEGRIDADE Declaro ter atuado com integridade na elaboração do presente trabalho académico e confirmo que não recorri à prática de plágio nem a qualquer forma de utilização indevida ou falsificação de informações ou resultados em nenhuma das etapas conducente à sua elaboração. Mais declaro que conheço e que respeitei o Código de Conduta Ética da Universidade do Minho. viii Abstract Nowadays, software applications are becoming larger, more expensive, and more complex due to high market demands and adoption of new technologies that have emerged in recent years. Several companies, in order to remain competitive and solve problems resulting from traditional architectural styles, have started to develop their applications following the microservice architectural style. Microservices are an approach to distributed systems, where a single application is developed as a collection of small services, each running its own process and communicating over a network using lightweight mechanisms, such as the HTTP protocol. All types of applications, including those based on microservices, may contain vulnerabilities, which when exploited by a malicious user can cause problems for the organization developing the application and/or the other users. One way to prevent such problems is through the detection and correction of vulnerabilities. However, due to the way microservice-based applications are developed, there exists an additional challenge on how to detect vulnerabilities resulting from service-toservice communications, since these type of interactions are entirely different from those that occur within traditional applications. In collaboration with Checkmarx, we developed a solution that detects vulnerabilities resulting from service-to-service interactions. This was achieved with the help of CxSAST, a static analyzer of Checkmarx built to discover vulnerabilities in source code, and CxQL queries, which implement a technique of data-flow analysis. We applied a compositional approach, which analyses one service at a time, and then connects the results. Our work is directed at services that communicate through the HTTP protocol and interact in a way that satisfies the REST architectural style. Our solution consists of a collection of queries and of an external tool. The queries produce results, searching for traces of vulnerabilities in the source code of services. The external tool, implemented in the C# language, connects these results and defines paths between them. This is done with the use of an adapted depth-first search algorithm over a graph where each vertex is a result, or a combination of results produced by the queries. In order to only provide relevant information, some decisions have been taken about the type of paths to be produced by the external tool, such as the decision to avoid subpaths, which in this context are a source of redundancy. Keywords: Data-flow analysis, Graphs, Microservices, Static analysis, Vulnerabilities. ix xvi List of Figures 2.1 Relating tests and result types [25]. . . . . . . . . . . . . . . . . . . . 11 2.2 The two types of virtualization [60]. . . . . . . . . . . . . . . . . . . . 20 2.3 Two bounded contexts in an application [72]. . . . . . . . . . . . . . . 22 2.4 An API Gateway and its functions [76]. . . . . . . . . . . . . . . . . . 23 3.1 Various kinds of undirected graphs [83]. . . . . . . . . . . . . . . . . . 32 3.2 Two directed graphs [83]. . . . . . . . . . . . . . . . . . . . . . . . . . 32 3.3 (a) A directed graph G. (b) An adjacency-list representation of G. (c) The adjacency-matrix representation of G[82]............ 33 3.4 The progress of DFS on a directed graph with timestamps and colors [82]. .................................... 35 3.5 Two control flow graphs [86]. . . . . . . . . . . . . . . . . . . . . . . 37 3.6 Interactive interface pointing the presence of an SQL injection [93]. . 40 3.7 Complete flow F composed of two partial flows from different services. 45 3.8 The interactions within the MVC pattern [95]. . . . . . . . . . . . . . 46 3.9 A complete flow composed of four partial flows. . . . . . . . . . . . . 50 3.10 The structure of partial flow 2. . . . . . . . . . . . . . . . . . . . . . 51 3.11 The structure of partial flow 4. . . . . . . . . . . . . . . . . . . . . . 52 3.12 A type I flow structure. . . . . . . . . . . . . . . . . . . . . . . . . . . 53 3.13 A type IV flow structure. . . . . . . . . . . . . . . . . . . . . . . . . . 53 3.14 A type IV flow structure composed of a type II and type III flow. . . 54 xvii 3.15 A type V flow structure composed of three type I flows and a type IV flow. .................................... 55 3.16 The interactions present in the small example. . . . . . . . . . . . . . 56 3.17 The graph of the small example. . . . . . . . . . . . . . . . . . . . . . 56 3.18 A complete flow structure composed of two additional type flows. . . 60 3.19 The first small graph. . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 3.20 The second small graph. . . . . . . . . . . . . . . . . . . . . . . . . . 67 3.21 The third small graph. . . . . . . . . . . . . . . . . . . . . . . . . . . 70 3.22 The fourth small graph. . . . . . . . . . . . . . . . . . . . . . . . . . 71 4.1 The database of service Two. . . . . . . . . . . . . . . . . . . . . . . 76 4.2 An interaction between the several components present in the Arithmeticapplication.............................. 77 4.3 A complete flow representing an SQL injection vulnerability. . . . . . 78 4.4 eShopOnContainers architecture overview [108]. . . . . . . . . . . . . 81 4.5 A compose file from the eShopOnContainers application [109]. . . . . 82 4.6 A draft of a more complete solution. . . . . . . . . . . . . . . . . . . 83 xviii List of Tables 3.1 Relating HTTP request data and action methods. . . . . . . . . . . . 49 xix xx Chapter 1 Introduction 1.1 Context Nowadays, software applications are becoming larger, more expensive, and more complex due to high market demands and adoption of new technologies that have emerged in recent years. Several companies, in order to remain competitive and solve problems resulting from traditional architectural styles, have started to develop their applications following the microservice architectural style [1]. Microservices are an approach to distributed systems, where a single application is developed as a collection of small services, each running its own process and communicating over a network using lightweight mechanisms, such as the HTTP protocol. The microservice architecture also integrates new technologies and techniques that have emerged over the last decade, such as on-demand virtualization and infrastructure automation, which helps it avoid some pitfalls of similar architectural implementations. In this approach, services are autonomous, work together, and can be created, initialized, duplicated, and destroyed independently of others [1, 2, 3]. Although the adoption of microservice architectures brings advantages in the development of complex systems, it also presents many challenges. In fact, security is an long-standing problem in network systems, but with microservices it becomes even more challenging. This is due to the large number of entry points and the overload on communication traffic arising from the decomposition of systems into smaller, independent, and distributed software units [1]. But security is not only a serious issue in microservices and, according to the nonprofit foundation Open Web Application Security Project (OWASP), insecure software is undermining critical areas of society such as financial, health, defense, and energy infrastructures. As software becomes increasingly connected and complex, 1 the difficulty of achieving application security increases exponentially [4]. Given the possibility of security breaches occurring, there is a need to detect and correct potential vulnerabilities in an application after its release, and throughout its software development life cycle. Application security provides measures for this, and aims to protect an organization against external threats and internal risks, ensuring that the code responsible for running an application is secure and free of high risk vulnerabilities [5]. 1.2 Goals Checkmarx is one of the leading companies specializing in application security, offering several products to its costumers. One of these products is CxSAST, a powerful static analyzer built to discover security vulnerabilities in source code. Without going into much detail, CxSAST normally follows the following process: build a logical graph based on the source code, queries it, and provides the user scan results. Each result provides a brief description of the identified vulnerability, a list of recommendations on how to solve it, an indication of which part of the application is insecure, among other features [6]. With CxSAST in mind and with the intention to increase its coverage, Checkmarx requested a solution to the challenge of detecting vulnerabilities resulting from service-to-service interactions within a microservice-based application. And in this dissertation, the security solution is designed for services that communicate through HTTP and interact in a way that satisfies the REST architectural style. From the start, it was requested that our solution would have to resort to a compositional approach, i.e., it would have to analyze one service at a time, and then connect the results. It was also established that our implementation would have to find vulnerabilities without hindering the way CxSAST naturally detects vulnerabilities present in a single service. 1.3 Contributions Our solution aims to define paths in the application through the connection of small results from different services, and thus making it possible to infer the presence of vulnerabilities. This desired solution was realized through the development of new queries, and a new external tool. 2 In short, when given the source code of a microservice-based application, CxSAST and these new queries will analyze the services, one at a time. In this first part, we search for traces of vulnerabilities, and for each service, we produce a collection of small results. After analyzing all the services and executing all the necessary queries, the external tool connects the small results, producing new and larger results. In the production of these results, we had to be aware of several aspects, such as making proper connections, and not sharing information that has already been shared. If the small results indicate with good certainty the presence of vulnerabilities, and the connections are done correctly, then we can say that the tool accurately detects vulnerabilities resulting from service-to-service interactions. 1.4 Place of Internship The work presented in this dissertation was partly developed during an internship at Checkmarx in the context of the Master’s Degree in Mathematics and Computing, of University of Minho. During this internship, I had the possibility to learn several important subjects used in this dissertation. For example, - In terms of application security, I learned its core concepts and some of the most important security vulnerabilities; - In the context of software development, I was exposed to some of its techniques, to essential concepts of web applications, and to some of the most important programming languages of today; - In regards to the comprehension of Checkmarx products, I had access to the fundamental details of both CxSAST and the Checkmarx Query Language. 1.5 Dissertation Outline The dissertation is organized as follows. Chapter 2 provides a detailed description of three topics that took a central position in the design of our solution, and can thus be seen as the background of this dissertation. The Section “Application Security” contains a definition of security vulnerabilities, a brief summary of those supported in our solution and a short description of application security testing. Secondly, the Section “Communication” is dedicated to specify the REST architecture and the HTTP protocol. Lastly, the 3 Section “Other Software Architectural Styles” not only describes microservices, but also briefly defines some of its contrasting architectural approaches. Chapter 3 describes our solution, and how it came to be. It starts with an overview of graph theory, which was fundamental in producing the new and larger results mentioned in Section 1.3. Afterwards, the necessary information about CxSAST and the Checkmarx query language is provided. After this, we present some specific aspects of security in the context of microservices, and how they relates to the main challenge of this dissertation. Finally, both parts of the solution (the queries and the external tool) are explained in detail. Chapter 4 covers the case studies and their impact on our solution. Additionally, at the end of this chapter, we present some ideas on how to support an important microservice pattern, which we believe is widely present in real-world applications. We have become aware of its considerable significance towards the end of the testing phase, and so its support can be seen as important future work. Finally, Chapter 5 contains general conclusions regarding the presented problems and methods. 4 Chapter 2 Background In this chapter we take a close look at application security, a type of RESTful communication, and the microservice architecture. Each of the corresponding sections begins with definitions of basic concepts that are needed to explain more advanced ones. 2.1 Application Security Application security encompasses measures taken to improve the security of an application. Its main objective is to reduce the overall risk that an application poses to an organization. This is achieved through the detection of weaknesses within an application [5, 7]. Before taking a closer look at application security, we need to define some basic concepts. 2.1.1 Basic Concepts Database A database is an organized collection of data, generally stored and accessed electronically from a computer system [8]. DBMS Database Management System (DBMS) is a software system that enables users to define, create, maintain, and control access to the database [8]. 5 URI A Uniform Resource Identifier (URI) is a compact sequence of characters that identifies an abstract or physical resource, where the term “resource” is used in a general sense for whatever might be identified by a URI [28]. URL A Uniform Resource Locator (URL) is a reference to a web resource that specifies its location on a network and a mechanism for retrieving it. URLs are a specific type of URIs, and occur most commonly to reference web pages [29]. Interface An interface is a shared boundary at which independent and often unrelated components meet and communicate with each other [30, 31]. API An Application Programming Interface (API) is a computing interface that defines interactions between multiple software intermediaries. It defines the kinds of calls or requests that can be made, how to make them, the data formats that should be used, the conventions to follow, and so forth [32]. Client–Server Model Client–server model is a distributed application structure that partitions workloads between service providers and service requesters. In this model, a server is called the provider and a client is called the requester [33]. Client-side and Server-side Over a network and in a client–server model, •Client-side refers to operations that are performed by the client [34]; •Server-side refers to operations that are performed by the server [35]. User Interface A user interface is the means by which a user controls a software application or hardware device [36]. 12 Communication Protocol A communication protocol is a system of rules that allow two or more entities of a system of communications to transmit information. The protocol defines rules, syntax, semantics, synchronization of communication, and possible error recovery methods [37]. Request/Response Pattern Request/response is a pattern used in order to communicate through a network, in which one application sends a request message for some data, and another application one receives and processes it, eventually returning a response message [38]. This pattern is typically implemented in a purely synchronous manner. An example of this is when a web service makes a call over HTTP, opens a connection, and waits until the response is delivered or until a timeout period expires. In this case, it can be said that a response message is received promptly and both entities must be available during the time required to complete this request. However, a request–response can also be implemented asynchronously, where instead a response is returned eventually and both entities can be available for the duration of this request [38, 39]. Having defined the basic concepts, we are now ready to have a closer look into the REST architecture and the HTTP protocol. 2.2.2 REST Representative state transfer (REST) is a software architectural style that defines a collection of constraints to be used when creating web services [40]. There exists six guiding constraints that define a RESTful system. These constraints restrict the ways that the server can process and respond to client requests so that, by operating within these constraints, the system gains desirable non-functional properties, such as performance, scalability, simplicity, modifiability, visibility, portability, and reliability. If a system violates any of the required constraints, it cannot be considered RESTful [40]. Architectural Constraints Although REST is mentioned as an important aspect of this dissertation, in reality not lot of work was put into our solution in relation to it, and thus the following 13 information is presented as a means to understand better this architectural style. The formal REST constraints are as follows [40, 41]: -Client-server architecture: The principle behind the client-server constraints is the separation of concerns. It looks to separate user interface concerns from data storage concerns. -Statelessness: The client-server communication is constrained by no client context being stored on the server between requests. Each request from any client contains all the information necessary to service the request, and the session state is held in the client. -Cacheability: Responses must, implicitly or explicitly, define themselves as either cacheable or non-cacheable to prevent clients from providing stale or inappropriate data in response to further requests. -Layered system: The layered system style allows an architecture to be composed of hierarchical layers by constraining component behavior such that each component cannot “see” beyond the immediate layer with which they are interacting. -Uniform interface: Uniform interface simplifies and decouples the architecture, which enables each part to evolve independently. The four constraints for this uniform interface are: – Identification of resources: Individual resources are identified in requests. The resources themselves are conceptually separate from the representations that are returned to the client. – Manipulation of resources through representations: When a client holds a representation of a resource, including any metadata attached, it has enough information to modify or delete the resource’s state. – Self-descriptive messages: Each message includes enough information to describe how to process the message. – Hypermedia as the engine of application state (HATEOAS): Having accessed an initial URI for the REST application, a REST client should then be able to use server-provided links dynamically to discover all the available resources it needs. As access proceeds, the server responds with text that includes hyperlinks to other resources that are currently available. 14 -Code on demand (optional): REST allows client functionality to be extended by downloading and executing code in the form of applets or scripts. Resource The key abstraction of information in REST is a resource. Any information that can be named can be a resource: a document, an image, a temporal service, a collection of other resources, a non-virtual object, and so on. REST uses a resource identifier to identify the particular resource involved in an interaction between components [41]. The state of the resource at any particular timestamp is known as resource representation. A representation consists of data, metadata describing the data and hypermedia links which can help the clients in transition to the next desired state [41]. The data format of a representation is known as a media type. The media type identifies a specification that defines how a representation is to be processed. Every addressable unit of information carries an address, either explicitly or implicitly [41]. Resource Methods Another important aspect of the REST architecture are the resource methods that are used in order to perform a desired transition [41]. Roy Fielding, the creator of REST, has never mentioned any recommendation around which method to be used in which condition. All he emphasizes is that the web service should satisfy the uniform interface constraint [41]. Ideally, everything that is needed to change the resource state shall be part of an API response for that resource. This includes information about the methods and in what state they will leave the representation [41]. There is no official standard for RESTful web APIs because REST is only an architectural style and not a standard in itself. RESTful implementations make use of standards such as HTTP, URI, JSON, and XML [40]. 2.2.3 HTTP The Hypertext Transfer Protocol (HTTP) is a stateless application-level request/response protocol that operates by exchanging messages across a reliable transport [42]. 15 HTTP is a generic interface protocol for information systems. It is designed to hide the details of how a service is implemented by presenting a uniform interface to clients that is independent of the types of resources provided [42]. HTTP is also designed for use as an intermediation protocol for translating communication to and from non-HTTP information systems [42]. HTTP Terminology The next terms refer to the roles played by participants in, and objects of, the HTTP communication [42, 43]. - Connection: A transport layer virtual circuit established between two programs for the purpose of communication. - Message: The basic unit of HTTP communication, consisting of a structured sequence of octets transmitted via the connection. - Request: An HTTP request message. - Response: An HTTP response message. - Resource: A network data object or service that can be identified by a URI. Resources may be available in multiple representations (e.g., multiple languages, data formats, size, and resolutions) or vary in other ways. - Entity: The information transferred as the payload of a request or response. An entity consists of metainformation in the form of entity-header fields and content in the form of an entity-body. - Representation: An entity included with a response that is subject to content negotiation. - Client: A program that establishes a connection to a server for the purpose of sending one or more HTTP requests. - Server: A program that accepts connections in order to service HTTP requests by sending HTTP responses. - Gateway: A server which acts as an intermediary for some other server. - Cache: A local store of previous response messages and the subsystem that controls its message storage, retrieval, and deletion. A cache stores cacheable 16 responses in order to reduce the response time and network bandwidth consumption on future, equivalent requests. - Cacheable: A response is cacheable if a cache is allowed to store a copy of the response message for use in answering subsequent requests. The terms “client” and “server” refer only to the roles that these programs perform for a particular connection. The same program might act as a client on some connections and a server on others [42]. Some additional features are as follows [42, 44]: •HTTP relies upon the URI standard to indicate the target resource and relationships between resources. •HTTP is defined as a stateless protocol, meaning that each request message can be understood in isolation. •The start-line and HTTP headers of an HTTP message are collectively known as the head of the request, whereas its payload is known as the body. HTTP Request Methods The request method token is the primary source of request semantics. It indicates the purpose for which the client has made this request, and what is expected by the client as a successful result. Although they can also be nouns, these request methods are sometimes referred to as HTTP verbs [45, 46]. The request methods supported in the solution of this dissertation are GET, POST, and PUT. The following is a brief overview of these methods [46, 47]: •The GET method requests a representation of the specified resource. Requests using GET should only retrieve data; •The POST method is used to submit an entity to the specified resource, often causing a change in state, or side effects on the server. This method should only be used to create a new representation of a resource; and •The PUT method replaces all current representations of a target resource with the request payload. This method should generally only be used to update the representation of a resource. 17 HTTP Requests An HTTP request consists of the following elements [48]: - An HTTP method; - The path of the resource to fetch; - The version of the HTTP protocol; - Optional headers that convey additional information for the servers; and - In some cases, a body for some methods like POST. HTTP Response An HTTP response consists of the following elements [48]: - The version of the HTTP protocol it follows; - A status code, indicating if the request was successful, or not, and why; - A status message, i.e., a non-authoritative short description of the status code; - HTTP headers, like those for requests; - Optionally, a body containing the fetched resource. REST and HTTP In the design of microservices, a popular architectural style for request/response communication is REST. This approach is based on, and tightly coupled to, the HTTP protocol [49]. To better understand the coupling between HTTP and REST, the presence of the following aspects usually define an HTTP-based RESTful API [40]: - A collection of base URIs, such as “http://api.example.com/collection”. - A collection of standard HTTP methods (e.g., GET, POST, PUT, and DELETE); - A collection of media types that define how representations can be processed (e.g., application/json and text/html). In this context, a REST API endpoint is the location of particular accessible resource [50]. 18 2.3 Other Software Architectural Styles In a broader sense, a software application can be seen as a program or group of programs designed for end users [51]. The next subsection is dedicated to virtualization and some software development practices, so that we can better understand microservices and the two other architectural styles mentioned in this section. 2.3.1 Basic Concepts Virtualization Virtualization is the process of running a virtual instance of a computer system in a layer abstracted from the actual hardware [52]. Operating System An operating system (OS) is a software system that manages hardware, software resources, and provides common services for computer programs [53]. Operating System Kernel The OS kernel is a computer program at the core of a OS with complete control over the system. It facilitates interactions between hardware and software components [54]. Virtual Machine A virtual machine (VM) is a software simulation of a hardware platform that provides a virtual operating environment [55]. Virtualization makes it possible to create multiple VMs, each with their own OS and applications. This is possible through a hypervisor, which is a small software layer that separates VMs and allocates processors, memory, and storage among them [56]. A physical machine on which a hypervisor runs one or more VMs is called a host machine, and each VM is called a guest machine [57]. 19 Container A container is a virtual runtime environment that runs on top of a single OS kernel and emulates an OS through a container engine. This engine allocates cores and memory to containers, enforces spatial isolation, and provides scalability by enabling the addition of containers [55]. One way to better understand the concept of containers is through its comparison with virtual machines [58]: - A container virtualizes the operating system. It contains an application, its libraries and dependencies. - A virtual machine uses a hypervisor to virtualize physical hardware. It contains a guest OS, a virtual copy of the hardware, an application, and its associated libraries and dependencies. Containers use fewer resources than virtual machines because they share the OS kernel of the machine and do not require an OS per application [59]. A traditional virtual machine and a container implementation are schematically presented in Figure 2.2. (a) Virtual Machine (b) Container Figure 2.2: The two types of virtualization [60]. Hybrid Container Architecture A hybrid container architecture is an architecture combining virtualization of virtual machines and containers, i.e., the container engine and associated containers execute on top of a virtual machine [55]. 20 Containter Orchestration Container orchestration is the automated deployment, management, scaling, and networking of containers [61]. Cloud Computing Cloud computing is the delivery of computing services over the Internet. The use of cloud services lowers operating costs, helps run infrastructure more efficiently, and gives the opportunity of scaling when a service needs it [62]. Loosely Coupled System A loosely coupled system is one in which each of its components has or makes use of little or no knowledge about the definitions of other separate components [63]. Continuous Integration Continuous integration (CI) is a software development practice where developers regularly merge their code changes into a central repository, after which automated builds and tests are run. Continuous integration tries to find and address bugs quicker, improve software quality, and reduce the time it takes to validate and release new software updates [64]. Continuous Delivery Continuous Delivery (CD) is a software development practice in which software is built in such a way that it can be released to production at any time. It automates the software delivery aspect, and thus is capable of yielding frequent production deployments. An important aspect of CD is that in order to create those production deployments, it is required the presence of manual approval [65, 66, 67]. DevOps DevOps is a set of practices that combines software development (Dev) and IT operations (Ops). It aims to shorten the development life cycle of an application [68]. Modern day DevOps implements certain practices, such as CI and CD. When both practices are in place, the resulting process is called CI/CD, which includes the full automation on all steps throughout the software development life cycle [69, 70]. 21 Loose coupling allows testing in isolation from the rest of the underlying systems and enables development using different programming languages and database technologies [1]. Fine Granularity A microservice architecture should be fine-grained, which means that there must be a minimum of centralized service management [81]. Advantages and Disadvantages Microservices are highly modular, distributed systems that can be reusable through a network-exposed API. This implies that microservices inherit advantages and disadvantages of both distributed systems and web services [77]. The most frequently listed benefits of the microservice architecture style are organizational alignment, faster and more frequent releases of software, independent scaling of components, and overall faster technology adoption [77]. The disadvantages of microservices include the need to make multiple design choices, the difficulty of testing and monitoring, the design for failure, operational overhead when compared to typical non-distributed solutions, and the appearance of new security challenges [2, 77]. The last mentioned disadvantage shows the relevance in the request made by Checkmarx. The next chapter gives more information about this topic. 28 Chapter 3 Detecting Vulnerabilities in Microservices This chapter introduces our solution, whose goal is to detect vulnerabilities resulting from service interactions. This chapter also describes some parts of graph theory, CxSAST, and microservice security. 3.1 Graph Theory Some concepts presented in this section will be of importance when introducing the CxSAST tool in Subsection 3.2.3. Moreover, some ideas, concepts, and results of this theory were instrumental in the development of our external tool, presented in Subsection 3.4.5. The concepts and results that follow came from the book Introduction to Algorithms by Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein [82]. 3.1.1 Basic Concepts Agraph Gis a pair (V, E), where: -Vand Eare finite sets. - The set Vis called the vertex set of G, and its elements are called vertices. - The set Eis called the edge set of G, and its elements are called edges. There exists two important types of graphs: 29 - An undirected graph G= (V, E) is a graph where the edge set E⊆ {{x, y} | x, y ∈Vand x6=y}; and - A directed graphG= (V, E) is a graph where the edge set E⊆ {(x, y)|x, y ∈ V}, that is, E⊆V×V. By convention, in undirected graphs we will use the notation (u, v) to denote an edge, and we will consider (u, v) and (v, u) to be the same edge. It is important to note that although both types of graphs are of relevance to the rest of this section, only the directed ones will be of importance beyond this section. Incidence If (u, v) is an edge in a directed graph, - We say the (u, v) is incident from or leaves vertex u; and - We say the (u, v) is incident to or enters vertex v. Adjacent Vertex - If (u, v) is an edge in a graph, we say that vertex vis adjacent to vertex u. When the graph is undirected, the adjacency relation is symmetric. Degree In a directed graph, - The out-degree of a vertex is the number of edges leaving it; - The in-degree of a vertex is the number of edges entering it; and - The degree of a vertex is its in-degree plus its out-degree. Path - A path of length kfrom a vertex uto vertex u ' in a graph G= (V, E) is a sequence hv0, v1, v2, ..., vkiof vertices such that u=v0,u ' =vkand (vi−1, vi)∈ Efor i= 1,2, ..., k. Note that the length of a path corresponds to the number of edges in the path. - A path hv0, v1, v2, ..., vkiis said to contain the vertices v0, v1, v2,..., vkand the edges (v0, v1),(v1, v2), ..., (vk−1, vk). - If there is a path pfrom uto u ' , then u ' is reachable from uvia p. 30 Simple Path - A path is simple if all vertices in the path are distinct. Subpath - A subpath of path p=hv0, v1, ..., vkiis a contiguous subsequence of its vertices. That is, for any 0 ≤i≤j≤k, the subsequence of vertices hvi, vi+1, ..., vjiis a subpath of p. Cycle - In a directed graph, a path hv0, v1, v2, ..., vkiforms a cycle if v0=vkand the path contains at least one edge. A self-loop is a cycle of length 1. - In an undirected graph, a path hv0, v1, v2, ..., vkiforms a cycle if k≥3 and v0=vk. - A graph with no cycles is called acyclic. Connected and Strongly Connected - A directed graph is strongly connected if every two vertices are reachable from each other. - An undirected graph is connected if every vertex is reachable from all other vertices. Tree - A tree is a connected and acyclic undirected graph. Forest - A forest is an acyclic undirected graph. Subgraph - A graph G ' = (V ' , E ' ) is a subgraph of G= (V, E) if V ' ⊆Vand E ' ⊆E. - Given V ' ⊆V, the subgraph of Ginduced by V ' is the graph G ' = (V ' , E ' ), where E ' ={(u, v)∈E|u, v ∈V ' } 31 Figures 3.1 and 3.2 illustrates some of these definitions. Figure 3.1: Various kinds of undirected graphs [83]. Figure 3.2: Two directed graphs [83]. 3.1.2 Representations of Graphs The two most common computational representations of graphs are as adjacency lists and as adjacency matrices. Either of these representations applies to both directed and undirected graphs. The adjacency-list representation provides a compact way to represent sparse graphs, i.e., those for which |E|is much less than |V2|. The adjacency-matrix representation, however, is preferable when the graph is dense, i.e., |E|is close to |V2|. Adjacency-List Representation The adjacency-list representation of a graph G= (V, E) consists of an array Adj of |V|lists, one for each vertex of V. For each u∈V, the adjacency-list Adj[u] contains all the vertices vsuch that there is an edge (u, v)∈E. That is, Adj[u] consists of all the vertices adjacent to u in G. Adjacency-Matrix Representation For the adjacency-matrix representation of a graph G= (V, E), we assume that the vertices are numbered 1,2, ..., |V|(or 0,1, ..., |V| − 1, depending on the coding 32 language) in some arbitrary manner. Then the adjacency-matrix representation of a graph Gconsists of a |V|×|V|matrix A= (aij) such that aij =   1 if (i, j)∈E, 0 otherwise. Figure 3.3 depicts the two representations of a particular directed graph. Figure 3.3: (a) A directed graph G. (b) An adjacency-list representation of G. (c) The adjacency-matrix representation of G[82]. 3.1.3 Representations of Attributes Most algorithms that operate on graphs need to maintain attributes for vertices and/or edges. For example, we use v.d to denote a attribute dof v. For adjacency lists, one way to represent vertex attributes is to use additional arrays. For the attribute d, there could exist an array d[1 ... |V|] that parallels the Adj array. 3.1.4 Depth-First Search Given a graph G= (V, E) and a distinguished source vertex s, the depth-first search systematically explores the edges of Gto “discover” every vertex that is reachable from s. This algorithm outputs a forest comprised of several trees. The input graph G= (V, E) may be directed or undirected and in what follows the graphs are represented using the adjacency-list representation. The strategy followed by depth-first search is, as its name implies, to search “deeper” in the graph whenever possible. Depth-first search (DFS) explores edges out of the most recently discovered vertex vthat still has unexplored edges leaving it. Once all of edges of vhave been explored, the search “backtracks” to explore edges leaving the vertex from which v was discovered. This process continues until we have discovered all the vertices that 33 are reachable from the original source vertex. If any undiscovered vertices remain, then depth-first search selects one of them as a new source, and it repeats the search from that source. The algorithm repeats this entire process until it has discovered every vertex. Depth-first search may constructs several trees. Whenever the search discovers a vertex vduring a scan of the adjacency list of an already discovered vertex u, the vertex vand the edge (u, v) are added to a tree. We say that uis the predecessor or parent of vin a depth-first tree. To keep track of progress, depth-first search colors each vertex white, gray, or black. Each vertex is initially white, is grayed when it is discovered in the search, and is blackened when it is finished, that is, when its adjacency list has been examined completely. This technique guarantees that each vertex ends up in exactly one depth-first tree, so that these trees are disjoint. So if the graph being traversed contains cycles, this algorithm does not reach the same vertex a second time and thus prevents infinite recursion. If a grey colored vertex is reached, that means the graph has a loop. This algorithm attaches two additional attributes to each vertex in the graph. We store the color of each vertex u∈Vin the attribute u.color and the predecessor of uin the attribute u.π. If uhas no predecessor, then u.π =NIL. Therefore, the predecessor subgraph of a depth-first search is denoted as Gπ= (V, Eπ), where Eπ={(v.π, v)|v∈Vand v.π 6=NIL}. The predecessor subgraph of a depth-first search forms a depth-first forest comprising several depth-first trees. The edges in Eπare called tree edges. Figure 3.4 shows the order of visit in a graph under the depth-first search (from (a) to (p)). It shows the progress of DFS on a directed graph. As edges are explored by the algorithm, they are shown as either shaded (if they are tree edges) or dashed (otherwise). Timestamps within vertices indicate discovery time and finishing times. Non tree edges are labeled B, F, or C according to whether they are back, forward, or cross edges. The idea behind these labels is as follows: - Back edges are edges (u, v) connecting a vertex uto an ancestor vin a depthfirst tree. Self-loops are considered back edges. - Forward edges are edges (u, v) connecting a vertex uto an descendant vin a depth-first tree. - Cross edges are all other non tree edges. 34 Figure 3.4: The progress of DFS on a directed graph with timestamps and colors [82]. The following pseudocode is the basic depth-first search algorithm. Algorithm 1 Depth-first search 1: procedure DFS(G) 2: for each vertex u∈Vdo 3: u.color := WHITE 4: u.π := NIL 5: end for 6: for each vertex u∈Vdo 7: if (u.color =WHITE)then 8: DFS-VISIT(G, u) 9: end if 10: end for 11: end procedure Algorithm 2 Depth-first search visit 1: procedure DFS-VISIT(G, u) 2: u.color := GRAY 3: for each vertex v∈G.Adj[u]do 4: if (v.color =WHITE)then 5: v.π := u 6: DFS-VISIT(G, v) 7: end if 8: end for 9: u.color := BLACK 10: end procedure 35 Procedure DFS works as follows: - Lines 2–5 paint all vertices white and initialize their πattributes to NIL; - Lines 6–10 check each vertex in Vin turn and, when a white vertex is found, visit it using DFS-VISIT; - Every time DFS-VISIT(G, u) is called in line 8, vertex ubecomes the root of a new tree in the depth-first forest. In procedure DFS-VISIT. - For each call DFS-VISIT(G, u), vertex uis initially white; - Line 2 paints ugray; - Lines 3–8 examine each vertex vadjacent to uand recursively visit vif it is white; - As each vertex v∈Adj[u] is considered in line 3, we say that edge (u, v) is explored by the depth-first search; - Finally, after every edge leaving uhas been explored, line 9 paints ublack. Before we describe the time complexity of DFS, we need to introduce the big-theta notation. Given two numeric functions fand g, we denote f(n) = Θ(g(n)) when [84]: ∃c1, c2, n0∈N,∀n>n0. c1g(n)≤f(n)≤c2g(n). Also, in order to present the time complexity of DFS, we utilize aggregate analysis. This type of analysis considers both the costly and less costly operations throughout the whole series of operations in an algorithm. In short, this analysis considers the worst-case run time per operation, rather than per algorithm [85]. The time complexity of the depth-first search algorithm shown next is from the book Introduction to Algorithms [82] mentioned at the start of this section. Time Complexity In the procedure DFG, the loops on lines 2–5 and lines 6–10 take time Θ(|V|), exclusive of the time to execute the calls to DFS-VISIT. The procedure DFS-VISIT is called exactly once for each vertex v∈V, since the vertex uon which DFS-VISIT is invoked must be white and the first thing DFSVISIT does is paint it gray. During an execution of DFS-VISIT(G, v), the loop on lines 3–8 executes |Adj[v]|times. Since 36 X v∈V |Adj[v]|= Θ(|E|), the total cost of executing lines 3–8 of DFS-VISIT is Θ(|E|), that is, it takes Θ(|E|) to check all elements of the adjacency-list representation. The running time of DFS is therefore Θ(|V|+|E|). 3.2 The Checkmarx SAST Product The beginning of this section acts as a bridge between graph theory and CxSAST. The notions of control flow graph and data-flow analysis are essential in order to understand CxSAST and our solution. 3.2.1 Control Flow Graph A control flow graph (CFG) is a representation of all paths that might be traversed through a program during its execution. A control flow graph is a directed graph in which the vertices represent basic blocks and the edges represent control flow paths [86, 87]. A basic block is a linear sequence of program instructions having one entry point and one exit point. An entry point is the first instruction executed and an exit point is last instruction executed [87]. It is important to define two special types of basic blocks: entry and exit blocks. An entry block is where a control flow enters a graph and an exit block is where all control flows leave [86]. A basic block may be preceded or succeeded by many other basic blocks. In a program, entry blocks might not have predecessors and exit blocks will never have successors [87]. Figure 3.5 illustrates two control flow graphs. The one on the left represents a simple if-then-else statement, and the second one represents a simple while loop [86]. Figure 3.5: Two control flow graphs [86]. 37 3.4 Our Solution This section starts with the disclosure of the decisions taken regarding the development of our solution. Afterwards, it is identified the type of microservice-based applications supported in this work, and the model that specifies how the new queries and the new external tool were to be developed. Finally, at the end of this section, we describe in detail our solution for vulnerability detection. 3.4.1 Decisions Taken As already mentioned, our solution does not directly help CxSAST to support all the security issues highlighted in Subsection 3.3.1. This responsibility is assigned to the various security experts of Checkmarx. The main objective of this solution is to produce paths between services. In the context of data-flow analysis, there is some evidence in the source code of a service that certain parts of it can influence other services. And in the case of communication through HTTP-based APIs, this evidence may very well consist of URLs and HTTP methods. Suppose there exists an application that uses an HTTP-based RESTful API in the communication between abstract services A and B. Suppose it also exists a particular SQL injection flow, called F, from service A to service B. Flow F could ideally be broken down into two other flows: •Flow F1, a flow from a user input to a particular service exit point, that is, from a user input to an HTTP request method; and •Flow F2, a flow from a service entry point to a method that processes an SQL command. In the above example, flow F1 represents only source code of service A, flow F2 represents only source code of service B, and flow F represents source code of both services (a service entry point is later defined in Subsection 3.4.2). From now on we will refer to flows like F1 and F2 as partial flows and flows like F as complete flows. A partial flow is one that represents a path contained in only one service. A complete flow is one that represents a path that goes through at least two services. We will also refer to complete flows produced by our external tool as complete results, because this tool only outputs a subset of all complete flows. Figure 3.7 illustrates a complete flow composed of these two partial flows: 44 Flow F1 Flow F2 HTTP Request Figure 3.7: Complete flow F composed of two partial flows from different services. From the start, Checkmarx made it clear that no changes had to be made in the source code of CxSAST, and the solution to be developed should scan microservicebased applications in a compositional way. This means that each service should be scanned separately from the others, producing in the end a set of data structures that represent a single loosely coupled application. As there are certainly several ways to tackle the problem at hand, some design decisions had to be made. The main decisions are presented next. First, it was planned that our solution could make connections between all the produced DFGs. But considering the time available for this dissertation, we decided to reject this idea, as it might well have proven to be too complex. The idea considered afterwards was to connect scan results. In the context of a scanning procedure, scan results are produced at a later stage than the set of DFGs. Ultimately, this was the chosen approach because of its simplicity, and the fact that it could benefit from some of the knowledge acquired during the internship with Checkmarx, namely knowledge about web development and the Checkmarx query language. This approach requires the creation of new queries in order to produce partial flows. It should be noted that each flow produced will not be able to declare the presence of vulnerabilities on its own, since its sole purpose is to be connected with others so that complete flows can be obtained. Complete results can be categorized as true positives or false positives. And this by itself brings the need to reduce the number of false positives, as we primarily seek to avoid them. To do this, we developed a model responsible for deciding what the structure of the partial flows could be, how to connect them, which partial flows should be filtered out, etc. CxSAST makes it possible to treat each service as if it were a single application in order to produce partial flows. Using an external tool and following a model, we can later connect these flows. Thus, in this process we apply the idea of compositionality. Before presenting the approaches used in our solution, let us see what type of services are supported. 45 3.4.2 Type of Services Supported It was previously mentioned that the goal of this dissertation is to support services that communicate through HTTP-based RESTful APIs. Something that has not been explicitly stated is that developers have several ways of writing their applications in order to implement this type of communication. An important restriction of our solution is that services must have their own HTTP-based RESTful API. Also, since it is very likely that the whole lifecycle of a service is owned by a small team, we have to think from the point of view of a single service. Hence we can think the communication of a service as if it was the interaction between itself and its clients. The clients of a singular service can be other services, a web application that handles user input, and even other applications developed by other organizations than the one we are looking at [79]. In this work, we restrict to only one way a service can receive data from its clients, that is, there exists only one type of service entry point for all manner of clients. This idea is closely related with the model–view–controller pattern, detailed below. MVC Pattern Model–view–controller (MVC) is a software design pattern commonly used for developing user interfaces that divides the logic of a program into three interconnected components [95]: •The model - the central component of the pattern. It directly manages the data, logic and rules of an application. •The view - any representation of information such as a chart, diagram or table. •The controller - receives the input, optionally validates it and then passes it to the model. Figure 3.8 illustrates the interactions within the MVC pattern. Figure 3.8: The interactions within the MVC pattern [95]. 46 In order to recognize a flow that represents a communication between various services, all but the last service of the communication will have to: - Use a collection of controller files that implement REST API endpoints; and - Execute at least one HTTP request using a hard coded URL, that is, the value of the address used in the HTTP request must be directly embedded in the source code of the service. Whereas the last service only needs to satisfy the first condition. It is important to note that in order to produce complete flows, HTTP requests must use URLs directly embedded into the source code of the service that initiates the request. In the interaction between two services, the first service that starts the interaction will have to satisfy both conditions, and the other service will have to satisfy the first one. The controllers mentioned here are very similar to the MVC ones. But in this case we are talking in the context of Web APIs, where controllers are simply components that handle HTTP requests [96]. In this work, controllers are the recognized service entry points. Therefore, at least one controller file must be present in an interaction between two services. A controller class generally consists of a set of request handler methods. Each method implements a REST API endpoint and the method parameters represent values from an HTTP request [79]. Our solution will only recognize service communications written in the C# and Java programming languages. It is possible to recognize a simple interaction of a service written in C# and another written in Java, since the product of this work follows an essentially compositional pattern. In theory, this pattern also allows the recognition of communications written in other languages. It is important to emphasize that most of the work done in this dissertation is directed at services written in C#, and that the support for those in Java is basic in nature. From now on, only C# directed ideas will be presented, since the Java version is very similar and follows the same ideas. All the functionalities supported in the Java side are supported in the C# side. The next subsection is closely related to C# and references the ASP.NET Web API framework. This was the supported framework and will give an idea about controller routing. This concepts are also available in Java through other means, such as the Play Framework [97]. The following information comes from the C# Microsoft documentation [96]. 47 Controller Routing In the ASP.NET Web API framework, a controller class is a class that handles HTTP requests, where its public methods are called action methods. When receiving an HTTP request, this framework tries to route it to a particular action method using a routing table. If no route matches, the client receives a 404 HTTP error message. Each entry in a routing table contains a route template. When receiving an HTTP request, the Web API framework tries to match the URI with one of the existing route templates. For example, the following URIs match the route template “api/<controller>/[<id>]”: •api/contacts; •api/contacts/1; •api/products/0. However, the following URI does not match, since it lacks the “api” segment: •contacts/1. In this example, the route template has the “api” part as a literal path segment, and “<controller>” and “[<id>]” as placeholder variables. Also, the “[<id>]” segment is optional. Afterwards, once a matching route is found, the framework selects the controller and the action method, taking into account: - The value of the “<controller>” variable, in order to find the controller. - The HTTP verb used in the request, and the action methods associated with this verb, in order to find the correct action method. - Other placeholder variables in the route template, such as “[<id>]” shown before. Table 3.1 shows possible HTTP requests, along with the action method that gets invoked for each. 48 HTTP Request Method Request URI Action Parameter GET api/contacts GetAllContacts (none) GET api/products GetAllProducts (none) GET api/products/1 GetProductById 1 POST api/products/2 CreateProduct 2 POST api/products/3 CreateProduct 3 PUT api/products/4 UpdateProduct 4 PUT api/contacts (no match) Table 3.1: Relating HTTP request data and action methods. It is important to highlight the following notes about this table: - All requests match the routing template “api/<controller>/[<id>]”. - Both POST requests match the same action method, but with different parameters. - The PUT request with the URI“api/contacts”will fail, since no correspondence is found. 3.4.3 Model Considering the framework mentioned before, we know that it uses and adheres to some specific logic in order to provide the development of Web applications. An application that uses this kind of frameworks and makes several HTTP requests, knows that each endpoint will have to receive and handle each request in a reasonable manner. Thus, it was important in this dissertation to understand some of the underlying logic of the targeted frameworks in order to simulate an interaction between two services. It is important to recall that this work is of a basic nature and not meant to provide complete support. We know that an HTTP-based API consists mainly of URLs, HTTP methods, and request and response formats. Based on this, it was decided that the main connection points between two partial flows of different services would be all code elements associated with the URLs and the HTTP methods. For this particular solution, the action methods in a controller file are service entry points and the methods that execute the HTTP requests are service exit points. In both programming languages, the latter methods should preferably be related to REST, and this is the only reference to it in our solution. We do not verify that an 49 API satisfies any of the REST constraints, and we allow the support of methods that create HTTP requests but do not explicitly reference REST in their documentation. For example, we support methods from C# third-party libraries such as“RestSharp” or from Java frameworks like “RestTemplate”. We know that a complete result is a flow that has to indicate a weakness in an application. This type of results have to point out attack vectors, that is, paths or means by which an attacker can cause a malicious outcome [98]. In order to produce complete flows, we have to look for unsanitized paths from source elements to sink elements. In other words, we follow basically the ideas of most queries, as highlighted in Subsection 3.2.4. Also in this model, the source elements are user inputs that enter the service layer through a controller component. Since complete flows rely entirely on partial ones, we must first specify the latter. And so, the model considers the following three paths of a DFG as the most important partial flows: A) From source elements to service exit points without any type of sanitation; B) From a action method to a service exit point without any type of sanitation; and C) From a action method to a sink element without any type of sanitation. Note that the sanitation aspect is present in all of the three flows. Type B flows represent the transport of unsanitized user input from one service to another. Also, we seek to connect the partial flows in the following order: (A →B→... →B→C). Suppose that Figure 3.9 represents an abstract complete flow. Partial Flow 1 Partial Flow 2 Partial Flow 3 Partial Flow 4 Figure 3.9: A complete flow composed of four partial flows. Since node is synonymous with vertex in graph theory, we will simply refer to the vertices of a DFG as nodes. 50 Let us assume that partial flow 1 is type A, partial flows 2 and 3 are type B and partial flow 4 is type C. Although type A and type B have well established distinct classifications, in the model, these flows are represented by the same type. Currently, nothing is used to differentiate them, so it is not really known which nodes are source elements. And therefore, a source element is always considered an action method. It would be useful to know whether the data entering a controller comes from a service or not, because then we would know which action methods connect the service layer to its exterior. Figure 3.10 shows the flow structure of type A and B. Let us suppose it represents the partial flow 2 of Figure 3.9. Route Template HTTP Verb (entry) Entry Data Type Entry Data ... Exit Data HTTP Verb (exit) URL Figure 3.10: The structure of partial flow 2. The first four nodes pertain to action methods, where: - Node “HTTP verb (entry)” is the type of verb associated with the action method, that is, the type of request that the method handles. - Node “entry data type” is the type of the method parameter; and - Node “entry data” is the code element representing the method parameter. The last three nodes pertain to HTTP requests, where: - Node “HTTP verb (exit)” represents the type of HTTP verb; - Node “exit data” represents the data being sent; and 51 - Node “URL” represents the URL to where the request is sent. It is important to note that these figures should only be considered as illustrations, since there could be several nodes to represent a route template, a URL, etc. Figure 3.11 depicts the flow structure of type C. Let us assume that it represents partial flow 4 of Figure 3.9. Route Template HTTP Verb (entry) Entry Data Type Entry Data ... Sink Element Figure 3.11: The structure of partial flow 4. In order to produce partial flows of type A, B or C, it was necessary to identify and produce subparts of them. Thus, from now on, we will use a different notation and identify four types of partial flows: type I, type II, type III, and type IV. Additionally, we identify type V as a type of complete flows. Flows of type I, II, and III are outputted by the new queries, and flows of type IV and V are outputted by the external tool. The relation between these five types can be summarized in the following grammar: - V := I+IV (type V is one or more type I followed by type IV). - IV := II III (type II followed by type III, in a particular way). We also have that: - Type I corresponds to a flow of type A or B; and - The sequence II III corresponds to a flow of type C. This grammar will serve as a reference point for the following five definitions. 52 Type I The Figure 3.12 illustrates the structure behind a type I flow. As the figure indicates, these flows are equivalent to the previously mentioned type A and type B flows. Route Template HTTP Verb (entry) Entry Data Type Entry Data ... Exit Data HTTP Verb (exit) URL Figure 3.12: A type I flow structure. Type IV Figure 3.13 illustrates the structure behind a type IV flow. As the figure indicates, these flows are equivalent to the previously mentioned type C flows, and are composed of type II and type III flows. Route Template HTTP Verb (entry) Entry Data Type Entry Data ... Sink Element Figure 3.13: A type IV flow structure. 53 Route Template HTTP Verb (entry 1) ... HTTP Verb (exit) URL Route Template GET Verb (entry 2) ... Return Stament Response Data ... Sink Element Figure 3.18: A complete flow structure composed of two additional type flows. Having presented all types of flows, we can now describe the queries developed in this dissertation. 3.4.4 Queries With the use of CxAudit, we developed a collection of queries specifically for microservices. These queries have strong resemblances on existing ones created by Checkmarx. There are five types of queries: type I, type II, type III, additional type A, and additional type B, producing flows of the corresponding type. There exists two type I queries in our solution, one for SQL injection, and another for insecure deserialization. These queries have the following structure: 1. Find service entry points; 2. Find service exit points; 3. Find places where the source code sanitizes input; 60 4. Define paths from service entry points to service exit points that do not pass through sanitation. Type I queries follow a structure very similar to the ones mentioned in Subsection 3.2.4. The difference between the SQL injection query and the insecure deserialization query is mainly the sanitizing building blocks used in the path construction. In our solution, there is only a type II query. Besides producing type II flows, it also is used to implement type I queries as a function. This is due to the evident similarities between both structures. Doing it this way avoids code repetition and also makes the query code more reader friendly. There exists two type III queries in our solution. They are simply the standard queries created by Checkmarx and existed long before the start of this work. They are regularly updated due to the evolving nature of application security, and we use these standard queries in order to provide continuous support to this work in a simple fashion. When given a microservice-based application, CxSAST and all these five queries are executed on it. For each flow produced we will have a scan file, which in turn is later exported to the external tool. One aspect to note is that we will have an excess of type II and type III scan files, because we will produce flows for each action method and for each vulnerability recognized in the source code. This can be corrected through the creation of a type IV query, thereby removing the production of type II and type III flows. Unfortunately, we did not have time to fully develop or test these ideas, since these changes are not, in principle, easy to implement. Also, we only developed queries for these two vulnerabilities mostly because of time constraints. It may take some time to create or find scenarios to test, and some vulnerabilities take into account far more details than these two. Also, we chose to implement SQL injection and insecure deserialization because they are usually mentioned as examples of OWASP Top 10 vulnerabilities found in microservices. The work done here takes into account the support of future vulnerabilities. We looked to ease the integration of additional vulnerabilities, especially those detected by queries that follow a similar structure to the ones mentioned Subsection 3.2.4. This was apparent at a time when we only detected SQL injection vulnerabilities. In other words, with the queries we had back then, we developed the support for insecure deserialization without much effort. The next subsection ends this section with the description of the external tool referred to as “Results Connector”. 61 3.4.5 Results Connector Tool This external tool is designed to connect partial flows and produce complete flows. The results connector tool was written in C# and has the following structure: 1. Receives scan files exported from CxSAST. 2. Categorize all results in relation to flow type and vulnerability type. For each vulnerability: 3. Connect type II and type III flows. 4. Construct a graph. 5. Define all simple paths between type I flows and type IV flows, in order to produce complete flows. 6. Produce complete flows with the additional type flows. 7. Output all the produced complete flows in the form of scan files. More specifically: - In the second point the tool knows, just by looking at the scan file, what is the flow type and the associated vulnerability, because this information is explicitly stated when creating these files. - In the fourth point, we define edges between two partial flows (Fa,Fb) if they satisfy three conditions. In these three tests, we have that Fais always of type I, and Fbis either of type I or of type IV. The three conditions are as follows: 1. Check whether the HTTP verb of the request (from Fa) matches the HTTP verb of the action method (from Fb). 2. Check whether the URL of the request (from Fa) matches the route template of the action method (from Fb). 3. Check some additional details, such as issues related to class instances. We will not explain them in detail, because they are too technical and specific. - In the fifth point, we produce paths using an adapted version of a depth-first search. 62 These three conditions are very important because without them we would have a large number of false positives. Suppose we have access to a partial flow that ends with a POST request, and then we connect it to another one that starts with action method that handles PUT requests. Note that in this case it would be absurd to link these two flows, and that our tool tries to prevent this connection with the enforcement of condition 1. Even so, meaningless connections like these may happen in our tool, due to the large number of scenarios that we have to support. In order to improve on this, we have made several tests on the application mentioned in Section 4.1, which we will discuss later. From now on and until the end of this chapter, we will focus on the algorithms that are closely related to the production of paths between flows, i.e., the algorithms from the fifth point of our presented structure. We will refer to this implementation as path production, and we will start our description with the disclosure of seven remarks. Remarks About Path Production First, it is important to note that in this dissertation we have not had access to any real-world microservice-based applications. All our tests were done on GitHub samples, or on samples developed or adapted exactly for this work. Although we have a general idea on how this type of applications should be developed, we do not have any concrete data on how real-world applications are written or developed. In other words, we do not have any confirmation on the type of graphs we expect to receive. The number of vertices depends on how vulnerable the application is, because the more vulnerabilities we detect, the more partial flows we obtain, and consequently the more vertices the graphs will have. Also, the number of edges depends on how often the services interact with each other. We assume that generally we are working with sparse graphs due to the nature of loosely coupled systems. This is the main reason why we use an adjacency-list representation. Secondly, we need to complete the definitions of entry vertex and target vertex. For the implementation, we define an entry vertex as a type I vertex with in-degree equal to zero and with out-degree different than zero. We also define a target vertex as a type IV vertex with in-degree different than zero and out-degree equal to zero. We added these restrictions because the previous definitions were not sufficient. For example, if we look for paths from a vertex with out-degree equal to zero, nothing is produced. 63 Thirdly, in our implementation, the vertices are numbered sequentially with integer values starting with 0. The first vertices of the sequence are the ones of type I, followed by the vertices of type IV. Fourthly, when we refer to a set or definition of vertices/paths that satisfy an abstract condition A, we are referring to all the vertices/paths that satisfy condition A from a given abstract directed graph. Fifthly, the pseudocode of our implementation is presented in algorithm 10. Auxiliary tasks are performed by algorithms 3-14. Additionally, in order to clarify certain aspects of our implementation, we will present four small graphs as examples of something we want to highlight. In an attempt to make their presentation easier, we have abstracted these graphs as much as possible, and we do not present most of the information, unlike the example presented in Subsection 3.4.3. And lastly, in the pseudocode that we will present later, we have the following details. Recalling that this external tool is written in C#, on what relates to data types: - A vertex is of type int, that is, an integer type between the range of -2147483648 to 2147483647. - A path is a list of int; - The adjacency-list representation is written as an array containing lists of int; - The visited and coverage attributes are written as arrays of boolean values. On what relates to supporting functions and procedures, we have that: - Function length calculates the length of a list or an array. In order to avoid ambiguity, it is important to note that in our implementation, the length of a path is not the number of edges in the path, but instead the number of vertices in the path. - Function Copy creates a copy of a specified list without it pointing to the original list. - Procedure Push adds a specific item to the end of a specified list. - Procedure Pop removes the last item from a specified list. - Procedure Remove removes a specified item from a specified list. - Procedure Extend adds to the end of the first specified list all the elements of the second specified list. Also, the notation Initialize means that we are adequately initializing data collections such as lists or arrays. 64 Having disclosed all remarks, we can now present our implementation. Adapted Depth-First Search The two most important algorithms in our implementation are algorithms 3 and 4, which produce all simple paths between two vertices. These algorithms are an adaptation of the basic depth-first search algorithm presented in Subsection 3.1.4. Paul E. Black [99] states that the solution to the problem of listing all simple paths between two vertices can be achieved using a modified depth-first search. He expands on this by saying that this modified algorithm can still mark the vertices as visited in procedure DFS-VISIT, but then has to remove the mark just before returning from the recursive call. The following pseudocode is the adapted version of algorithms 1 and 2. Algorithm 3 DFS AllSimplePaths 1: function DFS_AllSimplePaths(adjLists, source, target) 2: Initialize(path, paths) 3: for (i:= 0 to Length(adjLists)) do 4: visited[i] := FALSE 5: end for 6: DFS AllSimplePathsAux(adjLists, source, target, visited, path, paths) 7: return paths 8: end function Algorithm 4 DFS AllSimplePathsAux 1: procedure DFS_AllSimplePathsAux(adjLists, source, target, visited, path, paths) 2: visited[source] := TRUE 3: Push(path, source) 4: if (source = target) then 5: pathCopy := Copy(path) 6: Push(paths, pathCopy) 7: else 8: for (each vert ∈adjLists[source]) do 9: if (visited[v] = FALSE)then 10: DFS AllSimplePathsAux(adjLists, vert, target, visited, path, paths) 11: end if 12: end for 13: end if 14: Pop(path) 15: visited[source] := FALSE 16: end procedure 65 Algorithms 3 and 4 have additional characteristics. First of all, this depth-first search does not paint vertices, but instead simply marks each vertex as visited or not visited. Secondly, it does not construct a predecessor tree, and instead produces paths while it examines all the vertices reachable from the source vertex. The first small graph is depicted in Figure 3.19. It has seven type I vertices and one type IV vertex, this being vertex 7. Figure 3.19: The first small graph. Calling the DFS AllSimplePaths function in order to find out all the simple paths beginning in vertex 1 and ending in vertex 7, produces the following simple paths: -h1,3,0,2,6,7i; -h1,3,7i; -h1,4,5,0,2,6,7i; -h1,4,5,7i. From now on, in order to better present the rest of our implementation, we will divide it into three parts. The first part produces entry paths, the paths produced by calling the DFS AllSimplePaths function for every possible entry-target pair, the second one produces coverage paths, and the third describes the remaining, and supportive algorithms. 66 Entry Paths The first part produces a set of paths EP referred to as entry paths, which contains all simple paths from entry vertices to target vertices. Since all entry vertices have in-degree equal to zero, then EP does not contain subpaths. That is, ∀p1, p2∈EP . (p16=p2)⇒(p1is not a subpath of p2and p2is not a subpath of p1). The second small graph is depicted in Figure 3.20. It has six type I vertices and one type IV vertex, this being vertex 6. Figure 3.20: The second small graph. For this example, we have that the DFS AllSimplePaths algorithm only produces the entry path h0,1,4,6i, and that vertices 2, 3, and 5 are not present in this output. The reason why this happened was because the valid paths from 0 to 6 that pass through these vertices have loops, and thus are ignored by the DFS AllSimplePaths algorithm. Since important information has been left out, there is a need to cover missing vertices and produce more paths. From now on, we will refer to all vertices that are not contained in the entry paths as missing vertices. We also will refer to all paths starting from these vertices and ending in target vertices as missing paths. In order to determine which are the missing vertices, throughout the production of entry paths, we also update a vertex attribute called coverage. More specifically, for each path p∈EP, we see which vertices the path pcontains (with the exception of entry and target vertices), and mark them as covered (as seen in lines 5-10 of algorithm 6). Algorithms 5 and 6 produce the entry paths and update the vertex attribute coverage. 67 Algorithm 5 ComputeEntryPaths 1: function ComputeEntryPaths(adjLists, entryVerts, targetVert, coverage) 2: Initialize(entryPaths) 3: for each entryVert ∈entryVerts do 4: paths := DFS AllSimplePaths(adjLists, entryVert, targetVert) 5: Extend(entryPaths, paths) 6: UpdateCoverage(entryVert, paths, coverage) 7: end for 8: return entryPaths 9: end function Algorithm 6 UpdateCoverage 1: procedure UpdateCoverage(entryVert, paths, coverage) 2: if (Length(path) 6= 0) then 3: coverage[entryVert] := TRUE 4: end if 5: for each path ∈paths do 6: for (i:= 1 to Length(path) −1) do 7: vert := path[i] 8: coverage[vert] := TRUE 9: end for 10: end for 11: end procedure Coverage Paths The second part produces a set of paths CP referred to as coverage paths, which is a subset of all missing paths. In this second part, we iterate through all target vertices, and continuously add, and remove paths from sets Stand CPt. The first set Stis referred as the set of all skip vertices related to target vertex t, and the second set CPtis referred as the set of all coverage paths to target vertex t. For the next descriptions, let Tdenote the set of target vertices. There exists four important aspects to note for each abstract target vertex t∈T iteration: - We use the set Mof all missing vertices. - At the beginning of the iteration, sets St, and CPtare empty. - We add, and remove vertices from set Stalong the iteration. - We add, and remove paths from CPtalong the iteration. 68 The set CP is the union of all sets CPt,∀t∈T. Since missing vertices have in-degree different than zero, we have no guarantees that we will not output subpaths. In order to solve this problem, we have implemented an idea to reduce the number of subpaths produced. Unfortunately, we do not provide any mathematical proof that we completely avoid the production of subpaths. In order to understand this idea, let us focus on the end of an iteration i∈N0of the second part (lines 12-13 of algorithm 7). We assume that iteration iproduced some missing paths to a target vertex t, and are saved in set CPt(line 11 of algorithm 7). Let pmbe any path in CPt. We refer to all vertices that pmcontains, except for the first and the last, as skip vertices related to target vertex t, and save them in set St(line 13 of algorithm 7). In any iteration with target vertex t∈T, we refer to all vertices that are simultaneously a skip vertex and a missing vertex (vertices of set M∩St) as unwanted vertices, and we refer to all paths from unwanted vertices to target vertices as unwanted paths. In our idea, we assume that all unwanted paths should not be present in CPt. Since, we do not know which are the unwanted vertices from the start of any iteration i∈N0, we have to continuously update CPt(line 15 of algorithm 7), and to not produce paths from vertices of set M∩St(lines 7-9 in algorithm 7). Algorithms 7, 8 and 9 implement this idea. Algorithm 7 ComputeCoveragePaths 1: function ComputeCoveragePaths(adjLists, targetVert, coverage) 2: Initialize(coveragePaths, skipVertices) 3: for (vert := 0 to Length(coverage)) do 4: if (coverage[vert] = TRUE)then 5: continue; 6: end if 7: if (vert ∈skipVerts) then 8: Remove(skipVerts, vert) 9: continue 10: end if 11: paths := DFS AllSimplePaths(adjLists, vert, targetVert); 12: Extend(coveragePaths, paths) 13: UpdateSkipVerts(paths, skipVerts) 14: end for 15: coveragePaths := RemoveSubpaths(coveragePaths, skipVerts) 16: return coveragePaths 17: end function 69 the knowledge acquired until and after that initial phase. This application, written in C#, simply receives input from a user and performs two arithmetic operations. This application is composed by: - Two services; - A simple user interface that receives user input; and - A simple API Gateway that routes HTTP requests. Each service contains an SQL database, and an HTTP-based API with resource representation in JSON. These APIs satisfy some REST constraints, since some of them are too demanding for a simple application. Service One calculates the addition of numbers and Service Two calculates the division and addition of numbers. More specifically, when given an list Lof integers with length n≥2: - Service One calculates its sum, that is, sum = L[0] + L[1] + ... + L[n−1]. - Service Two produces list L ' by sorting Lby descending order, and calculates the following result: result = L ' [0] L ' [1] +L ' [1] L ' [2] + ... + L ' [n−2] L ' [n−1]. Both services can receive a list of numbers from the user interface, from the other service, or by using the contents of their respective databases. Both databases have a table composed of three columns, the first column stores numbers, the second stores strings, and the last one stores numbers. Figure 4.1 depicts a representation of the database of service Two. Figure 4.1: The database of service Two. These applications contain several controller files distributed over the two services, and there is a support for the three HTTP verbs mentioned (GET, POST, and PUT). These services interact in several ways in order to test our solution, i.e., they give an opportunity to exploit the various vulnerabilities inserted in this application. For example, the following interaction could lead to an exploit: 76 1. The user interface receives data from a user. 2. The user interface sends the data through an HTTP POST request to service One. 3. Service One handles the data. 4. Service One sends the data that it received to service Two through an HTTP PUT request. 5. Service Two handles the data and updates its database. Figure 4.2 illustrates the interaction between all of these components. User User Interface Service One Service Two Sends Data Makes POST Request Makes PUT Request Figure 4.2: An interaction between the several components present in the Arithmetic application. The example above can be used to test a particular exploit of SQL injection. Thinking of something similar to the example given in the Subsection 2.1.2, a malicious user can craft an SQL statement that negatively impacts the database of service Two. Figure 4.3 depicts this idea by illustrating the structure of a complete flow resulting from the exploit. In this figure, the shaded nodes are from service One, and the dashed nodes are from service Two. 77 URL 1 HTTP POST String User Input ... User Input HTTP PUT URL 2 URL 2 HTTP PUT String User Input ... Update Database Method Figure 4.3: A complete flow representing an SQL injection vulnerability. We implemented different versions of these interactions in order to test different scenarios, for instance when the application validates the user input. We used this application as a testing platform, having simulated insecure deserialization exploits such as the one presented in Subsection 2.1.2. This application was fundamental in the development of our solution, being the subject to several changes throughout the various phases of this dissertation. 4.2 Cloud Sec Application Another application that played an important role in the development of our solution was the Cloud Sec application, developed by a colleague from Checkmarx. This application serves as the Java counterpart of the Arithmetic application, although 78 assuming a much smaller role. We were not informed about the purpose of the application, and we only know that it simulates an e-commerce application for some microservice project not related to ours. Since Checkmarx requested the support of more than one programming language, we were given partial access to some source code of this application. With the help of another colleague, we modified and adapted the application to create test scenarios. In particular, we adapted a service that handles client data, and a service that handles orders from clients. The scenarios created here are similar to those of the Arithmetic application, although far less tests were performed. 4.3 Github Examples After reaching the last stages of development in this dissertation, we directed our attention to testing our work on samples available in the GitHub platform. We searched again for representations of microservice-based applications, but this time we were better prepared for what could possibly be presented. At this stage we had a general idea on how these applications were generally designed, written, and developed. In the end we chose several samples, such as the LAB Insurance Sales Portal project [100], the ECommerce project [101], the Manga project [102], and the spring cloud REST TCC project [103]. Due to the small amount of functionality that our solution supports, we cannot simply take a collection of GitHub samples and apply the solution to them. In order to get around this problem and test our work, we made several adaptations to the samples. The first thing we did was to find in the source code methods that create HTTP requests, and insert them into action methods. We tried to create flows that pass through controller files, while still trying to preserve some of the intended functionality of these applications. We made these changes because queries look for flows that start in action methods, and most of these applications rarely have their source code written this way. The second change we made was to ensure that the URLs values used in the HTTP requests were hard coded. This is because we want methods that use hard coded URL values, and the addresses usually are not directly embedded in the source code of these applications. The last change was actually done in our solution. We developed custom queries in order to support imaginary “vulnerabilities”. Since the applications are very simple 79 and have little functionality, we did not expect to find a reasonable number of vulnerabilities, and much less find SQL injection or insecure deserialization issues. We looked for names of methods that were influenced by other services, and we marked them as vulnerable in these new queries. We did this in order to simulate new sink elements. Naturally, we had to slightly adapt the external tool to recognize these query results. With all these changes, we found and connected partial flows. This case study has shown that our work is much open for improvement. The following section provides an example of a potential change in our solution. 4.4 Towards Real-World Applications This last section presents a microservice pattern that we consider important to support in future work. With the information provided here, we want to emphasize the importance of future work on top of this dissertation, and that our solution is not yet ready to be used in real-world applications. 4.4.1 Externalized Configuration Pattern In software development, the practice of hard coding is generally considered an antipattern, i.e., inefficient and potentially highly counterproductive. This is because most of the time hard coded data can only be modified by editing the source code and recompiling an executable, where it might be more convenient to obtain data from external sources or generate it at runtime. Data that is hard coded usually represents immutable pieces of information [104, 105]. In a microservice-based applications, services must run in multiple environments (development, testing, quality assurance, staging, production), preferably without the modification and/or recompilation of said services. Furthermore, configuration property values depend on the environment in which a service is running in. It is disadvantageous to hard code configuration property values into a deployable service, because that would require it to be rebuilt for each environment [79, 106]. In order to solve this and other problems, applications can be developed using the externalized configuration pattern. According to this pattern, all application configuration is externalized, and configuration property values are provided to service instances at runtime. In other words, services read configuration from external sources, i.e., OS environment variables, configuration files, and so forth [79, 106]. 80 Our solution will not produce complete flows when we receive an application that does not hard code URL values. That is, we will build graphs without edges, since we are missing the addresses of HTTP requests. In fact, partial flows instead of containing URL values, contain the names of URL variables. Before continuing with the topic at hand, it is necessary to introduce the Docker project, which offers developers a way to implement the externalized configuration pattern. Docker Docker is an open-source project that allows developers to automate the deployment of their applications as portable, and self-sufficient containers [60]. An important tool to highlight from this project is the Docker Compose. This tool runs multi-container Docker applications with the help of a Compose file, which defines how the containers that make up an application are configured and is written using the Compose file format [107]. eShopOnContainers Application We assume that a considerable part of the GitHub samples treat network locations (URL variables) as configuration properties, and apply the externalized configuration pattern. The eShopOnContainers application is an example of one of these samples, and is an open-source application developed by Microsoft. Figure 4.4 illustrates its various components. Figure 4.4: eShopOnContainers architecture overview [108]. 81 The application is mainly written in C# and consists of several subsystems. It includes a variety of user interfaces, API Gateways, and services. The client applications interact with the services through HTTP, and the services interact with each other through asynchronous communication [108]. We mention this application because it is a good reference point for microservice architectural patterns. More importantly, most of the application is designed using Docker containers, and the application is run using Docker Compose. Figure 4.5 shows part of the source code of a configuration file called “dockercompose.override.yml”. From what we have seen, at runtime, this application is provided with OS environment variable values. The “PurchaseUrl” variable highlighted in this figure is one of these variables and is configured with the help of Docker Compose. Although this variable is never used by a service, it is used by a Docker container, which gives us an idea of how this application applies the externalized configuration pattern. In particular, the value of the“PurchaseUrl”environment variable is part of an HTTP request address. Figure 4.5: A compose file from the eShopOnContainers application [109]. A More Complete Solution Since our solution only extracts information from the source code of services, there is a need to adapt our work for applications that use Docker Compose. This section has shown us that information present in configuration files, such as Compose files, is very important to produce complete flows, and is missing. Although these files are not taken into account, they are actually shipped with the application. The first idea we put forward in order to support these cases is to scan the source code of the configuration files with the use of an additional external tool. This tool may or may not be related to CxSAST. 82 The second idea is that the additional external tool can produce a list of key:value pairs. The keys are the variables names present in the services, and the values are the configuration property values present in Compose files. Since the variables of services are assigned with configuration values, we hope that something similar can be done by looking at the source code of configuration files. We believe that these files have both the values and the variable names, and with this we hope to produce a list of key:value pairs that gives us an idea on what the application is provided at runtime. With the list, we should be able to update the partial flows, replacing the variable names with their corresponding values, just before we create the graphs. Figure 4.6 provides an illustration of how these ideas could be integrated with our current solution, where the left side represents our current solution, and the right side is related to the externalized configuration pattern just mentioned. Figure 4.6: A draft of a more complete solution. It is important to note that the second idea is not simple to implement, because we may encounter several obstacles, such as having to check several files in order to produce a single key:value pair. It is also important to note that there are several ways to configure an application in addition to the one mentioned here. Since we have not had time to investigate or test any of these ideas, a solution for the externalized configuration pattern can be seen as important future work. 83 84 Chapter 5 Conclusion At the time of writing, microservices were becoming an important trend in software development. It is expected that more and more organizations will adhere to this architectural style, as companies like Amazon and Netflix have successfully done before. Time will tell if microservices are in fact the future direction for software architectures, but what we know now is that there exists an increasing demand for security solutions that identify vulnerabilities in microservice-based applications. This dissertation seeks to provide a basis for one of these solutions. A solution focused on service interactions, and intended for applications that make use of multiple HTTP-based RESTful APIs. The vulnerabilities that arise from these service interactions need to be detected because, like most vulnerabilities, they may cause serious problems. Our solution was achieved through the development of a new group of CxSAST queries and a new external tool, called Results Connector. Queries search for partial flows, and the external tool connects them together in order to produce complete flows. These two components of our solution do not support all types of services that communicate via the HTTP protocol, as this would be very difficult to do with the time we had available. Our solution is concerned with a particular type of services, such as those that make use of controller files as entry points. We also only support two programming languages (C# and Java) and two vulnerabilities (SQL injection and insecure deserialization). We designed three types of queries: type I, II, and III, in order to separate concerns. Each has a particular role, and helps us to achieve the two desired partial flows (type I and IV). There are also additional type A and B queries, but these are concerned with requests made with HTTP GET. The latter are very basic, have strong similarities with the other ones, and were developed primarily to help future 85 [65] Fowler, M. (2013). ContinuousDelivery. https://martinfowler.com/bliki/ ContinuousDelivery.html. Accessed 23 January 2021. [66] Fowler, M. (2020). DomainDrivenDesign. https://martinfowler.com/bliki/ DomainDrivenDesign.html. Accessed 23 January 2021. [67] What is Continuous Delivery. https://aws.amazon.com/devops/ continuous-delivery/?nc1=h_ls. Accessed 23 January 2021. [68] DevOps. (2021). https://en.wikipedia.org/wiki/DevOps. Accessed 23 January 2021. [69] CI/CD. (2021). https://en.wikipedia.org/wiki/CI/CD. Accessed 23 January 2021. [70] What is DevOps. https://azure.microsoft.com/en-us/overview/ what-is-devops. Accessed 23 January 2021. [71] Domain-Driven Design: What is it and how do you use it. (2017). https: //airbrake.io/blog/software-design/domain-driven-design. Accessed 23 January 2021. [72] Fowler, M. (2014). BoundedContext. https://martinfowler.com/bliki/ BoundedContext.html. Accessed 23 January 2021. [73] Domain-driven design. (2020). https://en.wikipedia.org/wiki/ Domain-driven_design. Accessed 23 January 2021. [74] Granularity. (2020). https://en.wikipedia.org/wiki/Granularity. Accessed 23 January 2021. [75] The API gateway pattern versus the Direct client-to-microservice communication. (2021). https://docs.microsoft.com/en-us/dotnet/architecture/ microservices/architect-microservice-container-applications/ direct-client-to-microservice-communication-versus-the-api-gateway-pattern. Accessed 23 January 2021. [76] Using API gateways in microservices. (2018). https://docs.microsoft. com/en-us/azure/architecture/microservices/design/gateway. Accessed 23 January 2021. 92 [77] Yarygina, T. and Bagge, A. H. (2018). Overcoming security challenges in microservice architectures. In: IEEE Symposium on Service-Oriented System Engineering, SOSE 2018, Bamberg, Germany, March 26-29, 2018 : 11-14. [78] Zimmermann, O. (2017). Microservices tenets. Comput. Sci. Res. Dev., 32(3-4): 301–304. [79] Richardson, C. (2018). Microservices Patterns: With examples in Java. Manning Publications. [80] Message Brokers. (2020). https://www.ibm.com/cloud/learn/ message-brokers. Accessed 23 January 2021.. [81] Yu, D., Jin, Y., Zhang, Y. and Zheng, X. (2019). A survey on security issues in services communication of microservices-enabled fog applications. Concurr. Comput. Pract. Exp., 31(22): 1-12. [82] Cormen, T. H., Leiserson, C. E., Rivest, R. L. and Stein, C. (2009). Introduction to Algorithms, 3rd Edition. MIT Press. [83] Kruse, R.L. and Ryba, A.L. (1998). Data structures and program design in C++. Prentice Hall. [84] Big O notation. (2021). https://en.wikipedia.org/wiki/Big_O_notation. Accessed 15 January 2021. [85] Amortized analysis. (2020). https://en.wikipedia.org/wiki/Amortized_ analysis. Accessed 23 January 2021. [86] Control-flow graph. (2020). https://en.wikipedia.org/wiki/ Control-flow_graph. Accessed 23 January 2021. [87] Allen, F. E. (1970). Control flow analysis. SIGPLAN Not., 5(7): 1–2. [88] Data-flow analysis. (2020). https://en.wikipedia.org/wiki/Data-flow_ analysis. Accessed 23 January 2021. [89] Dewhurst, R. Static Code Analysis. https://owasp.org/www-community/ controls/Static_Code_Analysis#Taint_Analysis. Accessed 23 January 2021. [90] Data-Flow Graphs. http://bears.ece.ucsb.edu/research-info/DP/dfg. html. Accessed 15 January 2021. 93 [91] CxAudit Overview. (2020). https://checkmarx.atlassian.net/wiki/ spaces/KC/pages/5406733. Accessed 23 January 2021. [92] Query Structure. (2020). https://checkmarx.atlassian.net/wiki/spaces/ KC/pages/5406747/Query+Structure. Accessed 23 January 2021. [93] Scan Results Example (v8.9.0 and up). (2020). https://checkmarx. atlassian.net/wiki/spaces/KC/pages/1170441768/Scan+Results+ Example+v8.9.0+and+up. Accessed 23 January 2021. [94] Dragoni, N., Giallorenzo, S., Lafuente, A.L., Mazzara, M., Montesi, F., Mustafin, R. and Safina, L. (2017). Microservices: Yesterday, Today, and Tomorrow. In: Mazzara, M. and Meyer, B. (eds) Present and Ulterior Software Engineering. Springer, Cham. https://doi.org/10.1007/978-3-319-67425-4_12. [95] Model–view–controller. (2021). https://en.wikipedia.org/wiki/ Model-view-controller. Accessed 23 January 2021. [96] Wasson, M. (2018). Routing in ASP.NET Web API. https: //docs.microsoft.com/en-us/aspnet/web-api/overview/ web-api-routing-and-actions/routing-in-aspnet-web-api. Accessed 23 January 2021. [97] HTTP routing. https://www.playframework.com/documentation/2.8.x/ JavaRouting. Accessed 23 January 2021. [98] Attack vector. (2012). https://searchsecurity.techtarget.com/ definition/attack-vector. Accessed 15 January 2021. [99] Black, E. P. (2008). All simple paths. https://xlinux.nist.gov/dads/ /HTML/allSimplePaths.html. Accessed 23 January 2021. [100] ASCLAB, LAB Insurance Sales Portal, (2020), GitHub repository, https:// github.com/asc-lab/dotnetcore-microservices-poc. Accessed 14 December 2020. [101] W lodek P., ECommerce Application, (2019), GitHub repository, https:// github.com/pwlodek/ECommerce.Microservices. Accessed 14 December 2020. [102] Paulovich I., Manga, (2020), GitHub repository, https://github.com/ ivanpaulovich/clean-architecture-manga. Accessed 14 December 2020. 94 [103] Chris, REST TCC, (2020), GitHub repository, https://github.com/ prontera/spring-cloud-rest-tcc. Accessed 14 December 2020. [104] Hard coding. (2020). https://en.wikipedia.org/wiki/Hard_coding. Accessed 03 January 2021. [105] Anti-pattern. (2021). https://en.wikipedia.org/wiki/Anti-pattern. Accessed 03 January 2021. [106] Pattern: Externalized configuration. https://microservices.io/patterns/ externalized-configuration.html. Accessed 03 January 2021. [107] Docker, compose, (2021), GitHub repository, https://github.com/docker/ compose. Accessed 03 January 2021. [108] Dotnet-architecture, eShopOnContainers, (2021), GitHub repository, https: //github.com/dotnet-architecture/eShopOnContainers. Accessed 03 January 2021. [109] Dotnet-architecture, eShopOnContainers, (2021), GitHub repository, https://github.com/dotnet-architecture/eShopOnContainers/blob/dev/ src/docker-compose.override.yml. Accessed 03 January 2021. 95