本次团体赛服务器考点比较新颖,主要涉及区块链取证、浏览器扩展程序分析,除了传统的服务器取证能力外,需要对区块链原理有些了解并且具备一定的代码审计能力。
本文主要是针对比赛题目进行分析和解题思路分享,只进行知识分享,不具一定的实战能力,后台不解答涉及可能侵害他人权利的问题,切勿用于违法犯罪活动。如果有工作方面的解答需求,请后台联系添加微信私聊。
环境构建
将镜像使用仿真软件或者手动进行仿真,重置密码。
点击右上角的图标 - Wired Connected
- Wired Settings
- Network
- Wired
- 点击齿轮 - IPv4
- IPv4 Method
。
将Manual
修改为Automatic(DHCP)
,点击Apply
,在这里你也可以看到ens33
网卡设置的静态IP地址。
返回桌面,点击右上角的图标 - Wired Connected
- Turn Off
。
再点击右上角的图标 - Wired Off
- Connect
。
这样便可以将服务器网络设置为DHCP并重新获取IP信息。
要想ssh连接服务器,我们还需要关闭防火墙。
systemctl stop ufw
之后便能愉快地连接服务器了。
通过一定的探索和后续题目的引导,可以知道整个题目的环境。
/home
目录下存储着网站后台的前端web
和后端project
的代码。
从/home/project/admin/nohup.out
可以看到项目地址。
上网搜索可以知道这是一个虚拟货币的交易平台,也就是说嫌疑人构建了一个虚拟货币交易所。
在/home/gass/Desktop/project/blockchain
下有嫌疑人使用geth
搭建的私有区块链网络。
因为上述服务都配有启动命令,我们直接执行启动命令将所有服务开启。
bash /home/project/admin/restart.sh && bash /home/project/cloud/restart.sh && bash /home/gass/Desktop/project/blockchain/start.sh
注意,在启动
bash /home/project/admin/restart.sh
时可能会发送报错java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed
,简单的解决方法是先手动连接一次数据库mysql -u root -p
进行一次公私钥交换,数据库密码在下文的java源码分析中会提及,密码为123568
。
之后就是访问前端登陆界面。
查看nginx配置文件/etc/ngixn/ngixn.conf
可以看到nginx监听的是8801端口。
前端/home/web
中写死了127.0.0.1
访问后端接口,方便起见,我们直接在虚拟机中访问前端登陆界面127.0.0.1:8801
。(有点小晕)
前端界面也有个小坑,可以看到登陆表单显示的是一个用户名,一个验证码,但你f12
查看html源码可以发现验证码表单实际上是密码表单,真正的验证码表单已被隐藏。
我们可以修改display将验证码显示出来。
接下来是绕密,也是跟第3、6、7题有关。
导出/home/project/admin/admin.jar
,反编译jar包。
在application.properties
可以找到数据库密码为123568
。
直接可以连接服务器数据库。
全局搜索login
找寻后台管理员登陆代码逻辑。
在com.bizzan.bitrade.controller.system.EmployeeController#adminLogin
实现了后台管理员的登陆逻辑。
密码使用MD5
拼接盐值进行加密。
找到盐值定义的位置。
再通过全局搜索找到定义的常量。
直接拼接计算md5值。
将原有的root密码替换为计算值。
之后便能登陆后台root/123456
。
赛题
1. [填空题]请写出服务器系统内核版本;(答案格式:1.1.1-11-abcdefe)(1分)
uname -r
6.8.0-48-generic
2.[填空题]请写出服务器的ens33网卡的ip地址;(1分)
ifconfig
10.172.29.128
3.[填空题]请写出mysql数据库密码;(4分)
在application.properties
中设置了mysql数据库密码。
123568
4.[填空题]后台服务中注册中心的服务端口是多少?(答案格式:纯数字)(2分)
查看/home/project/cloud
的启动日志可以知道这就是注册中心,服务端口为7000
。
7000
5.[填空题]服务器nginx日志中,哪个ip访问系统最为频繁?(答案格式:6.6.6.6)(6分)
awk '{print $1}' /var/log/nginx/access.log* | sort | uniq -c | sort -nr
56.111.197.176
6.[填空题]请写出平台管理员密码加密算法;(答案格式:aes)(3分)
在环境搭建
环节也已经提到,通过全局搜索login
可以锁定后台登陆的代码逻辑,找到平台管理员密码为md5
加密。
md5
7.[填空题]假设某管理员密码是123456,请问该管理员的密码在数据库中存储的值是多少?(答案格式:如有字母,全大写)(5分)
也在环境搭建
中提及,通过搜索spark.system.md5.key
,可以找到定义的盐值。
最后将123456
与盐值进行拼接计算出结果。
985EB5B028065701341A478A9215E7B2
8.[填空题]已知某人卖出了5.2个ETH/USDT,请问他的二级推荐人可以获得多少个ETH佣金?(答案格式:写出数字即可,保留小数点后5位)(6分)
在/home/project/exchange-core
搜索佣金
可以找到计算佣金的具体逻辑。
在com.bizzan.bitrade.service.ExchangeOrderService#promoteReward
中计算了一级推荐人和二级推荐人的推广佣金。
首先回去找一下传入的参数bigDecimal1
的赋值。
可以看见其具体的赋值逻辑如下,分为买入和卖出以及对特殊会员的处理。
trade
是一个ExchangeTrade
实体对象,trade.getAmount()
获取这次交易的总金额,再乘上coin.getFee()
,coin
为ExchangeCoin
实体对象,coin.getFee()
获取该种虚拟货币的交易费率。
在数据表exchange_coin
可以看到fee
为0.001
。如果交易的是USDT的话,则为 amount * 0.001。
之后将bigDecimal1
传入com.bizzan.bitrade.service.ExchangeOrderService#promoteReward
进行处理。
二级推荐人佣金的计算逻辑如下。
将fee
也就是bigDecimal1
与BigDecimalUtils.getRate(jsonObject.getBigDecimal("two"))
得到的值进行相乘。
找下jsonObject
的定义,可以看到是解析的rewardPromotionSetting.getInfo()
,rewardPromotionSetting
也是RewardPromotionSetting
的实体对象。
直接在表reward_promotion_setting
找下info
,可以看到two
为0.1,即fee * 0.1
所以如果是卖出了5.2个ETH/USDT,二级推荐人的佣金为 5.2 * 0.001 * 0.1 = 0.00052
0.00052
9.[填空题]请找到受害人“王涵”的手机号;(1分)
会员管理
-会员管理
搜索 王涵
获取手机号
15780139471
10.[填空题]请写出嫌疑人的违法交易网站的中文名称;(答案格式:2个汉字)(3分)
查看网站的title
可知中文名为币严
。
币严
11.[填空题]请写出数据库中Recharge表的status字段中,0代表的中文含义;(1分)
直接看表注释即可。
未到账
12.[填空题]平台中所有账户中ETH余额最多的地址是多少?(答案格式:0x123F ... )(6分)
会员管理
-余额管理
内的数据是缺失的,需要从区块链中拿取数据。
直接在geth
的console
中执行javascript代码。
var maxBalance =0;
var maxAddress ="";
var accounts = eth.accounts;
for(var i =0; i < accounts.length; i++){
var balance = web3.eth.getBalance(accounts[i]);
if(balance > maxBalance){
maxBalance = balance;
maxAddress = accounts[i];
}
}
console.log("Address with the most ETH: "+ maxAddress);
在数据库验证下,该地址确实是某个会员的地址。
0x928f5963c03340077a8d2375657fb3395fe4a790
13.[填空题]区块链搭建工具是?(答案格式:abcd)(1分)
前面已经提到了,就是geth
。
geth
14.[填空题]区块链对外提供的的http端口是?(2分)
查看/home/gass/Desktop/project/blockchain/geth.log
日志,可以看到区块链对外提供的端口为8545
,本地也开启了8551
端口。
8545
15.[填空题]服务器网站数据库使用的字符集为?(答案格式:如有字母,请小写)(1分)
直接查看建立数据库的sql。
utf8mb4
16.[填空题]由于服务器定时清理了交易数据,请找寻整个区块链中最大的交易金额(答案格式:0x123F ... )(6分)
还是写javascript
脚本在geth
的console
中执行获取整个区块链中最大的交易金额。
let blockCount = web3.eth.blockNumber;
console.log("当前区块链高度:", blockCount);
let maxTransactionAmount ={
hash:"",
value:-1,
from:"",
to:""
};
for(let i =0; i <= blockCount; i++){
let block = web3.eth.getBlock(i,true);
if(!block) continue;
for(let transaction of block.transactions){
let value = transaction.value;
if(value > maxTransactionAmount.value){
maxTransactionAmount.hash= transaction.hash;
maxTransactionAmount.value= value;
maxTransactionAmount.from= transaction.from;
maxTransactionAmount.to= transaction.to;
}
}
}
console.log("最大的交易哈希是:", maxTransactionAmount.hash);
console.log("最大的交易金额是:", maxTransactionAmount.value);
console.log("发送方地址:", maxTransactionAmount.from);
console.log("接收方地址:", maxTransactionAmount.to);
0xbb218c4bd443824260673a92b575f6c249085b6e482d89f1445fdb4ba487b9f2
17.[填空题]请写出嫌疑人在chrome上使用的钱包名称;(答案格式:如有字母全小写)(2分)
打开chrome
,可以看到是MetaMask
。
metamask
18.[填空题]请写出chrome钱包插件使用的pbkdf2加密算法的轮次;(答案格式:纯数字)(4分)
对Chrome
的MetaMask
插件进行调试。
每次输入错误密码会返回Incorrect password
,通过这个去搜索登陆的主要逻辑。
打上断点,可以看到变量中保有pbkdf2
的轮次信息,为600000
。
600000
19.[填空题]Chrome钱包密码的算法中对iv的加密方式是什么?(答案格式:如有字母请小写,如md5)(4分)
h
函数传入了e
密钥以及r
加密信息等数据。
执行crypto.subtle.decrypt
对r
中的加密数据进行解密,解密算法为AES-GCM
。
之后在对解密数据进行JSON.parse
解析,倘若解析不成功说明解密不成功,触发Incorrect password
报错。
从上面的分析可以看出对iv
的加密方式为aes
。
aes
20.[填空题]已知服务器中嫌疑人的钱包登录密码为八位纯数字生日1994 ****,请写出该密码;(6分)
传入的密钥e
已经是派生的密钥,我们原始传入的密码还没有看见。
在h
函数下方的p
函数很像通过PBKDF2
进行密钥派生的逻辑,打个断点看一下。
p
函数的e
传入的是我们的原始密码123456
,r
作为PBKDF2
算法的salt,与上文中的一致。
并且可以看到PBKDF2
密钥派生使用的哈希算法为SHA-256
。
加密数据、iv
、salt
、轮次都有了,写个脚本来爆破密码。
import base64
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
import re
data_base64 = "4OBSCQ3fpgiiQG1CUT2KVKU9Sma1ixgcZ1xBb+XeQXlX9yFbyj6HgpPH4vKktB39FPVD5wlV0fFrKkrB4YvkwS4y0P2y15GrSMvJ7ZPV2FdT+o7/s9ydryf4j/dvWssWhlpIf8+Z/GTWxrd0pEKCumJ0SgM7pNCn+LPufqgAAc8Phk1V2G78YFFn27hoPalU+mfyLirBvbcNCe7PZhUEf02OB9HJxc6NL8VGHZ0mugf8CMCU4CfoMWBjGB358XwYgqVCAYfPeP612BcqH/2qGsf4v5MUynaoWjR3CDxg6z5n/SzvayET9KxzpnP5/YwrI1Kr6KSuX8hfWa4G7Qect7gRcJ5OSP9vjDAE0Oa7+2RoOvSuDhONrit9JD1j3PlF/HLHjCWcAxFPAqQHnaXHUT7+O/UR/nHBBUjwZqXcA3NvY6Up9gEyp7v252JKw/ybv9PYsNVBNNzaOCHM+2vLu4AEdhsJjEmzz1BMnl2a10lX3PIxT6g+eVdHNVOkeESS7Xiufrh1BNEXemU+/Mj8zOzC8X3sC+h7k6V+j8FO5gFFIsGVsehmhjQ0g3hv5OjHLu+8UbJ19HVC6nzyopbHF1EbgVc4bEfnsqxpBQT4xGY27MQLFa2SlcpRpue1NpZWdhV2C8/wTSBmcgnm3PHWgvBiuA=="
salt_base64 = "fhlH2383hn7sqEKiLN8zSqv/F/v9x0s3xj/1zBI1zkA="
iv_base64 = "oi49chysOL0hAXfqbviWIA=="
iteration_count = 600000
data = base64.b64decode(data_base64)
salt = base64.b64decode(salt_base64)
iv = base64.b64decode(iv_base64)
for year inrange(1994,1995):
for month inrange(1,13):
for day inrange(1,32):
password =f"{year}{month:02}{day:02}"
key = PBKDF2(password, salt, dkLen=32, count=iteration_count, hmac_hash_module=SHA256)
cipher = AES.new(key, AES.MODE_GCM, nonce=iv)
decrypted_data = cipher.decrypt(data)
if re.findall(b"{[\x20-\x7E]+}", decrypted_data):
print("正确的密码:", password)
exit()
print(password)
可以成功登陆。