2019 黑盾杯 部分题解

我太南了。

WEB

i have the_flag

主要函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function ck(s) {
try {
ic
} catch (e) {
return;
}
var a = [118, 108, 112, 115, 111, 104, 104, 103, 120, 52, 53, 54];
if (s.length == a.length) {
for (i = 0; i < s.length; i++) {
if (a[i] - s.charCodeAt(i) != 3)
return ic = false;
}
return ic = true;
}
return ic = false;
}

3就行了。

1
2
3
4
a = [118, 108, 112, 115, 111, 104, 104, 103, 120, 52, 53, 54]
for i in a:
print "\b" + chr(i - 3),
# simpleedu123

提交得到flagmuWn9NU0H6erBN/w+C7HVg

a little hard

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
function GetIP()
{
if (!empty($_SERVER["HTTP_CLIENT_IP"]))
$cip = $_SERVER["HTTP_CLIENT_IP"];
else if (!empty($_SERVER["HTTP_X_FORWARDED_FOR"]))
$cip = $_SERVER["HTTP_X_FORWARDED_FOR"];
else if (!empty($_SERVER["REMOTE_ADDR"]))
$cip = $_SERVER["REMOTE_ADDR"];
else
$cip = "0.0.0.0";
return $cip;
}

$GetIPs = GetIP();
if ($GetIPs == "1.1.1.1") {
echo "Great! Key is *********";
} else {
echo "错误!你的IP不在访问列表之内!";
}
?>

伪造XFF请求头为1.1.1.1就好了。

click_1

一串js,将eval()以及document.write()等函数改成console.log()打印出来就行,得到key=700c,因此构造/?key=700c得到flag

花式过waf

去年黑盾杯原题,两种解法。

解法一

这个解法取自PHITHON师傅的一篇贷齐乐系统最新版SQL注入,同样的原理在安恒的一题奇怪的恐龙特性上出现过。

原理

当我们输入两个相同名字的参数的时候,PHP是取后一个的,这就是HTTP Parameter PollutionPHP中的表现。

同时,在解析请求的时候,如果参数名字中包含" "、"["、"+"、"."这几个字符,会将他们转换成下划线。

首先看代码。

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

print_r($_GET);
print_r("<br/>");
$request_uri = explode("?", $_SERVER['REQUEST_URI']);
if (isset($request_uri[1])) {
$rewrite_url = explode("&", $request_uri[1]);
foreach ($rewrite_url as $key => $value) {
$_value = explode("=", $value);
if (isset($_value[1])) {
$_REQUEST[$_value[0]] = (addslashes($_value[1]));
}
}
print_r($_REQUEST);
}

此时访问的url/?a_b=1&a.b=2,可以看到如下回显。

1
2
Array ( [a_b] => 2 )
Array ( [a_b] => 1 [a.b] => 2 )

这便是上文提到的HTTP Parameter Pollution漏洞,它允许出现多次相同名称的参数,而在PHP中,同名参数以最后一个为准,因此在直接获取参数的时候,由于.被转化成了**_,故最后获取到的a_b值为2,而下文手动获取URL链接分割赋值,则不会产生转化,因此a_b1,而a.b2**。

题目

然后回到题目,在waf.php中,首先对传入的参数做了一个过滤处理。

1
2
3
4
5
foreach ($_GET as $key => $value) {
if ($key != "username" && strstr($key, "password") == false) {
$_GET[$key] = filtering($value);
}
}

这儿是过滤函数。

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
function filtering($str)
{
$check = eregi('select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile', $str);
if ($check) {
echo "非法字符!";
exit();
}

$newstr = "";
while ($newstr != $str) {
$newstr = $str;
$str = str_replace("script", "", $str);
$str = str_replace("execute", "", $str);
$str = str_replace("update", "", $str);
$str = str_replace("master", "", $str);
$str = str_replace("truncate", "", $str);
$str = str_replace("declare", "", $str);
$str = str_replace("select", "", $str);
$str = str_replace("create", "", $str);
$str = str_replace("delete", "", $str);
$str = str_replace("insert", "", $str);
$str = str_replace("\'", "", $str);

}
return $str;
}

接着再次手工处理参数,使用**\(_SERVER['REQUEST_URI']**取值分割,后将数值覆盖写入**\)_REQUEST**中。

1
2
3
4
5
6
7
8
9
10
$request_uri = explode("?", $_SERVER['REQUEST_URI']);
if (isset($request_uri[1])) {
$rewrite_url = explode("&", $request_uri[1]);
foreach ($rewrite_url as $key => $value) {
$_value = explode("=", $value);
if (isset($_value[1])) {
$_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));
}
}
}

对于Payload/content.php?message_id=-1 union select username from user limit 0,1&$message.id=1而言。

首先从\(_GET**中取数据遍历,这时**PHP**会先将**\)message.id转化成$message_id,然后再取值,后面的覆盖前面的,因此值为1,从而成功通过filtering()函数的检测。

后使用\(_SERVER['REQUEST_URI']**取值,将**URI**进行了切割再赋值,对这个函数来说,**\)message_id和**\(message.id**是不同的两个变量,因此再使用**\)_REQUEST['message_id']获取到的值是-1 union select username from user limit 0,1**。

这样便可绕过waf,最后的Payload/content.php?message_id=-1 union 1,2,flag,4 from flag limit 0,1&message.id=1

解法二

eregi()函数可以使用%00截断,Payload/content.php?message_id=%00 union 1,2,flag,4 from flag limit 0,1

忘记密码了

填完email后提示了一个/forget/step2.php?email=youremail@address.com&check=?????

HTML源码中提示了一个vim,故发现了.submit.php.swp源码泄露,其中的主要代码段是这块。

1
2
3
4
5
6
7
8
9
10
11
12
13
if(!empty($token)&&!empty($emailAddress)){
if(strlen($token)!=10) die('fail');
if($token!='0') die('fail');
$sql = "SELECT count(*) as num from `user` where token='$token' AND email='$emailAddress'";
$r = mysql_query($sql) or die('db error');
$r = mysql_fetch_assoc($r);
$r = $r['num'];
if($r>0){
echo $flag;
}else{
echo "失败了呀";
}
}

根据代码构造/submit.php?emailAddress=admin@simplexue.com&token=0000000000

Crypto

MaybeBase

1
YMFZZTY0D3RMD3RMMTIZ 这一串到底是什么!!!!为什么这么像base32却不是!!!明文的md5值为16478a151bdd41335dcd69b270f6b985

给了一个脚本,大意是将字符串Base64编码后全部转成大写。

那么根据Base64编码的原理,大小写是隐藏着不同的信息的,因此爆破一下大小写组合。

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
# encoding=utf8
import base64
import hashlib

key = "YMFZZTY0D3RMD3RMMTIZ"


def generate_table(key):
res = [""]
for i in key:
next_res = []
for tmp in res:
next_res.append(tmp + i)
if not i.isdigit():
i = i.upper() if i.islower() else i.lower()
next_res.append(tmp + i)
res = next_res
return res


for i in generate_table(key):
try:
if hashlib.md5(base64.b64decode(i)).hexdigest() == "16478a151bdd41335dcd69b270f6b985":
print(bytes.decode(base64.b64decode(i)))
except:
continue
# base64wtfwtf123

guessthekey

代码。

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
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
if (argc != 3) {
printf("USAGE: %s INPUT OUTPUT\n", argv[0]);
return 0;
}
FILE* input = fopen(argv[1], "rb");
FILE* output = fopen(argv[2], "wb");
if (!input || !output) {
printf("Error\n");
return 0;
}
char key[] = "guessthekey";
char d, q, t = 0;
int ijk = 0;
while ((q = fgetc(input)) != EOF) {
d = (q + (key[ijk % strlen( key )] ^ t) + ijk*ijk) & 0xff;
t = q;
ijk++;
fputc(d, output);
}
return 0;
}

关键代码从16行开始,从文本中逐字节读取明文加密写入,且根据提示,给了一组明文和密文,还有另一组密文,再结合题目,便是根据已有的明文密文对爆破秘钥后解密了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
message = open('msg01', 'r').read()
secret = open('msg01.enc', 'r').read()
d, q, t = 0, 0, 0
count = 0
for i, j in zip(message, secret):
q = ord(i)
for k in range(0, 255):
d = (q + (k ^ t) + count * count) & 0xff
if d == ord(j):
print '\b' + chr(k),
t = q
count += 1
break
# VeryVeryLongKeyYouWillNeverKnowVeryV

那么就得到秘钥VeryVeryLongKeyYouWillNeverKnow,然后使用秘钥解密。

1
2
3
4
5
6
7
8
9
10
11
12
13
key = "VeryVeryLongKeyYouWillNeverKnow"
secret = open('msg02.enc', 'r').read()
d, q, t = 0, 0, 0
count = 0
for i in secret:
for q in range(0, 255):
d = (q + (ord(key[count % len(key)]) ^ t) + count * count) & 0xff;
if d == ord(i):
print '\b' + chr(q),
t = q
count += 1
break
# She had been shopping with her Mom in Wal-Mart. She must have been 6 years old, this beautiful brown haired, freckle-faced image of innocence. It was pouring outside. The kind of rain that gushes over the top of rain gutters, so much in a hurry to hit the Earth, it has no time to flow down the spout.flag{101a6ec9f938885df0a44f20458d2eb4}

Misc

猜谜语

1
2
方方格格绕花眼,手在电脑不离它!~~
27 18 21 19 16 17

键盘键位,jiaoyu,得到flag{d96e7b63-cc8b-4369-9d4f-90ed3be265ab}

适合做桌面的图片

stegsolve.jar打开,有一个二维码,扫不了,想起来官方给了一个修复二维码的工具,修好后给了一串十六进制编码。

1
03F30D0A79CB05586300000000000000000100000040000000730D0000006400008400005A000064010053280200000063000000000300000016000000430000007378000000640100640200640300640400640500640600640700640300640800640900640A00640600640B00640A00640700640800640C00640C00640D00640E00640900640F006716007D00006410007D0100781E007C0000445D16007D02007C01007400007C0200830100377D0100715500577C010047486400005328110000004E6966000000696C00000069610000006967000000697B000000693300000069380000006935000000693700000069300000006932000000693400000069310000006965000000697D000000740000000028010000007403000000636872280300000074030000007374727404000000666C6167740100000069280000000028000000007304000000312E7079520300000001000000730A0000000001480106010D0114014E280100000052030000002800000000280000000028000000007304000000312E707974080000003C6D6F64756C653E010000007300000000

尝试写入文件。

1
2
3
hex_str = "03F30D0A79CB05586300000000000000000100000040000000730D0000006400008400005A000064010053280200000063000000000300000016000000430000007378000000640100640200640300640400640500640600640700640300640800640900640A00640600640B00640A00640700640800640C00640C00640D00640E00640900640F006716007D00006410007D0100781E007C0000445D16007D02007C01007400007C0200830100377D0100715500577C010047486400005328110000004E6966000000696C00000069610000006967000000697B000000693300000069380000006935000000693700000069300000006932000000693400000069310000006965000000697D000000740000000028010000007403000000636872280300000074030000007374727404000000666C6167740100000069280000000028000000007304000000312E7079520300000001000000730A0000000001480106010D0114014E280100000052030000002800000000280000000028000000007304000000312E707974080000003C6D6F64756C653E010000007300000000"
f = open("hex_str", "w")
f.write(hex_str.decode('hex'))

file hex_str看了一下,是个python 2.7 byte-compiled文件,那么反编译一下。

1
2
3
4
5
6
7
8
9
10
def flag():
str = [102, 108, 97, 103, 123, 51, 56, 97, 53, 55, 48, 51, 50, 48, 56, 53, 52, 52, 49, 101, 55, 125]
flag = ''
for i in str:
flag += chr(i)
print flag


flag()
# flag{38a57032085441e7}

puppy

一个binwalk得到flag{BUIDwge348ry4HDEGH38rffg4}

找找找 找不到解开你心的钥匙

流量审计,第二段tcp流量中带了the password for zip file is : ZipYourMouth,第三段流量很明显是个zip包,但是解压出来不知道是个什么鬼东西。

于是尝试将第一段流量也导出,binwalk后得到一个压缩包,解压输入第二段的密码得到Flag-qscet5234diQ