Path Traversal 2
靶场
可以看到这里是让我们把文件上传到固定位置,先传一个试试
这里是把test文件传到了 C:\Users\Lenovo\.webgoat-2023.5\PathTraversal\cindahy
目标是C:\Users\Lenovo/.webgoat-2023.5/PathTraversal
所以只要把test文件的名字改成../test,传到上层目录中
成功
审计
总结一下整个流程
- 调用父类函数execute处理上传文件和文件名这两个参数
- execute函数中的流程:首先进行简单的空值检验之后清空特定文件路径(cleanupAndCreateDirectoryForUser函数)
- 再在特定路径下创建一个空文件,将传入的文件复制进去
- 分别经过(期望路径)=(上传路劲的父路径) and(上传路径的父目录的最后一部分)="PathTraversal" 的检验之后就会成功消息
接下来是具体实现细节
从前端可以看到是往此路由提交参数的"/PathTraversal/profile-upload"
所以重点看此函数
这里将uploadedFile和fullName两个参数传入,由前端id号或者抓包就能确定是哪两个参数
可以看到这里调用了父类中的execute处理三个参数
逐步解析代码,这里判断完两个参数是否为空之后,其中cleanupAndCreateDirectoryForUser
为特定用户清理并创建上传目录,大意是根据webgoat的主目录和用户名拼接好路径之后,检查此路径是否为空,若不为空则调用FileSystemUtils.deleteRecursively()
递归删除整个目录。(确保每次都是全新的空目录),最后返回路径
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
靶场
这里说从输入中删除了../
,那就试试复写先
成功了
审计
其他的都一样,增加了一句话
这里对传入的参数fullName进行了一些过滤:
fullName != null ? fullName.replace("../", "") : ""
若fullName不为空则将fullName中的../
替换成了空值,(就是删除了../
),若为空则赋值为""。
所以简单的复写就能绕过此过滤
Path Traversal 4
靶场
说它又有了一些改进
注意到这里的路径名变成了上传文件的真实名,那就在抓到的包里把文件真实名按照同样的原理改一下
可以看到成功了
审计
这里只从前端传入了一个参数(文件),把原来要输入的文件上传地址变成了自己提取的原始文件名(file.getOriginalFilename()函数)
Path Traversal 5
靶场
这里让我们找path-traversal-secret.jpg
这个文件
抓包发现这里我们提交了一个Priority: u=2
参数,返回了包含图片路径的图片包
第一反应是送去爆破,但这里是目录遍历
看一下前端
这里按下按钮之后会触发newRandomPicture()函数,应该是它进行的请求
但是没找到这函数在哪里
关注到GET /WebGoat/PathTraversal/random-picture这一行,尝试像右边一样构造
传入id参数
发现是可以成功传入的
最普通的传入失败,尝试复写和编码
发现编码可行,并且在返回两个目录之后看到了目标文件
那就(记得不能加后缀名)
得到密码
加密
解决
审计
我还是喜欢从前端开始看,思路比较完整
先在js文件里找一下前端的触发函数
,用于访问后端路径 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
属性,实现图片动态加载。
request.getQueryString()
获取 URL 的查询参数部分
检查是否包含 ../
或 /
,防止路径遍历攻击
id
参数可控,如果用户不传id
,则随机返回 1-10 的图片
拼接文件名:id + ".jpg"
,无路径过滤,存在路径遍历风险
- 如果文件名包含
path-traversal-secret.jpg
,直接返回文件内容(不 Base64 编码) - 其他情况返回图片的 Base64 编码,并附上
Location
头。
防御
- 白名单验证:只允许特定的字符或模式
- 避免直接拼接路径,使用语言提供的安全函数
- 限制用户输入的路径在某一个范围内。