目录遍历漏洞

目录遍历漏洞

Path Traversal 2

靶场

1749295491516-1696d5f1-8268-4801-ad31-f7e326a72f25.png

可以看到这里是让我们把文件上传到固定位置,先传一个试试

这里是把test文件传到了 C:\Users\Lenovo.webgoat-2023.5\PathTraversal\cindahy

目标是C:\Users\Lenovo/.webgoat-2023.5/PathTraversal

所以只要把test文件的名字改成**…/test,传到上层目录中**

1749295303111-f331ec16-38d2-4949-bce0-dd90ce8d225c.png

成功

审计

总结一下整个流程

  1. 调用父类函数execute处理上传文件和文件名这两个参数
  2. execute函数中的流程:首先进行简单的空值检验之后清空特定文件路径cleanupAndCreateDirectoryForUser函数)
  3. 再在特定路径下创建一个空文件,将传入的文件复制进去
  4. 分别经过**(期望路径)=(上传路劲的父路径)** and**(上传路径的父目录的最后一部分)=“PathTraversal” 的检验之后就会成功**消息

接下来是具体实现细节

1749298524189-f0aa9514-c499-4091-a3ed-ba2999b8570c.png

从前端可以看到是往此路由提交参数的**“/PathTraversal/profile-upload”**

1749298726643-5de3c1fd-3a79-4b2e-a017-b50159729b63.png

所以重点看此函数

这里将uploadedFilefullName两个参数传入,由前端id号或者抓包就能确定是哪两个参数

1749298866054-61837341-dadb-4755-8086-8b30d06a58fb.png

可以看到这里调用了父类中的execute处理三个参数

1749299067788-02c656e7-0870-4349-985b-395273f97e61.png

逐步解析代码,这里判断完两个参数是否为空之后,其中cleanupAndCreateDirectoryForUser为特定用户清理并创建上传目录,大意是根据webgoat的主目录和用户名拼接好路径之后,检查此路径是否为空,若不为空则调用FileSystemUtils.deleteRecursively()递归删除整个目录。(确保每次都是全新的空目录),最后返回路径

1749299256140-13d82c95-6ce1-4266-a8cc-1f4ee80e0f78.png

uploadedFile是一个指向目标位置的文件对象,创建一个空文件再把传入的文件复制进空文件

核心是这两行代码

if (attemptWasMade(uploadDirectory, uploadedFile)) {
    return solvedIt(uploadedFile);
}

private boolean attemptWasMade(File expectedUploadDirectory, File uploadedFile)
      throws IOException {
        return !expectedUploadDirectory
        .getCanonicalPath()
        .equals(uploadedFile.getParentFile().getCanonicalPath());
}

private AttackResult solvedIt(File uploadedFile) throws IOException {
    if (uploadedFile.getCanonicalFile().getParentFile().getName().endsWi	th("PathTraversal")) {
      return success(this).build();
    }
    return failed(this)
        .attemptWasMade()
        .feedback("path-traversal-profile-attempt")
        .feedbackArgs(uploadedFile.getCanonicalPath())
        .build();
}

其中**attemptWasMade**让(期望目录)和(上传目录的父目录)相比较,其中getCanonicalFile()返回文件的规范路径(绝对且唯一的路径)

**solvedIt**则判断上传目录的父目录的最后一部分是否是"PathTraversal"

以这种形式判断用户是否将文件上传到了指定目录。

Path Traversal 3

靶场

1749301675954-b14db5d4-4ae1-44c1-a1c0-309fba869216.png

这里说从输入中删除了…/,那就试试复写先

1749301757256-61dd0e3a-ee4e-4c75-a494-e49d9c13403d.png

成功了

审计

其他的都一样,增加了一句话

1749301853757-29c41d0a-4345-4a39-b366-26e6db255a4b.png

这里对传入的参数fullName进行了一些过滤:

fullName != null ? fullName.replace(“…/”, “”) : “”

若fullName不为空则将fullName中的…/替换成了空值,(就是删除了…/),若为空则赋值为""。

所以简单的复写就能绕过此过滤

Path Traversal 4

靶场

1749302459940-e3596d14-19e9-45b8-b498-cbab89d65595.png

说它又有了一些改进

1749302411805-f642c1f3-9fa7-4b01-beca-b5ad1f0e057c.png

注意到这里的路径名变成了上传文件的真实名,那就在抓到的包里把文件真实名按照同样的原理改一下

1749302538290-394b7467-74bb-4f22-ab86-9ac988a9cb11.png

可以看到成功了

审计

1749302664194-e8769db4-50e1-439f-8db4-12a34588c908.png

这里只从前端传入了一个参数(文件),把原来要输入的文件上传地址变成了自己提取的原始文件名(file.getOriginalFilename()函数

Path Traversal 5

靶场

1749303165171-a7779ed8-29bd-414b-a0b8-85bc8ce575b7.png

这里让我们找path-traversal-secret.jpg这个文件

抓包发现这里我们提交了一个Priority: u=2参数,返回了包含图片路径的图片包

1749303154334-9e917a7f-3d8f-4315-8208-ec2c2dd8c980.png

第一反应是送去爆破,但这里是目录遍历

1749311001100-4fd3d057-afc3-4b21-9c59-2b4ceb697441.png

看一下前端

这里按下按钮之后会触发**newRandomPicture()**函数,应该是它进行的请求1749312729233-681f1fed-c75e-4e22-871a-eed251c7cabe.png

但是没找到这函数在哪里

关注到GET /WebGoat/PathTraversal/random-picture这一行,尝试像右边一样构造

传入id参数

1749311203055-60474038-45c6-4737-8446-a91b2d22ad6a.png

发现是可以成功传入的

1749311236296-25084534-ebc1-409c-bdc9-7e527d1edb3a.png

最普通的传入失败,尝试复写和编码

1749311448408-9a64f3c9-f81c-43a0-9a67-18e109339608.png

发现编码可行,并且在返回两个目录之后看到了目标文件

那就(记得不能加后缀名)

1749311911024-5e2b13f1-fbe5-4885-876f-755440f63bc2.png

得到密码

加密

1749312365282-93c9f1f1-7f1c-44c4-a3ac-2833a8e997f5.png

解决

审计

我还是喜欢从前端开始看,思路比较完整

先在js文件里找一下前端的触发函数

1749313514664-56000380-d376-49f3-a20f-c5262eef75d2.png

,用于访问后端路径 PathTraversal/random-picture,从 WebGoat 后端获取随机图片的 Base64 编码数据,并动态更新前端 <img> 元素的 src 属性。

  • 发起 GET 请求,访问后端路径 PathTraversal/random-picture
  • function (result, status) { ... } 是一个 AJAX 回调函数,用于处理从服务器返回的响应
  • result:后端返回的响应内容
  • status:请求的 HTTP 状态(如 "success""error"),但在此函数中未使用,因为 $.get() 仅在成功时触发该回调。
  • randomCatPicture:前端 <img> 元素的 ID,函数会将 Base64 数据直接赋给它的 src 属性,实现图片动态加载。

1749315975313-92b12d2a-f4b2-4b1b-bf22-385b96aae964.png

request.getQueryString() 获取 URL 的查询参数部分

检查是否包含 ..//,防止路径遍历攻击

  1. id** 参数可控**,如果用户不传 id,则随机返回 1-10 的图片

拼接文件名id + ".jpg"无路径过滤,存在路径遍历风险

  1. 如果文件名包含 path-traversal-secret.jpg直接返回文件内容(不 Base64 编码)
  2. 其他情况返回图片的 Base64 编码,并附上 Location 头。

防御

  • 白名单验证:只允许特定的字符或模式
  • 避免直接拼接路径,使用语言提供的安全函数
  • 限制用户输入的路径在某一个范围内。

更新: 2025-06-08 01:55:01
原文: https://www.yuque.com/cindahy/aqfzwf/fq277dwghrcu30mp

LICENSED UNDER CC BY-NC-SA 4.0
评论