Go渗透测试学习笔记(七)–滥用数据库和文件系统

0x00 前言

在本节,我们学习如何安装和配置各种SQLNOSQL数据库,并学习如何通过Go来进行交互。

0x01 与Mongo交互

1. 安装MongoDB数据库然后写入数据

MongoDB是NOSQL数据库,这意味着,他与其他传统数据库不同,他是无架构的。

使用docker开启了之后,使用store数据库

db.use strore

然后写入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
db.transactions.insert([
{
"ccnum": "4444333322221111",
"date": "2019-01-05",
"amount": 100.12,
"cvv": "1234",
"exp": "09/2020"
},
{
"ccnum": "4444123456789012",
"date": "2019-01-07",
"amount": 2400.18,
"cvv": "5544",
"exp": "02/2021"
},
{
"ccnum": "4465122334455667",
"date": "2019-01-29",
"amount": 1450.87,
"cvv": "9876",
"exp": "06/2020"
}
]);

2. 下载包并写入代码

需要先下载包,因为官方没有与NoSQL直接交互的包,

所以我们安装如下mongo的驱动:go get gopkg.in/mgo.v2

然后写入以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"fmt"
"log"

mgo "gopkg.in/mgo.v2"
)

type Transaction struct {
CCNum string `bson:"ccnum"`
Date string `bson:"date"`
Amount float32 `bson:"amount"`
Cvv string `bson:"cvv"`
Expiration string `bson:"exp"`
}

func main() {
session, err := mgo.Dial("192.168.68.137")
if err != nil {
log.Panicln(err)
}
defer session.Close()

results := make([]Transaction, 0)
if err := session.DB("store").C("transactions").Find(nil).All(&results); err != nil {
log.Panicln(err)
}
for _, txn := range results {
fmt.Println(txn.CCNum, txn.Date, txn.Amount, txn.Cvv, txn.Expiration)
}
}

0x02与mysq交互

1.创建mysql 数据库,并且写入数据

1
2
3
4
5
6
7
mysql> create database store;
mysql> create table transactions(
-> ccnum varchar(32),
-> date date,
-> amount decimal(7,2),
-> cvv char(4),
-> exp date);
1
2
3
insert into transactions(ccnum, date, amount, cvv, exp) values ('4444333322221111', '2019-01-05', 100.12, '1234', '2020-09-01');
insert into transactions(ccnum, date, amount, cvv, exp) values ('4444123456789012', '2019-01-07', 2400.18, '5544', '2021-02-01');
insert into transactions(ccnum, date, amount, cvv, exp) values ('4465122334455667', '2019-01-29', 1450.87, '9876', '2020-06-01');

2. 编写代码

Go包包含了一个database/sql的标准包

go get github.com/go-sql-driver/mysql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
"database/sql"
"fmt"
"log"

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

func main() {
db,err := sql.Open("mysql","root:root@tcp(127.0.0.1:3306)/store")
if err != nil {
log.Panicln(err)
}
defer db.Close()
var (
ccnum,date,cvv,exp string
amount float32
)
rows,err := db.Query("SELECT ccnum,date,amount,cvv,exp FROM transactions ")
if err != nil {
log.Panicln(err)
}
defer rows.Close()
for rows.Next() {
err := rows.Scan(&ccnum,&date,&amount,&cvv,&exp)
if err != nil {
log.Panicln(err)
}
fmt.Println(ccnum,date,cvv,exp)
}
if rows.Err() != nil {
log.Panicln(err)
}
}

这里需要引入匿名包,否则加载不了数据库的驱动

0x03 构建数据库矿工

在本节中,我们将创建一个工具来检查数据库模式(例如列明),以确定其中的数据是否值得窃取。

1. 首先实现一个接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import (
"fmt"
"regexp"
)

type DatabaseMiner interface {
GetSchema() (*Schema, error)
}

type Schema struct {
Databases []Database
}

type Database struct {
Name string
Tables []Table
}

type Table struct {
Name string
Columns []string
}

func Search(m DatabaseMiner) error {
s, err := m.GetSchema()
if err != nil {
return err
}

re := getRegex()
for _, database := range s.Databases {
for _, table := range database.Tables {
for _, field := range table.Columns {
for _, r := range re {
if r.MatchString(field) {
fmt.Println(database)
fmt.Printf("[+] HIT: %s\n", field)
}
}
}
}
}
return nil
}

func getRegex() []*regexp.Regexp {
return []*regexp.Regexp{
regexp.MustCompile(`(?i)social`),
regexp.MustCompile(`(?i)ssn`),
regexp.MustCompile(`(?i)pass(word)?`),
regexp.MustCompile(`(?i)hash`),
regexp.MustCompile(`(?i)ccnum`),
regexp.MustCompile(`(?i)card`),
regexp.MustCompile(`(?i)security`),
regexp.MustCompile(`(?i)key`),
}
}

func (s Schema) String() string {
var ret string
for _, database := range s.Databases {
ret += fmt.Sprint(database.String() + "\n")
}
return ret
}

func (d Database) String() string {
ret := fmt.Sprintf("[DB] = %+s\n", d.Name)
for _, table := range d.Tables {
ret += table.String()
}
return ret
}

func (t Table) String() string {
ret := fmt.Sprintf(" [TABLE] = %+s\n", t.Name)
for _, field := range t.Columns {
ret += fmt.Sprintf(" [COL] = %+s\n", field)
}
return ret
}

该代码首先定义了一个名为DatabaseMiner的接口,实现接口的任何数据类型都需要一个名为GetSchema的方法。每个后端数据库都可能有特定的逻辑来检索数据库模式。

定义一个Schema类型,该类型由也在此处定义的一些子类组成。我们使用Schema类型在逻辑上表示数据库架构,即数据库,表和列。接口中定义的函数GetSchema()希望返回一个 * Schema(Schema类型的指针)

现在定义一个名为Search()的函数,函数Search()希望在函数调用期间将DatabaseMiner实例传递给它。它将miner的值储存在一个名为m的变量中,然后使用m.GetSchema()来检索。然后循环遍历整个模式。再根据正则表达式(regex)值列表搜索匹配到的列明。如果能找到,则将数据库模式和匹配字段打印到屏幕上。

最后,定义一个名为getRegex的函数,此函数使用Goregex包预编译正则表达式,并返回这些值的一部分。

2. 开始实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package main

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

"github.com/blackhat-go/bhg/ch-7/db/dbminer"
_ "github.com/go-sql-driver/mysql"
)

type MySQLMiner struct {
Host string
Db sql.DB
}

func New(host string) (*MySQLMiner, error) {
m := MySQLMiner{Host: host}
err := m.connect()
if err != nil {
return nil, err
}
return &m, nil
}

func (m *MySQLMiner) connect() error {

db, err := sql.Open("mysql", fmt.Sprintf("root:root@tcp(%s:3306)/information_schema", m.Host))
if err != nil {
log.Panicln(err)
}
m.Db = *db
return nil
}

func (m *MySQLMiner) GetSchema() (*dbminer.Schema, error) {
var s = new(dbminer.Schema)

sql := `SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME FROM columns
WHERE TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
ORDER BY TABLE_SCHEMA, TABLE_NAME`
schemarows, err := m.Db.Query(sql)
if err != nil {
return nil, err
}
defer schemarows.Close()

var prevschema, prevtable string
var db dbminer.Database
var table dbminer.Table
for schemarows.Next() {
var currschema, currtable, currcol string
if err := schemarows.Scan(&currschema, &currtable, &currcol); err != nil {
return nil, err
}

if currschema != prevschema {
if prevschema != "" {
db.Tables = append(db.Tables, table)
s.Databases = append(s.Databases, db)
}
db = dbminer.Database{Name: currschema, Tables: []dbminer.Table{}}
prevschema = currschema
prevtable = ""
}

if currtable != prevtable {
if prevtable != "" {
db.Tables = append(db.Tables, table)
}
table = dbminer.Table{Name: currtable, Columns: []string{}}
prevtable = currtable
}
table.Columns = append(table.Columns, currcol)
}
db.Tables = append(db.Tables, table)
s.Databases = append(s.Databases, db)
if err := schemarows.Err(); err != nil {
return nil, err
}

return s, nil
}

func main() {
mm, err := New(os.Args[1])
if err != nil {
panic(err)
}
defer mm.Db.Close()

if err := dbminer.Search(mm); err != nil {
panic(err)
}
}

0x04 文件掠夺系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"fmt"
"log"
"os"
"path/filepath"
"regexp"
)

var regexes = []*regexp.Regexp{
regexp.MustCompile(`(?i)user`),
regexp.MustCompile(`(?i)password`),
regexp.MustCompile(`(?i)kdb`),
regexp.MustCompile(`(?i)login`),
}

func walkFn(path string, f os.FileInfo, err error) error {
for _, r := range regexes {
if r.MatchString(path) {
fmt.Printf("[+] HIT: %s\n", path)
}
}
return nil
}

func main() {
root := os.Args[1]
if err := filepath.Walk(root, walkFn); err != nil {
log.Panicln(err)
}
}