Pipy: Protecting Kubernetes Apps from SQL Injection & XSS Attacks
Tags: kubernetes, protection, pipy, sqlinjection
Published: 2022-11-03 13:12:06

Injection attacks sliding down to 3rd position in 2021 have been on the [OWASP Top 10](https://owasp.org/www-project-top-ten/) list for many years and SQL Injection (SQLi) is a common injection technique used for attacking websites and web applications. Applications that do not cleanly separate user input from database commands are at risk of malicious input being executed as SQL commands. Successful injection attacks can lead to unauthorized access to sensitive data such as passwords, credit card details, and personal user information. Many recent high-profile data breaches have resulted from SQL injection attacks, resulting in reputational damage and regulatory fines.

The common and normal workaround which you might have seen with other solutions is using a list of **regular expressions** to filter out traffic, which works for some cases but falls short with some complex inputs or escaped inputs. We are not trying to bash `Regular Expressions`, they are good and have their own use cases, but they aren't a good fit for such use cases.


This blog post will demonstrate how to use [Pipy](https://flomesh.io) an open-source programmable proxy to harden the security by adding an extra layer of shield to your applications which might otherwise be vulnerable to such attacks.

In a previous blog post announcing the latest release of [Pipy 0.70.0](https://blog.flomesh.io/pipy-0-70-0-is-released-fe417b579fd2), it was introduced that Pipy added the support of extensions called [Native Module Interface (NMI)](https://flomesh.io/docs/en/reference/pjs/3-nmi) and we will be using Pipy NMI to develop a module to integrate with mature and stable open-source library [libinject](https://github.com/libinjection/libinjection) to scan incoming traffic against SQLi and XSS attacks before it reaches the application.

For the demo application, we will be using an open-source classical LAMP stack [Vulnerable Mama Shop (VMS)](https://hub.docker.com/r/naqvis/mamashop) which is intentionally developed to have SQLi flaws. We will have fun together by first hacking the basic application to demonstrate the SQLi attacks, then we will harden the application security by adding Pipy as a sidecar to block certain SQLi attacks.

## Pre-requisites

This blog post assumes you have access to:

* Running Kubernetes cluster (dev box)
* [kubectl](https://kubernetes.io/docs/tasks/tools/)

> The demo source code is available on GitHub and can be downloaded from [pipy-sqli-demo](https://github.com/flomesh-io/pipy-demos/tree/main/pipy-sqli-demo) repository.

## Deploy Kubernetes cluster and Vulnerable Mama Shop App

To run the demo locally, we recommend [k3d](https://k3d.io/) a lightweight wrapper to run [k3s](https://github.com/rancher/k3s) (Rancher Lab’s minimal Kubernetes distribution) in docker.

```sh
$ k3d cluster create my-cluster -p 8080:30060@server:0
```

In the above command, we are creating a cluster with a single server and mapping port 30060 of the k3d container to the local 8080 port, usage of this port will become clear in the later steps of this tutorial.

## Deploy Vulnerable Mama Shop App

We are going to deploy a simple online store application that comes installed with

* Apache Webserver
* MariaDB
* PHP app that runs on Apache and connects to the MariaDB database

1. Use the text editor of your choice and create a YAML file called 1-app.yaml with the following contents:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vms
spec:
  selector:
    matchLabels:
      app: vms
  template:
    metadata:
      labels:
        app: vms
    spec:
      containers:
        - name: vms
          image: naqvis/mamashop
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: vms
spec:
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30060
  selector:
    app: vms
  type: NodePort
```

2. Deploy the app

```sh
$ kubectl apply -f 1-app.yaml
```

3. Confirm that pod is up and running, as indicated by the value Running in the STATUS column. It can take 30-40 seconds for them to fully deploy, so it’s useful to run the command again to confirm all pods are running before continuing to the next step.

```sh
$ kubectl get pods
NAME                   READY   STATUS    RESTARTS   AGE
vms-6658dd4478-hn246   1/1     Running   0          2m12s
```

4. Query the Servcice

```sh
$ kubectl get svc vms
NAME   TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
vms    NodePort   10.43.244.253   <none>        80:30060/TCP   5m43s
```

Still remember at the beginning we did port mapping for the K3d container: `8080=>30060`? Keen users may also find that we have created a NodePort service for our Vulnerable Mama Shop web application, and the node port is `30060`.

Open the app in your browser by visiting the URL **http://localhost:8080**

![Vulnerable Mama Shop Welcome Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d04tnbay6h9v6j2xjhc0.jpg)

## Hack the App

Mama Shop is rather very basic, it comes with only 3 pages. The main page (shown above) is where a user can query items for sale through a drop-down box, a customer login page, and an about page. Play around with these 3 pages to see what each does and how each page works normally.

Try submitting a category and see how items are listed. The following shows a listing of items from the Drinks category.


![Listing items in a Category](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o6kt3c7p6qpn2dwcn1hp.jpg)

As you may have noticed, selecting a category and hitting submit doesn't change the URL, so the application is doing a `POST` request and we will need some tool to intercept or view the traffic the web page is generating.

Normally you will be using a proxy tool like Blurp suite or OWASP ZAP to intercept and modify the request to the application. But to keep things simple, we will be using Firefox browser Developer Console to view and modify the requests.

Go back to your browser (Firefox in this demo) and turn on **Web Developer Tools** either via `Tools | Browser Tools | Web Developer Tools` menu option or via short-cut key `Option+Cmd+I` on Mac or similar on your OS. Move to the `Network` tab, and hit submit button on a web page to view the network traffic.

![Firefox Web Developer Tool](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bedwcyffbqgmdv6u1lka.jpg)

We can see all of the requests along with all details, a web page makes to the webserver to display the page. Hit `Resend` button at the top right of *Request details window* displayed at the bottom right.

![Web Developer Tools](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4d3dxv0ph9a56oajiaet.png)

Network action window shows that to browse items in a category, a URL encoded HTTP POST with a single parameter `catid` is sent to `welcome.php`. To test for SQL injection, it is common to modify user input and send a single quotation mark like **`'`**.

### Modify Form Parameter
We will find out if the input is properly escaped with a simple experiment: changing the `catid` to a SQL query string, to see if it will cause a syntax error which may be shown on the webpage.

```sql
catid=' or 1 = 1 ; --
```

Make above change and hit `send` button at the bottom right of network action window.

![Web Developer Tools](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jzhtw1k3ao5ptk5ewymj.png)

Whoa, we are upto something, `welcome.php` function for displaying category items have a SQL injection vulnerability. The error message also tells us the database is **MariaDB**. And error is telling us that there is a syntax issue near `or 1 = 1`. Using our imaginations, we can assume the database query is something like

```sql
SELECT item_name, item_category, item_description from items_table where item_category = ' or 1 = 1 ; -- ;
```

If we were to change the `catid` ending to `" or 1 = 1 -- ;`, that should fix the quoting issue. It should select all rows from the database, which is useful in a hack.

![Retrieving all rows](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nbbvqvtpb471jeg1h5r4.png)

The structure of the assumed SQL query is probably correct, although it is not necessarily the exact query that the developer has used. This is sufficient for devasting attacks such as dumping customer information.

### Extract User Data

The customer login page tells us that the application contains customer data and this is likely stored in some sort of customer or user table.

Based on our information gathered so far we can make use of the MariaDB INFORMATION_SCHEMA and the SQL Union operator to retrieve database and tables details.

Change `catid` parameter to below to retrieve database name, which will be further used to retrieve table details.

```sql
catid=1000 union select database(), "A" , "B" from dual
```

The following shows the response from Vulnerable Mama Shop containing the database name, `appdb`.

![Retrieving database name](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gffnpi0lntqebti4hchn.png)

Now we have database name, we can obtain the tables in the database

```sql
catid=1000 union select table_name,version, table_comment from information_schema.TABLES where table_schema = 'appdb'
```

So we get a list of all tables, We can safely assume `users` table in the database contains usernames and passwords.

![Retrieving list of tables](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/95hfqfbew08v0nax2ko9.png)

We need to retrieve list of columns `users` table have and we will need to ensure that we use same number of columns as queried from products table for `UNION` to work.

```sql
catid=1000 union select table_name, COLUMN_NAME, DATA_TYPE from information_schema.COLUMNS where table_name = 'users'
```

![Retrieving user table structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9j6q1wzlufvq79ctyfxl.png)

Now that we know there are a total of six columns in the users table to contains details like firstname, lastname, nric, password, email are available from users table.

We have gathered enough information to dump out a users listing. The following input can be used to dump out a listing of users with their firstname, password and email address.

```sql
catid=1000 union select firstname, nric, email from users LIMIT 7, 100
```

The values for the LIMIT and offset can be worked out by observing how many item entries there are in a normal request. The offset value should remove these items. The LIMIT can be set a sufficiently large value so that customer records can be dumped out.

![Dumping all users](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ka41xiznqjd68a4yt4p.png)

Play around and use these gathered information to see if you can login with these credentials.

## Using Pipy Sidecar to block certain SQLi attacks

SQL injection occurs when unvalidated user input is mixed with SQL instructions, so developers should pay more attention to escape user input (such as use of parameterized queries), but you  – the Kubernetes engineer  – can also help avoid SQL injection by preventing this attack from reaching the app. That way, even if the app is vulnerable, attacks can still be stopped.

There are multiple options for protecting your apps, but for this blog post we will focus on **injecting a sidecar container** to proxy all of the traffic and deny any request that is detected as SQLi attacks.

> For demonstration purposes, we are following the approach of manually injecting sidecar, but in reality, **manually deploying proxies as sidecars isn’t the best solution**. The complexity of your apps and architecture might require more fine–grain control. If your organization requires **Zero Trust** and has a need for **end–to–end encryption**, consider a service mesh like [osm-edge](https://flomesh.io) a light weight, ultra fast, low resources, highly extensible,  Service Mesh Interface (SMI) compatible, built for edge and cloud computing service mesh.

### Deploying Pipy Sidecar

1. Create a YAML file called 2-app-sidecar.yaml with the contents below, and check out these noteworthy components:

* A sidecar container running Pipy is started on port 8000.
* The Pipy process forwards all traffic to the app.
* Requests URI or `POST` body containing SQLi is declined
* The service for the app routes all traffic to the Pipy sidecar container first.

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vms
spec:
  selector:
    matchLabels:
      app: vms
  template:
    metadata:
      labels:
        app: vms
    spec:
      containers:
        - name: vms
          image: naqvis/mamashop
          ports:
            - containerPort: 80
        - name: pipy # <-- sidecar
          image: naqvis/pipy-nmi
          env:
            - name: PIPY_CONFIG_FILE
              value: /etc/pipy/nmi/nmi.js
          ports:
            - containerPort: 8000
          volumeMounts:
            - mountPath: /etc/pipy/nmi
              name: pipy-pjs
      volumes:
        - name: pipy-pjs
          configMap:
            name: sidecar
---
apiVersion: v1
kind: Service
metadata:
  name: vms
spec:
  ports:
    - port: 80
      targetPort: 8000 # <-- the traffic is routed to the proxy
      nodePort: 30060
  selector:
    app: vms
  type: NodePort
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: sidecar
data:
  nmi.js: |-
    pipy({
      _rejected: undefined,
    })
      .import({
        __is_sqli: 'lib-inject',
        __is_xss: 'lib-inject',
        __sqli_fingerprint: 'lib-inject',
      })
      .listen(8000)
      .demuxHTTP().to(
        $=>$
            .use('/etc/pipy/modules/inject-nmi.so')
            .handleMessage(() => _rejected = (__is_sqli || __is_xss))
            .branch(
                () => _rejected === true, (
                $=>$
                .handleMessageStart(_ =>
                  console.log(`SQL Injection found with Fingerprint: ${__sqli_fingerprint}`))
                .replaceMessage(new Message({ status: 403 }, 'Forbidden'))
                ),
                () => _rejected === false, (
                  $=>$.muxHTTP().to(
                      $=>$.connect('localhost:80')
                  )
                )
            )
      )
```

1. Deploy the side

```sh
$ kubectl apply -f 2-app-sidecar.yaml
```

Wait for pods to be up and ready

```sh
$ kubectl get pods
NAME                  READY   STATUS    RESTARTS   AGE
vms-945f6f85c-8v7sb   2/2     Running   0          8m48s
```

### Test the Sidecar

Test whether the sidecar is filtering traffic by returning to the app and trying the SQL injection again. Pipy sidecar blocks the request before it reaches the app!

![Sidecar blocking SQLi traffic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/umwww30b1jbj8mfhmql2.png)

Sidecar is blocking the traffic by returning **403 Forbidden**. Run few more requests with any of the previous SQLi attempts we made and we will be getting 403 back in response.
We can validate that by looking at the logs of Pipy side car.

![Sidecar logs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/48b2h76sp13xqur33npn.png)

## Conclusion

The purpose of learning about offensive techniques is to enable developers, defenders and security professionals to protect the confidentiality, the integrity and availability of critical information assets and services.

Kubernetes is not secure by default. This blog post made use of Vulnerable Mama Shop, a simple application based on LAMP stack to demonstrate SQL injection attacks and how one can use techniques like SideCar to protect against them.

But as the complexity of your apps and architecture grows, you might require more fine–grain control over your services. And if your organization requires Zero Trust and has a need for end–to–end encryption like mTLS, you should consider a service mesh like [osm-edge](https://flomesh.io) a light weight, ultra fast, low resources, highly extensible,  Service Mesh Interface (SMI) compatible, built for edge and cloud computing service mesh. When you have communication between services (east–west traffic), a service mesh allows you to control traffic at that level.