Aggregator
Mybatis 的 foreach 批量模糊 like 查询及批量插入
全补丁域控30秒沦陷?加密降级
TCTF 2021 Secure Storage
Last weekend I played TCTF Qualifier online and spent all of my time on this challenge, but still failed to solve it in time. After the contest, I finally solved this challenge. This is a crazy nested challenge: we firstly need to use side channel attack to leak admin_key.txt; then we need to exploit ss_agent to get the ability to open and operate on /dev/ss; then we need to exploit ss.ko to get the root shell; finally we need to exploit qemu to get the flag outside. Since the qemu part has no relation to other parts of the challenge, and it’s my teammate rather than me who solved this part, I will not cover it in this writeup.
0x01 Reverse Engineering ss_agentThis is a menu Pwn challenge. In initialization, it firstly adds a flock to itself to prevent multiple instances being run, and reads admin_key.txt to a global buffer. It has 4 options:
- register: read a length and a name from stdin, and store them with admin_key to kernel storage slot 0; note that the length passed to kernel storage is buffer length instead of actual length of the name, so it is possible to leak uninitialized heap buffer data here.
- store: read slot, data and key from stdin, and store them to kernel storage with given slot. The slot here cannot be 0.
- retrieve: read slot and key from stdin, and compare the given key with key stored in kernel storage with given slot; if they are same, output the data stored in kernel storage.
- kick: read admin key from stdin, and compare the given key with key stored in kernel storage slot 0; if they are same, output the data in storage 0 (which is the name provided in register) and free the name pointer without setting it to NULL, so here is a double free.
The kernel storage access is implemented by ioctl and mmap, the user-space code is shown below:
char *__fastcall get_ss_mmap_page(unsigned int slot) { char *result; // rax signed int fd; // [rsp+10h] [rbp-10h] int fd_4; // [rsp+14h] [rbp-Ch] char *v4; // [rsp+18h] [rbp-8h] fd = open("/dev/ss", 2); // open /dev/ss if ( fd < 0 ) return 0LL; if ( (ioctl(fd, 0, slot) & 0x80000000) == 0LL ) // use ioctl to set the slot { v4 = (char *)mmap(0LL, 0x10000uLL, 3uLL, 1uLL, (unsigned int)fd, 0LL); // use mmap to map the kernel storage into user space fd_4 = close(fd); if ( v4 && fd_4 >= 0 ) result = v4; // return the kernel page if there is no problem else result = 0LL; } else ... return result; }The format of kernel storage is 8-byte data length + data + 32-byte key, as we can see when ss_agent writes the storage.
__int64 __fastcall write_to_storage(unsigned int slot, char *a2, unsigned __int64 len, char *a4) { __int64 result; // rax char *v7; // [rsp+28h] [rbp-8h] if ( len > 0xFFD7 ) return 0xFFFFFFFFLL; v7 = get_ss_mmap_page(slot); if ( !v7 ) return 0xFFFFFFFFLL; *(_QWORD *)v7 = len; _memcpy((v7 + 8), a2, len); _memcpy(&v7[len + 8], a4, 32LL);// layout: length + data + key if ( (int)munmap((__int64)v7) >= 0 ) result = 0LL; else result = 0xFFFFFFFFLL; return result; }However, to exploit this ss_agent, we have to leak admin_key.txt first, otherwise we cannot trigger the double free bug.
ss.koThis is the kernel module that implements /dev/ss device. The functionality can be briefly described as follows:
- In handler of open at 0x390, private_data field (+0xc8) of struct file* is initialized to a structure used to store slot, which is initialized to value -1.
- In handler of ioctl at 0x710, slot is stored into structure pointed by private_data; note that we can only call ioctl once for each fd.
- In handler of mmap at 0x7e0, page fault handler 0x3e0 is registered.
- The handler of page fault at 0x3e0 is an important function, so its code is shown below:
When we use this kernel module, following occurs step by step:
- We call open to open /dev/ss, ioctl to set slot, and mmap to register the fault handler and return a piece of virtual memory corresponding to the handler, without actually allocating physical memory for the virtual memory.
- When we first time use the virtual memory returned from mmap, the page fault handler at 0x3e0 is called.
- The handler firstly calculate the offset of accessed page to virtual address returned from mmap, the value is stored in v2; and then it checks value of v2, the process continues only if v2 <= 0xffff.
- The handler obtains value of slot from struct file*, it then calculates the page to be returned using &mmap_buffer[0x10000 * slot + v2]; in other word, this kernel page is going to be mapped into user space; mmap_buffer is a global buffer in ss.ko with size 0x100000.
- Then vmalloc_to_page is called to convert the kernel virtual address into physical address, and its return value is set to page field of struct vm_fault* as the result of this fault handler.
- Then the offset stored in v2 is shifted and used as index to a bitmap; if the returned bit is zero, sub_90 is called on the returned page, in which many operations are done; then that bit is set to one.
- After fault handler is returned, the corresponding virtual memory in user space now has physical memory mapping, which maps to corresponding page in mmap_buffer; so future access to this virtual page will not cause fault anymore.
To inspect memory of ss.ko and set breakpoint to ss.ko, we need to know its address in memory. My approach is shown as following:
- Use cat /proc/kallsyms | grep cleanup_module to get address of function cleanup_module
- In gdb, type x/2i on address from step 1, then we can get address of unk_1300
- In gdb, type x/10gx on address from step 2, then we can get address of sub_0, which is base address of code segment
- In gdb, type x/i on address from step 3 +0x669, we can get address of mmap_buffer
Now we are going to leak admin_key.txt using time-based side channel attack. We observed that: 1. memcmp function is implemented byte-by-byte; 2. page fault that calls sub_90 takes quite long time. Therefore, we can manipulate the layout of slot 0 so that the first byte of admin_key is in the first page and remaining parts are in the second page. Therefore, when we call kick, if first byte of our input does not equal to first byte of admin_key, the second page will not be accessed so comparison should be fast; otherwise the second byte will be accessed, causing page fault and sub_90 to be called, which makes comparison slow. We can use this approach to brute-force first byte of admin_key. Then we can shift the admin_key to left (e.i. reduce length of name) by 1 byte and use the same approach to get the following bytes.
Note that we cannot do this in Python pwntools, because network latency fluctuation is much more than the difference mentioned above. Instead, we have used C to implement such attack. We upload the program to remote and run it directly, so there will be no network latency. Anonymous pipe is used for IO interaction, and __rdtsc is used for time difference calculation, you can read exploit code for more details.
Another thing to note is we need to ensure sub_90 to be called when handling page fault, otherwise the latency might be insignificant. This is the case if we separate name registration and kick comparison into different process instances.
The full exploit is here.
0x03 Exploit ss_agentAs I briefly mentioned above, there is a double free bug in ss_agent. Trying the double free for small-size chunk, we found that there is no crash, and there is tcache string in the binary, so I would say the static binary is generated using possibly libc-2.27. Knowing this, we can write exploit like a normal menu challenge:
- Trigger double free to poison tcache, so we can leak heap address.
- By debugging, we found that some program data addresses are stored in heap section; although I am not sure why, program address can be leaked by allocating chunk at that region.
- There is also a stack address stored in heap section, so we can leak it in the same way as step 2.
- Allocate a chunk at stack, so we can write ROP, which means ss_agent has already been compromised.
Instead of using pwntools, I used C for this part again, because I found that sending binary data via qemu interface causes problem.
To debug this binary, I patched it so that only heap operations remain. Thus we can run it without /dev/ss. This makes debugging much more convenient since we don’t need to deal with kernel stuff anymore.
The full exploit is here.
Initially I thought I could have root once ss_agent is compromised, because it has a setgid being set. However, this is wrong. The access permission of /challenge/ss_agent is -rwxr-sr-x and access permission of /dev/ss is crw-rw----. Therefore, although ss_agent can access /dev/ss, it does not run in root; instead, it runs in the group of root; and such group privilege even does not persist after an execve system call.
However, we need to run arbitrary code that can operate on /dev/ss in order to exploit ss.ko. I came up with 2 approaches:
- Compile the exploit into shellcode, and run the shellcode in /challenge/ss_agent process to get the root shell; however, this is quite complicated to do.
- Open /dev/ss in ROP chain, and execve to our exploit; these opened file descriptors will remain valid after execve; thus we can operate on /dev/ss even if we don’t have permission to open it.
Obviously, the second one is more convenient for us.
0x04 Exploit ss.koNow we come back to ss.ko in order to get root shell. The bug is in page fault handler: v2 <= 0xFFFF is a signed comparison; if v2 is negative, we can pass the check and map unintended page into user space. Since v2 is a 32-bit signed integer, we can call mmap with size 0x100000000, and access the last few pages to make v2 = -0x1000 * n. It turns out returned_vpage will be page before mmap_buffer.
In addition, to prevent sub_90 from being called, we need to ensure _bittest to return 1. Fortunately, the bitmap is behind mmap_buffer exactly, so if we set the last page of mmap_buffer to 0xff, _bittest can always return 1 for small negative index.
By debugging, we found there are many useful leaks in the pages before mmap_buffer: we can leak the Linux kernel address and ss.ko address easily.
I have come up with 4 approaches for exploitation, but finally only the last one works:
- Map kernel heap into user space; however, heap is too far from ss.ko: heap address is usually 0xffffxxxxxxxxxxxx but ss.ko address is 0xffffffffxxxxxxxx, so we cannot reach heap in 32 bits.
- Map page that stores modprobe path into user space; however, when calling vmalloc_to_page on that page, the function returns NULL. I think the reason is probably that this function can convert virtual address to physical address only if this virtual address is allocated via vmalloc, and that page does not satisfy this condition.
- Change the function pointer at 0x1600 to hijack kernel rip. We can do this because when we call mmap, the function here will be registered as page fault handler. However, we can do nothing after controlling rip, since smep is enabled.
- Map code page of ss.ko into user space and write shellcode into kernel directly. Yes! We can do this! Although code page in kernel is not writable, this is not the case after we map it into user space. Therefore, what I did is rewriting code of mmap handler in kernel into commit_creds(prepare_kernel_cred(0)), so that we can get root privilege after mmap is called.
The full exploit is here.
0x05 ConclusionThis nested challenge is really complicated, but I have learned a lot from it. In addition, I think it is better to put one flag at each stage, instead of one flag for the whole exploit chain.
压缩包炸弹
SDL最佳实践原则
银针安全沙龙上海站议程新鲜出炉
应急响应之日志分析
从“滴滴出行”被审核说起
应急响应之工具使用
优化部署go的docker镜像大小
因为属实不想看论文不想做实验了(似乎我并没有认真搞多久呐)
决定摸鱼玩一玩 :)
方糖通知近期经常丢消息,延迟到达,他已经不是一个可靠的通知平台了,并且也因为腾讯限制了模板消息导致开发者准备下线业务。
除此之外,我还用了邮件通知作为辅助,不是很优雅,每次写代码的时候需要把一个写好的硬编码了 smtp 登录凭据的 python 脚本拿着到处跑,并且腾讯强制一个月修改一次密码,每次修改麻烦。此外从发送了通知到手机QQ 邮箱弹出消息可能延迟半分钟,保证尽量小的时间差这点,QQ 邮箱做得不够好。仔细想想似乎也不能把锅都给QQ,如果轮询时间间隔太小或者长连接可能消耗手机电量比较严重,半分钟已经是及时性和耗电二者的妥协了吧 :)
于是搞一个通知的小工具提上日程 :)
提出需求希望可以实现多种方式推送的平台,类似于 server 酱,发一个 http 请求,手机收到通知。
通知的平台希望支持
- 微信
- 邮箱
- 短信
那么这个通知平台需要实现一个 api,根据请求里面的 token判定用户,给用户发送通知消息。html页面可选。
寻找解决方案首先,因为网络限制,FCM 推送是不可能的,ifttt 的 webhook,pushbullet, pushover 这些就暂不考虑了。
兜兜转转找了一圈,发现点过 star 的项目两个
-
https://github.com/gotify/server
-
https://github.com/nikoksr/notify
gotify 支持安卓、电脑的浏览器等终端,但是他需要一个常驻后台的安卓程序,而我的 ColorOS7 杀后台比较激进,除了微信有免死金牌,其他的基本都活不下来。
notify 支持 mail,一定程度上符合我的需求,但是时效性有不少问题。
手机短信通知大可不必,要钱钱的,于是乎用微信的通知成了唯一解。
那么就找一个支持企业微信的通知的轮子。
找了一圈有一个比较类似的
- https://github.com/cloverzrg/wechat-work-message-push-go
但是存在一个问题,他还添加了 grafana,似乎是想用 grafana 的报警功能推送到微信
去除 grafana 基本就符合我用企业微信发送通知的功能了,但是除此之外我还需要短信推送、邮件推送的功能。
那么决定在他的基础上,根据我的需求写写。
写业务代码思考了下用 python 还是 go 写这个东西。python的话就 flask/fastapi 一把梭,但是写起来少了不少乐趣,用 python 写更多的是为了实现这个需求,而不是开心的摸鱼 :)
非常巧,申请了开源之夏的项目过了,这个项目是要根据需求写 go 代码和改代码 bug,由于我写过的 go 代码不及 python 的百分之一,要顺利地完成开源之夏的项目我似乎还得再学习学习。
看完了 wechat-work-message-push-go 的执行逻辑,结合企业微信开发文档,发现企业微信发通知的逻辑比较简单,分为 2 步:
- 根据 corpID, corpSecret 请求微信的 api 获取 access_token,access_token 两个小时内有效,获取到之后保存起来,过期了及时更新即可
- 携带 access_token 请求消息通知的 api,请求体中放 AgentId ,接收人的 id和通知内容
用 python 实现这个基础的需求并且不考虑异常处理,我觉得用不到 30 行代码 :)
但是我还是决定用 go 写 :)
三下五除二写好了,目前只写了企业微信的通知,邮件通知就下次摸鱼的时候再写吧,剩下的就是部署了。
编译成二进制放到到服务器上,然后 caddy 套个反代加了层 https 就收工了。
docker 部署丢一个二进制到服务器就完事儿了,似乎感觉少了点什么 :)
还没有摸鱼完呢 就搞完了
那么就再套娃一个 docker 吧
能跑就行的 dockerfile v1.0首先写一个 dockerfile,因为 1.13 后默认开启了 go mod,因此 go build 的时候会自动下载 go mod的依赖,这里我们只需要设定 goproxy 环境变量就行。此外我之前一直用的七牛的 goproxy.cn,但是最近在实验室连接他会有网络问题,于是换成了 goproxy.io,速度伯仲之间吧。 这里专门为设定环境变量加了一层RUN大可不必,可以直接加 environment或者和 go build放到一起,不过和这巨大的尺寸比起来,这点优化 duck 不必
1 2 3 4 5 6 7 FROM golang:1.16 AS builder WORKDIR /app COPY . . RUN go env -w GOPROXY=https://goproxy.io,direct RUN go build -o app . CMD ["./app"]好的,构建完了,972MB,中规中矩,但这对于一个简单的 web 应用的镜像来说,可以用巨大来形容了
1 2 3 docker images wxworkmsgbot_bot REPOSITORY TAG IMAGE ID CREATED SIZE wxworkmsgbot_bot latest 5c7364d4e983 2 minutes ago 972MB 多步构建,减小尺寸既然用 go 写的,运行时的依赖不是问题,可以多步构建把 artifact 复制到第二部分的镜像里面。 于是,花了一分钟时间,把 docker官方文档示例 里面的多步构建的示例复制过来。
1 2 3 4 5 6 7 8 9 10 11 FROM golang:1.16 WORKDIR /go/src/github.com/alexellis/href-counter/ RUN go get -d -v golang.org/x/net/html COPY app.go . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=0 /go/src/github.com/alexellis/href-counter/app . CMD ["./app"]示例里面加了不少参数,我刚复制的时候还不知道为什么他要做这些操作,于是减法减法减法,只留下我想要的部分,得到了 v2.0 版本的 dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 FROM golang:1.16 AS builder WORKDIR /app COPY . . RUN go env -w GOPROXY=https://goproxy.io,direct RUN go build -o app . CMD ["./app"] FROM alpine:latest WORKDIR /app COPY --from=builder /app/app . CMD ["./app"]好的,非常完美, docker-compose up --build 一气呵成
一看大小只有 16.1MB,比刚刚好多了,缩小到了原来的 16.1/972=1.66% 心情愉悦 ^_^
1 2 3 4 docker images wxworkmsgbot_bot REPOSITORY TAG IMAGE ID CREATED SIZE wxworkmsgbot_bot latest 5e8c0cb18517 6 seconds ago 16.1MB再去看看启动情况,留下了一行日志就退出了
1 2 3 Recreating wxworkmsgbot_bot_1 ... done Attaching to wxworkmsgbot_bot_1 bot_1 | standard_init_linux.go:219: exec user process caused: no such file or directory问题不大,复制这行报错,面向 stackoverflow 编程
很快发现这是因为默认启用了 CGO 导致的,把他禁用就行了
此时此刻,恰如彼时彼刻,想起来, docker 文档里面就是做了这个操作的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 FROM golang:1.16 AS builder WORKDIR /app COPY . . RUN go env -w GOPROXY=https://goproxy.io,direct RUN CGO_ENABLED=0 go build -o app . CMD ["./app"] FROM alpine:latest WORKDIR /app COPY --from=builder /app/app . CMD ["./app"]加上这一行,没有问题
1 2 3 $ docker images wxworkmsgbot_bot REPOSITORY TAG IMAGE ID CREATED SIZE wxworkmsgbot_bot latest 1dfff8d58731 18 hours ago 16.1MB那么 docker文档剩下的参数是干什么的,尤其是 installsuffix,以前没看到过。
1 2 3 4 5 $ go tool link ... -installsuffix suffix set package directory suffix可以看到意思是安装的路径前缀,不过还是不太懂,继续搜了下,发现 go 开发者 ianlancetaylor 在一个 issue 里面说在新版本的 go 里面不需要了,那么我就暂且不管他。
正常运行了,于是请求一下 api
1 Get "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ffffff&corpsecret=xfdasfasf": x509: certificate signed by unknown authority看起来是缺了证书
此时此刻,恰如彼时彼刻
docker 的文档里面给 alpine 装了个证书,原来是搞这个用的
那么再加进去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 FROM golang:1.16 AS builder WORKDIR /app COPY . . RUN go env -w GOPROXY=https://goproxy.io,direct RUN CGO_ENABLED=0 go build -o app . CMD ["./app"] FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /app COPY --from=builder /app/app . CMD ["./app"]好的,这下真的一整个流程都 work 了
镜像大小为 16.4MB
1 2 3 docker images wxworkmsgbot_bot REPOSITORY TAG IMAGE ID CREATED SIZE wxworkmsgbot_bot latest 59af2bbb0b1c 34 seconds ago 16.4MB 继续优化大小真正的缩减大小,从现在开始,思路有两个,第一,减小二进制的大小;第二,使用 scratch 而不是 alpine。
首先减小二进制的大小。
很久以前摸鱼的时候,发现go build的结果有不少优化的空间,比如 -s去除符号表 -w 去除调试信息、-trimpath 去除路径信息(反溯源的目的)等,于是乎再修改一下 dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 FROM golang:1.16 AS builder WORKDIR /app COPY . . RUN go env -w GOPROXY=https://goproxy.io,direct RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" --trimpath -o app . FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /app COPY --from=builder /app/app . CMD ["./app"]经过测试,正常运行,不过缩减大小并不明显,事后想想似乎是 alpine占据太大的空间,二进制文件可能已经减小了 1/3了。
1 2 3 docker images wxworkmsgbot_bot REPOSITORY TAG IMAGE ID CREATED SIZE wxworkmsgbot_bot latest ad00fe8a6c01 24 seconds ago 13.5MB下一步就是并不常见的压缩二进制的步骤:upx加壳,虽然他更多的是用来混淆对抗杀软的,但是他的压缩性能是真的可以,让我想在这里试试
放一个 upx 进去,再加一个最大压缩比例的参数,于是有了新的 dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 FROM golang:1.16 AS builder COPY upx /app/upx WORKDIR /app COPY . . RUN go env -w GOPROXY=https://goproxy.io,direct RUN CGO_ENABLED=0 go build -ldflags="-w -s" --trimpath -o app . RUN /app/upx --best app FROM alpine #FROM scratch RUN apk --no-cache add ca-certificates WORKDIR /app COPY --from=builder /app/app . CMD ["./app"]其中构建过程中压缩的输出如下
1 2 3 4 5 6 7 8 9 10 11 12 ---> Running in d9e31badd0b6 Ultimate Packer for eXecutables Copyright (C) 1996 - 2020 UPX 3.96 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020 File size Ratio Format Name -------------------- ------ ----------- ----------- 7462912 -> 2428916 32.55% linux/amd64 app Packed 1 file. Removing intermediate container d9e31badd0b6经过测试,工作正常,现在的容器只有8.51MB了,其中,二进制文件压缩前有 7462912/1024/1024=7.1MB,压缩后只有 2.3 MB,剩下的空间是 alpine 占据的
1 2 3 4 docker images wxworkmsgbot_bot REPOSITORY TAG IMAGE ID CREATED SIZE wxworkmsgbot_bot latest 72ec286b8d99 35 seconds ago 8.51MB那么是时候干掉 alpine 了
使用 scratchalpine 相比于 go 的二进制文件,还是过大了,那么可以试试把他去掉,用 scratch ,整个容器有且仅有这一个二进制文件,岂不是很酷 :)
只在多年以前刚开始看 docker 教程的时候用过静态编译的丢进去运行,从此再也没玩过了 :(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 FROM golang:1.16 AS builder COPY upx /app/upx WORKDIR /app COPY . . RUN go env -w GOPROXY=https://goproxy.io,direct RUN CGO_ENABLED=0 go build -ldflags="-w -s" --trimpath -o app . RUN /app/upx --best app FROM scratch #FROM alpine #RUN apk --no-cache add ca-certificates WORKDIR /app COPY --from=builder /app/app . CMD ["./app"]构建后大小为
1 2 3 4 docker images wxworkmsgbot_bot REPOSITORY TAG IMAGE ID CREATED SIZE wxworkmsgbot_bot latest aa2d5689d063 2 minutes ago 2.43MB很明显,这里也会有缺证书的问题,搜索一圈可以发现有个全干工程师 在一年前发的一篇文章,从前到后和我的思路一样,连 upx 的参数都一样 太离谱了:)
那么就参考他的,直接拷贝证书过去
1 COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/于是乎有了最终能用的版本的 dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 FROM golang:1.16 AS builder COPY upx /app/upx WORKDIR /app COPY . . RUN go env -w GOPROXY=https://goproxy.io,direct RUN CGO_ENABLED=0 go build -ldflags="-w -s" --trimpath -o app . RUN /app/upx --best app FROM scratch WORKDIR /app COPY --from=builder /app/app . COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ CMD ["./app"]最终容器的大小
1 2 3 docker images wxworkmsgbot_bot REPOSITORY TAG IMAGE ID CREATED SIZE wxworkmsgbot_bot latest 706e3549f0ad 2 minutes ago 2.64MB到此告一段落,一个有且仅有一个二进制,还有证书的容器搞完了。
到头来想想,似乎没必要这样追求极致。
毕竟这距离极致还有很远。
还可以把二进制里面再继续分析,继续减小。
就像看到了终极笔记的终极一样,终极真的就是想要追求的终极么?
alpine 因为用的 musl 而不是 glibc而导致的问题,过于精简导致的依赖缺失的问题,让我感觉还是 debian 比较靠谱 :)
所以我还是回归我最爱的 debian,把二进制放进去,完事儿 :)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 FROM golang:1.16 AS builder WORKDIR /app COPY . . RUN go env -w GOPROXY=https://goproxy.io,direct RUN CGO_ENABLED=0 go build -ldflags="-w -s" --trimpath -o app . FROM debian WORKDIR /app COPY --from=builder /app/app . COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ CMD ["./app"]主流的镜像都是基于 debian 的,因此我这里再开一次不会多占用空间的 :)
1 2 3 docker images wxworkmsgbot_bot REPOSITORY TAG IMAGE ID CREATED SIZE wxworkmsgbot_bot latest d83396e4d977 About a minute ago 122MB所以,这就是真正的最后的镜像了 :)
refer-
https://docs.docker.com/develop/develop-images/multistage-build/
- https://colobu.com/2018/08/13/create-minimal-docker-image-for-go-applications/
- https://juejin.cn/post/6844904174396637197
- https://github.com/golang/go/issues/9344#issuecomment-69944514
适度摸鱼,有益健康
过度摸鱼,不能毕业 :)
看文档写代码花了半天时间
摸鱼写这篇博客,半天时间又没了
笑看下周一组会我如何交代我给导师说的我这周准备复现的论文
我现在还在看这篇论文,还没写代码 :)
事情越多越不想搞 :(
6月30号的时候,王爷说我们现在三年级了 :(
那么一瞬间
我突然就慌了55555
不慌
晚上再来一把 csgo
看看我白银一的真正实力
Les1ie
2021.7.3 15:19