Skip to content

Commit 5d6b257

Browse files
LtmThinkLtmThink
authored andcommitted
first
0 parents  commit 5d6b257

15 files changed

+733
-0
lines changed

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# SQLRecorder
2+
3+
## 简介
4+
5+
SQLRecorder能够实时记录应用运行时产生的SQL查询以及语法错误,方便代码审计时对SQL注入的实时关注。
6+
7+
SQLRecorder以代理的形式运行,区别于MySQL日志记录查询,SQLRecorder能够更好的感知SQL查询及其查询结果,并能记录日志所不能记录的MySQL语法错误。
8+
9+
SQLRecorder目前支持MySQL数据库查询记录。
10+
11+
## 使用
12+
13+
### 1.命令行窗口监控
14+
15+
#### 运行指令
16+
17+
命令:
18+
19+
```
20+
sqlrecorder command -s 127.0.0.1:3306 -p 127.0.0.1:43306
21+
```
22+
23+
该命令将使SQLRecorder作为代理端监听127.0.0.1:43306,并指定后续连接MySQL服务端地址为127.0.0.1:3306
24+
25+
![image-20250213203303123](./images/image-20250213203303123.png)
26+
27+
#### 使用场景一
28+
29+
Web应用程序作为客户端连接SQLRecorder,SQLRecorder将实时记录产生的SQL查询以及语法错误
30+
31+
![image-20250213203351348](./images/image-20250213203351348.png)
32+
33+
![image-20250213203402153](./images/image-20250213203402153.png)
34+
35+
#### 使用场景二
36+
37+
使用mysql命令行工具(或其他连接工具)作为客户端连接SQLRecorder,SQLRecorder将实时记录产生的SQL查询以及语法错误
38+
39+
![image-20250213203504045](./images/image-20250213203504045.png)
40+
41+
![image-20250213203512721](./images/image-20250213203512721.png)

go.mod

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module SQLRecorder
2+
3+
go 1.23
4+
5+
require (
6+
github.com/gookit/color v1.5.4
7+
github.com/urfave/cli/v2 v2.27.5
8+
)
9+
10+
require (
11+
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
12+
github.com/russross/blackfriday/v2 v2.1.0 // indirect
13+
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
14+
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
15+
golang.org/x/sys v0.10.0 // indirect
16+
)

go.sum

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
2+
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
3+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
4+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
6+
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
7+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
8+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
9+
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
10+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
11+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
12+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
13+
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
14+
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
15+
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
16+
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
17+
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
18+
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
19+
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
20+
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
21+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
22+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

images/image-20250213203303123.png

1010 KB
Loading

images/image-20250213203351348.png

199 KB
Loading

images/image-20250213203402153.png

1 MB
Loading

images/image-20250213203504045.png

1.3 MB
Loading

images/image-20250213203512721.png

1.04 MB
Loading

main.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package main
2+
3+
import (
4+
"SQLRecorder/mysql"
5+
"errors"
6+
"fmt"
7+
"github.com/urfave/cli/v2"
8+
"os"
9+
"os/signal"
10+
"syscall"
11+
)
12+
13+
func display_banner() {
14+
fmt.Println(" _____ ____ _ _____ _ ")
15+
fmt.Println(" / ____|/ __ \\| | | __ \\ 𝚋𝚢:𝙻𝚝𝚖𝚃𝚑𝚒𝚗𝚔 | | ")
16+
fmt.Println(" | (___ | | | | | | |__) |___ ___ ___ _ __ __| | ___ _ __ ")
17+
fmt.Println(" \\___ \\| | | | | | _ // _ \\/ __/ _ \\| '__/ _` |/ _ \\ '__|")
18+
fmt.Println(" ____) | |__| | |____| | \\ \\ __/ (_| (_) | | | (_| | __/ | ")
19+
fmt.Println(" |_____/ \\___\\_\\______|_| \\_\\___|\\___\\___/|_| \\__,_|\\___|_| ")
20+
fmt.Println(" ")
21+
fmt.Println(" ")
22+
}
23+
func main() {
24+
display_banner()
25+
// 创建通道监听系统信号
26+
sigs := make(chan os.Signal, 1)
27+
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
28+
var app = cli.App{
29+
Name: "SQLRecorder",
30+
Usage: "Create a proxy to record all passing SQL statements.",
31+
UsageText: "sqlrecorder command -s 127.0.0.1:3306 -p 127.0.0.1:43306",
32+
Commands: []*cli.Command{
33+
{
34+
Name: "command",
35+
Aliases: []string{"c"},
36+
Usage: "Create a proxy and all SQL will be displayed in the command line window",
37+
UsageText: "sqlrecorder command -s 127.0.0.1:3306 -p 127.0.0.1:43306",
38+
Flags: []cli.Flag{
39+
&cli.StringFlag{
40+
Name: "server",
41+
Aliases: []string{"s"},
42+
Usage: "The address of the SQL server.",
43+
Required: false,
44+
Value: "127.0.0.1:3306",
45+
},
46+
&cli.StringFlag{
47+
Name: "proxy",
48+
Aliases: []string{"p"},
49+
Usage: "The address where the SQLRecorder agent wants to run the listening.",
50+
Required: false,
51+
Value: "127.0.0.1:43306",
52+
},
53+
},
54+
Action: func(context *cli.Context) error {
55+
var server = context.String("server")
56+
var proxy = context.String("proxy")
57+
if server == "" || proxy == "" {
58+
return errors.New("Please enter the correct parameters")
59+
}
60+
sqlName := "mysql"
61+
switch sqlName {
62+
case "mysql":
63+
err := mysql.Recorder(server, proxy)
64+
return err
65+
default:
66+
return errors.New("Please enter the correct parameters")
67+
}
68+
},
69+
},
70+
},
71+
}
72+
// 隐藏光标
73+
fmt.Print("\033[?25l")
74+
go func() {
75+
<-sigs
76+
fmt.Println("\033[32m\nbye👋\033[0m")
77+
// 显示光标
78+
fmt.Print("\033[?25h")
79+
os.Exit(0)
80+
}()
81+
err := app.Run(os.Args)
82+
if err != nil {
83+
fmt.Fprintf(os.Stderr, "\033[31m[Error] %v\n\033[0m", err.Error())
84+
fmt.Print("\033[?25h")
85+
}
86+
}

mysql/buffer.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package mysql
2+
3+
import (
4+
"io"
5+
"net"
6+
"time"
7+
)
8+
9+
const defaultBufSize = 4096
10+
const maxCachedBufSize = 256 * 1024
11+
12+
// A buffer which is used for both reading and writing.
13+
// This is possible since communication on each connection is synchronous.
14+
// In other words, we can't write and read simultaneously on the same connection.
15+
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
16+
// Also highly optimized for this particular use case.
17+
// This buffer is backed by two byte slices in a double-buffering scheme
18+
type buffer struct {
19+
buf []byte // buf is a byte buffer who's length and capacity are equal.
20+
nc net.Conn
21+
idx int
22+
length int
23+
timeout time.Duration
24+
dbuf [2][]byte // dbuf is an array with the two byte slices that back this buffer
25+
flipcnt uint // flipccnt is the current buffer counter for double-buffering
26+
}
27+
28+
// newBuffer allocates and returns a new buffer.
29+
func newBuffer(nc net.Conn) buffer {
30+
fg := make([]byte, defaultBufSize)
31+
return buffer{
32+
buf: fg,
33+
nc: nc,
34+
dbuf: [2][]byte{fg, nil},
35+
}
36+
}
37+
38+
// flip replaces the active buffer with the background buffer
39+
// this is a delayed flip that simply increases the buffer counter;
40+
// the actual flip will be performed the next time we call `buffer.fill`
41+
func (b *buffer) flip() {
42+
b.flipcnt += 1
43+
}
44+
45+
// fill reads into the buffer until at least _need_ bytes are in it
46+
func (b *buffer) fill(need int) error {
47+
n := b.length
48+
// fill data into its double-buffering target: if we've called
49+
// flip on this buffer, we'll be copying to the background buffer,
50+
// and then filling it with network data; otherwise we'll just move
51+
// the contents of the current buffer to the front before filling it
52+
dest := b.dbuf[b.flipcnt&1]
53+
54+
// grow buffer if necessary to fit the whole packet.
55+
if need > len(dest) {
56+
// Round up to the next multiple of the default size
57+
dest = make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
58+
59+
// if the allocated buffer is not too large, move it to backing storage
60+
// to prevent extra allocations on applications that perform large reads
61+
if len(dest) <= maxCachedBufSize {
62+
b.dbuf[b.flipcnt&1] = dest
63+
}
64+
}
65+
66+
// if we're filling the fg buffer, move the existing data to the start of it.
67+
// if we're filling the bg buffer, copy over the data
68+
if n > 0 {
69+
copy(dest[:n], b.buf[b.idx:])
70+
}
71+
72+
b.buf = dest
73+
b.idx = 0
74+
75+
for {
76+
if b.timeout > 0 {
77+
if err := b.nc.SetReadDeadline(time.Now().Add(b.timeout)); err != nil {
78+
return err
79+
}
80+
}
81+
82+
nn, err := b.nc.Read(b.buf[n:])
83+
n += nn
84+
85+
switch err {
86+
case nil:
87+
if n < need {
88+
continue
89+
}
90+
b.length = n
91+
return nil
92+
93+
case io.EOF:
94+
if n >= need {
95+
b.length = n
96+
return nil
97+
}
98+
return io.ErrUnexpectedEOF
99+
100+
default:
101+
return err
102+
}
103+
}
104+
}
105+
106+
// returns next N bytes from buffer.
107+
// The returned slice is only guaranteed to be valid until the next read
108+
func (b *buffer) readNext(need int) ([]byte, error) {
109+
if b.length < need {
110+
// refill
111+
if err := b.fill(need); err != nil {
112+
return nil, err
113+
}
114+
}
115+
116+
offset := b.idx
117+
b.idx += need
118+
b.length -= need
119+
return b.buf[offset:b.idx], nil
120+
}
121+
122+
// takeBuffer returns a buffer with the requested size.
123+
// If possible, a slice from the existing buffer is returned.
124+
// Otherwise a bigger buffer is made.
125+
// Only one buffer (total) can be used at a time.
126+
func (b *buffer) takeBuffer(length int) ([]byte, error) {
127+
if b.length > 0 {
128+
return nil, ErrBusyBuffer
129+
}
130+
131+
// test (cheap) general case first
132+
if length <= cap(b.buf) {
133+
return b.buf[:length], nil
134+
}
135+
136+
if length < maxPacketSize {
137+
b.buf = make([]byte, length)
138+
return b.buf, nil
139+
}
140+
141+
// buffer is larger than we want to store.
142+
return make([]byte, length), nil
143+
}
144+
145+
// takeSmallBuffer is shortcut which can be used if length is
146+
// known to be smaller than defaultBufSize.
147+
// Only one buffer (total) can be used at a time.
148+
func (b *buffer) takeSmallBuffer(length int) ([]byte, error) {
149+
if b.length > 0 {
150+
return nil, ErrBusyBuffer
151+
}
152+
return b.buf[:length], nil
153+
}
154+
155+
// takeCompleteBuffer returns the complete existing buffer.
156+
// This can be used if the necessary buffer size is unknown.
157+
// cap and len of the returned buffer will be equal.
158+
// Only one buffer (total) can be used at a time.
159+
func (b *buffer) takeCompleteBuffer() ([]byte, error) {
160+
if b.length > 0 {
161+
return nil, ErrBusyBuffer
162+
}
163+
return b.buf, nil
164+
}
165+
166+
// store stores buf, an updated buffer, if its suitable to do so.
167+
func (b *buffer) store(buf []byte) error {
168+
if b.length > 0 {
169+
return ErrBusyBuffer
170+
} else if cap(buf) <= maxPacketSize && cap(buf) > cap(b.buf) {
171+
b.buf = buf[:cap(buf)]
172+
}
173+
return nil
174+
}

0 commit comments

Comments
 (0)