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

implement basic sqli service #1

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sqli-play
72 changes: 72 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
.PHONY: default
default: help

DOCKER_DBIMAGE=mysql:8.0.28
DOCKER_DBCNT=mysqlinjection
DOCKER_DBNAME=sqli
DOCKER_DBUSER=root
DOCKER_DBPASS=123456
DOCKER_DBHOST=0.0.0.0
DOCKER_DBPORT=3306
DOCKER_DBADDR=$(DOCKER_DBHOST):$(DOCKER_DBPORT)

ASROOT=sudo
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be very curious if we can't make this work with podman 100% sudoless, this sudo stuff/sudo group shit on docker always annoyed me x_x. But unrelated to the PR, just sharing etc.

ifeq (, $(shell which $(ASROOT) 2>/dev/null))
ASROOT=doas
endif


.PHONY: db
db: ## Start db
-$(ASROOT) docker rm -vf $(DOCKER_DBCNT)
$(ASROOT) docker run --rm --name $(DOCKER_DBCNT) \
--net host \
-e MYSQL_ROOT_PASSWORD=$(DOCKER_DBPASS) \
-e MYSQL_DATABASE=$(DOCKER_DBNAME) \
-d $(DOCKER_DBIMAGE)
# check if database is ready
@while !(make dbtest 2>/dev/null 1>/dev/null); do echo -n "."; sleep 1; done

$(ASROOT) docker run -i --net host --rm $(DOCKER_DBIMAGE) mysql \
-h $(DOCKER_DBHOST) -u$(DOCKER_DBUSER) -p$(DOCKER_DBPASS) \
-D $(DOCKER_DBNAME) < populate.sql


.PHONY:dbcli
dbcli: ## connects and retrieves a database a shell.
@docker run -it --net host --rm mysql mysql -h $(DOCKER_DBHOST) \
-u$(DOCKER_DBUSER) -p$(DOCKER_DBPASS) -D $(DOCKER_DBNAME)


.PHONY: dbtest
dbtest: ## test db connectivity
@docker run -it --net host --rm mysql mysql -h $(DOCKER_DBHOST) \
-u$(DOCKER_DBUSER) -p$(DOCKER_DBPASS) -D $(DOCKER_DBNAME) \
-e "show status;" >/dev/null


.PHONY: build
build: ## build sqli
go build -o ./sqli-play


.PHONY: up
up: db run ## build, setup db and start sqli service.


.PHONY: run
run: build ## run
@DBNAME=$(DOCKER_DBNAME) \
DBUSER=$(DOCKER_DBUSER) \
DBPASS=$(DOCKER_DBPASS) \
DBADDR=$(DOCKER_DBADDR) \
./sqli-play


.PHONY: help
help: ## Show this help message.
@echo "usage: make [target] ..."
@echo
@echo -e "targets:"
@egrep '.*?:.*?## [^$$]*?$$' ${MAKEFILE_LIST} | \
sed -r 's/(.*?):\ .*?\#\# (.+?)/\1:\t\2/g'
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,20 @@
# sqli-playground

A SQL Injection vulnerable service for teaching how to identify and explore the
issue.

# help

```
$ make help
usage: make [target] ...

targets:
db: Start db
dbcli: connects and retrieves a database a shell.
dbtest: test db connectivity
build: build sqli
up: build, setup db and start sqli service.
run: run
help: Show this help message.
```
Comment on lines +6 to +20
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# help
```
$ make help
usage: make [target] ...
targets:
db: Start db
dbcli: connects and retrieves a database a shell.
dbtest: test db connectivity
build: build sqli
up: build, setup db and start sqli service.
run: run
help: Show this help message.
```
# help
Just run `make help`.

Rationale: Copying the help here will only make the docs eventually outdated.

5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/madlambda/sqli-playground

go 1.17

require github.com/go-sql-driver/mysql v1.6.0
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
148 changes: 148 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package main

import (
"database/sql"
"fmt"
"log"
"net/http"
"os"

_ "github.com/go-sql-driver/mysql"
)

var (
dbuser, dbpass, dbaddr, dbname string
)

func main() {
fmt.Println("sqli example")

dbuser = getenv("DBUSER")
dbpass = getenv("DBPASS")
dbaddr = getenv("DBADDR")
dbname = getenv("DBNAME")

checkdb()

http.HandleFunc("/news", newsHandler)

err := http.ListenAndServe(":8080", nil)
abortif(err != nil, "failed to start http server: %v", err)
}

type newsDetail struct {
title string
body string
}

func newsHandler(w http.ResponseWriter, r *http.Request) {
db, err := sql.Open("mysql", dbconn())
if err != nil {
httpError(w, "failed to connect to database")
return
}

defer db.Close()

var news []newsDetail
var query = "SELECT title,body from news"

filters, ok := r.URL.Query()["filter"]
if ok && len(filters) > 0 && len(filters[0]) > 1 {
query += " WHERE title LIKE '%" + filters[0] + "%'"
}

rows, err := db.Query(query)
if err != nil {
httpError(w, "failed to execute query: %s (error: %v)", query, err)
return
}

for rows.Next() {
var entry newsDetail
err = rows.Scan(&entry.title, &entry.body)
if err == sql.ErrNoRows {
break
}

if err != nil {
// We return the error message in the HTTP response to easily
// exploit it. Later we can have an option to hide them, so we can
// also teach how to blindly recognize the errors.
httpError(w, "failed to scan resultset: %s (error: %v)", query, err)
return
}

news = append(news, entry)
}

renderNews(w, news)
}

func renderNews(w http.ResponseWriter, news []newsDetail) {
w.WriteHeader(http.StatusOK)
w.Header().Add("Content-Type", "text/plain; charset=utf-8")

writeBanner(w)
for _, entry := range news {
writeNews(w, entry.title, entry.body)
}
writeFooter(w)
}

func writeNews(w http.ResponseWriter, title, body string) {
fmt.Fprintf(w, "-> %s\n", title)
fmt.Fprintf(w, " %s\n\n", body)
}

func writeBanner(w http.ResponseWriter) {
fmt.Fprintf(w,
`+------------------------------------------------------------------------------+
| madlambda news network |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤣 🚀

+------------------------------------------------------------------------------+
`)
}

func writeFooter(w http.ResponseWriter) {
fmt.Fprintf(w,
`+-----------------------------------------------------------------------------+
| Copyright (c) madlambda |
+------------------------------------------------------------------------------+`)
}

func httpError(w http.ResponseWriter, format string, args ...interface{}) {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, format, args...)

log.Printf("error: "+format, args...)
}

func getenv(name string) string {
val := os.Getenv(name)
abortif(val == "", "env %s does not exists or is empty", name)
return val
}

func abortif(cond bool, format string, args ...interface{}) {
if cond {
abort(format, args...)
}
}

func abort(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
os.Exit(1)
}

func dbconn() string {
return sprintf("%s:%s@tcp(%s)/%s?charset=utf8", dbuser, dbpass, dbaddr, dbname)
}

func checkdb() {
db, err := sql.Open("mysql", dbconn())
abortif(err != nil, "failed to open db connection: %v", err)

db.Close()
}

var sprintf = fmt.Sprintf
26 changes: 26 additions & 0 deletions populate.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
CREATE TABLE IF NOT EXISTS users (
id INT(6) NOT NULL AUTO_INCREMENT PRIMARY KEY,
user VARCHAR(255) NOT NULL,
pass VARCHAR(255) NOT NULL
);

CREATE TABLE IF NOT EXISTS news (
id INT(6) NOT NULL AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
body VARCHAR(1024) NOT NULL
);

INSERT INTO users (user,pass) VALUES
("admin", "very-secret-pass"),
("i4k", "****************"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
("i4k", "****************"),
("i4k", "i love ken thompson"),

😆

("katz", "i love alan kay");

INSERT INTO news (title,body) VALUES
(
"BITCOIN FALLS BELOW $38,000 AS EVERGROW SET TO BREAK NEW CRYPTO RECORDS",
"Bitcoin price has fallen to below $38,000 for the second time in 2022. Cryptocurrency largest token has struggled since starting the year at $47,000 and despite a rally in early February Bitcoin price is back where it was a month ago. A combination of factors means that investors are increasingly avoiding risk, and in the current climate risk means Bitcoin."
),
(
"Russia retreats from crypto ban as it pushes rules for industry",
"Russias Ministry of Finance is planning to regulate cryptocurrencies in the country, despite earlier calls by the central bank for a ban on crypto."
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing a newline